In Python 3.7 int(x-1) == x
is True
for x = 5e+17
Why is this so and how do I prevent this bug?
To reproduce, paste this into your Python console:
int(5e+17-1) == 5e+17
>True
(I am using int
because x is the result of a division and I need to parse it as int.)
Type int(x) to convert x to a plain integer. Type long(x) to convert x to a long integer.
In Python True and False are equivalent to 1 and 0. Use the int() method on a boolean to get its int values. int() turns the boolean into 1 or 0. Note: that any value not equal to 'true' will result in 0 being returned.
It's a problem caused when the internal representation of floating-point numbers, which uses a fixed number of binary digits to represent a decimal number. It is difficult to represent some decimal number in binary, so in many cases, it leads to small roundoff errors.
Python can handle the precision of floating point numbers using different functions. Most functions for precision handling are defined in the math module. So to use them, at first we have to import the math module, into the current namespace. import math. Now we will see some of the functions for precision handling.
Let's start by establishing that 5 == 5.0
is True
even though 5
is an int
and 5.0
is a float
. This is by design.
If we keep that in mind, then we can also accept that int(5e+17) == 5e+17
is True
.
Finally, we see that int(5e+17) == int(5e+17-1)
is also True
because of precision errors (Thanks @juanpa.arrivillaga for the link).
Now it is clear why int(5e+17-1) == 5e+17
is True
.
This can be solved by using Decimal
but be sure to initialize it with a string:
from decimal import Decimal
Decimal('5e+17') - 1 == Decimal('5e+17')
# False
Python float
is stored as a double-precision floating point number. They only have 53 bits of precision, so integers larger than 253 stored as floats begin to lose precision. Here's a clear example of how large numbers begin to lose precision:
>>> x = float(2**53-10)
>>> x
9007199254740982.0
>>> for i in range(20):
... print(x+i)
...
9007199254740982.0
9007199254740983.0
9007199254740984.0
9007199254740985.0
9007199254740986.0
9007199254740987.0
9007199254740988.0
9007199254740989.0
9007199254740990.0
9007199254740991.0 <--- 2**53-1
9007199254740992.0 <--- 2**53
9007199254740992.0 <--- NOT 2**53+1
9007199254740994.0 <--- 2**53+2
9007199254740996.0
9007199254740996.0
9007199254740996.0
9007199254740998.0
9007199254741000.0
9007199254741000.0
9007199254741000.0
The above number is approximately 9e+15, so your 1e+17 number is well into loss of precision. You have to add/subtract 16 from floats that large to expect a change in stored value:
>>> x = 1e17
>>> for i in range(20):
... print(f'{x+i:.1f}')
...
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000000.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
100000000000000016.0
Python has functions to convert to and from an exact binary floating point value. The 1 before and 13 hexadecimal digits after the decimal indicate the 53-bit value:
>>> (1e17).hex()
'0x1.6345785d8a000p+56'
>>> print(f"{float.fromhex('0x1.6345785d8a000p56'):.1f}")
100000000000000000.0
Adding one to the 53-bit value:
>>> print(f"{float.fromhex('0x1.6345785d8a001p56'):.1f}")
100000000000000016.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With