diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index aa3ed580..9e73fd20 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -29,76 +29,6 @@ namespace units::mag { -/** - * @brief A basis vector in our magnitude representation, raised to some rational power. - * - * The set of basis vectors must be linearly independent: that is, no product of basis powers can ever equal 1, unless - * all exponents are zero. To achieve this, we use arbitrarily many prime numbers as basis vectors, and also various - * irrational numbers such as pi. - * - * @tparam Base A type representing the basis vector. Must have a numeric static `value` member. - * @tparam Power The rational power to which the base is raised. - */ -template -struct base_power { - using base = Base; - static constexpr ratio power = Power; -}; - -/** - * @brief A type trait and concept to detect whether something is a valid "base power". - */ -template -struct is_base_power; -template -inline constexpr bool is_base_power_v = is_base_power::value; -template -concept BasePower = is_base_power_v; - -/** - * @brief A representation for positive real numbers which optimizes taking products and rational powers. - * - * Magnitudes can be treated as values. Each type encodes exactly one value. Users can multiply, divide, and compare - * for equality using this value API. - */ -template -struct magnitude; - -/** - * @brief A type trait and concept to detect whether something is a valid magnitude. - * - * In particular, these traits check for canonicalized forms: the base powers must be sorted by increasing base value, - * and all exponents must be nonzero. - */ -template -struct is_magnitude; -template -inline constexpr bool is_magnitude_v = is_magnitude::value; -template -concept Magnitude = is_magnitude_v; - -/** - * @brief Compute the inverse of a Magnitude. - */ -template -struct inverse; -template -using inverse_t = typename inverse::type; - -/** - * @brief Compute the product of 0 or more Magnitudes. - */ -template -struct product; -template -using product_t = typename product::type; - -/** - * @brief Compute the quotient of 2 Magnitudes. - */ -template -using quotient_t = product_t>; - namespace detail { // Helpers to perform prime factorization at compile time. @@ -106,19 +36,119 @@ template requires requires { N > 0; } struct prime_factorization; template - requires requires { N > 0; } -using prime_factorization_t = typename prime_factorization::type; +static constexpr auto prime_factorization_v = prime_factorization::value; // A way to check whether a number is prime at compile time. constexpr bool is_prime(std::intmax_t n); } // namespace detail +// Integer rep is for prime numbers; long double is for any irrational base we permit. +template +concept BaseRep = std::is_same_v || std::is_same_v; + /** - * @brief A template to represent prime number bases. + * @brief A basis vector in our magnitude representation, raised to some rational power. + * + * The set of basis vectors must be linearly independent: that is, no product of basis powers can ever equal 1, unless + * all exponents are zero. To achieve this, we use the following kinds of basis vectors. + * - Prime numbers. (These are the only allowable values for `int` base.) + * - Certain selected irrational numbers, such as pi. + * + * Before allowing a new irrational base, make sure that it _cannot_ be represented as the product of rational powers of + * _existing_ bases, including both prime numbers and any other irrational bases. For example, even though sqrt(2) is + * irrational, we must not ever use it as a base */ -template - requires requires { detail::is_prime(N); } -struct prime_base : std::integral_constant {}; +template +struct base_power { + // The value of the basis vector. + T base; + + // The rational power to which the base is raised. + ratio power{1}; +}; + +template U> +base_power(T, U) -> base_power; + +template +base_power(T) -> base_power; + +template +constexpr bool operator==(base_power t, base_power u) { + return std::is_same_v && (t.base == u.base) && (t.power == u.power); +} + +template +constexpr auto inverse(base_power bp) { + bp.power = -bp.power; + return bp; +} + +namespace detail +{ +template +constexpr bool is_valid_base_power(const base_power &bp) { + if (bp.power == 0) { return false; } + + if constexpr (std::is_same_v) { return is_prime(bp.base); } + else if constexpr (std::is_same_v) { return bp.base > 0; } + else { return false; } // Unreachable. +} +} // namespace detail + +template +concept BasePower = std::is_same_v> || std::is_same_v>; + +/** + * @brief A representation for positive real numbers which optimizes taking products and rational powers. + * + * Magnitudes can be treated as values. Each type encodes exactly one value. Users can multiply, divide, and compare + * for equality using this value API. + */ +template + requires requires { + // (is_valid_base_power(BasePowers) && ... && strictly_increasing(std::make_tuple(BasePowers.base...))); + (detail::is_valid_base_power(BasePowers) && ...); + } +struct magnitude {}; + +template +constexpr bool operator==(magnitude, magnitude) { return ((LeftBPs == RightBPs) && ...); } + +template +constexpr auto inverse(magnitude) { return magnitude{}; } + +constexpr auto operator*(magnitude<>, magnitude<>) { return magnitude<>{}; } + +template +constexpr auto operator*(magnitude<>, magnitude m) { return m; } + +template +constexpr auto operator*(magnitude m, magnitude<>) { return m; } + +template +constexpr auto operator*(magnitude, magnitude) { + // Shortcut for prepending, which makes it easier to implement some of the other cases. + if constexpr ((sizeof...(T1) == 0) && H1.base < H2.base) { return magnitude{}; } + + if constexpr (H1.base == H2.base) { + constexpr auto partial_product = magnitude{} * magnitude{}; + constexpr base_power new_head{H1.base, (H1.power + H2.power)}; + + if constexpr (new_head.power == 0) { + return partial_product; + } else { + return magnitude{} * partial_product; + } + } else if constexpr(H1.base < H2.base){ + return magnitude

{} * (magnitude{} * magnitude{}); + } else { // We know H2.base < H1.base + return magnitude

{} * (magnitude{} * magnitude{}); + } +} + +template +constexpr auto operator/(magnitude l, magnitude r) { return l * inverse(r); } /** * @brief Make a Magnitude that is a rational number. @@ -127,66 +157,25 @@ struct prime_base : std::integral_constant {}; * manually adding base powers. */ template -constexpr auto make_ratio() { - return quotient_t, detail::prime_factorization_t>{}; -} - -/** - * @brief Make a Magnitude from a single base raised to a particular power. - * - * This should handle all of the remaining use cases which can't be captured by make_ratio(), i.e., any irrational - * magnitudes. For example: - * - `make_base_power()` to represent pi - * - `make_base_power, 1, 2>()` to represent sqrt(2) - */ -template -constexpr auto make_base_power() { - return magnitude>{}; -} +constexpr auto make_ratio() { return detail::prime_factorization_v / detail::prime_factorization_v; } /** * @brief A base to represent pi. */ -struct pi { - static constexpr long double value = std::numbers::pi_v; -}; +template + requires requires { Power != 0; } +constexpr auto pi_power() { return base_power{std::numbers::pi_v, Power}; } + +template +constexpr auto pi_to_the() { return magnitude()>{}; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation details below. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template -struct magnitude { - template - constexpr bool operator==(M) const { return std::is_same_v; } - - template - constexpr friend auto operator*(magnitude, M) { return product_t{}; } - - template - constexpr friend auto operator/(magnitude, M) { return quotient_t{}; } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// BasePower concept implementation. - -// Default implementation: most things are not base powers. -template -struct is_base_power: std::false_type {}; - -// To be a valid base power, one must be a base_power, where B has a static value member which is positive. -template - requires requires() { B::value; } -struct is_base_power> - : std::bool_constant<(B::value > 0)> {}; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude concept implementation. -// Default implementation: most things are not magnitudes. -template -struct is_magnitude: std::false_type {}; - template constexpr bool pairwise_all(const std::tuple &ts, const Predicate &pred) { // Carefully handle different sizes, avoiding unsigned integer underflow. @@ -206,78 +195,6 @@ constexpr bool strictly_increasing(const std::tuple &ts) { return pairwise_all(ts, std::less{}); } -// To be a valid magnitude, one must be a magnitude<...> of BasePowers with nonzero exponents, sorted by increasing base -// value. -template -struct is_magnitude> - : std::bool_constant<(strictly_increasing(std::make_tuple(Bs::base::value...)) && ((Bs::power.num != 0) && ...))> {}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Inverse implementation. - -// To invert a BasePower, negate all exponents. -template -using base_power_inverse = base_power; - -template -struct inverse> { using type = magnitude...>; }; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Product implementation. - -// Convenience utility to prepend a base_power to a magnitude. -// -// Assumes that the prepended power has a smaller base than every base power in the magnitude. -template -struct prepend_base; -template -using prepend_base_t = typename prepend_base::type; -template -struct prepend_base> { using type = magnitude; }; - -// Nullary case. -template<> -struct product<> { using type = magnitude<>; }; - -// Unary case. -template -struct product { using type = M; }; - -// Binary case, where right argument is null magnitude. -template -struct product> { using type = M; }; - -// Binary case, where left argument is null magnitude, and right is non-null. -template -struct product, magnitude> { using type = magnitude; }; - -// Binary case, with distinct and non-null heads. -template -struct product, magnitude> -{ - using type = std::conditional_t< - (Head1::base::value < Head2::base::value), - prepend_base_t, magnitude>>, - prepend_base_t, magnitude>>>; -}; - -// Binary case, same head. -template -struct product, Tail1...>, - magnitude, Tail2...>> -{ - using tail_product = product_t, magnitude>; - static constexpr auto Pow = Pow1 + Pow2; - using type = std::conditional_t< - Pow.num == 0, - tail_product, - prepend_base_t, tail_product>>; -}; - -// N-ary case (N > 2). -template -struct product { using type = product_t, Tail...>; }; - namespace detail { @@ -317,18 +234,18 @@ constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std: // Specialization for the prime factorization of 1 (base case). template<> -struct prime_factorization<1> { using type = magnitude<>; }; +struct prime_factorization<1> { static constexpr magnitude<> value{}; }; // Specialization for the prime factorization of larger numbers (recursive case). template requires requires { N > 0; } struct prime_factorization { - static constexpr std::intmax_t first_base = find_first_factor(N); + static constexpr int first_base = find_first_factor(N); static constexpr std::intmax_t first_power = multiplicity(first_base, N); static constexpr std::intmax_t remainder = remove_power(first_base, first_power, N); - using type = product_t< - magnitude, ratio{first_power}>>, prime_factorization_t>; + static constexpr auto value = magnitude{} + * prime_factorization_v; }; constexpr bool is_prime(std::intmax_t n) { return (n > 1) && (find_first_factor(n) == n); } diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index afbaf62f..9cd54835 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,175 +28,6 @@ namespace units::mag { -// Convenience utility to create an integral base power. For unit tests only. -template -struct IntBasePower -{ - // This setup is more complicated than might appear necessary in the hopes of appeasing a - // (possibly spurious) MSVC 14 compiler error. - static constexpr std::intmax_t num = Num; - static constexpr std::intmax_t den = Den; - static constexpr ratio power = ratio{num, den}; - using type = base_power, power>; -}; -template -using int_base_power = typename IntBasePower::type; - -TEST_CASE("Magnitude is invertible") -{ - CHECK(std::is_same_v>, magnitude<>>); - CHECK(std::is_same_v< - inverse_t>>, magnitude>>); - CHECK(std::is_same_v< - inverse_t, int_base_power<11, -5>>>, - magnitude, int_base_power<11, 5>>>); -} - -TEST_CASE("Magnitude supports products") -{ - SECTION ("The nullary product gives the unit magnitude") { - CHECK(std::is_same_v, magnitude<>>); - } - - SECTION ("The unary product is the identity operation") { - CHECK(std::is_same_v< - product_t>>, - magnitude>>); - - CHECK(std::is_same_v< - product_t, int_base_power<13, -2>>>, - magnitude, int_base_power<13, -2>>>); - } - - SECTION ("Binary product with null magnitude is identity") { - using arbitrary_mag = magnitude>; - CHECK(std::is_same_v, magnitude<>>, magnitude<>>); - CHECK(std::is_same_v>, arbitrary_mag>); - CHECK(std::is_same_v, arbitrary_mag>, arbitrary_mag>); - } - - SECTION ("Binary product with distinct bases maintains sorted order") { - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<7, -2>>, - magnitude, int_base_power<5, 5>>>, - magnitude< - int_base_power<2, 1, 3>, - int_base_power<3>, - int_base_power<5, 5>, - int_base_power<7, -2>>>); - } - - SECTION ("Binary products add exponents for same bases") { - CHECK(std::is_same_v< - product_t< - magnitude>, - magnitude>>, - magnitude>>); - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<3, -1, 3>>, - magnitude, int_base_power<5, 4>>>, - magnitude, int_base_power<3, -1, 3>, int_base_power<5, 4>>>); - } - - SECTION ("Binary products omit bases whose exponents cancel") { - CHECK(std::is_same_v< - product_t< - magnitude>, magnitude>>, - magnitude<>>); - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<7, -2>>, - magnitude, int_base_power<5, 5>>>, - magnitude, int_base_power<7, -2>>>); - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<3, -2>, int_base_power<7, -2>>, - magnitude, int_base_power<5, 5>, int_base_power<7, 2>>>, - magnitude, int_base_power<5, 5>>>); - } - - SECTION ("N-ary products recurse") { - CHECK(std::is_same_v< - product_t< - magnitude>, - magnitude>, - magnitude>, - magnitude>, - magnitude>>, - magnitude, int_base_power<5>>>); - } -} - -TEST_CASE("is_base_power detects well formed base powers") -{ - SECTION ("Arbitrary other types are not base powers") - { - CHECK(!is_base_power_v); - CHECK(!is_base_power_v); - CHECK(!is_base_power_v>>); - } - - SECTION ("int_base_power forms base powers") - { - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - } - - SECTION ("base_power forms base powers with pi and ratio") - { - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - } - - SECTION ("base_power disqualified by base without value") - { - CHECK(!is_base_power_v>); - CHECK(!is_base_power_v>); - CHECK(!is_base_power_v>); - } -} - -TEST_CASE("is_magnitude detects well formed magnitudes") -{ - SECTION ("Arbitrary other types are not magnitudes") - { - CHECK(!is_magnitude_v); - CHECK(!is_magnitude_v); - CHECK(!is_magnitude_v>); - } - - SECTION ("Null magnitude is magnitude") - { - CHECK(is_magnitude_v>); - } - - SECTION ("Single-base magnitude is magnitude") - { - CHECK(is_magnitude_v>>); - } - - SECTION ("Out-of-order bases disqualify magnitudes") - { - CHECK(!is_magnitude_v, int_base_power<2>>>); - } - - SECTION ("Repeated bases disqualify magnitudes") - { - CHECK(!is_magnitude_v, int_base_power<2, 2>>>); - } - - SECTION ("Mixed base types are magnitudes if sorted") - { - CHECK(is_magnitude_v, base_power>>); - CHECK(is_magnitude_v, base_power>>); - CHECK(!is_magnitude_v, base_power>>); - } -} - TEST_CASE("strictly_increasing") { SECTION ("Empty tuple is sorted") @@ -219,66 +50,52 @@ TEST_CASE("strictly_increasing") } } -TEST_CASE("make_ratio performs prime factorization correctly") -{ - SECTION("Performs prime factorization when denominator is 1") - { - CHECK(std::is_same_v()), magnitude<>>); - CHECK(std::is_same_v()), magnitude>>); - CHECK(std::is_same_v()), magnitude>>); - CHECK(std::is_same_v()), magnitude>>); +// TEST_CASE("make_ratio performs prime factorization correctly") +// { +// SECTION("Performs prime factorization when denominator is 1") +// { +// CHECK(std::is_same_v()), magnitude<>>); +// CHECK(std::is_same_v()), magnitude>); +// CHECK(std::is_same_v()), magnitude>); +// CHECK(std::is_same_v()), magnitude>); +// +// CHECK(std::is_same_v< +// decltype(make_ratio<792>()), +// magnitude>); +// } +// +// SECTION("Reduces fractions to lowest terms") +// { +// CHECK(std::is_same_v()), magnitude<>>); +// CHECK(std::is_same_v< +// decltype(make_ratio<50, 80>()), magnitude>); +// } +// } - CHECK(std::is_same_v< - decltype(make_ratio<792>()), - magnitude, int_base_power<3, 2>, int_base_power<11>>>); - } +// TEST_CASE("Equality works for magnitudes") +// { +// SECTION("Equivalent ratios are equal") +// { +// CHECK(make_ratio<1>() == make_ratio<1>()); +// CHECK(make_ratio<3>() == make_ratio<3>()); +// CHECK(make_ratio<3, 4>() == make_ratio<9, 12>()); +// } +// +// SECTION("Different ratios are unequal") +// { +// CHECK(make_ratio<3>() != make_ratio<5>()); +// CHECK(make_ratio<3>() != make_ratio<3, 2>()); +// } +// +// SECTION("Supports constexpr") +// { +// constexpr auto eq = (make_ratio<4, 5>() == make_ratio<4, 3>()); +// CHECK(!eq); +// } +// } - SECTION("Reduces fractions to lowest terms") - { - CHECK(std::is_same_v()), magnitude<>>); - CHECK(std::is_same_v< - decltype(make_ratio<50, 80>()), - magnitude, int_base_power<5>>>); - } -} - -TEST_CASE("make_magnitude handles arbitrary bases") -{ - SECTION("Equivalent to std::integral_constant for integer bases") - { - CHECK(make_base_power>() == make_ratio<2>()); - CHECK(make_base_power>() == make_ratio<7>()); - } - - SECTION("Handles non-integer bases") - { - CHECK(make_base_power() == magnitude>{}); - CHECK(make_base_power() == magnitude>{}); - CHECK(make_base_power() == magnitude>{}); - } -} - -TEST_CASE("Equality works for magnitudes") -{ - SECTION("Equivalent ratios are equal") - { - CHECK(make_ratio<1>() == make_ratio<1>()); - CHECK(make_ratio<3>() == make_ratio<3>()); - CHECK(make_ratio<3, 4>() == make_ratio<9, 12>()); - } - - SECTION("Different ratios are unequal") - { - CHECK(make_ratio<3>() != make_ratio<5>()); - CHECK(make_ratio<3>() != make_ratio<3, 2>()); - } - - SECTION("Supports constexpr") - { - constexpr auto eq = make_ratio<4, 5>() == make_ratio<4, 3>(); - CHECK(!eq); - } -} +template +using double_v = 2 * x; TEST_CASE("Multiplication works for magnitudes") { @@ -290,15 +107,15 @@ TEST_CASE("Multiplication works for magnitudes") SECTION("Products work as expected") { CHECK(make_ratio<4, 5>() * make_ratio<4, 3>() == make_ratio<16, 15>()); + CHECK(double_v<1.5> == 3.0); } - SECTION("Products handle pi correctly") - { - CHECK( - make_base_power() * make_ratio<2, 3>() * make_base_power() == - magnitude, int_base_power<3, -1>, base_power>{}); - - } + //SECTION("Products handle pi correctly") + //{ + // CHECK( + // pi_to_the<1>() * make_ratio<2, 3>() * pi_to_the() == + // magnitude()>{}); + //} SECTION("Supports constexpr") { @@ -334,25 +151,24 @@ TEST_CASE("Prime factorization") { SECTION ("1 factors into the null magnitude") { - CHECK(std::is_same_v, magnitude<>>); + CHECK(prime_factorization_v<1> == magnitude<>{}); } SECTION ("Prime numbers factor into themselves") { - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); + CHECK(prime_factorization_v<2> == magnitude{}); + CHECK(prime_factorization_v<3> == magnitude{}); + CHECK(prime_factorization_v<5> == magnitude{}); + CHECK(prime_factorization_v<7> == magnitude{}); + CHECK(prime_factorization_v<11> == magnitude{}); - CHECK(std::is_same_v, magnitude>>); + CHECK(prime_factorization_v<41> == magnitude{}); } SECTION("Prime factorization finds factors and multiplicities") { - CHECK(std::is_same_v< - prime_factorization_t<792>, - magnitude, int_base_power<3, 2>, int_base_power<11>>>); + CHECK(prime_factorization_v<792> == + magnitude{}); } }