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
aorbis zero (or both are close to zero),largest * epsilonshrinks 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_EPSILONfor single-precisionfloat(approximately \(1.19 \times 10^{-7}\))DBL_EPSILONfor double-precisiondouble(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.