Testing Float Equality with Epsilon in C

Due to the constraints of the IEEE 754 floating-point standard, calculating fractional values in the C programming language frequently introduces tiny rounding errors, making exact equality checks using == unreliable. To determine if two floating-point values are practically identical, developers rely on epsilon-based comparison methods. This article covers the primary epsilon techniques used in C—absolute epsilon comparison, relative epsilon comparison, and a combined hybrid approach—explaining how each works, their limitations, and how to choose appropriate thresholds using standard C libraries.

The Problem with Direct Equality

In C, variables of type float and double represent real numbers using binary fractions. Many decimal fractions cannot be represented exactly in binary (such as 0.1), leading to minor inaccuracies after arithmetic operations. For instance, evaluating (0.1 + 0.2) == 0.3 usually yields false. To solve this, you define a small tolerance value known as epsilon (\(\epsilon\)) and evaluate whether the difference between two numbers falls within that bound.

1. Absolute Epsilon Comparison

The absolute difference method checks if the absolute difference between two numbers is less than a fixed threshold.

#include <stdbool.h>
#include <math.h>

bool nearly_equal_abs(double a, double b, double epsilon) {
    return fabs(a - b) < epsilon;
}

When to Use

  • Effective when the expected magnitude of the numbers is known in advance and remains close to 1.0 or near zero.
  • Ideal for fixed-precision contexts, such as game physics coordinates constrained to a small bounding box.

Limitations

  • Scale Sensitivity: If numbers are very large (e.g., \(10^9\)), standard precision gaps between representable floats are much larger than a tiny static epsilon like 0.00001, causing valid approximations to fail. Conversely, for very small numbers (e.g., \(10^{-9}\)), the static epsilon may be far too loose.

2. Relative Epsilon Comparison

To handle numbers across widely varying scales, compare the difference relative to the magnitude of the operands.

#include <stdbool.h>
#include <math.h>

bool nearly_equal_rel(double a, double b, double epsilon) {
    double diff = fabs(a - b);
    double largest = fmax(fabs(a), fabs(b));
    return diff <= largest * epsilon;
}

When to Use

  • Effective for scientific calculations where numbers span multiple orders of magnitude.
  • Scales automatically with the size of the inputs.

Limitations

  • Failure Near Zero: When either a or b is zero (or both are close to zero), largest * epsilon shrinks to zero or near zero, causing the relative check to fail even when values are practically identical.

3. Combined (Hybrid) Epsilon Comparison

The most robust general-purpose solution combines absolute and relative tolerance. It uses an absolute epsilon when numbers are close to zero and transitions to a relative epsilon as numbers scale up.

#include <stdbool.h>
#include <math.h>
#include <float.h>

bool nearly_equal_combined(double a, double b, double abs_epsilon, double rel_epsilon) {
    double diff = fabs(a - b);
    
    // Check absolute difference for values near zero
    if (diff <= abs_epsilon) {
        return true;
    }
    
    // Fall back to relative difference for larger values
    double largest = fmax(fabs(a), fabs(b));
    return diff <= largest * rel_epsilon;
}

Selecting Epsilon Values

C provides built-in machine epsilon constants in <float.h>:

  • FLT_EPSILON for single-precision float (approximately \(1.19 \times 10^{-7}\))
  • DBL_EPSILON for double-precision double (approximately \(2.22 \times 10^{-16}\))

Machine epsilon defines the difference between 1.0 and the next representable value. Because rounding errors accumulate over multiple mathematical operations, using DBL_EPSILON directly is often too strict. A common practice is scaling the standard epsilon by an acceptable error factor (such as 10 * DBL_EPSILON or 1e-9 for double precision) depending on the complexity and depth of the calculations involved.