diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index fcd2cca81..30c379951 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -26,6 +26,7 @@ find_package(Catch2 3 REQUIRED) add_executable( unit_tests_runtime distribution_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp atomic_test.cpp + truncation_test.cpp ) if(${projectPrefix}BUILD_CXX_MODULES) target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES) diff --git a/test/runtime/almost_equals.h b/test/runtime/almost_equals.h index 22de4a183..373e79433 100644 --- a/test/runtime/almost_equals.h +++ b/test/runtime/almost_equals.h @@ -37,18 +37,36 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { explicit AlmostEqualsMatcher(const T& target) : target_{target} {} template U> - requires std::same_as && treat_as_floating_point + requires std::same_as bool match(const U& other) const { using std::abs; - using common = std::common_type_t; + using rep = typename T::rep; + using common = conditional, std::common_type_t, T>; const auto x = common(target_).numerical_value_in(common::unit); const auto y = common(other).numerical_value_in(common::unit); - const auto maxXYOne = std::max({typename T::rep{1}, abs(x), abs(y)}); - return abs(x - y) <= std::numeric_limits::epsilon() * maxXYOne; + if constexpr (treat_as_floating_point) { + const auto maxXYOne = std::max({rep{1}, abs(x), abs(y)}); + return abs(x - y) <= std::numeric_limits::epsilon() * maxXYOne; + } else { + if (x >= 0) { + return x - 1 <= y && y - 1 <= x; + } else { + return x <= y + 1 && y <= x + 1; + } + } } - std::string describe() const override { return "almost equals: " + MP_UNITS_STD_FMT::format("{}", target_); } + std::string describe() const override + { + if constexpr (treat_as_floating_point) { + return "almost equals: " + MP_UNITS_STD_FMT::format("{}", target_); + } else { + return "almost equals: " + MP_UNITS_STD_FMT::format("[ {0} ({0:#x}) +/- 1 ] {1}", + target_.numerical_value_is_an_implementation_detail_, + target_.unit); + } + } private: const T& target_; @@ -60,4 +78,5 @@ AlmostEqualsMatcher AlmostEquals(const T& target) return AlmostEqualsMatcher{target}; } + } // namespace mp_units diff --git a/test/runtime/truncation_test.cpp b/test/runtime/truncation_test.cpp new file mode 100644 index 000000000..eaef11b91 --- /dev/null +++ b/test/runtime/truncation_test.cpp @@ -0,0 +1,126 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "almost_equals.h" +#include +#include +#include +#include +#include +#ifdef MP_UNITS_MODULES +import mp_units; +#else +#include // IWYU pragma: keep +#include +#endif + +using namespace mp_units; +using namespace mp_units::angular; +using namespace mp_units::angular::unit_symbols; + +inline constexpr struct half_revolution : named_unit<"hrev", mag_pi * radian> { +} half_revolution; +inline constexpr auto hrev = half_revolution; + +// constexpr auto revb6 = mag_ratio<1,3> * mag_pi * rad; + +TEST_CASE("value_cast should not truncate for valid inputs", "[value_cast]") +{ + SECTION("num > den > 1, irr = 1") + { + REQUIRE_THAT(value_cast(9 * deg), AlmostEquals(10 * grad)); + REQUIRE_THAT(value_cast(360 * deg), AlmostEquals(400 * grad)); + } + + SECTION("1 < num < den, irr = 1") + { + REQUIRE_THAT(value_cast(10 * grad), AlmostEquals(9 * deg)); + REQUIRE_THAT(value_cast(400 * grad), AlmostEquals(360 * deg)); + } + + + SECTION("num > den = 1, irr > 1") + { + REQUIRE_THAT(value_cast(1 * rev), AlmostEquals(6 * rad)); + REQUIRE_THAT(value_cast(5 * rev), AlmostEquals(31 * rad)); + REQUIRE_THAT(value_cast(10 * rev), AlmostEquals(63 * rad)); + REQUIRE_THAT(value_cast(20 * rev), AlmostEquals(126 * rad)); + } + + SECTION("1 = num < den, irr < 1") + { + REQUIRE_THAT(value_cast(6 * rad), AlmostEquals(1 * rev)); + REQUIRE_THAT(value_cast(31 * rad), AlmostEquals(5 * rev)); + REQUIRE_THAT(value_cast(63 * rad), AlmostEquals(10 * rev)); + REQUIRE_THAT(value_cast(126 * rad), AlmostEquals(20 * rev)); + } + + SECTION("rational = 1, irrational > 1") + { + REQUIRE_THAT(value_cast(1 * hrev), AlmostEquals(3 * rad)); + REQUIRE_THAT(value_cast(10 * hrev), AlmostEquals(31 * rad)); + REQUIRE_THAT(value_cast(20 * hrev), AlmostEquals(63 * rad)); + REQUIRE_THAT(value_cast(40 * hrev), AlmostEquals(126 * rad)); + } + + SECTION("rational = 1, irrational < 1") + { + REQUIRE_THAT(value_cast(3 * rad), AlmostEquals(1 * hrev)); + REQUIRE_THAT(value_cast(31 * rad), AlmostEquals(10 * hrev)); + REQUIRE_THAT(value_cast(63 * rad), AlmostEquals(20 * hrev)); + REQUIRE_THAT(value_cast(126 * rad), AlmostEquals(40 * hrev)); + } +} + + +TEMPLATE_TEST_CASE("value_cast should not overflow internally for valid inputs", "[value_cast]", std::int8_t, + std::uint8_t, std::int16_t, std::uint16_t, std::int32_t, std::uint32_t) +{ + // max()/20: small enough so that none of the tested factors are likely to cause overflow, but still be nonzero; + // the "easy" test to verify the test itself is good. + std::vector test_values = {std::numeric_limits::max() / 20, + std::numeric_limits::max() - 1}; + if (std::is_signed_v) { + test_values.push_back(std::numeric_limits::min() + 1); + } + + for (TestType tv : test_values) { + SECTION("grad <-> deg") + { + auto deg_number = static_cast(std::trunc(0.9 * tv)); + auto grad_number = static_cast(std::round(deg_number / 0.9)); + INFO(MP_UNITS_STD_FMT::format("{} deg ~ {} grad", deg_number, grad_number)); + REQUIRE_THAT(value_cast(deg_number * deg), AlmostEquals(grad_number * grad)); + REQUIRE_THAT(value_cast(grad_number * grad), AlmostEquals(deg_number * deg)); + } + + + SECTION("rad <-> rev") + { + auto rev_number = static_cast(std::trunc(0.5 * std::numbers::inv_pi * tv)); + auto rad_number = static_cast(std::round(2 * std::numbers::pi * rev_number)); + INFO(MP_UNITS_STD_FMT::format("{} rev ~ {} rad", rev_number, rad_number)); + REQUIRE_THAT(value_cast(rev_number * rev), AlmostEquals(rad_number * rad)); + REQUIRE_THAT(value_cast(rad_number * rad), AlmostEquals(rev_number * rev)); + } + } +}