/* * Copyright (C) 2014 Pavel Kirienko */ #ifndef UAVCAN_UTIL_COMPARISON_HPP_INCLUDED #define UAVCAN_UTIL_COMPARISON_HPP_INCLUDED #include #include namespace uavcan { /** * Exact comparison of two floats that suppresses the compiler warnings. */ template UAVCAN_EXPORT inline bool areFloatsExactlyEqual(const T& left, const T& right) { return (left <= right) && (left >= right); } /** * This function performs fuzzy comparison of two floating point numbers. * Type of T can be either float, double or long double. * For details refer to http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ * See also: @ref UAVCAN_FLOAT_COMPARISON_EPSILON_MULT. */ template UAVCAN_EXPORT inline bool areFloatsClose(T a, T b, const T& absolute_epsilon, const T& relative_epsilon) { // NAN if (isNaN(a) || isNaN(b)) { return false; } // Infinity if (isInfinity(a) || isInfinity(b)) { return areFloatsExactlyEqual(a, b); } // Close numbers near zero const T diff = std::fabs(a - b); if (diff <= absolute_epsilon) { return true; } // General case a = std::fabs(a); b = std::fabs(b); const T largest = (b > a) ? b : a; return (diff <= largest * relative_epsilon); } /** * This namespace contains implementation details for areClose(). * Don't try this at home. */ namespace are_close_impl_ { struct Applicable { char foo[1]; }; struct NotApplicable { long long foo[16]; }; template struct HasIsCloseMethod { template struct ConstRef { }; template struct ByValue { }; template static Applicable test(ConstRef*); template static Applicable test(ByValue*); template static NotApplicable test(...); enum { Result = sizeof(test(NULL)) }; }; /// First stage: bool L::isClose(R) template UAVCAN_EXPORT inline bool areCloseImplFirst(const L& left, const R& right, IntToType) { return left.isClose(right); } /// Second stage: bool R::isClose(L) template UAVCAN_EXPORT inline bool areCloseImplSecond(const L& left, const R& right, IntToType) { return right.isClose(left); } /// Second stage: L == R template UAVCAN_EXPORT inline bool areCloseImplSecond(const L& left, const R& right, IntToType) { return left == right; } /// First stage: select either L == R or bool R::isClose(L) template UAVCAN_EXPORT inline bool areCloseImplFirst(const L& left, const R& right, IntToType) { return are_close_impl_::areCloseImplSecond(left, right, IntToType::Result>()); } } // namespace are_close_impl_ /** * Generic fuzzy comparison function. * * This function properly handles floating point comparison, including mixed floating point type comparison, * e.g. float with long double. * * Two objects of types A and B will be fuzzy comparable if either method is defined: * - bool A::isClose(const B&) const * - bool A::isClose(B) const * - bool B::isClose(const A&) const * - bool B::isClose(A) const * * Call areClose(A, B) will be dispatched as follows: * * - If A and B are both floating point types (float, double, long double) - possibly different - the call will be * dispatched to @ref areFloatsClose(). If A and B are different types, value of the larger type will be coerced * to the smaller type, e.g. areClose(long double, float) --> areClose(float, float). * * - If A defines isClose() that accepts B, the call will be dispatched there. * * - If B defines isClose() that accepts A, the call will be dispatched there (A/B swapped). * * - Last resort is A == B. * * Alternatively, a custom behavior can be implemented via template specialization. * * See also: @ref UAVCAN_FLOAT_COMPARISON_EPSILON_MULT. * * Examples: * areClose(1.0, 1.0F) --> true * areClose(1.0, 1.0F + std::numeric_limits::epsilon()) --> true * areClose(1.0, 1.1) --> false * areClose("123", std::string("123")) --> true (using std::string's operator ==) * areClose(inf, inf) --> true * areClose(inf, -inf) --> false * areClose(nan, nan) --> false (any comparison with nan returns false) * areClose(123, "123") --> compilation error: operator == is not defined */ template UAVCAN_EXPORT inline bool areClose(const L& left, const R& right) { return are_close_impl_::areCloseImplFirst(left, right, IntToType::Result>()); } /* * Float comparison specializations */ template <> UAVCAN_EXPORT inline bool areClose(const float& left, const float& right) { return areFloatsClose(left, right, NumericTraits::epsilon(), NumericTraits::epsilon() * FloatComparisonEpsilonMult); } template <> UAVCAN_EXPORT inline bool areClose(const double& left, const double& right) { return areFloatsClose(left, right, NumericTraits::epsilon(), NumericTraits::epsilon() * FloatComparisonEpsilonMult); } template <> UAVCAN_EXPORT inline bool areClose(const long double& left, const long double& right) { return areFloatsClose(left, right, NumericTraits::epsilon(), NumericTraits::epsilon() * FloatComparisonEpsilonMult); } /* * Mixed floating type comparison - coercing larger type to smaller type */ template <> UAVCAN_EXPORT inline bool areClose(const float& left, const double& right) { return areClose(left, static_cast(right)); } template <> UAVCAN_EXPORT inline bool areClose(const double& left, const float& right) { return areClose(static_cast(left), right); } template <> UAVCAN_EXPORT inline bool areClose(const float& left, const long double& right) { return areClose(left, static_cast(right)); } template <> UAVCAN_EXPORT inline bool areClose(const long double& left, const float& right) { return areClose(static_cast(left), right); } template <> UAVCAN_EXPORT inline bool areClose(const double& left, const long double& right) { return areClose(left, static_cast(right)); } template <> UAVCAN_EXPORT inline bool areClose(const long double& left, const double& right) { return areClose(static_cast(left), right); } /** * Comparison against zero. * Helps to compare a floating point number against zero if the exact type is unknown. * For non-floating point types performs exact comparison against integer zero. */ template UAVCAN_EXPORT inline bool isCloseToZero(const T& x) { return x == 0; } template <> UAVCAN_EXPORT inline bool isCloseToZero(const float& x) { return areClose(x, static_cast(0.0F)); } template <> UAVCAN_EXPORT inline bool isCloseToZero(const double& x) { return areClose(x, static_cast(0.0)); } template <> UAVCAN_EXPORT inline bool isCloseToZero(const long double& x) { return areClose(x, static_cast(0.0L)); } } #endif // UAVCAN_UTIL_COMPARISON_HPP_INCLUDED