From 74c75998b099ec340ba68ec9451c6445436aada1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 1 Sep 2016 18:20:49 +0200 Subject: [PATCH] Math: added TypeTraits::equalsZero(). --- src/Magnum/Math/Test/TypeTraitsTest.cpp | 89 ++++++++++++++++++++++++- src/Magnum/Math/TypeTraits.h | 35 ++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/src/Magnum/Math/Test/TypeTraitsTest.cpp b/src/Magnum/Math/Test/TypeTraitsTest.cpp index 7790269b8..cb24fc050 100644 --- a/src/Magnum/Math/Test/TypeTraitsTest.cpp +++ b/src/Magnum/Math/Test/TypeTraitsTest.cpp @@ -41,8 +41,37 @@ struct TypeTraitsTest: Corrade::TestSuite::Tester { template void equalsFloatingPointLarge(); template void equalsFloatingPointInfinity(); template void equalsFloatingPointNaN(); + + template void equalsZeroIntegral(); + template void equalsZeroFloatingPoint(); + template void equalsZeroFloatingPointSmall(); + template void equalsZeroFloatingPointLarge(); +}; + +namespace { + +enum: std::size_t { EqualsZeroDataCount = 3 }; + +struct { + const char* name; + Float a, aStep; + Double b, bStep; + long double c, cStep; + + Float get(Float) const { return a; } + Float getStep(Float) const { return aStep; } + Double get(Double) const { return b; } + Double getStep(Double) const { return bStep; } + long double get(long double) const { return c; } + long double getStep(long double) const { return cStep; } +} EqualsZeroData[EqualsZeroDataCount] = { + {"", -3.141592653589793f, 5.0e-5f, -3.141592653589793, 5.0e-14, -3.141592653589793l, 5.0e-17l}, + {"small", 1.0e-6f, 5.0e-6f, -1.0e-15, 5.0e-15, 1.0e-18l, 5.0e-18l}, + {"large", 12345.0f, 0.2f, 12345678901234.0, 0.2, -12345678901234567.0l, 0.2l}, }; +} + TypeTraitsTest::TypeTraitsTest() { addTests({ &TypeTraitsTest::name, @@ -76,7 +105,27 @@ TypeTraitsTest::TypeTraitsTest() { &TypeTraitsTest::equalsFloatingPointInfinity, &TypeTraitsTest::equalsFloatingPointInfinity, &TypeTraitsTest::equalsFloatingPointNaN, - &TypeTraitsTest::equalsFloatingPointNaN}); + &TypeTraitsTest::equalsFloatingPointNaN, + + &TypeTraitsTest::equalsZeroIntegral, + &TypeTraitsTest::equalsZeroIntegral, + &TypeTraitsTest::equalsZeroIntegral, + &TypeTraitsTest::equalsZeroIntegral, + &TypeTraitsTest::equalsZeroIntegral, + &TypeTraitsTest::equalsZeroIntegral, + #ifndef CORRADE_TARGET_EMSCRIPTEN + &TypeTraitsTest::equalsZeroIntegral, + &TypeTraitsTest::equalsZeroIntegral, + #endif + }); + + addInstancedTests({ + &TypeTraitsTest::equalsZeroFloatingPoint, + &TypeTraitsTest::equalsZeroFloatingPoint, + #ifndef CORRADE_TARGET_EMSCRIPTEN + &TypeTraitsTest::equalsZeroFloatingPoint + #endif + }, EqualsZeroDataCount); } void TypeTraitsTest::name() { @@ -127,6 +176,44 @@ template void TypeTraitsTest::equalsFloatingPointNaN() { Constants::nan())); } +namespace { + /* Argh! Why there is no standard std::abs() for unsigned types? */ + template::value>::type> T abs(T value) { + return value; + } + template::value>::type> T abs(T value) { + return std::abs(value); + } +} + +template void TypeTraitsTest::equalsZeroIntegral() { + setTestCaseName(std::string{"equalsZeroIntegral<"} + TypeTraits::name() + ">"); + + const T a(-123); + const T b(-123); + const T magnitude = std::max(abs(a), abs(b)); + + CORRADE_VERIFY(TypeTraits::equals(a, b)); + CORRADE_VERIFY(TypeTraits::equalsZero(a - b, magnitude)); + CORRADE_VERIFY(!TypeTraits::equalsZero(a - b + TypeTraits::epsilon(), magnitude)); +} + +template void TypeTraitsTest::equalsZeroFloatingPoint() { + setTestCaseName(std::string{"equalsZeroFloatingPoint<"} + TypeTraits::name() + ">"); + setTestCaseDescription(EqualsZeroData[testCaseInstanceId()].name); + + const T a = EqualsZeroData[testCaseInstanceId()].get(T{}); + const T b = EqualsZeroData[testCaseInstanceId()].get(T{}); + const T step = EqualsZeroData[testCaseInstanceId()].getStep(T{}); + const T magnitude = std::max(abs(a), abs(b)); + + CORRADE_VERIFY(TypeTraits::equals(a + step/T(2.0), b)); + CORRADE_VERIFY(TypeTraits::equalsZero(a + step/T(2.0) - b, magnitude)); + + CORRADE_VERIFY(!TypeTraits::equals(a - step*T(2.0), b)); + CORRADE_VERIFY(!TypeTraits::equalsZero(a - step*T(2.0) - b, magnitude)); +} + }}} CORRADE_TEST_MAIN(Magnum::Math::Test::TypeTraitsTest) diff --git a/src/Magnum/Math/TypeTraits.h b/src/Magnum/Math/TypeTraits.h index 1e0db2f5b..22d725c16 100644 --- a/src/Magnum/Math/TypeTraits.h +++ b/src/Magnum/Math/TypeTraits.h @@ -72,6 +72,10 @@ namespace Implementation { constexpr static bool equals(T a, T b) { return a == b; } + + constexpr static bool equalsZero(T a, T) { + return !a; + } }; } @@ -123,6 +127,22 @@ template struct TypeTraits: Implementation::TypeTraitsDefault { * value), pure equality comparison everywhere else. */ static bool equals(T a, T b); + + /** + * @brief Fuzzy compare to zero with magnitude + * + * Uses fuzzy compare for floating-point types (using @ref epsilon() + * value), pure equality comparison everywhere else. Use this function when + * comparing e.g. a calculated nearly-zero difference with zero, knowing + * the magnitude of original values so the epsilon can be properly scaled. + * In other words, the following lines are equivalent: + * @code + * Float a, b; + * Math::TypeTraits::equals(a, b); + * Math::TypeTraits::equalsZero(a - b, Math::max(Math::abs(a), Math::abs(b))); + * @endcode + */ + static bool equalsZero(T a, T magnitude); #endif }; @@ -187,6 +207,7 @@ template struct TypeTraitsFloatingPoint: TypeTraitsName { TypeTraitsFloatingPoint() = delete; static bool equals(T a, T b); + static bool equalsZero(T a, T epsilon); }; /* Adapted from http://floating-point-gui.de/errors/comparison/ */ @@ -207,6 +228,20 @@ template bool TypeTraitsFloatingPoint::equals(const T a, const T b) return difference/(absA + absB) < TypeTraits::epsilon(); } +template bool TypeTraitsFloatingPoint::equalsZero(const T a, const T magnitude) { + /* Shortcut for binary equality */ + if(a == T(0.0)) return true; + + const T absA = std::abs(a); + + /* The value is extremely close to zero, relative error is meaningless */ + if(absA < TypeTraits::epsilon()) + return absA < TypeTraits::epsilon(); + + /* Relative error */ + return absA*T(0.5)/magnitude < TypeTraits::epsilon(); +} + } template<> struct TypeTraits: Implementation::TypeTraitsFloatingPoint {