In This Article
In this comprehensive Python tutorial, we’ll explore the intricacies of comparing float values, shedding light on why relying on the ‘==’ operator for such comparisons can often yield unexpected outcomes. We’ll then introduce a robust solution: Python math.isclose function.
When delving into the realm of scientific computations in Python, one frequently encounters float values. However, comparing these values can be a daunting task due to the idiosyncrasies of how computers represent them.
Prepare to enhance your Python programming skills by mastering the art of precise float comparison. Let’s embark on this journey together, with ‘Python math.isclose’ as our guiding beacon.
The problem with using ==
for float
comparison
Internally, Python represents a float
value using a finite binary representation. If we run the following code to print 0.1
,
print(0.1)
We get the output
However, if we want to see more of the fractional part, we can display it with 30 decimal places of precision.
print(f"{float(0.1):.30f}")
we will get more details about the fractional part.
Now we can see that internally, 0.1
is not represented as an exact value because it is represented internally using binary. Binary can only give an exact representation if the fractional number is a power of two or a sum of powers of two. For example:
# print 0.5 formatted as a string with 30 digits
# after the decimal place
print(f"{float(0.5):.30f}")
# evaluate 0.5+0.25 and print the result formatted
# as a string with 30 digits after the decimal place
print(f"{float(0.5+0.25):.30f}")
# evaluate 0.5+0.125 and print the result formatted
# as a string with 30 digits after the decimal place
print(f"{float(0.5+0.125):.30f}")
The output is as follows:
From the output, we see that in this case, the representation is exact.
However, Python can only represent float
values like 0.1
approximately.
This raises a problem when trying to carry out comparisons using the ==
operator. The following code snippet illustrates the issue.
expr = 0.5 + 0.125
num = 0.625
print(f"0.5 + 0.125 (to 30dp) gives {expr:.30f}")
print(f"0.625 (to 30 dp) gives {num:.30f}")
print(f"expr==num evaluates to {expr==num}")
The output we get is:
From our output, we can see that the internal representation of 0.625
and the sum 0.5 + 0.125
are the same. So, comparison returns True
.
The situation changes when we work with float
values that are approximately represented internally, as the next code snippet shows.
expr = 0.1 + 0.2
num = 0.3
print(f"0.1 + 0.2 (to 30dp) gives {expr:.30f}")
print(f"0.3 (to 30 dp) gives {num:.30f}")
print(f"expr==num evaluates to {expr==num}")
The output is as follows:
The comparison returns False
since the sum stored in expr
is represented differently from the value stored in num
, even if we intended to hold the same value of 0.3
in both variables.
The approximate representation of float
values make comparing them using the ==
operator unpredictable. Serious bugs could result if a situation similar to the above scenario occurred in our code. However, Python provides an alternative way that allows us to compare float
values without running into this kind of bug.
The solution: Python math.isclose
function
Python version 3.5 introduced the math.isclose
function to address this challenge that arises when float
values are compared. Instead of checking if the two numbers compared are the same, math.isclose
checks if they are close to each other within a specified tolerance value.
The math.isclose
Python function is called like so:
math.isclose(a, b, rel_tol, abs_tol)
Each parameter is explained below:
a
: The first value to compare for closeness (Required)b
: The second value to compare for closeness (Required)rel_tol
: The relative tolerance. The maximum allowed difference betweena
andb
, relative to the bigger value compared. It has a default value of1e-09
(Optional)abs_tol
: The absolute tolerance. The maximum allowed difference betweena
andb
. It has a default value of0.0
The function carries out the following calculation when doing comparisons:
abs_a, abs_b = abs(a), abs(b)
abs_tol_rel = rel_tol * max(abs_a, abs_b)
# The comparison...
abs(a - b) <= max(abs_tol_rel, abs_tol)
If a
and b
are close, the comparison returns True
.
Examples of using Python math.isclose
for float
comparison
In the previous sections, we discussed the challenges of comparing float
values using the equality operator, and how we could make use of an alternative, math.isclose
, to check if two float
numbers are close enough within a specific tolerance value. In this section, we will dive into examples of making use of math.isclose
.
Python Comparing two float
values with default tolerances
The math.isclose
function is defined with a default rel_tol
value of 1e-09
and a default abs_tol
value of 0.0
. The next example will make use of these default tolerance settings to compare two float
values.
from math import isclose
a = 0.1 + 0.1 + 0.1
b = 0.3
print(isclose(a, b)) # Output: True
In the example, we used math.isclose
to check if the sum 0.1 + 0.1 + 0.1
and the float
literal 0.3
are close based on the default settings. The code prints out True
which means that their internal representations are as close as allowed by the default tolerance values.
On the other hand, we will get a return value of False
if we use isclose
with default tolerance values to check if 1e-10
and 1e-11
are close.
result = isclose(1e-10, 1e-11)
print(result) # Output: False
From the tolerance formula discussed in the section that defines math.isclose
, the absolute difference between 1e-10
and 1e-11
should be smaller than max(rel_tol * max(1e-10, 1e-11), abs_tol)
.
The absolute difference between 1e-10
and 1e-11
(a value of 9e-11
) is not smaller than the value returned by max(rel_tol * max(1e-10, 1e-11), abs_tol)
(which is 1.0000000000000001e-19
).
Therefore, isclose
returns False
for the comparison between 1e-10
and 1e-11
using the default tolerance values. Thus, the current tolerance values are not sufficient to consider 1e-10
and 1e-11
close.
Python Comparing two float
values with custom tolerance values
The default tolerance values work for many situations.
However, different values can be passed to math.isclose
to suit specific requirements.
Let’s explore examples that pass different values for rel_tol
and abs_tol
.
Hers an example that compares two values 1000.0
and 1001.0
using rel_tol
value 1e-5
. Running the code snippet will show how the comparison plays out.
from math import isclose
result = isclose(1000.0, 1001.0, rel_tol=1e-5)
print(result) # Output: False
Here, math.isclose
returns False
because the absolute difference between 1000.0
and 1001.0
relative to the magnitude of the bigger number 1001.0
does not fall within the specified relative tolerance.
This next example finds out if the sum 0.1 + 0.2
is within 1e-10
of 0.3
.
from math import isclose
result = isclose(0.1+0.2, 0.3, abs_tol=1e-10)
print(result) # Output: True
math.isclose
returns True
since the absolute difference between the internal representation of the result of 0.1 + 0.2
and the internal representation of 0.3
is smaller than1e-10
(the abs_tol
).
In conclusion math.isclose
offers a solution arising when trying to compare float
values. Instead of directly checking for equality, the function checks if the absolute difference of the compared values falls under a tolerance value, effectively overcoming rounding errors and precision issues.
Understanding the use of math.isclose
is crucial if we want to avoid bugs when carrying out float comparisons in our code. If you liked this tutorial, please consider checking out other tutorials like this on our website that can help you in your coding journey.