/* * Copyright (C) 2014 Pavel Kirienko */ #pragma once #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&) * - bool A::isClose(B) * - bool B::isClose(const A&) * - bool B::isClose(A) * Alternatively, a custom specialization of this function can be defined. * * Note that all floating types and their combinations are fuzzy comparable by default: * - float * - double * - long double * * If the arguments aren't fuzzy comparable, this function will resort to the plain comparison operator ==. * * 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 * 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)); } }