From 24803a96cc6a0441b10eecc12f514df613ee6f30 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 29 Aug 2023 16:05:48 +0200 Subject: [PATCH] feat: math trigonometric functions can now be called with integral quantities Relates to #485 --- src/utility/include/mp-units/math.h | 132 +++++++++++++++++++-------- test/unit_test/runtime/math_test.cpp | 108 +++++++++++----------- 2 files changed, 150 insertions(+), 90 deletions(-) diff --git a/src/utility/include/mp-units/math.h b/src/utility/include/mp-units/math.h index a6115291..d3f6d591 100644 --- a/src/utility/include/mp-units/math.h +++ b/src/utility/include/mp-units/math.h @@ -297,57 +297,87 @@ template namespace isq { template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity sin(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto sin(const quantity& q) noexcept requires requires { sin(q.value()); } || requires { std::sin(q.value()); } { using std::sin; - return make_quantity(static_cast(sin(q.in(si::radian).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(sin(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(sin(value_cast(q).value_in(si::radian))); + } else + return make_quantity(sin(q.value_in(si::radian))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity cos(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto cos(const quantity& q) noexcept requires requires { cos(q.value()); } || requires { std::cos(q.value()); } { using std::cos; - return make_quantity(static_cast(cos(q.in(si::radian).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(cos(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(cos(value_cast(q).value_in(si::radian))); + } else + return make_quantity(cos(q.value_in(si::radian))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity tan(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto tan(const quantity& q) noexcept requires requires { tan(q.value()); } || requires { std::tan(q.value()); } { using std::tan; - return make_quantity(static_cast(tan(q.in(si::radian).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(tan(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(tan(value_cast(q).value_in(si::radian))); + } else + return make_quantity(tan(q.value_in(si::radian))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity asin(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto asin(const quantity& q) noexcept requires requires { asin(q.value()); } || requires { std::asin(q.value()); } { using std::asin; - return make_quantity(static_cast(asin(value_cast(q).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(asin(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(asin(value_cast(q).value_in(one))); + } else + return make_quantity(asin(q.value_in(one))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity acos(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto acos(const quantity& q) noexcept requires requires { acos(q.value()); } || requires { std::acos(q.value()); } { using std::acos; - return make_quantity(static_cast(acos(value_cast(q).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(acos(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(acos(value_cast(q).value_in(one))); + } else + return make_quantity(acos(q.value_in(one))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity atan(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto atan(const quantity& q) noexcept requires requires { atan(q.value()); } || requires { std::atan(q.value()); } { using std::atan; - return make_quantity(static_cast(atan(value_cast(q).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(atan(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(atan(value_cast(q).value_in(one))); + } else + return make_quantity(atan(q.value_in(one))); } } // namespace isq @@ -355,57 +385,87 @@ template auto R, typename Rep> namespace angular { template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity sin(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto sin(const quantity& q) noexcept requires requires { sin(q.value()); } || requires { std::sin(q.value()); } { using std::sin; - return make_quantity(static_cast(sin(q.in(radian).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(sin(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(sin(value_cast(q).value_in(radian))); + } else + return make_quantity(sin(q.value_in(radian))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity cos(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto cos(const quantity& q) noexcept requires requires { cos(q.value()); } || requires { std::cos(q.value()); } { using std::cos; - return make_quantity(static_cast(cos(q.in(radian).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(cos(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(cos(value_cast(q).value_in(radian))); + } else + return make_quantity(cos(q.value_in(radian))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity tan(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto tan(const quantity& q) noexcept requires requires { tan(q.value()); } || requires { std::tan(q.value()); } { using std::tan; - return make_quantity(static_cast(tan(q.in(radian).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(tan(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(tan(value_cast(q).value_in(radian))); + } else + return make_quantity(tan(q.value_in(radian))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity asin(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto asin(const quantity& q) noexcept requires requires { asin(q.value()); } || requires { std::asin(q.value()); } { using std::asin; - return make_quantity(static_cast(asin(value_cast(q).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(asin(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(asin(value_cast(q).value_in(one))); + } else + return make_quantity(asin(q.value_in(one))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity acos(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto acos(const quantity& q) noexcept requires requires { acos(q.value()); } || requires { std::acos(q.value()); } { using std::acos; - return make_quantity(static_cast(acos(value_cast(q).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(acos(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(acos(value_cast(q).value_in(one))); + } else + return make_quantity(acos(q.value_in(one))); } template auto R, typename Rep> - requires treat_as_floating_point -[[nodiscard]] inline quantity atan(const quantity& q) noexcept +[[nodiscard]] inline QuantityOf auto atan(const quantity& q) noexcept requires requires { atan(q.value()); } || requires { std::atan(q.value()); } { using std::atan; - return make_quantity(static_cast(atan(value_cast(q).value()))); + if constexpr (!treat_as_floating_point) { + // check what is the return type when called with the integral value + using rep = decltype(atan(value_cast(q).value())); + // use this type ahead of calling the function to prevent narrowing if a unit conversion is needed + return make_quantity(atan(value_cast(q).value_in(one))); + } else + return make_quantity(atan(q.value_in(one))); } } // namespace angular diff --git a/test/unit_test/runtime/math_test.cpp b/test/unit_test/runtime/math_test.cpp index 4085d09e..fda646c6 100644 --- a/test/unit_test/runtime/math_test.cpp +++ b/test/unit_test/runtime/math_test.cpp @@ -311,26 +311,26 @@ TEST_CASE("ISQ trigonometric functions", "[trig][isq]") { SECTION("sin") { - REQUIRE_THAT(isq::sin(0. * isq::angular_measure[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(isq::sin(90. * isq::angular_measure[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(isq::sin(180. * isq::angular_measure[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(isq::sin(270. * isq::angular_measure[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(isq::sin(0 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(isq::sin(90 * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(isq::sin(180 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(isq::sin(270 * deg), AlmostEquals(-1. * one)); } SECTION("cos") { - REQUIRE_THAT(isq::cos(0. * isq::angular_measure[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(isq::cos(90. * isq::angular_measure[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(isq::cos(180. * isq::angular_measure[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(isq::cos(270. * isq::angular_measure[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(isq::cos(0 * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(isq::cos(90 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(isq::cos(180 * deg), AlmostEquals(-1. * one)); + REQUIRE_THAT(isq::cos(270 * deg), AlmostEquals(0. * one)); } SECTION("tan") { - REQUIRE_THAT(isq::tan(0. * isq::angular_measure[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(isq::tan(45. * isq::angular_measure[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(isq::tan(135. * isq::angular_measure[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(isq::tan(180. * isq::angular_measure[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(isq::tan(0 * deg), AlmostEquals(0. * one)); + REQUIRE_THAT(isq::tan(45. * deg), AlmostEquals(1. * one)); + REQUIRE_THAT(isq::tan(135. * deg), AlmostEquals(-1. * one)); + REQUIRE_THAT(isq::tan(180. * deg), AlmostEquals(0. * one)); } } @@ -338,23 +338,23 @@ TEST_CASE("ISQ inverse trigonometric functions", "[inv trig][isq]") { SECTION("asin") { - REQUIRE_THAT(isq::asin(-1. * one), AlmostEquals(-90. * isq::angular_measure[deg])); - REQUIRE_THAT(isq::asin(0. * one), AlmostEquals(0. * isq::angular_measure[deg])); - REQUIRE_THAT(isq::asin(1. * one), AlmostEquals(90. * isq::angular_measure[deg])); + REQUIRE_THAT(isq::asin(-1 * one), AlmostEquals(-90. * deg)); + REQUIRE_THAT(isq::asin(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(isq::asin(1 * one), AlmostEquals(90. * deg)); } SECTION("acos") { - REQUIRE_THAT(isq::asin(-1. * one), AlmostEquals(-90. * isq::angular_measure[deg])); - REQUIRE_THAT(isq::asin(0. * one), AlmostEquals(0. * isq::angular_measure[deg])); - REQUIRE_THAT(isq::asin(1. * one), AlmostEquals(90. * isq::angular_measure[deg])); + REQUIRE_THAT(isq::asin(-1 * one), AlmostEquals(-90. * deg)); + REQUIRE_THAT(isq::asin(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(isq::asin(1 * one), AlmostEquals(90. * deg)); } SECTION("atan") { - REQUIRE_THAT(isq::atan(-1. * one), AlmostEquals(-45. * isq::angular_measure[deg])); - REQUIRE_THAT(isq::atan(0. * one), AlmostEquals(0. * isq::angular_measure[deg])); - REQUIRE_THAT(isq::atan(1. * one), AlmostEquals(45. * isq::angular_measure[deg])); + REQUIRE_THAT(isq::atan(-1 * one), AlmostEquals(-45. * deg)); + REQUIRE_THAT(isq::atan(0 * one), AlmostEquals(0. * deg)); + REQUIRE_THAT(isq::atan(1 * one), AlmostEquals(45. * deg)); } } @@ -367,41 +367,41 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]") SECTION("sin") { - REQUIRE_THAT(sin(0. * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(90. * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(180. * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(270. * angle[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(sin(0 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(90 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(sin(180 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(270 * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(sin(0. * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(100. * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(200. * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(sin(300. * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); } SECTION("cos") { - REQUIRE_THAT(cos(0. * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(cos(90. * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(180. * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(cos(270. * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(0 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(cos(90 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(180 * angle[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(cos(270 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(0. * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(cos(100. * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(cos(200. * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(cos(300. * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(0 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(cos(100 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(cos(200 * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(cos(300 * angle[grad]), AlmostEquals(0. * one)); } SECTION("tan") { - REQUIRE_THAT(tan(0. * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(45. * angle[deg]), AlmostEquals(1. * one)); - REQUIRE_THAT(tan(135. * angle[deg]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(180. * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(0 * angle[deg]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(45 * angle[deg]), AlmostEquals(1. * one)); + REQUIRE_THAT(tan(135 * angle[deg]), AlmostEquals(-1. * one)); + REQUIRE_THAT(tan(180 * angle[deg]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(0. * angle[grad]), AlmostEquals(0. * one)); - REQUIRE_THAT(tan(50. * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(tan(150. * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(200. * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); + REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); + REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one)); } } @@ -413,22 +413,22 @@ TEST_CASE("Angle inverse trigonometric functions", "[inv trig][angle]") SECTION("asin") { - REQUIRE_THAT(asin(-1. * one), AlmostEquals(-90. * angle[deg])); - REQUIRE_THAT(asin(0. * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(asin(1. * one), AlmostEquals(90. * angle[deg])); + REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); + REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); } SECTION("acos") { - REQUIRE_THAT(asin(-1. * one), AlmostEquals(-90. * angle[deg])); - REQUIRE_THAT(asin(0. * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(asin(1. * one), AlmostEquals(90. * angle[deg])); + REQUIRE_THAT(asin(-1 * one), AlmostEquals(-90. * angle[deg])); + REQUIRE_THAT(asin(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(asin(1 * one), AlmostEquals(90. * angle[deg])); } SECTION("atan") { - REQUIRE_THAT(atan(-1. * one), AlmostEquals(-45. * angle[deg])); - REQUIRE_THAT(atan(0. * one), AlmostEquals(0. * angle[deg])); - REQUIRE_THAT(atan(1. * one), AlmostEquals(45. * angle[deg])); + REQUIRE_THAT(atan(-1 * one), AlmostEquals(-45. * angle[deg])); + REQUIRE_THAT(atan(0 * one), AlmostEquals(0. * angle[deg])); + REQUIRE_THAT(atan(1 * one), AlmostEquals(45. * angle[deg])); } }