From d2d418a9cbda255e193c4ed6c57cdf20137fa309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 13 Oct 2019 11:56:50 +0200 Subject: [PATCH] Add a Relative Comparison matcher for floating point numbers It checks Knuth's _close enough with tolerance_ relationship, that is `|lhs - rhs| <= epsilon * max(|lhs|, |rhs|)`, rather then the _very close with tolerance_ relationship that can be written down as `|lhs - rhs| <= epsilon * min(|lhs|, |rhs|)`. This is because it is the more common model around the internet, and as such is likely to be less surprising to the users. In the future we might want to provide the other model as well. Closes #1746 --- include/internal/catch_matchers_floating.cpp | 51 +++++- include/internal/catch_matchers_floating.h | 20 +++ .../Baselines/compact.sw.approved.txt | 18 ++ .../Baselines/console.std.approved.txt | 2 +- .../Baselines/console.sw.approved.txt | 110 +++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 6 +- .../SelfTest/Baselines/xml.sw.approved.txt | 168 +++++++++++++++++- .../SelfTest/UsageTests/Matchers.tests.cpp | 45 +++++ 8 files changed, 410 insertions(+), 10 deletions(-) diff --git a/include/internal/catch_matchers_floating.cpp b/include/internal/catch_matchers_floating.cpp index c9a79ad1..be969397 100644 --- a/include/internal/catch_matchers_floating.cpp +++ b/include/internal/catch_matchers_floating.cpp @@ -11,6 +11,7 @@ #include "catch_to_string.hpp" #include "catch_tostring.h" +#include #include #include #include @@ -57,7 +58,8 @@ namespace { auto ulpDiff = std::abs(lc - rc); return static_cast(ulpDiff) <= maxUlpDiff; } -} + +} //end anonymous namespace #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) @@ -98,6 +100,17 @@ FP step(FP start, FP direction, uint64_t steps) { } return start; } + +namespace { + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); + } + +} + } // end anonymous namespace namespace Matchers { @@ -180,6 +193,25 @@ namespace Floating { //return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); } + WithinRelMatcher::WithinRelMatcher(double target, double epsilon): + m_target(target), + m_epsilon(epsilon){ + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); + } + + bool WithinRelMatcher::match(double const& matchee) const { + const auto relMargin = m_epsilon * std::max(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin)? 0 : relMargin); + } + + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); + } + }// namespace Floating @@ -196,6 +228,23 @@ Floating::WithinAbsMatcher WithinAbs(double target, double margin) { return Floating::WithinAbsMatcher(target, margin); } +Floating::WithinRelMatcher WithinRel(double target, double eps) { + return Floating::WithinRelMatcher(target, eps); +} + +Floating::WithinRelMatcher WithinRel(double target) { + return Floating::WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); +} + +Floating::WithinRelMatcher WithinRel(float target, float eps) { + return Floating::WithinRelMatcher(target, eps); +} + +Floating::WithinRelMatcher WithinRel(float target) { + return Floating::WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); +} + + } // namespace Matchers } // namespace Catch diff --git a/include/internal/catch_matchers_floating.h b/include/internal/catch_matchers_floating.h index 7db9cc30..0f2ff49f 100644 --- a/include/internal/catch_matchers_floating.h +++ b/include/internal/catch_matchers_floating.h @@ -35,6 +35,20 @@ namespace Matchers { FloatingPointKind m_type; }; + // Given IEEE-754 format for floats and doubles, we can assume + // that float -> double promotion is lossless. Given this, we can + // assume that if we do the standard relative comparison of + // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get + // the same result if we do this for floats, as if we do this for + // doubles that were promoted from floats. + struct WithinRelMatcher : MatcherBase { + WithinRelMatcher(double target, double epsilon); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_epsilon; + }; } // namespace Floating @@ -43,6 +57,12 @@ namespace Matchers { Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); Floating::WithinAbsMatcher WithinAbs(double target, double margin); + Floating::WithinRelMatcher WithinRel(double target, double eps); + // defaults epsilon to 100*numeric_limits::epsilon() + Floating::WithinRelMatcher WithinRel(double target); + Floating::WithinRelMatcher WithinRel(float target, float eps); + // defaults epsilon to 100*numeric_limits::epsilon() + Floating::WithinRelMatcher WithinRel(float target); } // namespace Matchers } // namespace Catch diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 9a5ce16f..ed47d349 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -407,6 +407,11 @@ Misc.tests.cpp:: passed: Factorial(1) == 1 for: 1 == 1 Misc.tests.cpp:: passed: Factorial(2) == 2 for: 2 == 2 Misc.tests.cpp:: passed: Factorial(3) == 6 for: 6 == 6 Misc.tests.cpp:: passed: Factorial(10) == 3628800 for: 3628800 (0x) == 3628800 (0x) +Matchers.tests.cpp:: passed: 10., WithinRel(11.1, 0.1) for: 10.0 and 11.1 are within 10% of each other +Matchers.tests.cpp:: passed: 10., !WithinRel(11.2, 0.1) for: 10.0 not and 11.2 are within 10% of each other +Matchers.tests.cpp:: passed: 1., !WithinRel(0., 0.99) for: 1.0 not and 0 are within 99% of each other +Matchers.tests.cpp:: passed: -0., WithinRel(0.) for: -0.0 and 0 are within 2.22045e-12% of each other +Matchers.tests.cpp:: passed: v1, WithinRel(v2) for: 0.0 and 2.22507e-308 are within 2.22045e-12% of each other Matchers.tests.cpp:: passed: 1., WithinAbs(1., 0) for: 1.0 is within 0.0 of 1.0 Matchers.tests.cpp:: passed: 0., WithinAbs(1., 1) for: 0.0 is within 1.0 of 1.0 Matchers.tests.cpp:: passed: 0., !WithinAbs(1., 0.99) for: 0.0 not is within 0.99 of 1.0 @@ -423,9 +428,18 @@ Matchers.tests.cpp:: passed: 1., WithinULP(1., 0) for: 1.0 is withi Matchers.tests.cpp:: passed: -0., WithinULP(0., 0) for: -0.0 is within 0 ULPs of 0.0 ([0.00000000000000000, 0.00000000000000000]) Matchers.tests.cpp:: passed: 1., WithinAbs(1., 0.5) || WithinULP(2., 1) for: 1.0 ( is within 0.5 of 1.0 or is within 1 ULPs of 2.0 ([1.99999999999999978, 2.00000000000000044]) ) Matchers.tests.cpp:: passed: 1., WithinAbs(2., 0.5) || WithinULP(1., 0) for: 1.0 ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0 ([1.00000000000000000, 1.00000000000000000]) ) +Matchers.tests.cpp:: passed: 0.0001, WithinAbs(0., 0.001) || WithinRel(0., 0.1) for: 0.0001 ( is within 0.001 of 0.0 or and 0 are within 10% of each other ) Matchers.tests.cpp:: passed: WithinAbs(1., 0.) Matchers.tests.cpp:: passed: WithinAbs(1., -1.), std::domain_error Matchers.tests.cpp:: passed: WithinULP(1., 0) +Matchers.tests.cpp:: passed: WithinRel(1., 0.) +Matchers.tests.cpp:: passed: WithinRel(1., -0.2), std::domain_error +Matchers.tests.cpp:: passed: WithinRel(1., 1.), std::domain_error +Matchers.tests.cpp:: passed: 10.f, WithinRel(11.1f, 0.1f) for: 10.0f and 11.1 are within 10% of each other +Matchers.tests.cpp:: passed: 10.f, !WithinRel(11.2f, 0.1f) for: 10.0f not and 11.2 are within 10% of each other +Matchers.tests.cpp:: passed: 1.f, !WithinRel(0.f, 0.99f) for: 1.0f not and 0 are within 99% of each other +Matchers.tests.cpp:: passed: -0.f, WithinRel(0.f) for: -0.0f and 0 are within 0.00119209% of each other +Matchers.tests.cpp:: passed: v1, WithinRel(v2) for: 0.0f and 1.17549e-38 are within 0.00119209% of each other Matchers.tests.cpp:: passed: 1.f, WithinAbs(1.f, 0) for: 1.0f is within 0.0 of 1.0 Matchers.tests.cpp:: passed: 0.f, WithinAbs(1.f, 1) for: 0.0f is within 1.0 of 1.0 Matchers.tests.cpp:: passed: 0.f, !WithinAbs(1.f, 0.99f) for: 0.0f not is within 0.9900000095 of 1.0 @@ -443,10 +457,14 @@ Matchers.tests.cpp:: passed: 1.f, WithinULP(1.f, 0) for: 1.0f is wi Matchers.tests.cpp:: passed: -0.f, WithinULP(0.f, 0) for: -0.0f is within 0 ULPs of 0.0f ([0.00000000000000000, 0.00000000000000000]) Matchers.tests.cpp:: passed: 1.f, WithinAbs(1.f, 0.5) || WithinULP(1.f, 1) for: 1.0f ( is within 0.5 of 1.0 or is within 1 ULPs of 1.0f ([0.99999994039535522, 1.00000011920928955]) ) Matchers.tests.cpp:: passed: 1.f, WithinAbs(2.f, 0.5) || WithinULP(1.f, 0) for: 1.0f ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0f ([1.00000000000000000, 1.00000000000000000]) ) +Matchers.tests.cpp:: passed: 0.0001f, WithinAbs(0.f, 0.001f) || WithinRel(0.f, 0.1f) for: 0.0001f ( is within 0.001 of 0.0 or and 0 are within 10% of each other ) Matchers.tests.cpp:: passed: WithinAbs(1.f, 0.f) Matchers.tests.cpp:: passed: WithinAbs(1.f, -1.f), std::domain_error Matchers.tests.cpp:: passed: WithinULP(1.f, 0) Matchers.tests.cpp:: passed: WithinULP(1.f, static_cast(-1)), std::domain_error +Matchers.tests.cpp:: passed: WithinRel(1.f, 0.f) +Matchers.tests.cpp:: passed: WithinRel(1.f, -0.2f), std::domain_error +Matchers.tests.cpp:: passed: WithinRel(1.f, 1.f), std::domain_error Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 5a3fb409..0143bbd2 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1381,5 +1381,5 @@ due to unexpected exception with message: =============================================================================== test cases: 303 | 229 passed | 70 failed | 4 failed as expected -assertions: 1597 | 1445 passed | 131 failed | 21 failed as expected +assertions: 1615 | 1463 passed | 131 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 1e290592..5c140a96 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -3088,6 +3088,46 @@ Misc.tests.cpp:: PASSED: with expansion: 3628800 (0x) == 3628800 (0x) +------------------------------------------------------------------------------- +Floating point matchers: double + Relative +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 10., WithinRel(11.1, 0.1) ) +with expansion: + 10.0 and 11.1 are within 10% of each other + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 10., !WithinRel(11.2, 0.1) ) +with expansion: + 10.0 not and 11.2 are within 10% of each other + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1., !WithinRel(0., 0.99) ) +with expansion: + 1.0 not and 0 are within 99% of each other + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( -0., WithinRel(0.) ) +with expansion: + -0.0 and 0 are within 2.22045e-12% of each other + +------------------------------------------------------------------------------- +Floating point matchers: double + Relative + Some subnormal values +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( v1, WithinRel(v2) ) +with expansion: + 0.0 and 2.22507e-308 are within 2.22045e-12% of each other + ------------------------------------------------------------------------------- Floating point matchers: double Margin @@ -3191,6 +3231,11 @@ with expansion: 1.0 ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0 ([1.00000000000000000, 1.00000000000000000]) ) +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 0.0001, WithinAbs(0., 0.001) || WithinRel(0., 0.1) ) +with expansion: + 0.0001 ( is within 0.001 of 0.0 or and 0 are within 10% of each other ) + ------------------------------------------------------------------------------- Floating point matchers: double Constructor validation @@ -3207,6 +3252,55 @@ Matchers.tests.cpp:: PASSED: Matchers.tests.cpp:: PASSED: REQUIRE_NOTHROW( WithinULP(1., 0) ) +Matchers.tests.cpp:: PASSED: + REQUIRE_NOTHROW( WithinRel(1., 0.) ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( WithinRel(1., -0.2), std::domain_error ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( WithinRel(1., 1.), std::domain_error ) + +------------------------------------------------------------------------------- +Floating point matchers: float + Relative +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 10.f, WithinRel(11.1f, 0.1f) ) +with expansion: + 10.0f and 11.1 are within 10% of each other + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 10.f, !WithinRel(11.2f, 0.1f) ) +with expansion: + 10.0f not and 11.2 are within 10% of each other + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1.f, !WithinRel(0.f, 0.99f) ) +with expansion: + 1.0f not and 0 are within 99% of each other + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( -0.f, WithinRel(0.f) ) +with expansion: + -0.0f and 0 are within 0.00119209% of each other + +------------------------------------------------------------------------------- +Floating point matchers: float + Relative + Some subnormal values +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( v1, WithinRel(v2) ) +with expansion: + 0.0f and 1.17549e-38 are within 0.00119209% of each other + ------------------------------------------------------------------------------- Floating point matchers: float Margin @@ -3316,6 +3410,11 @@ with expansion: 1.0f ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0f ([1. 00000000000000000, 1.00000000000000000]) ) +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 0.0001f, WithinAbs(0.f, 0.001f) || WithinRel(0.f, 0.1f) ) +with expansion: + 0.0001f ( is within 0.001 of 0.0 or and 0 are within 10% of each other ) + ------------------------------------------------------------------------------- Floating point matchers: float Constructor validation @@ -3335,6 +3434,15 @@ Matchers.tests.cpp:: PASSED: Matchers.tests.cpp:: PASSED: REQUIRE_THROWS_AS( WithinULP(1.f, static_cast(-1)), std::domain_error ) +Matchers.tests.cpp:: PASSED: + REQUIRE_NOTHROW( WithinRel(1.f, 0.f) ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( WithinRel(1.f, -0.2f), std::domain_error ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( WithinRel(1.f, 1.f), std::domain_error ) + ------------------------------------------------------------------------------- Generators -- adapters Filtering by predicate @@ -12806,5 +12914,5 @@ Misc.tests.cpp:: PASSED: =============================================================================== test cases: 303 | 213 passed | 86 failed | 4 failed as expected -assertions: 1614 | 1445 passed | 148 failed | 21 failed as expected +assertions: 1632 | 1463 passed | 148 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index ed1ef6b7..6003cd73 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -402,10 +402,14 @@ Message.tests.cpp: + + + + diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index f61217b6..27a3a2ad 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -3657,6 +3657,52 @@ Nor would this +
+ + + 10., WithinRel(11.1, 0.1) + + + 10.0 and 11.1 are within 10% of each other + + + + + 10., !WithinRel(11.2, 0.1) + + + 10.0 not and 11.2 are within 10% of each other + + + + + 1., !WithinRel(0., 0.99) + + + 1.0 not and 0 are within 99% of each other + + + + + -0., WithinRel(0.) + + + -0.0 and 0 are within 2.22045e-12% of each other + + +
+ + + v1, WithinRel(v2) + + + 0.0 and 2.22507e-308 are within 2.22045e-12% of each other + + + +
+ +
@@ -3792,7 +3838,15 @@ Nor would this 1.0 ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0 ([1.00000000000000000, 1.00000000000000000]) ) - + + + 0.0001, WithinAbs(0., 0.001) || WithinRel(0., 0.1) + + + 0.0001 ( is within 0.001 of 0.0 or and 0 are within 10% of each other ) + + +
@@ -3819,11 +3873,81 @@ Nor would this WithinULP(1., 0) - + + + WithinRel(1., 0.) + + + WithinRel(1., 0.) + + + + + WithinRel(1., -0.2), std::domain_error + + + WithinRel(1., -0.2), std::domain_error + + + + + WithinRel(1., 1.), std::domain_error + + + WithinRel(1., 1.), std::domain_error + + +
+
+ + + 10.f, WithinRel(11.1f, 0.1f) + + + 10.0f and 11.1 are within 10% of each other + + + + + 10.f, !WithinRel(11.2f, 0.1f) + + + 10.0f not and 11.2 are within 10% of each other + + + + + 1.f, !WithinRel(0.f, 0.99f) + + + 1.0f not and 0 are within 99% of each other + + + + + -0.f, WithinRel(0.f) + + + -0.0f and 0 are within 0.00119209% of each other + + +
+ + + v1, WithinRel(v2) + + + 0.0f and 1.17549e-38 are within 0.00119209% of each other + + + +
+ +
@@ -3967,7 +4091,15 @@ Nor would this 1.0f ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0f ([1.00000000000000000, 1.00000000000000000]) ) - + + + 0.0001f, WithinAbs(0.f, 0.001f) || WithinRel(0.f, 0.1f) + + + 0.0001f ( is within 0.001 of 0.0 or and 0 are within 10% of each other ) + + +
@@ -4002,7 +4134,31 @@ Nor would this WithinULP(1.f, static_cast<uint64_t>(-1)), std::domain_error - + + + WithinRel(1.f, 0.f) + + + WithinRel(1.f, 0.f) + + + + + WithinRel(1.f, -0.2f), std::domain_error + + + WithinRel(1.f, -0.2f), std::domain_error + + + + + WithinRel(1.f, 1.f), std::domain_error + + + WithinRel(1.f, 1.f), std::domain_error + + +
@@ -15220,7 +15376,7 @@ loose text artifact
- + - + diff --git a/projects/SelfTest/UsageTests/Matchers.tests.cpp b/projects/SelfTest/UsageTests/Matchers.tests.cpp index 8e9148b3..ae05a73c 100644 --- a/projects/SelfTest/UsageTests/Matchers.tests.cpp +++ b/projects/SelfTest/UsageTests/Matchers.tests.cpp @@ -339,6 +339,20 @@ namespace { namespace MatchersTests { } TEST_CASE("Floating point matchers: float", "[matchers][floating-point]") { + SECTION("Relative") { + REQUIRE_THAT(10.f, WithinRel(11.1f, 0.1f)); + REQUIRE_THAT(10.f, !WithinRel(11.2f, 0.1f)); + REQUIRE_THAT( 1.f, !WithinRel(0.f, 0.99f)); + REQUIRE_THAT(-0.f, WithinRel(0.f)); + SECTION("Some subnormal values") { + auto v1 = std::numeric_limits::min(); + auto v2 = v1; + for (int i = 0; i < 5; ++i) { + v2 = std::nextafter(v1, 0); + } + REQUIRE_THAT(v1, WithinRel(v2)); + } + } SECTION("Margin") { REQUIRE_THAT(1.f, WithinAbs(1.f, 0)); REQUIRE_THAT(0.f, WithinAbs(1.f, 1)); @@ -366,6 +380,7 @@ namespace { namespace MatchersTests { SECTION("Composed") { REQUIRE_THAT(1.f, WithinAbs(1.f, 0.5) || WithinULP(1.f, 1)); REQUIRE_THAT(1.f, WithinAbs(2.f, 0.5) || WithinULP(1.f, 0)); + REQUIRE_THAT(0.0001f, WithinAbs(0.f, 0.001f) || WithinRel(0.f, 0.1f)); } SECTION("Constructor validation") { REQUIRE_NOTHROW(WithinAbs(1.f, 0.f)); @@ -373,10 +388,28 @@ namespace { namespace MatchersTests { REQUIRE_NOTHROW(WithinULP(1.f, 0)); REQUIRE_THROWS_AS(WithinULP(1.f, static_cast(-1)), std::domain_error); + + REQUIRE_NOTHROW(WithinRel(1.f, 0.f)); + REQUIRE_THROWS_AS(WithinRel(1.f, -0.2f), std::domain_error); + REQUIRE_THROWS_AS(WithinRel(1.f, 1.f), std::domain_error); } } TEST_CASE("Floating point matchers: double", "[matchers][floating-point]") { + SECTION("Relative") { + REQUIRE_THAT(10., WithinRel(11.1, 0.1)); + REQUIRE_THAT(10., !WithinRel(11.2, 0.1)); + REQUIRE_THAT(1., !WithinRel(0., 0.99)); + REQUIRE_THAT(-0., WithinRel(0.)); + SECTION("Some subnormal values") { + auto v1 = std::numeric_limits::min(); + auto v2 = v1; + for (int i = 0; i < 5; ++i) { + v2 = std::nextafter(v1, 0); + } + REQUIRE_THAT(v1, WithinRel(v2)); + } + } SECTION("Margin") { REQUIRE_THAT(1., WithinAbs(1., 0)); REQUIRE_THAT(0., WithinAbs(1., 1)); @@ -402,12 +435,17 @@ namespace { namespace MatchersTests { SECTION("Composed") { REQUIRE_THAT(1., WithinAbs(1., 0.5) || WithinULP(2., 1)); REQUIRE_THAT(1., WithinAbs(2., 0.5) || WithinULP(1., 0)); + REQUIRE_THAT(0.0001, WithinAbs(0., 0.001) || WithinRel(0., 0.1)); } SECTION("Constructor validation") { REQUIRE_NOTHROW(WithinAbs(1., 0.)); REQUIRE_THROWS_AS(WithinAbs(1., -1.), std::domain_error); REQUIRE_NOTHROW(WithinULP(1., 0)); + + REQUIRE_NOTHROW(WithinRel(1., 0.)); + REQUIRE_THROWS_AS(WithinRel(1., -0.2), std::domain_error); + REQUIRE_THROWS_AS(WithinRel(1., 1.), std::domain_error); } } @@ -415,6 +453,13 @@ namespace { namespace MatchersTests { REQUIRE_THAT(NAN, !WithinAbs(NAN, 0)); REQUIRE_THAT(NAN, !(WithinAbs(NAN, 100) || WithinULP(NAN, 123))); REQUIRE_THAT(NAN, !WithinULP(NAN, 123)); + REQUIRE_THAT(INFINITY, WithinRel(INFINITY)); + REQUIRE_THAT(-INFINITY, !WithinRel(INFINITY)); + REQUIRE_THAT(1., !WithinRel(INFINITY)); + REQUIRE_THAT(INFINITY, !WithinRel(1.)); + REQUIRE_THAT(NAN, !WithinRel(NAN)); + REQUIRE_THAT(1., !WithinRel(NAN)); + REQUIRE_THAT(NAN, !WithinRel(1.)); } TEST_CASE("Arbitrary predicate matcher", "[matchers][generic]") {