From e82bfa23a5b57fc44117d98f0fd2cbc4b6436024 Mon Sep 17 00:00:00 2001 From: Markus Hofbauer Date: Sat, 13 Nov 2021 12:17:43 +0100 Subject: [PATCH] Feat: Floor (#309) * feat: first draft floor * apply review feedback and move to desired files * implement floor considering float types * reduce code duplication * apply review feedback and improve docstring * enable static floor tests for gcc only * Use recommended feature-test macro * add floor runtime unittests * apply review feedback * make lambda const --- src/core/include/units/math.h | 46 +++++++++++++++++++++++++--- test/unit_test/runtime/math_test.cpp | 42 +++++++++++++++++++++++++ test/unit_test/static/math_test.cpp | 24 +++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/core/include/units/math.h b/src/core/include/units/math.h index d20c7261..1c30574e 100644 --- a/src/core/include/units/math.h +++ b/src/core/include/units/math.h @@ -104,9 +104,9 @@ template /** * @brief Computes Euler's raised to the given power - * + * * @note Such an operation has sense only for a dimensionless quantity. - * + * * @param q Quantity being the base of the operation * @return Quantity The value of the same quantity type */ @@ -120,7 +120,7 @@ template /** * @brief Computes the absolute value of a quantity - * + * * @param q Quantity being the base of the operation * @return Quantity The absolute value of a provided quantity */ @@ -134,9 +134,9 @@ template /** * @brief Returns the epsilon of the quantity - * + * * The returned value is defined by a std::numeric_limits::epsilon(). - * + * * @tparam Q Quantity type being the base of the operation * @return Quantity The epsilon value for quantity's representation type */ @@ -147,4 +147,40 @@ template return Q(std::numeric_limits::epsilon()); } +/** + * @brief Computes the largest quantity with integer representation and unit type To with its number not greater than q + * + * @tparam q Quantity being the base of the operation + * @return Quantity The rounded quantity with unit type To + */ +template +[[nodiscard]] constexpr quantity floor(const quantity& q) noexcept + requires (!treat_as_floating_point) || + requires { floor(q.number()); } || + requires { std::floor(q.number()); } +{ + const auto handle_signed_results = [&](const T& res) { + if (res > q) + return res - T::one(); + return res; + }; + if constexpr(treat_as_floating_point) { + using std::floor; + if constexpr(std::is_same_v) { + return quantity(floor(q.number())); + } + else { + return handle_signed_results(quantity(floor(quantity_cast(q).number()))); + } + } + else { + if constexpr(std::is_same_v) { + return q; + } + else { + return handle_signed_results(quantity_cast(q)); + } + } +} + } // namespace units diff --git a/test/unit_test/runtime/math_test.cpp b/test/unit_test/runtime/math_test.cpp index 3aac9d3a..c360facb 100644 --- a/test/unit_test/runtime/math_test.cpp +++ b/test/unit_test/runtime/math_test.cpp @@ -24,10 +24,12 @@ #include #include #include +#include #include #include using namespace units; +using namespace units::isq; using namespace units::isq::si; // classical @@ -108,6 +110,46 @@ TEST_CASE("numeric_limits functions", "[limits]") } } +TEST_CASE("floor functions", "[floor]") +{ + SECTION ("floor 1 second with target unit second should be 1 second") { + REQUIRE(floor(1_q_s) == 1_q_s); + } + SECTION ("floor 1000 milliseconds with target unit second should be 1 second") { + REQUIRE(floor(1000_q_ms) == 1_q_s); + } + SECTION ("floor 1001 milliseconds with target unit second should be 1 second") { + REQUIRE(floor(1001_q_ms) == 1_q_s); + } + SECTION ("floor 1999 milliseconds with target unit second should be 1 second") { + REQUIRE(floor(1999_q_ms) == 1_q_s); + } + SECTION ("floor -1000 milliseconds with target unit second should be -1 second") { + REQUIRE(floor(-1000_q_ms) == -1_q_s); + } + SECTION ("floor -999 milliseconds with target unit second should be -1 second") { + REQUIRE(floor(-999_q_ms) == -1_q_s); + } + SECTION ("floor 1.3 seconds with target unit second should be 1 second") { + REQUIRE(floor(1.3_q_s) == 1_q_s); + } + SECTION ("floor -1.3 seconds with target unit second should be -1 second") { + REQUIRE(floor(-1.3_q_s) == -2_q_s); + } + SECTION ("floor 1001. milliseconds with target unit second should be 1 second") { + REQUIRE(floor(1001._q_ms) == 1_q_s); + } + SECTION ("floor 1999. milliseconds with target unit second should be 1 second") { + REQUIRE(floor(1999._q_ms) == 1_q_s); + } + SECTION ("floor -1000. milliseconds with target unit second should be -1 second") { + REQUIRE(floor(-1000._q_ms) == -1_q_s); + } + SECTION ("floor -999. milliseconds with target unit second should be -1 second") { + REQUIRE(floor(-999._q_ms) == -1_q_s); + } +} + TEMPLATE_TEST_CASE_SIG("pow() implementation exponentiates values to power N", "[math][pow][exp]", (std::intmax_t N, N), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25) { diff --git a/test/unit_test/static/math_test.cpp b/test/unit_test/static/math_test.cpp index 46004d8c..9ec4eb39 100644 --- a/test/unit_test/static/math_test.cpp +++ b/test/unit_test/static/math_test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ namespace { using namespace units; +using namespace units::isq; using namespace units::isq::si::literals; using namespace units::isq::si::international::literals; @@ -55,4 +57,26 @@ static_assert(compare(4_q_m2)), decltype(sqrt(2_q_m))>); static_assert(compare(4_q_km2)), decltype(sqrt(2_q_km))>); static_assert(compare(4_q_ft2)), decltype(sqrt(2_q_ft))>); +#if __cpp_lib_constexpr_cmath // TODO remove once std::floor is constexpr for all compilers +// floor +// integral types +static_assert(compare(1_q_s)), decltype(1_q_s)>); + +static_assert(compare(1000_q_ms)), decltype(1_q_s)>); +static_assert(compare(1001_q_ms)), decltype(1_q_s)>); +static_assert(compare(1999_q_ms)), decltype(1_q_s)>); +static_assert(compare(-1000_q_ms)), decltype(-1_q_s)>); +static_assert(compare(-999_q_ms)), decltype(-1_q_s)>); + +// floating-point +static_assert(floor(1.3_q_s) == 1_q_s); +static_assert(floor(-1.3_q_s) == -2_q_s); + +// static_assert(floor(1000._q_ms) == 1_q_s); // does not work due to a bug in fpow10() see #311 +static_assert(floor(1001._q_ms) == 1_q_s); +static_assert(floor(1999._q_ms) == 1_q_s); +static_assert(floor(-1000._q_ms) == -1_q_s); +static_assert(floor(-999._q_ms) == -1_q_s); +#endif + } // namespace