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.