I have designed a class to perform float compare. I used Knuth's strong compare to do it. Float compare is really really tricky, I'm sure there's something wrong :) Any comments?
#include <limits>
#include <cmath>
#include <assert.h>
class FloatUtils {
private:
FloatUtils();
template<typename T>
static T safeDivision( const T& f1, const T& f2 );
public:
template<typename T>
static bool almostEqual( const T& a, const T& b, const T& relEpsilon = std::numeric_limits<T>::epsilon(),const T& absEpsilon = std::numeric_limits<T>::epsilon()) noexcept;
template<typename T>
static bool almostZero( const T& a, const T& absEpsilon = std::numeric_limits<T>::epsilon() ) noexcept;
};
template<typename T>
inline T FloatUtils::safeDivision( const T& f1, const T& f2 ) {
// Avoid overflow.
if ( (f2 < static_cast<T>(1)) && (f1 > f2 * std::numeric_limits<T>::max()) )
return std::numeric_limits<T>::max();
// Avoid underflow.
if ( (f1 == static_cast<T>(0)) || ((f2 > static_cast<T>(1)) && (f1 < f2 * std::numeric_limits<T>::min())) )
return static_cast<T>(0);
return f1 / f2;
}
template<typename T>
inline bool FloatUtils::almostEqual( const T& a, const T& b, const T& relEpsilon, const T& absEpsilon ) noexcept {
static_assert(!std::is_integral<T>::value, "T can be only no integral type");
static_assert(std::numeric_limits<T>::is_iec559, "IEEE754 supported only");
if ( !std::isfinite(a) || !std::isfinite(b) ) {
return false;
}
/**
* avoid division by zero checking exactly 0 value
*/
if ( a == 0 ) {
return almostZero(b, absEpsilon);
}
/**
* avoid division by zero checking exactly 0 value
*/
if ( b == 0 ) {
return almostZero(a, absEpsilon);
}
/**
* We use Knuth's formula modified, i.e. the right side of inequations may cause underflow,
* to prevent this, the formula scales the difference rather than epsilon.
* We don't nanage 0 or Inf here so we need to add the special cases above.
*/
T diff = std::abs(a - b);
T d1 = safeDivision<T>(diff, std::abs(a));
T d2 = safeDivision<T>(diff, std::abs(b));
return d1 <= relEpsilon && d2 <= relEpsilon;
}
template<typename T>
inline bool FloatUtils::almostZero( const T& a, const T& absEpsilon ) noexcept {
if ( std::abs(a) <= absEpsilon )
return true;
return false;
}
Test cases:
int main() {
assert(FloatUtils::almostEqual(3.145646, 3.145646) == true);
assert(FloatUtils::almostEqual(145697.78965, 145553.09186, 0.001) == true);
assert(FloatUtils::almostEqual(145697.78965, 145552.09186, 0.001) == false);
assert(FloatUtils::almostEqual(0.00001, 0.0, 0.1, 0.001) == true);
assert(FloatUtils::almostEqual(1.0, 1.0, 0.0) == true);
assert(FloatUtils::almostEqual(0.0, 1E-20, 1E-5) == true);
assert(FloatUtils::almostEqual(0.0, 1E-30, 1E-5) == true);
assert(FloatUtils::almostEqual(0.0, -1E-10, 0.1, 1E-9) == true);
assert(FloatUtils::almostEqual(0.0, -1E-10, 0.1, 1E-11) == false);
assert(FloatUtils::almostEqual(0.123456, 0.123457, 1E-6) == false);
assert(FloatUtils::almostEqual(0.123456, 0.123457, 1E-3) == true);
assert(FloatUtils::almostEqual(0.123456, -0.123457, 1E-3) == false);
assert(FloatUtils::almostEqual(1.23456E28, 1.23457E28, 1E-3) == true);
assert(FloatUtils::almostEqual(1.23456E-10, 1.23457E-10, 1E-3) == true);
assert(FloatUtils::almostEqual(1.111E-10, 1.112E-10, 0.000899) == false);
assert(FloatUtils::almostEqual(1.111E-10, 1.112E-10, 0.1) == true);
assert(FloatUtils::almostEqual(1.0, 1.0001, 1.1E-2) == true);
assert(FloatUtils::almostEqual(1.0002, 1.0001, 1.1E-2) == true);
assert(FloatUtils::almostEqual(1.0, 1.0002, 1.1E-4) == false);
return 0;
}