math.isclose()The math.isclose() function (new in Python 3.5) is the one obvious way™ to solve a common problem in floating point math: how to safely compare two values when we can't get exact equality due to the limitations of floating point arithmetic.
For example, consider this:
In [1]:
three_tenths = .1 + .1 + .1
three_tenths
Out[1]:
In [2]:
three_tenths == .3
Out[2]:
The issue is the efficient but approximate binary representation of some numbers (like .1) in modern CPUs using the IEEE-754 floating point standard. There are several ways of making such a comparison with a practical tolerance, but since Python 3.5 the canonical way is to use math.isclose(), like this:
In [3]:
import math
math.isclose(three_tenths, .3)
Out[3]:
The math.isclose() function has two additional arguments to fine tune the tolerance, but I'll not cover them here.
Now let's see a real example using isclose().
math.isclose() with Newton's methodNewton's method of succesive approximations can be used to compute the square root.
How Newton's method works
To compute
sqrt(n), the algorithm starts withguess=n/2and computes abetter_guess: the average ofguessandn/guess. If those two guesses are equal or very close, the square root is thebetter_guess. If not, thebetter_guessis used as theguess, and a newbetter_guessis computed as the average of that andn/guess. This process quickly converges to a very good approximation of the square root.
The implementation below uses math.is_close() to test whether the better_guess is close to the current one, which means further approximations will not be useful and the better_guess is an acceptable result.
In [4]:
import math
def newton_sqrt(n, verbose=False):
guess = n / 2
while True:
if verbose: print('guess ->', guess)
better_guess = (guess + n/guess) / 2
if math.isclose(guess, better_guess):
return better_guess
guess = better_guess
Sample use:
In [5]:
newton_sqrt(100)
Out[5]:
Using verbose=True, we can see how quickly Newton's algorithm converges to the solution:
In [6]:
newton_sqrt(100, True)
Out[6]:
Applying newton_sqrt() to numbers from 9 to 16, we see that some results are a little different from those from math.sqrt(). However, the results are all considered close enough by isclose() with the default tolerance.
In [7]:
for n in range(9, 17):
computed = newton_sqrt(n)
expected = math.sqrt(n)
close = math.isclose(computed, expected)
delta = computed - expected
print('sqrt({:2d}): {:.20f} {:.20f} {} {:.20f}'.format(n, computed, expected, close, delta))
Since Python already has a math.sqrt() function, our newton_sqrt() is only a didactic example. However, it is an elegant algorithm to know, and a fine example of the utility of math.isclose().
In [8]:
from decimal import Decimal
one_tenth = Decimal('.1')
three_tenths = one_tenth + one_tenth + one_tenth
three_tenths == Decimal('.3')
Out[8]:
Note the use of string arguments in the Decimal constructors above. Using a float to build a Decimal is often a bad idea, because, the Decimal value will reflect the imprecision of the float:
In [9]:
Decimal(.1)
Out[9]:
In [10]:
Decimal(.1) == one_tenth
Out[10]:
Again, the isclose function can be helpful:
In [11]:
math.isclose(Decimal(.1), one_tenth)
Out[11]:
But if you need exact precision you should only use Decimal numbers built from strings or integers.
In [12]:
Decimal('.1') * 10
Out[12]:
In general, calculations with money should use Decimal values to represent money amounts.