Why Floating-Point Equality Fails in C
Directly comparing floating-point numbers using the equality operator
(==) in C frequently produces incorrect results due to how
fractional numbers are stored in hardware. Because computers represent
floating-point values using finite binary representations defined by the
IEEE 754 standard, simple decimal fractions cannot always be represented
with complete precision. This limitation introduces minute rounding
errors during calculation, causing two mathematically identical values
to possess slightly different bit patterns in memory. Consequently,
exact bitwise comparisons fail, making direct equality checks unreliable
for floating-point arithmetic.
Binary Representation Limits (IEEE 754)
Standard C data types like float and double
rely on the IEEE 754 standard, which encodes numbers into three
components: a sign bit, an exponent, and a mantissa (fraction). While
integers and powers of two can be represented exactly, base-10 fractions
like 0.1, 0.2, or 0.3 translate
into recurring binary fractions that require an infinite sequence of
bits.
Because a 32-bit float or a 64-bit double
allocates a finite number of bits to the mantissa, the infinite binary
representation must be truncated and rounded. For instance, storing
0.1 in a double does not store the exact value
\(0.1\), but rather an approximation
close to 0.100000000000000005551115123126.
Rounding Errors and Operator Behavior
The equality operator (==) in C evaluates whether two
operands have the exact same bitwise value. It does not account for
minor discrepancies caused by rounding.
Consider this classic example:
#include <stdio.h>
int main(void) {
double a = 0.1 + 0.2;
double b = 0.3;
if (a == b) {
printf("Equal\n");
} else {
printf("Not Equal\n");
}
return 0;
}This program outputs Not Equal. The sum
0.1 + 0.2 produces
0.300000000000000044408920985006, whereas 0.3
is stored as 0.299999999999999988897769753748. Because the
bit sequences differ at the least significant bits, ==
evaluates to false.
Compiler Optimizations and Register Precision
Discrepancies can also arise from how compilers optimize code and use processor registers. On architectures such as x86, intermediate calculations may be held in 80-bit extended-precision registers (the x87 FPU) instead of standard 64-bit memory locations. If one value is truncated to 64 bits in memory and another remains at 80 bits in a register, an equality check between the two values will fail, even if both originated from the same computation.
The Correct Approach: Epsilon Comparison
To safely compare two floating-point values in C, you must check whether their absolute difference falls within an acceptable tolerance threshold, commonly referred to as an "epsilon" (\(\epsilon\)).
For numbers near \(1.0\), an
absolute epsilon check using fabs() from
<math.h> is typically sufficient:
#include <stdio.h>
#include <math.h>
#define EPSILON 0.00001
int main(void) {
double a = 0.1 + 0.2;
double b = 0.3;
if (fabs(a - b) < EPSILON) {
printf("Equal within tolerance\n");
}
return 0;
}For numbers that span drastically different magnitudes (very large or very small values), a relative epsilon check is preferred:
int are_close(double a, double b, double epsilon) {
return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon );
}This method scales the threshold relative to the magnitude of the inputs, ensuring reliable comparisons across wide numerical ranges.