diff --git a/docs/matchers.md b/docs/matchers.md index 355158895f..536d1f81d9 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -161,6 +161,11 @@ away from the `target` value. The short version of what this means is that there is no more than `maxUlpDiff - 1` representeable floating point numbers between the argument for matching and the `target` value. +**Important**: The WithinULP matcher requires the platform to use the +[IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) representation for +floating point numbers. + + `WithinRel` creates a matcher that accepts floating point numbers that are _approximately equal_ with the `target` with tolerance of `eps.` Specifically, it matches if diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 270f3fdd8f..5d7aa8c369 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,7 @@ set(INTERNAL_HEADERS ${SOURCES_DIR}/internal/catch_errno_guard.hpp ${SOURCES_DIR}/internal/catch_exception_translator_registry.hpp ${SOURCES_DIR}/internal/catch_fatal_condition_handler.hpp + ${SOURCES_DIR}/internal/catch_floating_point_helpers.hpp ${SOURCES_DIR}/internal/catch_unique_name.hpp ${SOURCES_DIR}/generators/catch_generator_exception.hpp ${SOURCES_DIR}/generators/catch_generators.hpp @@ -160,6 +161,7 @@ set(IMPL_SOURCES ${SOURCES_DIR}/internal/catch_enum_values_registry.cpp ${SOURCES_DIR}/internal/catch_exception_translator_registry.cpp ${SOURCES_DIR}/internal/catch_fatal_condition_handler.cpp + ${SOURCES_DIR}/internal/catch_floating_point_helpers.cpp ${SOURCES_DIR}/generators/internal/catch_generators_combined_tu.cpp ${SOURCES_DIR}/interfaces/catch_interfaces_combined_tu.cpp ${SOURCES_DIR}/interfaces/catch_interfaces_reporter.cpp diff --git a/src/catch2/catch_all.hpp b/src/catch2/catch_all.hpp index c6ab59ecab..6b8ecf4e05 100644 --- a/src/catch2/catch_all.hpp +++ b/src/catch2/catch_all.hpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include diff --git a/src/catch2/internal/catch_floating_point_helpers.cpp b/src/catch2/internal/catch_floating_point_helpers.cpp new file mode 100644 index 0000000000..55a95a2846 --- /dev/null +++ b/src/catch2/internal/catch_floating_point_helpers.cpp @@ -0,0 +1,32 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include + +#include + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f) { + static_assert(sizeof(float) == sizeof(uint32_t), "Important ULP matcher assumption violated"); + uint32_t i; + std::memcpy(&i, &f, sizeof(f)); + return i; + } + + uint64_t convertToBits(double d) { + static_assert(sizeof(double) == sizeof(uint64_t), "Important ULP matcher assumption violated"); + uint64_t i; + std::memcpy(&i, &d, sizeof(d)); + return i; + } + + } // end namespace Detail +} // end namespace Catch + diff --git a/src/catch2/internal/catch_floating_point_helpers.hpp b/src/catch2/internal/catch_floating_point_helpers.hpp new file mode 100644 index 0000000000..3e73b3a723 --- /dev/null +++ b/src/catch2/internal/catch_floating_point_helpers.hpp @@ -0,0 +1,88 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED +#define CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED + +#include + +#include +#include +#include +#include + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f); + uint64_t convertToBits(double d); + + } // end namespace Detail + + + /** + * Calculates the ULP distance between two floating point numbers + * + * The ULP distance of two floating point numbers is the count of + * valid floating point numbers representable between them. + * + * There are some exceptions between how this function counts the + * distance, and the interpretation of the standard as implemented. + * by e.g. `nextafter`. For this function it always holds that: + * * `(x == y) => ulpDistance(x, y) == 0` (so `ulpDistance(-0, 0) == 0`) + * * `ulpDistance(maxFinite, INF) == 1` + * * `ulpDistance(x, -x) == 2 * ulpDistance(x, 0)` + * + * \pre `!isnan( lhs )` + * \pre `!isnan( rhs )` + * \pre floating point numbers are represented in IEEE-754 format + */ + template + uint64_t ulpDistance( FP lhs, FP rhs ) { + assert( std::numeric_limits::is_iec559 && + "ulpDistance assumes IEEE-754 format for floating point types" ); + assert( !Catch::isnan( lhs ) && + "Distance between NaN and number is not meaningful" ); + assert( !Catch::isnan( rhs ) && + "Distance between NaN and number is not meaningful" ); + + // We want X == Y to imply 0 ULP distance even if X and Y aren't + // bit-equal (-0 and 0), or X - Y != 0 (same sign infinities). + if ( lhs == rhs ) { return 0; } + + // We need a properly typed positive zero for type inference. + static constexpr FP positive_zero{}; + + // We want to ensure that +/- 0 is always represented as positive zero + if ( lhs == positive_zero ) { lhs = positive_zero; } + if ( rhs == positive_zero ) { rhs = positive_zero; } + + // If arguments have different signs, we can handle them by summing + // how far are they from 0 each. + if ( std::signbit( lhs ) != std::signbit( rhs ) ) { + return ulpDistance( std::abs( lhs ), positive_zero ) + + ulpDistance( std::abs( rhs ), positive_zero ); + } + + // When both lhs and rhs are of the same sign, we can just + // read the numbers bitwise as integers, and then subtract them + // (assuming IEEE). + uint64_t lc = Detail::convertToBits( lhs ); + uint64_t rc = Detail::convertToBits( rhs ); + + // The ulp distance between two numbers is symmetric, so to avoid + // dealing with overflows we want the bigger converted number on the lhs + if ( lc < rc ) { + std::swap( lc, rc ); + } + + return lc - rc; + } + +} // end namespace Catch + +#endif // CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_floating_point.cpp b/src/catch2/matchers/catch_matchers_floating_point.cpp index b3c1c33555..ad0fd378c5 100644 --- a/src/catch2/matchers/catch_matchers_floating_point.cpp +++ b/src/catch2/matchers/catch_matchers_floating_point.cpp @@ -10,12 +10,12 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -24,20 +24,6 @@ namespace Catch { namespace { - int32_t convert(float f) { - static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); - int32_t i; - std::memcpy(&i, &f, sizeof(f)); - return i; - } - - int64_t convert(double d) { - static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); - int64_t i; - std::memcpy(&i, &d, sizeof(d)); - return i; - } - template bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { // Comparison with NaN should always be false. @@ -46,17 +32,10 @@ namespace { return false; } - auto lc = convert(lhs); - auto rc = convert(rhs); - - if ((lc < 0) != (rc < 0)) { - // Potentially we can have +0 and -0 - return lhs == rhs; - } + // This should also handle positive and negative zeros, infinities + const auto ulpDist = ulpDistance(lhs, rhs); - // static cast as a workaround for IBM XLC - auto ulpDiff = std::abs(static_cast(lc - rc)); - return static_cast(ulpDiff) <= maxUlpDiff; + return ulpDist <= maxUlpDiff; } #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) @@ -131,6 +110,9 @@ namespace Detail { CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double || m_ulps < (std::numeric_limits::max)(), "Provided ULP is impossibly large for a float comparison."); + CATCH_ENFORCE( std::numeric_limits::is_iec559, + "WithinUlp matcher only supports platforms with " + "IEEE-754 compatible floating point representation" ); } #if defined(__clang__) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1a49039824..a1e3422680 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -80,6 +80,7 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/IntrospectiveTests/Clara.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/CmdLine.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/Details.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/FloatingPoint.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/InternalBenchmark.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 04ba422007..5d599b6efa 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -24,6 +24,8 @@ Nor would this :test-result: PASS #1954 - 7 arg template test case sig compiles - 1, 1, 1, 1, 1, 0, 0 :test-result: PASS #1954 - 7 arg template test case sig compiles - 5, 1, 1, 1, 1, 0, 0 :test-result: PASS #1954 - 7 arg template test case sig compiles - 5, 3, 1, 1, 1, 0, 0 +:test-result: PASS #2152 - ULP checks between differently signed values were wrong - double +:test-result: PASS #2152 - ULP checks between differently signed values were wrong - float :test-result: XFAIL #748 - captures with unexpected exceptions :test-result: PASS #809 :test-result: PASS #833 @@ -282,6 +284,7 @@ Message from section two :test-result: PASS classify_outliers :test-result: PASS comparisons between const int variables :test-result: PASS comparisons between int variables +:test-result: PASS convertToBits :test-result: PASS empty tags are not allowed :test-result: PASS erfc_inv :test-result: PASS estimate_clock_resolution diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index 11e0ac043c..556db26166 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -80,6 +80,10 @@ PartTracker.tests.cpp:: passed: n for: 3 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: +Matchers.tests.cpp:: passed: smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0 is within 2 ULPs of -4.9406564584124654e-324 ([-1.4821969375237396e-323, 4.9406564584124654e-324]) +Matchers.tests.cpp:: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e-324, -0.0000000000000000e+00]) +Matchers.tests.cpp:: passed: smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) +Matchers.tests.cpp:: passed: smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) Exception.tests.cpp:: failed: unexpected exception with message: 'answer := 42' with 1 message: 'expected exception' Exception.tests.cpp:: failed: unexpected exception with message: 'answer := 42'; expression was: thisThrows() with 1 message: 'expected exception' Exception.tests.cpp:: passed: thisThrows() with 1 message: 'answer := 42' @@ -586,6 +590,7 @@ Matchers.tests.cpp:: passed: 10.f, !WithinAbs( 11.f, 0.5f ) for: 10 Matchers.tests.cpp:: passed: -10.f, WithinAbs( -10.f, 0.5f ) for: -10.0f is within 0.5 of -10.0 Matchers.tests.cpp:: passed: -10.f, WithinAbs( -9.6f, 0.5f ) for: -10.0f is within 0.5 of -9.6000003815 Matchers.tests.cpp:: passed: 1.f, WithinULP( 1.f, 0 ) for: 1.0f is within 0 ULPs of 1.00000000e+00f ([1.00000000e+00, 1.00000000e+00]) +Matchers.tests.cpp:: passed: -1.f, WithinULP( -1.f, 0 ) for: -1.0f is within 0 ULPs of -1.00000000e+00f ([-1.00000000e+00, -1.00000000e+00]) Matchers.tests.cpp:: passed: nextafter( 1.f, 2.f ), WithinULP( 1.f, 1 ) for: 1.0f is within 1 ULPs of 1.00000000e+00f ([9.99999940e-01, 1.00000012e+00]) Matchers.tests.cpp:: passed: 0.f, WithinULP( nextafter( 0.f, 1.f ), 1 ) for: 0.0f is within 1 ULPs of 1.40129846e-45f ([0.00000000e+00, 2.80259693e-45]) Matchers.tests.cpp:: passed: 1.f, WithinULP( nextafter( 1.f, 0.f ), 1 ) for: 1.0f is within 1 ULPs of 9.99999940e-01f ([9.99999881e-01, 1.00000000e+00]) @@ -2065,6 +2070,16 @@ Condition.tests.cpp:: passed: long_var == unsigned_char_var for: 1 Condition.tests.cpp:: passed: long_var == unsigned_short_var for: 1 == 1 Condition.tests.cpp:: passed: long_var == unsigned_int_var for: 1 == 1 Condition.tests.cpp:: passed: long_var == unsigned_long_var for: 1 == 1 +FloatingPoint.tests.cpp:: passed: convertToBits( 0.f ) == 0 for: 0 == 0 +FloatingPoint.tests.cpp:: passed: convertToBits( -0.f ) == ( 1ULL << 31 ) for: 2147483648 (0x) +== +2147483648 (0x) +FloatingPoint.tests.cpp:: passed: convertToBits( 0. ) == 0 for: 0 == 0 +FloatingPoint.tests.cpp:: passed: convertToBits( -0. ) == ( 1ULL << 63 ) for: 9223372036854775808 (0x) +== +9223372036854775808 (0x) +FloatingPoint.tests.cpp:: passed: convertToBits( std::numeric_limits::denorm_min() ) == 1 for: 1 == 1 +FloatingPoint.tests.cpp:: passed: convertToBits( std::numeric_limits::denorm_min() ) == 1 for: 1 == 1 Tag.tests.cpp:: passed: Catch::TestCaseInfo("", { "test with an empty tag", "[]" }, dummySourceLineInfo) InternalBenchmark.tests.cpp:: passed: erfc_inv(1.103560) == Approx(-0.09203687623843015) for: -0.0920368762 == Approx( -0.0920368762 ) InternalBenchmark.tests.cpp:: passed: erfc_inv(1.067400) == Approx(-0.05980291115763361) for: -0.0598029112 == Approx( -0.0598029112 ) diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index f38cedfec3..8704f8b373 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1386,6 +1386,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 365 | 289 passed | 70 failed | 6 failed as expected -assertions: 2092 | 1940 passed | 129 failed | 23 failed as expected +test cases: 368 | 292 passed | 70 failed | 6 failed as expected +assertions: 2103 | 1951 passed | 129 failed | 23 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index bcf0a3ab7e..08cef9ace3 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -744,6 +744,41 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: +------------------------------------------------------------------------------- +#2152 - ULP checks between differently signed values were wrong - double +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) ) +with expansion: + 0.0 is within 2 ULPs of -4.9406564584124654e-324 ([-1.4821969375237396e-323, + 4.9406564584124654e-324]) + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) ) +with expansion: + 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e- + 324, -0.0000000000000000e+00]) + +------------------------------------------------------------------------------- +#2152 - ULP checks between differently signed values were wrong - float +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) ) +with expansion: + 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) ) +with expansion: + 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0. + 00000000e+00]) + ------------------------------------------------------------------------------- #748 - captures with unexpected exceptions outside assertions @@ -4490,6 +4525,12 @@ Matchers.tests.cpp:: PASSED: with expansion: 1.0f is within 0 ULPs of 1.00000000e+00f ([1.00000000e+00, 1.00000000e+00]) +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( -1.f, WithinULP( -1.f, 0 ) ) +with expansion: + -1.0f is within 0 ULPs of -1.00000000e+00f ([-1.00000000e+00, -1.00000000e+ + 00]) + Matchers.tests.cpp:: PASSED: REQUIRE_THAT( nextafter( 1.f, 2.f ), WithinULP( 1.f, 1 ) ) with expansion: @@ -14597,6 +14638,46 @@ Condition.tests.cpp:: PASSED: with expansion: 1 == 1 +------------------------------------------------------------------------------- +convertToBits +------------------------------------------------------------------------------- +FloatingPoint.tests.cpp: +............................................................................... + +FloatingPoint.tests.cpp:: PASSED: + CHECK( convertToBits( 0.f ) == 0 ) +with expansion: + 0 == 0 + +FloatingPoint.tests.cpp:: PASSED: + CHECK( convertToBits( -0.f ) == ( 1ULL << 31 ) ) +with expansion: + 2147483648 (0x) + == + 2147483648 (0x) + +FloatingPoint.tests.cpp:: PASSED: + CHECK( convertToBits( 0. ) == 0 ) +with expansion: + 0 == 0 + +FloatingPoint.tests.cpp:: PASSED: + CHECK( convertToBits( -0. ) == ( 1ULL << 63 ) ) +with expansion: + 9223372036854775808 (0x) + == + 9223372036854775808 (0x) + +FloatingPoint.tests.cpp:: PASSED: + CHECK( convertToBits( std::numeric_limits::denorm_min() ) == 1 ) +with expansion: + 1 == 1 + +FloatingPoint.tests.cpp:: PASSED: + CHECK( convertToBits( std::numeric_limits::denorm_min() ) == 1 ) +with expansion: + 1 == 1 + ------------------------------------------------------------------------------- empty tags are not allowed ------------------------------------------------------------------------------- @@ -16852,6 +16933,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 365 | 273 passed | 86 failed | 6 failed as expected -assertions: 2109 | 1940 passed | 146 failed | 23 failed as expected +test cases: 368 | 276 passed | 86 failed | 6 failed as expected +assertions: 2120 | 1951 passed | 146 failed | 23 failed as expected diff --git a/tests/SelfTest/Baselines/console.swa4.approved.txt b/tests/SelfTest/Baselines/console.swa4.approved.txt index ee504415a8..df003bd3fc 100644 --- a/tests/SelfTest/Baselines/console.swa4.approved.txt +++ b/tests/SelfTest/Baselines/console.swa4.approved.txt @@ -744,6 +744,41 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: +------------------------------------------------------------------------------- +#2152 - ULP checks between differently signed values were wrong - double +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) ) +with expansion: + 0.0 is within 2 ULPs of -4.9406564584124654e-324 ([-1.4821969375237396e-323, + 4.9406564584124654e-324]) + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) ) +with expansion: + 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e- + 324, -0.0000000000000000e+00]) + +------------------------------------------------------------------------------- +#2152 - ULP checks between differently signed values were wrong - float +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) ) +with expansion: + 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) + +Matchers.tests.cpp:: PASSED: + CHECK_THAT( smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) ) +with expansion: + 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0. + 00000000e+00]) + ------------------------------------------------------------------------------- #748 - captures with unexpected exceptions outside assertions @@ -924,6 +959,6 @@ Condition.tests.cpp:: FAILED: CHECK( true != true ) =============================================================================== -test cases: 31 | 26 passed | 3 failed | 2 failed as expected -assertions: 99 | 92 passed | 4 failed | 3 failed as expected +test cases: 33 | 28 passed | 3 failed | 2 failed as expected +assertions: 103 | 96 passed | 4 failed | 3 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 1edfdc934e..dfd2ebe331 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -47,6 +47,8 @@ Nor would this + + FAILED: @@ -1548,6 +1550,7 @@ Misc.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index a36a617138..924a14b19c 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -77,6 +77,9 @@ + + + @@ -988,6 +991,8 @@ Exception.tests.cpp: + + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 89c7997355..724cd499b9 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -158,6 +158,14 @@ ok {test-number} - ok {test-number} - # #1954 - 7 arg template test case sig compiles - 5, 3, 1, 1, 1, 0, 0 ok {test-number} - +# #2152 - ULP checks between differently signed values were wrong - double +ok {test-number} - smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0 is within 2 ULPs of -4.9406564584124654e-324 ([-1.4821969375237396e-323, 4.9406564584124654e-324]) +# #2152 - ULP checks between differently signed values were wrong - double +ok {test-number} - smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e-324, -0.0000000000000000e+00]) +# #2152 - ULP checks between differently signed values were wrong - float +ok {test-number} - smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) for: 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) +# #2152 - ULP checks between differently signed values were wrong - float +ok {test-number} - smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) for: 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) # #748 - captures with unexpected exceptions not ok {test-number} - unexpected exception with message: 'answer := 42' with 1 message: 'expected exception' # #748 - captures with unexpected exceptions @@ -1155,6 +1163,8 @@ ok {test-number} - -10.f, WithinAbs( -9.6f, 0.5f ) for: -10.0f is within 0.5 of # Floating point matchers: float ok {test-number} - 1.f, WithinULP( 1.f, 0 ) for: 1.0f is within 0 ULPs of 1.00000000e+00f ([1.00000000e+00, 1.00000000e+00]) # Floating point matchers: float +ok {test-number} - -1.f, WithinULP( -1.f, 0 ) for: -1.0f is within 0 ULPs of -1.00000000e+00f ([-1.00000000e+00, -1.00000000e+00]) +# Floating point matchers: float ok {test-number} - nextafter( 1.f, 2.f ), WithinULP( 1.f, 1 ) for: 1.0f is within 1 ULPs of 1.00000000e+00f ([9.99999940e-01, 1.00000012e+00]) # Floating point matchers: float ok {test-number} - 0.f, WithinULP( nextafter( 0.f, 1.f ), 1 ) for: 0.0f is within 1 ULPs of 1.40129846e-45f ([0.00000000e+00, 2.80259693e-45]) @@ -3703,6 +3713,18 @@ ok {test-number} - long_var == unsigned_short_var for: 1 == 1 ok {test-number} - long_var == unsigned_int_var for: 1 == 1 # comparisons between int variables ok {test-number} - long_var == unsigned_long_var for: 1 == 1 +# convertToBits +ok {test-number} - convertToBits( 0.f ) == 0 for: 0 == 0 +# convertToBits +ok {test-number} - convertToBits( -0.f ) == ( 1ULL << 31 ) for: 2147483648 (0x) == 2147483648 (0x) +# convertToBits +ok {test-number} - convertToBits( 0. ) == 0 for: 0 == 0 +# convertToBits +ok {test-number} - convertToBits( -0. ) == ( 1ULL << 63 ) for: 9223372036854775808 (0x) == 9223372036854775808 (0x) +# convertToBits +ok {test-number} - convertToBits( std::numeric_limits::denorm_min() ) == 1 for: 1 == 1 +# convertToBits +ok {test-number} - convertToBits( std::numeric_limits::denorm_min() ) == 1 for: 1 == 1 # empty tags are not allowed ok {test-number} - Catch::TestCaseInfo("", { "test with an empty tag", "[]" }, dummySourceLineInfo) # erfc_inv @@ -4220,5 +4242,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2109 +1..2120 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index 3a463fd172..5291c85d22 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -50,6 +50,10 @@ Tricky.tests.cpp:|nexplicit failure with message:|n "1514"'] ##teamcity[testFinished name='#1954 - 7 arg template test case sig compiles - 5, 1, 1, 1, 1, 0, 0' duration="{duration}"] ##teamcity[testStarted name='#1954 - 7 arg template test case sig compiles - 5, 3, 1, 1, 1, 0, 0'] ##teamcity[testFinished name='#1954 - 7 arg template test case sig compiles - 5, 3, 1, 1, 1, 0, 0' duration="{duration}"] +##teamcity[testStarted name='#2152 - ULP checks between differently signed values were wrong - double'] +##teamcity[testFinished name='#2152 - ULP checks between differently signed values were wrong - double' duration="{duration}"] +##teamcity[testStarted name='#2152 - ULP checks between differently signed values were wrong - float'] +##teamcity[testFinished name='#2152 - ULP checks between differently signed values were wrong - float' duration="{duration}"] ##teamcity[testStarted name='#748 - captures with unexpected exceptions'] Exception.tests.cpp:|nunexpected exception with messages:|n "answer := 42"|n "expected exception"- failure ignore as test marked as |'ok to fail|'|n'] Exception.tests.cpp:|nunexpected exception with messages:|n "answer := 42"|n "expected exception"|n REQUIRE_NOTHROW( thisThrows() )|nwith expansion:|n thisThrows()|n- failure ignore as test marked as |'ok to fail|'|n'] @@ -690,6 +694,8 @@ Misc.tests.cpp:|nexpression failed|n REQUIRE( testCheckedIf( false ##teamcity[testFinished name='comparisons between const int variables' duration="{duration}"] ##teamcity[testStarted name='comparisons between int variables'] ##teamcity[testFinished name='comparisons between int variables' duration="{duration}"] +##teamcity[testStarted name='convertToBits'] +##teamcity[testFinished name='convertToBits' duration="{duration}"] ##teamcity[testStarted name='empty tags are not allowed'] ##teamcity[testFinished name='empty tags are not allowed' duration="{duration}"] ##teamcity[testStarted name='erfc_inv'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 9c6fd5f4a1..5dde47fc9f 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -650,6 +650,44 @@ Nor would this + + + + smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) + + + 0.0 is within 2 ULPs of -4.9406564584124654e-324 ([-1.4821969375237396e-323, 4.9406564584124654e-324]) + + + + + smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) + + + 0.0 not is within 1 ULPs of -4.9406564584124654e-324 ([-9.8813129168249309e-324, -0.0000000000000000e+00]) + + + + + + + + smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) + + + 0.0f is within 2 ULPs of -1.40129846e-45f ([-4.20389539e-45, 1.40129846e-45]) + + + + + smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) + + + 0.0f not is within 1 ULPs of -1.40129846e-45f ([-2.80259693e-45, -0.00000000e+00]) + + + +
@@ -5087,6 +5125,14 @@ Nor would this 1.0f is within 0 ULPs of 1.00000000e+00f ([1.00000000e+00, 1.00000000e+00]) + + + -1.f, WithinULP( -1.f, 0 ) + + + -1.0f is within 0 ULPs of -1.00000000e+00f ([-1.00000000e+00, -1.00000000e+00]) + + nextafter( 1.f, 2.f ), WithinULP( 1.f, 1 ) @@ -5135,7 +5181,7 @@ Nor would this -0.0f is within 0 ULPs of 0.00000000e+00f ([0.00000000e+00, 0.00000000e+00]) - +
@@ -17257,6 +17303,61 @@ There is no extra whitespace here + + + + convertToBits( 0.f ) == 0 + + + 0 == 0 + + + + + convertToBits( -0.f ) == ( 1ULL << 31 ) + + + 2147483648 (0x) +== +2147483648 (0x) + + + + + convertToBits( 0. ) == 0 + + + 0 == 0 + + + + + convertToBits( -0. ) == ( 1ULL << 63 ) + + + 9223372036854775808 (0x) +== +9223372036854775808 (0x) + + + + + convertToBits( std::numeric_limits<float>::denorm_min() ) == 1 + + + 1 == 1 + + + + + convertToBits( std::numeric_limits<double>::denorm_min() ) == 1 + + + 1 == 1 + + + + @@ -19826,9 +19927,9 @@ loose text artifact
- - + + - - + + diff --git a/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp b/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp new file mode 100644 index 0000000000..7a1ca110a2 --- /dev/null +++ b/tests/SelfTest/IntrospectiveTests/FloatingPoint.tests.cpp @@ -0,0 +1,66 @@ +#include +#include +#include + + +TEST_CASE("convertToBits", "[floating-point][conversion]") { + using Catch::Detail::convertToBits; + + CHECK( convertToBits( 0.f ) == 0 ); + CHECK( convertToBits( -0.f ) == ( 1ULL << 31 ) ); + CHECK( convertToBits( 0. ) == 0 ); + CHECK( convertToBits( -0. ) == ( 1ULL << 63 ) ); + CHECK( convertToBits( std::numeric_limits::denorm_min() ) == 1 ); + CHECK( convertToBits( std::numeric_limits::denorm_min() ) == 1 ); +} + +TEMPLATE_TEST_CASE("type-shared ulpDistance tests", "[floating-point][ulp][approvals]", float, double) { + using FP = TestType; + using Catch::ulpDistance; + + // Distance between zeros is zero + CHECK( ulpDistance( FP{}, FP{} ) == 0 ); + CHECK( ulpDistance( FP{}, -FP{} ) == 0 ); + CHECK( ulpDistance( -FP{}, -FP{} ) == 0 ); + + // Distance between same-sign infinities is zero + static constexpr FP infinity = std::numeric_limits::infinity(); + CHECK( ulpDistance( infinity, infinity ) == 0 ); + CHECK( ulpDistance( -infinity, -infinity ) == 0 ); + + // Distance between max-finite-val and same sign infinity is 1 + static constexpr FP max_finite = std::numeric_limits::max(); + CHECK( ulpDistance( max_finite, infinity ) == 1 ); + CHECK( ulpDistance( -max_finite, -infinity ) == 1 ); + + // Distance between X and 0 is half of distance between X and -X + CHECK( ulpDistance( -infinity, infinity ) == + 2 * ulpDistance( infinity, FP{} ) ); + CHECK( 2 * ulpDistance( FP{ -2. }, FP{} ) == + ulpDistance( FP{ -2. }, FP{ 2. } ) ); + CHECK( 2 * ulpDistance( FP{ 2. }, FP{} ) == + ulpDistance( FP{ -2. }, FP{ 2. } ) ); + + // Denorms are supported + CHECK( ulpDistance( std::numeric_limits::denorm_min(), FP{} ) == 1 ); + CHECK( ulpDistance( std::numeric_limits::denorm_min(), -FP{} ) == 1 ); + CHECK( ulpDistance( -std::numeric_limits::denorm_min(), FP{} ) == 1 ); + CHECK( ulpDistance( -std::numeric_limits::denorm_min(), -FP{} ) == 1 ); + CHECK( ulpDistance( std::numeric_limits::denorm_min(), + -std::numeric_limits::denorm_min() ) == 2 ); + + // Machine epsilon + CHECK( ulpDistance( FP{ 1. }, + FP{ 1. } + std::numeric_limits::epsilon() ) == 1 ); + CHECK( ulpDistance( -FP{ 1. }, + -FP{ 1. } - std::numeric_limits::epsilon() ) == 1 ); +} + +TEST_CASE("UlpDistance", "[floating-point][ulp][approvals]") { + using Catch::ulpDistance; + + CHECK( ulpDistance( 1., 2. ) == 0x10'00'00'00'00'00'00 ); + CHECK( ulpDistance( -2., 2. ) == 0x80'00'00'00'00'00'00'00 ); + CHECK( ulpDistance( 1.f, 2.f ) == 0x80'00'00 ); + CHECK( ulpDistance( -2.f, 2.f ) == 0x80'00'00'00 ); +} diff --git a/tests/SelfTest/UsageTests/Matchers.tests.cpp b/tests/SelfTest/UsageTests/Matchers.tests.cpp index 36bab396e0..6f83279f33 100644 --- a/tests/SelfTest/UsageTests/Matchers.tests.cpp +++ b/tests/SelfTest/UsageTests/Matchers.tests.cpp @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -465,6 +466,7 @@ TEST_CASE( "Floating point matchers: float", "[matchers][floating-point]" ) { } SECTION( "ULPs" ) { REQUIRE_THAT( 1.f, WithinULP( 1.f, 0 ) ); + REQUIRE_THAT(-1.f, WithinULP( -1.f, 0 ) ); REQUIRE_THAT( nextafter( 1.f, 2.f ), WithinULP( 1.f, 1 ) ); REQUIRE_THAT( 0.f, WithinULP( nextafter( 0.f, 1.f ), 1 ) ); @@ -1081,3 +1083,17 @@ TEST_CASE( "Matchers can take references", #ifdef __clang__ # pragma clang diagnostic pop #endif + +TEMPLATE_TEST_CASE( + "#2152 - ULP checks between differently signed values were wrong", + "[matchers][floating-point][ulp]", + float, + double ) { + using Catch::Matchers::WithinULP; + + static constexpr TestType smallest_non_zero = + std::numeric_limits::denorm_min(); + + CHECK_THAT( smallest_non_zero, WithinULP( -smallest_non_zero, 2 ) ); + CHECK_THAT( smallest_non_zero, !WithinULP( -smallest_non_zero, 1 ) ); +}