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/2
and computes abetter_guess
: the average ofguess
andn/guess
. If those two guesses are equal or very close, the square root is thebetter_guess
. If not, thebetter_guess
is used as theguess
, and a newbetter_guess
is 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.