Why is 9007199254740993! = 9007199254740993.0?

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

>>> 9007199254740993 == 9007199254740993.0 False 

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

Python fully supports mixed arithmetic: when a binary arithmetic operator has operands of different number types, an operand with a narrower type expands to another where the integer is narrower than a floating point, which is narrower than complex. Comparison between numbers of mixed type uses the same thing same rule. The constructors int (), float (), and complex () can be used to create numbers of a particular type.

This does not seem to be happening:

 >>> float(9007199254740993) == 9007199254740993.0 True 

What's going on here?

+7
python floating-point numbers comparison-operators
source share
1 answer

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

 >>> 9007199254740993 == int(9007199254740993.0) False 

This is not so, because int (9007199254740993.0) is actually 9007199254740992:

 >>> 9007199254740992 == 9007199254740993.0 True 

See the float_richcompare() function . In particular, a comment before him:

 /* Comparison is pretty much a nightmare. [...] * When mixing float with an integer type, there 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 (eg, 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:

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

In this function v is your float, w is an integer. The following is a selection of code that runs for this 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 this conversion where the floating point number turns out to be 9007199254740992 . This is because the float cannot accurately express 9007199254740993.0 exactly:

 >>> 9007199254740993.0 9007199254740992.0 
+7
source share

All Articles