Search code examples
pythonprecisionfloating-accuracy

Why does python get confused by these comparisons?


If I run this code in Python 3.8.6 I get the output False:

(((3980 - 91 + 1)/(3980)) * ((1 / 3980)**(91 - 1))) > (((3981 - 91 + 1)/(3981)) * ((1 / 3981)**(91 - 1)))

However, if I run this in WolframAlpha it returns True.

I'm guessing this happens due to float imprecision. But why does it happen exactly here? Out of 12 million combinations of s and z that I've tested for the following function this only happens twice. z = 91 with s = 3980 and z = 92 with s = 3457.

(((s - z + 1)/(s)) * ((1 / s)**(z - 1))) > ((((s+1) - z + 1)/(s+1)) * ((1 / (s+1))**(z - 1)))

Also, try these in your interpreter:

  1. (((3458 - 92 + 1)/(3458)) * ((1 / 3458)**(92 - 1))) > (((3459 - 92 + 1)/(3459)) * ((1 / 3459)**(92 - 1)))
  2. (((3457 - 92 + 1)/(3457)) * ((1 / 3457)**(92 - 1))) > (((3458 - 92 + 1)/(3458)) * ((1 / 3458)**(92 - 1)))
  3. (((3456 - 92 + 1)/(3456)) * ((1 / 3456)**(92 - 1))) > (((3457 - 92 + 1)/(3457)) * ((1 / 3457)**(92 - 1)))

They give these results:

  1. True
  2. False
  3. True

Why does the pattern True False True happen at only these inputs? Holding z constant, no other value of s returns False.


Solution

  • Rounding error. If you want to know precisely go read the IEEE 754 specification. Your numbers are so small they are in the subnormal range and have 5-10 bits of precision in your examples. Floats in the normal range have 53 bits of precision.


    If you look at the two values using float.hex() it shows their exact binary representation:

    >>> (((3980 - 91 + 1)/(3980)) * ((1 / 3980)**(91 - 1))).hex()
    '0x0.0p+0'
    >>> (((3981 - 91 + 1)/(3981)) * ((1 / 3981)**(91 - 1))).hex()
    '0x0.0p+0'
    

    They are both so small at IEEE 754 float64 precision they are both rounded to zero. If fact the last term is really small:

    >>> ((1/3981)**89).hex()
    '0x0.0000000000327p-1022'