diff --git a/libuavcan/include/uavcan/build_config.hpp b/libuavcan/include/uavcan/build_config.hpp index 1a686df550..028d19bd01 100644 --- a/libuavcan/include/uavcan/build_config.hpp +++ b/libuavcan/include/uavcan/build_config.hpp @@ -15,9 +15,9 @@ * This definition contains the integer year number after which the standard was named: * - 2003 for C++03 * - 2011 for C++11 - * + * * This config automatically sets according to the actual C++ standard used by the compiler. - * + * * In C++03 mode the library has almost zero dependency on the C++ standard library, which allows * to use it on platforms with a very limited C++ support. On the other hand, C++11 mode requires * many parts of the standard library (e.g. ), thus the user might want to force older @@ -125,7 +125,6 @@ namespace uavcan { - /** * Memory pool block size. * @@ -173,4 +172,16 @@ struct UAVCAN_EXPORT IsDynamicallyAllocatable } }; +/** + * Float comparison precision. + * For details refer to: + * http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + * https://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h + */ +#ifdef UAVCAN_FLOAT_COMPARISON_EPSILON_MULT +static const unsigned FloatComparisonEpsilonMult = UAVCAN_FLOAT_COMPARISON_EPSILON_MULT; +#else +static const unsigned FloatComparisonEpsilonMult = 10; +#endif + } diff --git a/libuavcan/include/uavcan/marshal/type_util.hpp b/libuavcan/include/uavcan/marshal/type_util.hpp index 4f433025d2..45df4d03c4 100644 --- a/libuavcan/include/uavcan/marshal/type_util.hpp +++ b/libuavcan/include/uavcan/marshal/type_util.hpp @@ -4,8 +4,9 @@ #pragma once -#include #include +#include +#include namespace uavcan { diff --git a/libuavcan/include/uavcan/util/comparison.hpp b/libuavcan/include/uavcan/util/comparison.hpp new file mode 100644 index 0000000000..11b712d3c5 --- /dev/null +++ b/libuavcan/include/uavcan/util/comparison.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#pragma once + +#include +#include + +namespace uavcan +{ +/** + * 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/ + */ +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 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); +} + +/** + * Generic comparison function. + * This function properly handles floating point comparison, including mixed floating point type comparison, + * e.g. float with long double. + */ +template +UAVCAN_EXPORT +inline bool areClose(const L& left, const R& right) +{ + return left == right; +} + +/* + * 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); +} + +} diff --git a/libuavcan/include/uavcan/util/templates.hpp b/libuavcan/include/uavcan/util/templates.hpp index 79bfdc41b2..703c05d57d 100644 --- a/libuavcan/include/uavcan/util/templates.hpp +++ b/libuavcan/include/uavcan/util/templates.hpp @@ -422,4 +422,50 @@ struct UAVCAN_EXPORT NumericTraits }; #endif +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 +# undef isnan +# undef isinf +# undef signbit +#endif + +/** + * Replacement for std::isnan() + */ +template +inline bool isNaN(T arg) +{ +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + return std::isnan(arg); +#else + // cppcheck-suppress duplicateExpression + return arg != arg; +#endif +} + +/** + * Replacement for std::isinf() + */ +template +inline bool isInfinity(T arg) +{ +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + return std::isinf(arg); +#else + return arg == NumericTraits::infinity() || arg == -NumericTraits::infinity(); +#endif +} + +/** + * Replacement for std::signbit() + */ +template +inline bool getSignBit(T arg) +{ +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + return std::signbit(arg); +#else + return arg < T(0) || (arg == T(0) && T(1) / arg < T(0)); +#endif +} + } diff --git a/libuavcan/src/marshal/uc_float_spec.cpp b/libuavcan/src/marshal/uc_float_spec.cpp index e09e94b052..07c0d980c1 100644 --- a/libuavcan/src/marshal/uc_float_spec.cpp +++ b/libuavcan/src/marshal/uc_float_spec.cpp @@ -14,59 +14,24 @@ # include #endif -#undef signbit -#undef isnan -#undef isinf - namespace uavcan { /* * IEEE754Converter * Float16 conversion algorithm: http://half.sourceforge.net/ (MIT License) */ -template -static inline bool signbit(T arg) -{ -#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 - return std::signbit(arg); -#else - return arg < T(0) || (arg == T(0) && T(1) / arg < T(0)); -#endif -} - -template -static inline bool isnan(T arg) -{ -#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 - return std::isnan(arg); -#else - // cppcheck-suppress duplicateExpression - return arg != arg; -#endif -} - -template -static inline bool isinf(T arg) -{ -#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 - return std::isinf(arg); -#else - return arg == NumericTraits::infinity() || arg == -NumericTraits::infinity(); -#endif -} - uint16_t IEEE754Converter::nativeNonIeeeToHalf(float value) { - uint16_t hbits = signbit(value) << 15; + uint16_t hbits = static_cast(getSignBit(value)) << 15; if (value == 0.0f) { return hbits; } - if (isnan(value)) + if (isNaN(value)) { return hbits | 0x7FFFU; } - if (isinf(value)) + if (isInfinity(value)) { return hbits | 0x7C00U; } diff --git a/libuavcan/test/util/comparison.cpp b/libuavcan/test/util/comparison.cpp new file mode 100644 index 0000000000..f261ddf68b --- /dev/null +++ b/libuavcan/test/util/comparison.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#include +#include + +TEST(Comparison, Basic) +{ + // Basic same type floats + ASSERT_TRUE(uavcan::areClose(0.1, 0.1)); + ASSERT_TRUE(uavcan::areClose(0.1F, 0.1F)); + ASSERT_TRUE(uavcan::areClose(0.1L, 0.1L)); + + // Basic mixed type floats + ASSERT_TRUE(uavcan::areClose(0.1F, 0.1)); + ASSERT_TRUE(uavcan::areClose(0.1, 0.1F)); + ASSERT_TRUE(uavcan::areClose(0.1F, 0.1L)); + ASSERT_TRUE(uavcan::areClose(0.1L, 0.1F)); + ASSERT_TRUE(uavcan::areClose(0.1, 0.1L)); + ASSERT_TRUE(uavcan::areClose(0.1L, 0.1)); + + // Basic floats + ASSERT_TRUE(uavcan::areClose(0x07, '\x07')); + ASSERT_TRUE(uavcan::areClose(123456789LL, 123456789)); + ASSERT_TRUE(uavcan::areClose("123", std::string("123"))); + + // Non-equality + ASSERT_FALSE(uavcan::areClose(-0.1, 0.1)); + ASSERT_FALSE(uavcan::areClose(-0.1F, 0.0L)); + ASSERT_FALSE(uavcan::areClose("123", std::string("12"))); + ASSERT_FALSE(uavcan::areClose(0x07L, '\0')); +} + +TEST(Comparison, FloatSpecialCase) +{ + ASSERT_FALSE(uavcan::areClose(0.1, std::numeric_limits::infinity())); + + ASSERT_TRUE(uavcan::areClose(std::numeric_limits::infinity(), + std::numeric_limits::infinity())); + + ASSERT_FALSE(uavcan::areClose(std::numeric_limits::infinity(), -std::numeric_limits::infinity())); + + ASSERT_FALSE(uavcan::areClose(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN())); +} + +TEST(Comparison, FloatULP) +{ + ASSERT_FALSE(uavcan::areClose(0.100000000000000001L, 0.1L)); + ASSERT_TRUE( uavcan::areClose(0.100000000000000001, 0.1L)); + ASSERT_TRUE( uavcan::areClose(0.100000000000000001F, 0.1L)); + + // Near zero + ASSERT_TRUE( uavcan::areClose(0.0F, std::numeric_limits::epsilon())); + ASSERT_TRUE( uavcan::areClose(0.0F, -std::numeric_limits::epsilon())); + ASSERT_FALSE(uavcan::areClose(0.0F, std::numeric_limits::epsilon() * 2)); +} + +TEST(Comparison, BruteforceValidation) +{ + const std::streamsize default_precision = std::cout.precision(); + std::cout.precision(20); + + float x = -uavcan::NumericTraits::max(); + + while (x < uavcan::NumericTraits::max()) + { + const float y1 = x + std::abs(x) * (uavcan::NumericTraits::epsilon() * 2); // Still equal + const float y2 = x + uavcan::max(std::abs(x), 1.F) * (uavcan::NumericTraits::epsilon() * 20); // Nope + + if (!uavcan::areClose(y1, x)) + { + std::cout << "y1=" << y1 << " y2=" << y2 << " x=" << x << std::endl; + ASSERT_TRUE(false); + } + if (uavcan::areClose(y2, x)) + { + std::cout << "y1=" << y1 << " y2=" << y2 << " x=" << x << std::endl; + ASSERT_TRUE(false); + } + + x = y2; + } + + std::cout.precision(default_precision); +}