3 min read

Epsilon Equal

Also available as PDF and at GitHub.

In C, when are two floating-point numbers equal? The answer is not exactly simple. It depends on what equal means. One might assume naively that x == y answers true if the two numbers at x and y match, but not always so since floating-point arithmetic attempts to model real numbers within a limited level of precision.

Most of the time, the distinction is not significant. In the real world, the difference sometimes does matter, however, e.g. where values derive from computations. C compilers may even issue a warning message when comparing floats.

Rcpp::evalCpp("0.0 == 0.0")
[1] TRUE
Rcpp::evalCpp("0.0 == DBL_EPSILON")
[1] FALSE

This result demonstrates that two quantities need only differ by a minuscule amount for inequality when under direct comparison. The standard C maths library defines DBL_EPSILON as the smallest possible floating-point positive number.

Solution in Impure Logic

In logic, the solution appears below.

%!  epsilon_equal(+X:number, +Y:number) is semidet.
%!  epsilon_equal(+Epsilons:number, +X:number, +Y:number) is semidet.
%
%   Succeeds only when the absolute  difference   between  the two given
%   numbers X and Y is less than  or   equal  to epsilon, or some factor
%   (Epsilons) of epsilon according to rounding limitations.

epsilon_equal(X, Y) :- epsilon_equal(1, X, Y).

epsilon_equal(Epsilons, X, Y) :- Epsilons * epsilon >= abs(X - Y).

In C99

The C99 solution works better for embedded systems where fancy backtracking logic is not readily available.

#pragma once

/*
 * float.h for DBL_EPSILON and friends
 * math.h for fabs(3) and friends
 */
#include <float.h>
#include <math.h>

/*!
 * \brief Epsilon equality for double-precision floating-point numbers.
 * \details Succeeds only when the absolute difference between the two given
 * numbers X and Y is less than or equal to epsilon, or some factor (epsilons)
 * of epsilon according to rounding limitations.
 */
// [[Rcpp::export]]
static inline bool fepsiloneq(unsigned n, double x, double y) {
  return n * DBL_EPSILON >= fabs(x - y);
}

/*!
 * \brief Epsilon equality for single-precision floating-point numbers.
 */
// [[Rcpp::export]]
static inline bool fepsiloneqf(unsigned n, float x, float y) {
  return n * FLT_EPSILON >= fabsf(x - y);
}

The \(\epsilon\)-equal family use factors of the smallest possible difference to determine equality. Two function implementations exist: one for single- and another for double-precision.

The implementation avoids division since that reduces precision. Subtraction computes the differences between two measurements. The \(n\times\epsilon\) threshold typically computes out at compile time provided that n is some constant factor—since the functions appear as static and inline. That makes the computation of equality pretty light on the floating-point unit.

Now we can apply an \(\epsilon\)-precision level when matching real numbers. In the examples below, the second test fails because \(1\times10^{−15}\) exceeds the \(\epsilon\) equality threshold. It would succeed if using fepsiloneqf because single-precision floating-point \(\epsilon\) is a larger quantity.

fepsiloneq(1, 0.0, 0.0)
[1] TRUE
fepsiloneq(1, 0.0, 1e-15)
[1] FALSE
fepsiloneq(1, 0.0, 1e-16)
[1] TRUE

Conclusions

For embedded systems, this approach assumes a floating-point unit, else a soft-float library. Embedded FPUs are not uncommon these days, especially in 32-bit cores.

In effect, \(\epsilon\)-equality amounts to

\[ x-n\epsilon\leq y\leq x+n\epsilon \]

or \(y\) between \(x\pm n\epsilon\) or vice versa. Such comparisons become, effectively, a symmetrical interval intersection test. Ideal for floating-point number matching.