Floating-point number comparison has always been a problem in computer applications. Because a real number cannot often be precisely represented by floating-point numbers and floating-pointer computations have rounding errors, the order of floating point calculation matters for bitwise equality. For example, a + b + c might not be bitwise equal to c + b + a. Because the floating-point errors could accumulate and propagate, solving a problem using different but correct mathematical recipes might result in different outcomes. Therefore, we would often need to compare floating-point numbers to determine if our certain algorithm implementation produces correct results.
In this blog post, I would like to discuss how to compare floating-point numbers in C++ empirically.
C++ Floating-Point Number Comparison
It is common to see that a absolute tolerance value and a relative tolerance value were used to compare floating-point numbers, such as the numpy.allclose function. However, the problem is that the relative error between two small close floating-point numbers might be larger than the relative error between two large close floating-point numbers. Similarly, the absolute error between two small close floating-point numbers might be smaller than the absolute error between two large close floating-point numbers.
Therefore, we could use different absolute tolerance value and relative tolerance value for floating numbers in different ranges.
// Commutative (symmetric) template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> boolfloat_close(T a, T b, std::function<T(T, T)> const& rtol_func, std::function<T(T, T)> const& atol_func) { if (std::isinf(a) || std::isinf(b)) { returnfalse; } if (std::isnan(a) || std::isnan(b)) { returnfalse; }
T const abs_a{std::abs(a)}; T const abs_b{std::abs(b)}; T const abs_diff{std::abs(a - b)}; T const abs_sum{abs_a + abs_b}; T const rtol{rtol_func(a, b)}; T const atol{atol_func(a, b)};
if (a == b) { returntrue; } else { return (abs_diff / abs_sum < rtol && abs_diff < atol); } }
// This function can be customized to the user's needs. template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> T dynamic_rtol_func(T a, T b) { T const abs_a{std::abs(a)}; T const abs_b{std::abs(b)}; T const abs_sum{abs_a + abs_b};
// This function can be customized to the user's needs. template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> T dynamic_atol_func(T a, T b) { T const abs_a{std::abs(a)}; T const abs_b{std::abs(b)}; T const abs_sum{abs_a + abs_b};