Search code examples
pythonpython-3.xfloating-pointprecisionlargenumber

Why can the difference of different floating numbers be 0 in python?


Why is the result of below code 0 in python3?

a = "4.15129406851375e+17"
a = float(a)
b = "415129406851375001"
b = float(b)
a-b

Solution

  • This happens because both 415129406851375001 and 4.15129406851375e+17 are greater than the integer representational limits of a C double (which is what a Python float is implemented in terms of).

    Typically, C doubles are IEEE 754 64 bit binary floating point values, which means they have 53 bits of integer precision (the last consecutive integer values float can represent are 2 ** 53 - 1 followed by 2 ** 53; it can't represent 2 ** 53 + 1). Problem is, 415129406851375001 requires 59 bits of integer precision to store ((415129406851375001).bit_length() will provide this information). When a value is too large for the significand (the integer component) alone, the exponent component of the floating point value is used to scale a smaller integer value by powers of 2 to be roughly in the ballpark of the original value, but this means that the representable integers start to skip, first by 2 (as you require >53 bits), then by 4 (for >54 bits), then 8 (>55 bits), then 16 (>56 bits), etc., skipping twice as far between representable values for each bit of magnitude you have that can't be represented in 53 bits.

    In your case, both numbers, converted to float, have an integer value of 415129406851374976 (print(int(a), int(b)) will show you the true integer value; they're too large to have any fractional component), having lost precision in the low digits.

    If you need arbitrarily precise base-10 floating point math, replace your use of float with decimal.Decimal (conveniently, your values are already strings, so you don't risk loss of precision between how you type a float and the actual value stored); the default precision will handle these values, and you can increase it if you need larger values. If you do that, you get the behavior you expected:

    from decimal import Decimal as Dec  # Import class with shorter name
    
    a = "4.15129406851375e+17"
    a = Dec(a)  # Convert to Decimal instead of float
    b = "415129406851375001"
    b = Dec(b)  # Ditto
    print(a-b)
    

    which outputs -1. If you echoed it in an interactive interpreter instead of using print, you'd see Decimal('-1'), which is the repr form of Decimals, but it's numerically -1, and if converted to int, or stringified via any method that doesn't use the repr, e.g. print, it displays as just -1.

    Try it online!