org.sonar.l10n.py.rules.python.S1244.html Maven / Gradle / Ivy
This rule raises an issue when direct and indirect equality/inequality checks are made on floats.
Why is this an issue?
Floating point math is imprecise because of the challenges of storing such values in a binary representation.
In base 10, the fraction 1/3
is represented as 0.333…
which, for a given number of significant digit, will never exactly
be 1/3
. The same problem happens when trying to represent 1/10
in base 2, with leads to the infinitely repeating fraction
0.0001100110011…
. This makes floating point representations inherently imprecise.
Even worse, floating point math is not associative; push a float
through a series of simple mathematical operations and the answer
will be different based on the order of those operation because of the rounding that takes place at each step.
Even simple floating point assignments are not simple, as can be vizualized using the format
function to check for significant
digits:
>>> format(0.1, ".17g")
'0.10000000000000001'
This can also be vizualized as a fraction using the as_integer_ratio
method:
>>> my_float = 0.1
>>> numerator, denominator = my_float.as_integer_ratio()
>>> f"{numerator} / {denominator}"
'3602879701896397 / 36028797018963968'
Therefore, the use of the equality (==
) and inequality (!=
) operators on float
values is almost always
erroneous.
How to fix it
Whenever attempting to compare float values, it is important to consider the inherent imprecision of floating-point arithmetic.
One common solution to this problem is to use a tolerance value (also called epsilon) to define an acceptable range of difference between two
floats. A tolerance value may be relative (based on the magnitude of the numbers being compared) or absolute. Note that comparing a value to 0 is a
special case: as it has no magnitude, it is important to use an absolute tolerance value.
The math.isclose
function allows to compare floats with a relative and absolute tolerance. One should however be careful when
comparing values to 0, as by default, the absolute tolerance of math.isclose
is 0.0
(this case is covered by rule
{rule:python:S6727}) . Depending on the library you’re using, equivalent functions exist, with possibly different default tolerances (e.g
numpy.isclose
or torch.isclose
which are respectively designed to work with numpy
arrays and
pytorch
tensors).
If precise decimal arithmetic is needed, another option is to use the Decimal
class of the decimal
module, which allows
for exact decimal arithmetic.
Code examples
Noncompliant code example
def foo(a, b):
return a == b - 0.1
Compliant solution
import math
def foo(a, b):
return math.isclose(a, b - 0.1, rel_tol=1e-09, abs_tol=1e-09)
Resources
Documentation
- Python Documentation - Floating Point Arithmetic: Issues and
Limitations
- Python Documentation - Decimal fixed point and floating point
arithmetic
- NumPy Documentation - numpy.isclose
- PyTorch Documentation - torch.isclose
Related rules
- {rule:python:S6727} - The
abs_tol
parameter should be provided when using math.isclose
to compare values to
0