Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does 9007199254740993 != 9007199254740993.0?

The result of this comparison surprised me (CPython 3.4):

>>> 9007199254740993 == 9007199254740993.0
False

My understanding of the the docs is that the left operand should be cast to float to match the type of the right operand:

Python fully supports mixed arithmetic: when a binary arithmetic operator has operands of different numeric types, the operand with the “narrower” type is widened to that of the other, where integer is narrower than floating point, which is narrower than complex. Comparisons between numbers of mixed type use the same rule. The constructors int(), float(), and complex() can be used to produce numbers of a specific type.

This does not seem to be happening:

>>> float(9007199254740993) == 9007199254740993.0
True

What's going on here?

like image 764
flornquake Avatar asked Jul 15 '15 17:07

flornquake


People also ask

Is float or int bigger?

The exponent allows type float to represent a larger range than that of type int . However, the 23-bit mantissa means that float supports exact representation only of integers whose representation fits within 23 bits; float supports only approximate representation of integers outside that range.

Can a float hold more than an int?

Floating point types can hold much larger values. The usual suspects Float, and Double are 32 and 64 bits respectively. So even a 32bit float can hold a number with nearly twice the number of digits as a 64 bit Integer.


1 Answers

Python doesn't exactly convert the integer to a float here; it converts the float to an integer:

>>> 9007199254740993 == int(9007199254740993.0)
False

That fails because int(9007199254740993.0) is actually 9007199254740992:

>>> 9007199254740992 == 9007199254740993.0
True

See the float_richcompare() function. Specifically, the comment before it:

/* Comparison is pretty much a nightmare. 

 [...]

 * When mixing float with an integer type, there's no good *uniform* approach.
 * Converting the double to an integer obviously doesn't work, since we
 * may lose info from fractional bits.  Converting the integer to a double
 * also has two failure modes:  (1) an int may trigger overflow (too
 * large to fit in the dynamic range of a C double); (2) even a C long may have
 * more bits than fit in a C double (e.g., on a 64-bit box long may have
 * 63 bits of precision, but a C double probably has only 53), and then
 * we can falsely claim equality when low-order integer bits are lost by
 * coercion to double.  So this part is painful too.

What happens for these two numbers is this:

  • Python tries the int.__eq__(float) route, but that returns NotImplemented
  • Python tries the float.__eq__(int) route, which float_richcompare() handles.

In that function v is your float, w is the integer. The following is a selection of code that is executed for that path:

else if (PyLong_Check(w)) {   /* true because the other number is an Python integer */

    /* ... */

    nbits = _PyLong_NumBits(w);   /* 54 for your integer */

    /* ... */

    if (nbits <= 48) {  /* nope, can't make it a float outright */
        /* ... */
    }

    (void) frexp(i, &exponent);  /* the exponent is 54 for your float */

    if (exponent < 0 || (size_t)exponent < nbits) {
        /* not true */
    }
    if ((size_t)exponent > nbits) {
        /* also not true */
    }
    /* v and w have the same number of bits before the radix
     * point.  Construct two ints that have the same comparison
     * outcome.
     */
    {
        /* code to convert v to an integer vv, copy w to ww */

        r = PyObject_RichCompareBool(vv, ww, op);

        /* ... */

        result = PyBool_FromLong(r);

        /* ... */

        return result;
    }

So in the end due to the size of the numbers involved, Python converts the float to an integer, and it is that conversion where the floating point number turns out to be 9007199254740992. That's because a float cannot actually express 9007199254740993.0 accurately:

>>> 9007199254740993.0
9007199254740992.0
like image 147
Martijn Pieters Avatar answered Sep 23 '22 09:09

Martijn Pieters