diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 94140397..88d003e7 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -392,7 +392,7 @@ template constexpr T get_value(const magnitude&) { // Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow. - constexpr auto result = detail::checked_static_cast((detail::compute_base_power(BPs) * ...)); + constexpr auto result = detail::checked_static_cast((detail::compute_base_power(BPs) * ... * T{1})); return result; } @@ -480,6 +480,51 @@ constexpr auto operator*(magnitude, magnitude) constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Magnitude numerator and denominator implementation. + +namespace detail { + +// The largest integer which can be extracted from any magnitude with only a single basis vector. +template +constexpr auto integer_part(magnitude) +{ + constexpr auto power_num = numerator(BP.power); + constexpr auto power_den = denominator(BP.power); + + if constexpr (std::is_integral_v && (power_num >= power_den)) { + constexpr auto largest_integer_power = [=](BasePower auto bp) { + bp.power = (power_num / power_den); // Note: integer division intended. + return bp; + }(BP); // Note: lambda is immediately invoked. + + return magnitude{}; + } else { + return magnitude<>{}; + } +} + +} // namespace detail + +template +constexpr auto numerator(magnitude) +{ + return (detail::integer_part(magnitude{}) * ... * magnitude<>{}); +} + +constexpr auto denominator(Magnitude auto m) { return numerator(pow<-1>(m)); } + +// Implementation of conversion to ratio goes here, because it needs `numerator()` and `denominator()`. +constexpr ratio as_ratio(Magnitude auto m) + requires(is_rational(decltype(m){})) +{ + return ratio{ + get_value(numerator(m)), + get_value(denominator(m)), + }; +} + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // `as_magnitude()` implementation. diff --git a/src/core/include/units/ratio.h b/src/core/include/units/ratio.h index 1e35b47e..0fb2f428 100644 --- a/src/core/include/units/ratio.h +++ b/src/core/include/units/ratio.h @@ -87,6 +87,24 @@ struct ratio { } [[nodiscard]] friend constexpr ratio operator/(const ratio& lhs, const ratio& rhs) { return lhs * inverse(rhs); } + + [[nodiscard]] friend constexpr std::intmax_t numerator(const ratio& r) + { + std::intmax_t true_num = r.num; + for (auto i = r.exp; i > 0; --i) { + true_num *= 10; + } + return true_num; + } + + [[nodiscard]] friend constexpr std::intmax_t denominator(const ratio& r) + { + std::intmax_t true_den = r.den; + for (auto i = r.exp; i < 0; ++i) { + true_den *= 10; + } + return true_den; + } }; [[nodiscard]] constexpr ratio inverse(const ratio& r) { return ratio(r.den, r.num, -r.exp); } diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 9db7b696..e5d46f26 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -63,6 +63,18 @@ void check_same_type_and_value(T actual, U expected) CHECK(actual == expected); } +template +void check_ratio_round_trip_is_identity() +{ + constexpr Magnitude auto m = as_magnitude(); + constexpr ratio round_trip = ratio{ + get_value(numerator(m)), + get_value(denominator(m)), + }; + CHECK(round_trip == R); +} + + TEST_CASE("base_power") { SECTION("base rep deducible for integral base") @@ -351,6 +363,33 @@ TEST_CASE("can distinguish integral, rational, and irrational magnitudes") } } +TEST_CASE("Constructing ratio from rational magnitude") +{ + SECTION("Round trip is identity") + { + // Note that not every Magnitude can be represented as a ratio. However, if we _start_ with a + // ratio, we must guarantee to recover the same ratio in a round trip. + check_ratio_round_trip_is_identity<1>(); + check_ratio_round_trip_is_identity<9>(); + check_ratio_round_trip_is_identity(); + } + + SECTION("Rational magnitude converts to ratio") + { + constexpr ratio r = as_ratio(as_magnitude()); + CHECK(r == ratio{22, 7}); + } + + SECTION("Irrational magnitude does not convert to ratio") + { + // The following code should not compile. + // as_ratio(pow(as_magnitude<2>())); + + // The following code should not compile. + // as_ratio(as_magnitude<180>() / pi_to_the<1>()); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Detail function tests below. @@ -374,6 +413,34 @@ TEST_CASE("int_power computes integer powers") } } +TEST_CASE("integer_part picks out integer part of single-basis magnitude") +{ + SECTION("integer_part of non-integer base is identity magnitude") + { + CHECK(integer_part(pi_to_the<1>()) == magnitude<>{}); + CHECK(integer_part(pi_to_the<-8>()) == magnitude<>{}); + CHECK(integer_part(pi_to_the()) == magnitude<>{}); + } + + SECTION("integer_part of integer base to negative power is identity magnitude") + { + CHECK(integer_part(magnitude{}) == magnitude<>{}); + CHECK(integer_part(magnitude{}) == magnitude<>{}); + } + + SECTION("integer_part of integer base to fractional power is identity magnitude") + { + CHECK(integer_part(magnitude{}) == magnitude<>{}); + } + + SECTION("integer_part of integer base to power at least one takes integer part") + { + CHECK(integer_part(magnitude{}) == magnitude{}); + CHECK(integer_part(magnitude{}) == magnitude{}); + CHECK(integer_part(magnitude{}) == magnitude{}); + } +} + TEST_CASE("Prime helper functions") { SECTION("multiplicity") diff --git a/test/unit_test/static/ratio_test.cpp b/test/unit_test/static/ratio_test.cpp index 9eac9963..4abe5094 100644 --- a/test/unit_test/static/ratio_test.cpp +++ b/test/unit_test/static/ratio_test.cpp @@ -102,4 +102,10 @@ static_assert(common_ratio(ratio(100, 1), ratio(1, 10)) == ratio(1, 10)); static_assert(common_ratio(ratio(1), ratio(1, 1, 3)) == ratio(1)); static_assert(common_ratio(ratio(10, 1, -1), ratio(1, 1, -3)) == ratio(1, 1, -3)); +// numerator and denominator +static_assert(numerator(ratio(3, 4)) == 3); +static_assert(numerator(ratio(3, 7, 2)) == 300); +static_assert(denominator(ratio(3, 4)) == 4); +static_assert(denominator(ratio(3, 7, -2)) == 700); + } // namespace