Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does math.isclose() fail to detect minor differences between very large values?

math.isclose() exists to determine if the difference between two values is within a tolerance.
As far as I understand it, the default values for this tolerance are:

  • rel_tol = 1e-09
  • abs_tol = 0.0


If I set both values to 0.0, then it should be testing if the two values are identical.

This seems to work fine for smaller values:

import math

math.isclose(1000, 1000, rel_tol=0.0, abs_tol=0.0)                       # True
math.isclose(1000, 1001, rel_tol=0.0, abs_tol=0.0)                       # False

But it fails with very large values:

import math
import sys

math.isclose(sys.maxsize, sys.maxsize,      rel_tol=0.0, abs_tol=0.0)    # True
math.isclose(sys.maxsize, sys.maxsize-1,    rel_tol=0.0, abs_tol=0.0)    # True
math.isclose(sys.maxsize, sys.maxsize-100,  rel_tol=0.0, abs_tol=0.0)    # True
math.isclose(sys.maxsize, sys.maxsize-1000, rel_tol=0.0, abs_tol=0.0)    # False

It seems that there is still a relative tolerance?


Why does this behavior occur?

The above code was run with Python 3.5.2.


UPDATE 1:

It seems that a similar behavior occurs when using very large float values:

import math
import sys

m = sys.float_info.max                                                    # type 'float'

math.isclose(m, m)                                                        # True
math.isclose(m, m-1.0)                                                    # True
math.isclose(m, m-1e100)                                                  # True
math.isclose(m, m-1e300)                                                  # False

math.isclose(m, m,       rel_tol=0.0, abs_tol=0.0)                        # True
math.isclose(m, m-1.0,   rel_tol=0.0, abs_tol=0.0)                        # True
math.isclose(m, m-1e100, rel_tol=0.0, abs_tol=0.0)                        # True
math.isclose(m, m-1e300, rel_tol=0.0, abs_tol=0.0)                        # False

On the other hand, the comparison operators do not work here either.

import math
import sys

m = sys.float_info.max                                                    # type 'float'

m == m                                                                    # True
m < m                                                                     # False
m > m                                                                     # False

m == m-1.0                                                                # True
m < m-1.0                                                                 # False
m > m-1.0                                                                 # False

m == m-1e100                                                              # True
m < m-1e100                                                               # False
m > m-1e100                                                               # False

m == m-1e300                                                              # False
m > m-1e300                                                               # True
m < m-1e300                                                               # False

UPDATE 2:

The answer to 'UPDATE 1' was given here.

like image 765
AFoeee Avatar asked Oct 26 '18 23:10

AFoeee


1 Answers

sys.maxsize is an integer, but math.isclose() works on floating-point values. On a 64-bit box, sys.maxsize has more bits of precision than a float can represent, so float(sys.maxsize - N) == float(sys.maxsize) for all sufficiently small postive integer N.

>>> from sys import maxsize as m
>>> m
9223372036854775807
>>> float(m)
9.223372036854776e+18
>>> float(m - 1)  # identical
9.223372036854776e+18
>>> float(m - 100) # identical 
9.223372036854776e+18
>>> float(m - 1000)  # finally subtracting enough to make a tiny difference
9.223372036854775e+18

Short course: when you want to compare integers for equality, converting to float first simply doesn't make sense.

like image 183
Tim Peters Avatar answered Oct 23 '22 10:10

Tim Peters