diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 7e766b7e..88d231de 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -29,106 +29,165 @@ namespace units::mag { -template +/** + * @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 inline constexpr ratio power = power_; + using base = Base; + static inline constexpr ratio power = Power; }; -template +/** + * @brief A type trait and concept to detect whether something is a valid "base power". + */ +template struct is_base_power; -template +template inline constexpr bool is_base_power_v = is_base_power::value; -template +template concept BasePower = is_base_power_v; -// A `magnitude` represents a positive real number in a format which optimizes taking products and -// rational powers. -template +/** + * @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; -template +/** + * @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 +template inline constexpr bool is_magnitude_v = is_magnitude::value; -template +template concept Magnitude = is_magnitude_v; -template -struct int_base : std::integral_constant {}; -template -using int_base_power = base_power, ratio{num, den}>; - -template +/** + * @brief Compute the inverse of a Magnitude. + */ +template struct inverse; -template +template using inverse_t = typename inverse::type; -template +/** + * @brief Compute the product of 0 or more Magnitudes. + */ +template struct product; -template +template using product_t = typename product::type; -template +/** + * @brief Compute the quotient of 2 Magnitudes. + */ +template using quotient_t = product_t>; namespace detail { -template +// Helpers to perform prime factorization at compile time. +template struct prime_factorization; -template +template using prime_factorization_t = typename prime_factorization::type; + +// A way to check whether a number is prime at compile time. +constexpr bool is_prime(std::intmax_t n); } // namespace detail -template +/** + * @brief A template to represent prime number bases. + */ +template +struct prime_base : std::integral_constant { + static_assert(detail::is_prime(N)); +}; + +/** + * @brief Make a Magnitude that is a rational number. + * + * This will be the main way end users create Magnitudes. They should rarely (if ever) create a magnitude<...> by + * manually adding base powers. + */ +template constexpr auto make_ratio() { return quotient_t, detail::prime_factorization_t>{}; } -template +/** + * @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>{}; } +/** + * @brief A base to represent pi. + */ struct pi { static inline constexpr long double value = std::numbers::pi_v; }; -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation details below. -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template +template struct magnitude { - template + template constexpr bool operator==(M) const { return std::is_same_v; } - template + template constexpr friend auto operator*(magnitude, M) { return product_t{}; } - template + template constexpr friend auto operator/(magnitude, M) { return quotient_t{}; } }; -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BasePower concept implementation. -template +// Default implementation: most things are not base powers. +template struct is_base_power: std::false_type {}; -template +// 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. -template +// Default implementation: most things are not magnitudes. +template struct is_magnitude: std::false_type {}; // Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. -template +template constexpr bool strictly_increasing(const std::tuple &ts) { // Carefully handle different sizes, avoiding unsigned integer underflow. constexpr auto num_comparisons = [](auto num_elements) { @@ -141,56 +200,53 @@ constexpr bool strictly_increasing(const std::tuple &ts) { }(std::make_index_sequence()); } -template +// 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) && ...))> {}; + : 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...>; -}; +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 +template struct prepend_base; -template +template using prepend_base_t = typename prepend_base::type; -template -struct prepend_base> { - using type = magnitude; -}; +template +struct prepend_base> { using type = magnitude; }; // Nullary case. -template <> +template<> struct product<> { using type = magnitude<>; }; // Unary case. -template +template struct product { using type = M; }; // Binary case, where right argument is null magnitude. -template +template struct product> { using type = M; }; // Binary case, where left argument is null magnitude, and right is non-null. -template +template struct product, magnitude> { using type = magnitude; }; // Binary case, with distinct and non-null heads. -template +template struct product, magnitude> { using type = std::conditional_t< @@ -200,7 +256,7 @@ struct product, magnitude> }; // Binary case, same head. -template +template struct product, Tail1...>, magnitude, Tail2...>> { @@ -212,16 +268,13 @@ struct product, Tail1...>, }; // N-ary case (N > 2). -template -struct product -{ - using type = product_t, Tail...>; -}; +template +struct product { using type = product_t, Tail...>; }; namespace detail { -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Prime factorization implementation. // Find the smallest prime factor of `n`. @@ -251,19 +304,16 @@ constexpr std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n) // Undefined unless base > 1, pow >= 0, and (base ^ pow) evenly divides n. constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std::intmax_t n) { - while (pow-- > 0) - { - n /= base; - } + while (pow-- > 0) { n /= base; } return n; } // Specialization for the prime factorization of 1 (base case). -template <> +template<> struct prime_factorization<1> { using type = magnitude<>; }; // Specialization for the prime factorization of larger numbers (recursive case). -template +template struct prime_factorization { static_assert(N > 1, "Can only factor positive integers."); @@ -272,8 +322,10 @@ struct prime_factorization { static inline constexpr std::intmax_t remainder = remove_power(first_base, first_power, N); using type = product_t< - magnitude>, prime_factorization_t>; + magnitude, ratio{first_power}>>, prime_factorization_t>; }; +constexpr bool is_prime(std::intmax_t n) { return (n > 1) && (find_first_factor(n) == n); } + } // namespace detail } // namespace units::mag diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index f95c37f9..61115a4f 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,6 +28,9 @@ namespace units::mag { +template +using int_base_power = base_power, ratio{Num, Den}>; + TEST_CASE("Magnitude is invertible") { CHECK(std::is_same_v>, magnitude<>>); @@ -138,12 +141,6 @@ TEST_CASE("is_base_power detects well formed base powers") CHECK(is_base_power_v>); } - SECTION ("base_power disqualified by negative or zero base") - { - CHECK(!is_base_power_v>); - CHECK(!is_base_power_v>); - } - SECTION ("base_power disqualified by base without value") { CHECK(!is_base_power_v>); @@ -238,8 +235,8 @@ 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>()); + CHECK(make_base_power>() == make_ratio<2>()); + CHECK(make_base_power>() == make_ratio<7>()); } SECTION("Handles non-integer bases") @@ -348,6 +345,35 @@ TEST_CASE("Prime factorization") } } +TEST_CASE("is_prime detects primes") +{ + SECTION("Non-positive numbers are not prime") + { + CHECK(!is_prime(-1328)); + CHECK(!is_prime(-1)); + CHECK(!is_prime(0)); + } + + SECTION("1 is not prime") + { + CHECK(!is_prime(1)); + } + + SECTION("Discriminates between primes and non-primes") + { + CHECK(is_prime(2)); + CHECK(is_prime(3)); + CHECK(!is_prime(4)); + CHECK(is_prime(5)); + CHECK(!is_prime(6)); + CHECK(is_prime(7)); + CHECK(!is_prime(8)); + CHECK(!is_prime(9)); + + CHECK(is_prime(7919)); + } +} + } // namespace detail } // namespace units::mag