From af8eec11022230756896f430740eebf0237e6fb6 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 9 Apr 2022 17:41:08 +0000 Subject: [PATCH] Implement "common Magnitude" of two Magnitudes --- src/core/include/units/magnitude.h | 68 +++++++++++++++++++++++ test/unit_test/runtime/magnitude_test.cpp | 21 +++++++ 2 files changed, 89 insertions(+) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 88d003e7..545b7d99 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -525,6 +525,74 @@ constexpr ratio as_ratio(Magnitude auto m) } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Common Magnitude. +// +// The "common Magnitude" C, of two Magnitudes M1 and M2, is the largest Magnitude such that each of its inputs is +// expressible by only positive basis powers relative to C. That is, both (M1 / C) and (M2 / C) contain only positive +// powers in the expansion on our basis. +// +// For rational Magnitudes (or, more precisely, Magnitudes that are rational _relative to each other_), this reduces to +// the familiar convention from the std::chrono library: it is the largest Magnitude C such that each input Magnitude is +// an _integer multiple_ of C. The connection can be seen by considering the definition in the above paragraph, and +// recognizing that both the bases and the powers are all integers for rational Magnitudes. +// +// For relatively _irrational_ Magnitudes (whether from irrational bases, or fractional powers of integer bases), the +// notion of a "common type" becomes less important, because there is no way to preserve pure integer multiplication. +// When we go to retrieve our value, we'll be stuck with a floating point approximation no matter what choice we make. +// Thus, we make the _simplest_ choice which reproduces the correct convention in the rational case: namely, taking the +// minimum power for each base (where absent bases implicitly have a power of 0). + +namespace detail { +template +constexpr auto remove_positive_power(magnitude m) +{ + if constexpr (numerator(BP.power) < 0) { + return m; + } else { + return magnitude<>{}; + } +} + +template +constexpr auto remove_positive_powers(magnitude) +{ + return (magnitude<>{} * ... * remove_positive_power(magnitude{})); +} +} // namespace detail + +// Base cases, for when either (or both) inputs are the identity. +constexpr auto common_magnitude(magnitude<>, magnitude<>) { return magnitude<>{}; } +constexpr auto common_magnitude(magnitude<>, Magnitude auto m) { return detail::remove_positive_powers(m); } +constexpr auto common_magnitude(Magnitude auto m, magnitude<>) { return detail::remove_positive_powers(m); } + +// Recursive case for the common Magnitude of any two non-identity Magnitudes. +template +constexpr auto common_magnitude(magnitude m1, magnitude m2) +{ + using namespace detail; + + // Case for when H1 has the smaller base. + if constexpr (H1.get_base() < H2.get_base()) { + return remove_positive_power(magnitude

{}) * common_magnitude(magnitude{}, m2); + } + + // Case for when H2 has the smaller base. + if constexpr (H2.get_base() < H1.get_base()) { + return remove_positive_power(magnitude

{}) * common_magnitude(m1, magnitude{}); + } + + // Case for equal bases. + if constexpr (H1.get_base() == H2.get_base()) { + constexpr auto common_tail = common_magnitude(magnitude{}, magnitude{}); + if constexpr (H1.power < H2.power) { + return magnitude

{} * common_tail; + } else { + return magnitude

{} * common_tail; + } + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // `as_magnitude()` implementation. diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index e5d46f26..0715df8f 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -292,6 +292,27 @@ TEST_CASE("Multiplication works for magnitudes") } } +TEST_CASE("Common Magnitude") +{ + SECTION("Identity for identical magnitudes") + { + CHECK(common_magnitude(as_magnitude<1>(), as_magnitude<1>()) == as_magnitude<1>()); + CHECK(common_magnitude(as_magnitude<15>(), as_magnitude<15>()) == as_magnitude<15>()); + CHECK(common_magnitude(pi_to_the(), pi_to_the()) == pi_to_the()); + } + + SECTION("Greatest Common Factor for integers") + { + CHECK(common_magnitude(as_magnitude<24>(), as_magnitude<36>()) == as_magnitude<12>()); + CHECK(common_magnitude(as_magnitude<24>(), as_magnitude<37>()) == as_magnitude<1>()); + } + + SECTION("Handles fractions") + { + CHECK(common_magnitude(as_magnitude(), as_magnitude()) == as_magnitude()); + } +} + TEST_CASE("Division works for magnitudes") { SECTION("Dividing anything by itself reduces to null magnitude")