Exact float comparison
| Vulnerability potential | Low |
| DDoS potential | None |
Floating point integers shouldn’t be compared using == operator
Impact
Comparing two floating-point values with == (or !=) asks whether they are
bit-for-bit identical, which is rarely what the programmer wants. Because most
decimal fractions cannot be represented exactly in binary, and because rounding
differs with the order of operations, two computations that are mathematically
equal often differ in the last bit. The equality test then fails when it
“should” succeed: a loop that waits for an accumulator to reach an exact value
never stops, a state machine misses a target, a convergence check never fires.
Conversely, values that differ by a negligible amount are treated as different.
The program produces wrong control flow without any error or warning.
Vulnerability potential
This defect has little direct security relevance; it is primarily a correctness and reliability bug.
- The most concrete risk is denial of service: a termination or convergence condition expressed as an exact equality may never become true, leaving a loop spinning forever and hanging the thread or process.
- As an indirect concern, if an equality test guards a security or financial decision (matching an exact amount, an exact key-derivation parameter, a sentinel value), platform- or optimization-dependent rounding could make the decision flip between builds. There is no memory-safety impact.
Technical details
IEEE-754 binary floating point represents numbers as sign * mantissa *
2^exponent. Only sums of powers of two are exact, so common decimals such as
0.1, 0.2, and 0.3 are stored as the nearest representable value, with a
tiny error. 0.1 + 0.2 rounds to a value just above 0.3, so 0.1 + 0.2 ==
0.3 is false.
Rounding and reassociation
Each operation rounds its result to the nearest representable value, and the
error depends on operand magnitudes and the order of operations. Because
floating-point addition and multiplication are not associative, (a + b) + c
and a + (b + c) can differ in the last bit, so two routes to the “same” value
need not be bit-identical.
Extra precision and contraction
Intermediate results may be kept at higher precision than the variable type:
historically x87 used 80-bit registers (controlled by FLT_EVAL_METHOD), and
fused multiply-add (-ffp-contract) computes a*b+c with a single rounding.
The same source can thus yield different bits depending on registers,
optimization level, and target, making exact equality non-portable.
NaN
NaN compares unequal to everything, including itself: x == x is false when
x is NaN, so ==/!= cannot even be used to test a value against itself.
Catching the issue
Compiler warnings
GCC and Clang have -Wfloat-equal, which warns about every ==/!= between
floating-point operands. It is not in -Wall, so enable it explicitly.
Static analysis
clang-tidy (clang-diagnostic-float-equal), PVS-Studio (V550), Coverity, and
CodeQL all flag direct floating-point equality comparisons. Linters for most
languages have an equivalent rule.
How to compare correctly
Compare with a tolerance instead of ==. Use an absolute epsilon for values
near a known scale, fabs(a - b) <= eps, or a relative/ULP comparison for
values spanning many magnitudes, fabs(a - b) <= eps * fmax(fabs(a), fabs(b)).
Choose the epsilon from the problem’s precision, not blindly from
FLT_EPSILON. Comparing against exact representable values (e.g. 0.0, or
integers small enough to be exact) is legitimate; the rule targets results of
inexact computation. Always handle NaN explicitly.
How to reproduce
Observe that the exact comparison reports “not equal” even though the values are mathematically equal, while the tolerance-based test succeeds.
#include <stdio.h>
#include <math.h>
int main(void)
{
double a = 0.1 + 0.2;
double b = 0.3;
if (a == b)
printf("exact: equal\n");
else
printf("exact: NOT equal (a - b = %.17g)\n", a - b);
if (fabs(a - b) <= 1e-9)
printf("epsilon: equal\n");
return 0;
}