From 3671f64153c7db13a9997909ba5930f51abc2f4d Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Mon, 23 Sep 2024 12:39:22 +0200 Subject: [PATCH] refactor: :boom: magnitudes code cleanup + `mag_pi` is now `mag` --- .../faster_than_lightspeed_constants.md | 2 +- .../framework_basics/systems_of_units.md | 7 +- src/core/CMakeLists.txt | 3 +- .../include/mp-units/bits/constexpr_math.h | 224 +++++ src/core/include/mp-units/bits/sudo_cast.h | 8 +- .../include/mp-units/framework/magnitude.h | 890 +++++------------- src/core/include/mp-units/framework/unit.h | 4 +- .../include/mp-units/systems/angular/units.h | 2 +- .../include/mp-units/systems/si/chrono.h | 2 +- .../include/mp-units/systems/si/constants.h | 2 +- .../include/mp-units/systems/si/units.h | 2 +- test/runtime/truncation_test.cpp | 4 +- test/static/magnitude_test.cpp | 6 +- test/static/unit_test.cpp | 4 +- 14 files changed, 494 insertions(+), 666 deletions(-) create mode 100644 src/core/include/mp-units/bits/constexpr_math.h diff --git a/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md b/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md index cef490db..08dee875 100644 --- a/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md +++ b/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md @@ -45,7 +45,7 @@ inline constexpr struct speed_of_light_in_vacuum final : } // namespace si2019 inline constexpr struct magnetic_constant final : - named_unit<{u8"μ₀", "u_0"}, mag<4> * mag_pi * mag_power<10, -7> * henry / metre> {} magnetic_constant; + named_unit<{u8"μ₀", "u_0"}, mag<4> * mag * mag_power<10, -7> * henry / metre> {} magnetic_constant; } // namespace mp_units::si ``` diff --git a/docs/users_guide/framework_basics/systems_of_units.md b/docs/users_guide/framework_basics/systems_of_units.md index a45e8200..7b1e3c10 100644 --- a/docs/users_guide/framework_basics/systems_of_units.md +++ b/docs/users_guide/framework_basics/systems_of_units.md @@ -184,11 +184,14 @@ For some units, a magnitude might also be irrational. The best example here is a is defined using a floating-point magnitude having a factor of the number π (Pi): ```cpp -inline constexpr struct mag_pi final : magnitude> {} mag_pi; +struct pi : mag_constant { + static constexpr auto value = std::numbers::pi_v; +}; +inline constexpr pi pi; ``` ```cpp -inline constexpr struct degree final : named_unit<{u8"°", "deg"}, mag_pi / mag<180> * si::radian> {} degree; +inline constexpr struct degree final : named_unit<{u8"°", "deg"}, mag / mag<180> * si::radian> {} degree; ``` diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1adba1e9..ffbfc17b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,7 +31,8 @@ endfunction() # core library definition add_mp_units_module( core mp-units-core - HEADERS include/mp-units/bits/core_gmf.h + HEADERS include/mp-units/bits/constexpr_math.h + include/mp-units/bits/core_gmf.h include/mp-units/bits/get_associated_quantity.h include/mp-units/bits/hacks.h include/mp-units/bits/math_concepts.h diff --git a/src/core/include/mp-units/bits/constexpr_math.h b/src/core/include/mp-units/bits/constexpr_math.h new file mode 100644 index 00000000..c3c49307 --- /dev/null +++ b/src/core/include/mp-units/bits/constexpr_math.h @@ -0,0 +1,224 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#include +#endif +#endif + +namespace mp_units::detail { + +// Raise an arbitrary arithmetic type to a positive integer power at compile time. +template +[[nodiscard]] consteval T int_power(T base, std::integral auto exp) +{ + // As this function should only be called at compile time, the terminations herein function as + // "parameter-compatible static_asserts", and should not result in terminations at runtime. + if (exp < 0) { + std::abort(); // int_power only supports positive integer powers + } + + constexpr auto checked_multiply = [](auto a, auto b) { + const auto result = a * b; + MP_UNITS_DIAGNOSTIC_PUSH + MP_UNITS_DIAGNOSTIC_IGNORE_FLOAT_EQUAL + if (result / a != b) { + std::abort(); // Wraparound detected + } + MP_UNITS_DIAGNOSTIC_POP + return result; + }; + + constexpr auto checked_square = [checked_multiply](auto a) { return checked_multiply(a, a); }; + + if (exp == 0) return T{1}; + if (exp % 2 == 1) return checked_multiply(base, int_power(base, exp - 1)); + return checked_square(int_power(base, exp / 2)); +} + +template +[[nodiscard]] consteval std::optional checked_int_pow(T base, std::uintmax_t exp) +{ + T result = T{1}; + while (exp > 0u) { + if (exp % 2u == 1u) { + if (base > std::numeric_limits::max() / result) { + return std::nullopt; + } + result *= base; + } + + exp /= 2u; + + if (base > std::numeric_limits::max() / base) { + return (exp == 0u) ? std::make_optional(result) : std::nullopt; + } + base *= base; + } + return result; +} + +template +[[nodiscard]] consteval std::optional root(T x, std::uintmax_t n) +{ + // The "zeroth root" would be mathematically undefined. + if (n == 0) { + return std::nullopt; + } + + // The "first root" is trivial. + if (n == 1) { + return x; + } + + // We only support nontrivial roots of floating point types. + if (!std::is_floating_point::value) { + return std::nullopt; + } + + // Handle negative numbers: only odd roots are allowed. + if (x < 0) { + if (n % 2 == 0) { + return std::nullopt; + } else { + const auto negative_result = root(-x, n); + if (!negative_result.has_value()) { + return std::nullopt; + } + return static_cast(-negative_result.value()); + } + } + + // Handle special cases of zero and one. + if (x == 0 || x == 1) { + return x; + } + + // Handle numbers bewtween 0 and 1. + if (x < 1) { + const auto inverse_result = root(T{1} / x, n); + if (!inverse_result.has_value()) { + return std::nullopt; + } + return static_cast(T{1} / inverse_result.value()); + } + + // + // At this point, error conditions are finished, and we can proceed with the "core" algorithm. + // + + // Always use `long double` for intermediate computations. We don't ever expect people to be + // calling this at runtime, so we want maximum accuracy. + long double xld = static_cast(x); + long double lo = 1.0; + long double hi = xld; + + // Do a binary search to find the closest value such that `checked_int_pow` recovers the input. + // + // Because we know `n > 1`, and `x > 1`, and x^n is monotonically increasing, we know that + // `checked_int_pow(lo, n) < x < checked_int_pow(hi, n)`. We will preserve this as an + // invariant. + while (lo < hi) { + long double mid = lo + (hi - lo) / 2; + + auto result = checked_int_pow(mid, n); + + if (!result.has_value()) { + return std::nullopt; + } + + // Early return if we get lucky with an exact answer. + if (result.value() == xld) { + return static_cast(mid); + } + + // Check for stagnation. + if (mid == lo || mid == hi) { + break; + } + + // Preserve the invariant that `checked_int_pow(lo, n) < x < checked_int_pow(hi, n)`. + if (result.value() < xld) { + lo = mid; + } else { + hi = mid; + } + } + + // Pick whichever one gets closer to the target. + const auto lo_diff = xld - checked_int_pow(lo, n).value(); + const auto hi_diff = checked_int_pow(hi, n).value() - xld; + return static_cast(lo_diff < hi_diff ? lo : hi); +} + +// A converter for the value member variable of magnitude (below). +// +// The input is the desired result, but in a (wider) intermediate type. The point of this function +// is to cast to the desired type, but avoid overflow in doing so. +template + requires(!std::is_integral_v || std::is_integral_v) +[[nodiscard]] consteval To checked_static_cast(From x) +{ + // This function should only ever be called at compile time. The purpose of these terminations is + // to produce compiler errors, because we cannot `static_assert` on function arguments. + if constexpr (std::is_integral_v) { + if (!std::in_range(x)) { + std::abort(); // Cannot represent magnitude in this type + } + } + + return static_cast(x); +} + +// The exponent of `factor` in the prime factorization of `n`. +[[nodiscard]] consteval std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n) +{ + std::intmax_t m = 0; + while (n % factor == 0) { + n /= factor; + ++m; + } + return m; +} + +// Divide a number by a given base raised to some power. +// +// Undefined unless base > 1, pow >= 0, and (base ^ pow) evenly divides n. +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) +[[nodiscard]] consteval std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std::intmax_t n) +{ + while (pow-- > 0) { + n /= base; + } + return n; +} + +} // namespace mp_units::detail diff --git a/src/core/include/mp-units/bits/sudo_cast.h b/src/core/include/mp-units/bits/sudo_cast.h index 845761b6..9f24949a 100644 --- a/src/core/include/mp-units/bits/sudo_cast.h +++ b/src/core/include/mp-units/bits/sudo_cast.h @@ -76,8 +76,8 @@ struct conversion_type_traits { */ template struct conversion_value_traits { - static constexpr Magnitude auto num = numerator(M); - static constexpr Magnitude auto den = denominator(M); + static constexpr Magnitude auto num = _numerator(M); + static constexpr Magnitude auto den = _denominator(M); static constexpr Magnitude auto irr = M * (den / num); static constexpr T num_mult = get_value(num); static constexpr T den_mult = get_value(den); @@ -121,9 +121,9 @@ template(numerator(c_mag)); }); + return scale([&](auto value) { return value * get_value(_numerator(c_mag)); }); else if constexpr (is_integral(pow<-1>(c_mag))) - return scale([&](auto value) { return value / get_value(denominator(c_mag)); }); + return scale([&](auto value) { return value / get_value(_denominator(c_mag)); }); else { using value_traits = conversion_value_traits; if constexpr (std::is_floating_point_v) diff --git a/src/core/include/mp-units/framework/magnitude.h b/src/core/include/mp-units/framework/magnitude.h index c865cbdc..7cdb9b30 100644 --- a/src/core/include/mp-units/framework/magnitude.h +++ b/src/core/include/mp-units/framework/magnitude.h @@ -23,13 +23,12 @@ #pragma once // IWYU pragma: private, include -#include +#include #include #include #include #include #include -#include #include #include #include @@ -42,6 +41,7 @@ import std; #include #include #include +#include #include #include #endif @@ -56,116 +56,28 @@ using factorizer = wheel_factorizer<4>; } // namespace detail -namespace detail { +MP_UNITS_EXPORT struct mag_constant {}; template -constexpr bool is_magnitude = false; - -template -constexpr bool is_specialization_of_magnitude = false; - -} // namespace detail - -/** - * @brief Concept to detect whether T is a valid Magnitude. - */ -MP_UNITS_EXPORT template -concept Magnitude = detail::is_magnitude; - -/** - * @brief A type to represent a standalone constant value. - */ -// template -// struct constant { -// static constexpr auto value = V; -// }; - -// // is_derived_from_specialization_of_constant -// namespace detail { - -// template -// void to_base_specialization_of_constant(const volatile constant*); - -// template -// constexpr bool is_derived_from_specialization_of_constant = -// requires(T * t) { to_base_specialization_of_constant(t); }; - -// } // namespace detail - - -// template -// concept Constant = detail::is_derived_from_specialization_of_constant; - -// struct pi_v : constant> {}; +concept MagConstant = std::derived_from && std::is_empty_v && requires { + { +T::value } -> std::same_as; +}; /** * @brief Any type which can be used as a basis vector in a PowerV. * * We have two categories. * - * The first is just an `std::intmax_t`. This is for prime number bases. These can always be used directly as NTTPs. + * The first is just an integral type (either `int` or `std::intmax_t`). This is for prime number bases. + * These can always be used directly as NTTPs. * - * The second category is a _custom type_, which has a static member variable of type `long double` that holds its - * value. We choose `long double` to get the greatest degree of precision; users who need a different type can convert - * from this at compile time. This category is for any irrational base we admit into our representation (on which, more - * details below). - * - * The reason we can't hold the value directly for floating point bases is so that we can support some compilers (e.g., - * GCC 10) which don't yet permit floating point NTTPs. + * The second category is a _custom tag type_, which inherits from `mag_constant` and has a static member variable + * `value` of type `long double` that holds its value. We choose `long double` to get the greatest degree of precision; + * users who need a different type can convert from this at compile time. This category is for any irrational base + * we admit into our representation (on which, more details below). */ - -/** - * @brief A basis vector in our magnitude representation, raised to some rational power. - * - * The public API is that there is a `power` member variable (of type `ratio`), and a `get_base_value()` member function - * (of type either `std::intmax_t` or `long double`, as appropriate), for any specialization. - * - * These types exist to be used as NTTPs for the variadic `magnitude<...>` template. We represent a magnitude (which is - * a positive real number) as the product of rational powers of "basis vectors", where each "basis vector" is a positive - * real number. "Addition" in this vector space corresponds to _multiplying_ two real numbers. "Scalar multiplication" - * corresponds to _raising_ a real number to a _rational power_. Thus, this representation of positive real numbers - * maps them onto a vector space over the rationals, supporting the operations of products and rational powers. - * - * (Note that this is the same representation we already use for dimensions.) - * - * As in any vector space, the set of basis vectors must be linearly independent: that is, no product of basis powers - * can ever give the null vector (which in this case represents the number 1), unless all scalars (again, in this case, - * _powers_) are zero. To achieve this, we use the following kinds of basis vectors. - * - Prime numbers. (These are the only allowable values for `std::intmax_t` 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; instead, we would use `base_power{2, ratio{1, 2}}`. - */ - -namespace detail { - template -constexpr bool is_named_magnitude = Magnitude && !detail::is_specialization_of_magnitude; - -} - -#if MP_UNITS_COMP_CLANG - -template -struct mag_value { - T value; -}; - -template -mag_value(T) -> mag_value; - -template -concept PowerVBase = - one_of || is_specialization_of || detail::is_named_magnitude; - -#else - -template -concept PowerVBase = one_of || detail::is_named_magnitude; - -#endif +concept PowerVBase = one_of || MagConstant; // TODO Unify with `power` if UTPs (P1985) are accepted by the Committee template @@ -175,29 +87,25 @@ struct power_v { static constexpr detail::ratio exponent{Num, Den...}; }; -namespace detail { - template -constexpr bool is_specialization_of_power_v = false; - -template -constexpr bool is_specialization_of_power_v> = true; - -} // namespace detail - - -template -concept MagnitudeSpec = PowerVBase || detail::is_specialization_of_power_v; +concept MagnitudeSpec = PowerVBase || is_specialization_of_v; +// TODO refactor to typenames? template struct magnitude; +/** + * @brief Concept to detect whether T is a valid Magnitude. + */ +MP_UNITS_EXPORT template +concept Magnitude = is_specialization_of_v; + namespace detail { template [[nodiscard]] consteval auto get_base(Element element) { - if constexpr (detail::is_specialization_of_power_v) + if constexpr (is_specialization_of_v) return Element::base; else return element; @@ -206,11 +114,10 @@ template template [[nodiscard]] consteval auto get_base_value(Element element) { - if constexpr (detail::is_specialization_of_power_v) return get_base_value(Element::base); -#if MP_UNITS_COMP_CLANG - else if constexpr (is_specialization_of) + if constexpr (is_specialization_of_v) + return get_base_value(Element::base); + else if constexpr (MagConstant) return element.value; -#endif else return element; } @@ -218,34 +125,22 @@ template template [[nodiscard]] MP_UNITS_CONSTEVAL ratio get_exponent(Element) { - if constexpr (detail::is_specialization_of_power_v) + if constexpr (is_specialization_of_v) return Element::exponent; else return ratio{1}; } -} // namespace detail - - -/** - * @brief Concept to detect whether a _type_ is a valid base power. - * - * Note that this is somewhat incomplete. We must also detect whether a _value_ of that type is valid for use with - * `magnitude<...>`. We will defer that second check to the constraints on the `magnitude` template. - */ -namespace detail { - // We do not want magnitude type to have the `l` literal after a value for a small integral number. // For example this modifies `magnitude<3l>` to be `magnitude<3>` template [[nodiscard]] consteval auto shorten_T() { if constexpr (std::integral) { - if constexpr (V <= std::numeric_limits::max()) { + if constexpr (V <= std::numeric_limits::max()) return static_cast(V); - } else { + else return static_cast(V); - } } else if constexpr (std::floating_point) { return static_cast(V); } else { @@ -279,149 +174,6 @@ using widen_t = conditional, conditional, std::intmax_t, std::uintmax_t>>, T>; -// Raise an arbitrary arithmetic type to a positive integer power at compile time. -template -[[nodiscard]] consteval T int_power(T base, std::integral auto exp) -{ - // As this function should only be called at compile time, the terminations herein function as - // "parameter-compatible static_asserts", and should not result in terminations at runtime. - if (exp < 0) { - std::abort(); // int_power only supports positive integer powers - } - - constexpr auto checked_multiply = [](auto a, auto b) { - const auto result = a * b; - MP_UNITS_DIAGNOSTIC_PUSH - MP_UNITS_DIAGNOSTIC_IGNORE_FLOAT_EQUAL - if (result / a != b) { - std::abort(); // Wraparound detected - } - MP_UNITS_DIAGNOSTIC_POP - return result; - }; - - constexpr auto checked_square = [checked_multiply](auto a) { return checked_multiply(a, a); }; - - if (exp == 0) return T{1}; - if (exp % 2 == 1) return checked_multiply(base, int_power(base, exp - 1)); - return checked_square(int_power(base, exp / 2)); -} - -template -[[nodiscard]] consteval std::optional checked_int_pow(T base, std::uintmax_t exp) -{ - T result = T{1}; - while (exp > 0u) { - if (exp % 2u == 1u) { - if (base > std::numeric_limits::max() / result) { - return std::nullopt; - } - result *= base; - } - - exp /= 2u; - - if (base > std::numeric_limits::max() / base) { - return (exp == 0u) ? std::make_optional(result) : std::nullopt; - } - base *= base; - } - return result; -} - -template -[[nodiscard]] consteval std::optional root(T x, std::uintmax_t n) -{ - // The "zeroth root" would be mathematically undefined. - if (n == 0) { - return std::nullopt; - } - - // The "first root" is trivial. - if (n == 1) { - return x; - } - - // We only support nontrivial roots of floating point types. - if (!std::is_floating_point::value) { - return std::nullopt; - } - - // Handle negative numbers: only odd roots are allowed. - if (x < 0) { - if (n % 2 == 0) { - return std::nullopt; - } else { - const auto negative_result = root(-x, n); - if (!negative_result.has_value()) { - return std::nullopt; - } - return static_cast(-negative_result.value()); - } - } - - // Handle special cases of zero and one. - if (x == 0 || x == 1) { - return x; - } - - // Handle numbers bewtween 0 and 1. - if (x < 1) { - const auto inverse_result = root(T{1} / x, n); - if (!inverse_result.has_value()) { - return std::nullopt; - } - return static_cast(T{1} / inverse_result.value()); - } - - // - // At this point, error conditions are finished, and we can proceed with the "core" algorithm. - // - - // Always use `long double` for intermediate computations. We don't ever expect people to be - // calling this at runtime, so we want maximum accuracy. - long double xld = static_cast(x); - long double lo = 1.0; - long double hi = xld; - - // Do a binary search to find the closest value such that `checked_int_pow` recovers the input. - // - // Because we know `n > 1`, and `x > 1`, and x^n is monotonically increasing, we know that - // `checked_int_pow(lo, n) < x < checked_int_pow(hi, n)`. We will preserve this as an - // invariant. - while (lo < hi) { - long double mid = lo + (hi - lo) / 2; - - auto result = checked_int_pow(mid, n); - - if (!result.has_value()) { - return std::nullopt; - } - - // Early return if we get lucky with an exact answer. - if (result.value() == xld) { - return static_cast(mid); - } - - // Check for stagnation. - if (mid == lo || mid == hi) { - break; - } - - // Preserve the invariant that `checked_int_pow(lo, n) < x < checked_int_pow(hi, n)`. - if (result.value() < xld) { - lo = mid; - } else { - hi = mid; - } - } - - // Pick whichever one gets closer to the target. - const auto lo_diff = xld - checked_int_pow(lo, n).value(); - const auto hi_diff = checked_int_pow(hi, n).value() - xld; - return static_cast(lo_diff < hi_diff ? lo : hi); -} - template [[nodiscard]] consteval widen_t compute_base_power(MagnitudeSpec auto el) { @@ -455,132 +207,6 @@ template } } -// A converter for the value member variable of magnitude (below). -// -// The input is the desired result, but in a (wider) intermediate type. The point of this function -// is to cast to the desired type, but avoid overflow in doing so. -template - requires(!std::is_integral_v || std::is_integral_v) -[[nodiscard]] consteval To checked_static_cast(From x) -{ - // This function should only ever be called at compile time. The purpose of these terminations is - // to produce compiler errors, because we cannot `static_assert` on function arguments. - if constexpr (std::is_integral_v) { - if (!std::in_range(x)) { - std::abort(); // Cannot represent magnitude in this type - } - } - - return static_cast(x); -} - -} // namespace detail - - -// A variety of implementation detail helpers. -namespace detail { - -// The exponent of `factor` in the prime factorization of `n`. -[[nodiscard]] consteval std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n) -{ - std::intmax_t m = 0; - while (n % factor == 0) { - n /= factor; - ++m; - } - return m; -} - -// Divide a number by a given base raised to some power. -// -// Undefined unless base > 1, pow >= 0, and (base ^ pow) evenly divides n. -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -[[nodiscard]] consteval std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std::intmax_t n) -{ - while (pow-- > 0) { - n /= base; - } - return n; -} - -// A way to check whether a number is prime at compile time. -// [[nodiscard]] consteval bool is_prime(std::intmax_t n) -// { -// return (n >= 0) && factorizer::is_prime(static_cast(n)); -// } - -// template -// [[nodiscard]] consteval bool is_valid_element(Element element) -// { -// if (get_exponent(element) == 0) { -// return false; -// } -// if constexpr (std::integral) { -// // Some prime numbers are so big, that we can't check their primality without exhausting limits on constexpr -// steps -// // and/or iterations. We can still _perform_ the factorization for these by using the `known_first_factor` -// // workaround. However, we can't _check_ that they are prime, because this workaround depends on the input being -// // usable in a constexpr expression. This is true for `prime_factorization` (below), where the input `N` is a -// // template parameter, but is not true for our case, where the input `bp.get_base_value()` is a function -// parameter. (See -// // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1045r1.html for some background reading on this -// // distinction.) -// // -// // In our case: we simply give up on excluding every possible ill-formed base power, and settle for catching the -// // most likely and common mistakes. -// if (const bool too_big_to_check = (get_base_value(element) > 1'000'000'000)) { -// return true; -// } - -// return is_prime(get_base_value(element)); -// } else { -// return get_base_value(element) > 0; -// } -// } - -// A function object to apply a predicate to all consecutive pairs of values in a sequence. -// template -// struct pairwise_all { -// Predicate predicate; - -// template -// [[nodiscard]] consteval bool operator()(Ts&&... ts) const -// { -// // Carefully handle different sizes, avoiding unsigned integer underflow. -// constexpr auto num_comparisons = [](auto num_elements) { -// return (num_elements > 1) ? (num_elements - 1) : 0; -// }(sizeof...(Ts)); - -// // Compare zero or more pairs of neighbours as needed. -// return [this](std::tuple && t, std::index_sequence) -// { -// return (predicate(std::get(t), std::get(t)) && ...); -// } -// (std::make_tuple(std::forward(ts)...), std::make_index_sequence()); -// } -// }; - -// Deduction guide: permit constructions such as `pairwise_all{std::less{}}`. -// template -// pairwise_all(T) -> pairwise_all; - -// Check whether a sequence of (possibly heterogeneously typed) values are strictly increasing. -// template -// requires(std::is_signed_v && ...) -// [[nodiscard]] consteval bool strictly_increasing(Ts&&... ts) -// { -// return pairwise_all{std::less{}}(std::forward(ts)...); -// } - -// template -// constexpr bool all_elements_valid = (is_valid_element(Elements) && ...); - -// template -// constexpr bool all_elements_in_order = strictly_increasing(get_base_value(Elements)...); - -// template -// constexpr bool is_element_pack_valid = all_elements_valid && all_elements_in_order; - [[nodiscard]] consteval bool is_rational(MagnitudeSpec auto element) { static_assert(!Magnitude); // magnitudes are handles by another overload @@ -597,53 +223,98 @@ namespace detail { // Magnitude product implementation. [[nodiscard]] consteval bool less(MagnitudeSpec auto lhs, MagnitudeSpec auto rhs) { - using lhs_base_t = decltype(get_base_value(lhs)); - using rhs_base_t = decltype(get_base_value(rhs)); - - if constexpr (is_named_magnitude && is_named_magnitude) - return detail::type_name() < detail::type_name(); - else if constexpr (!is_named_magnitude && !is_named_magnitude) - return get_base_value(lhs) < get_base_value(rhs); - else - return is_named_magnitude; + return get_base_value(lhs) < get_base_value(rhs); } -template -[[nodiscard]] consteval Magnitude auto multiply_impl(magnitude, magnitude) -{ - using namespace detail; +// The largest integer which can be extracted from any magnitude with only a single basis vector. +template +[[nodiscard]] consteval auto integer_part(magnitude); +[[nodiscard]] consteval std::intmax_t integer_part(ratio r) { return r.num / r.den; } - if constexpr (less(H1, H2)) { - if constexpr (sizeof...(T1) == 0) { - // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. - return magnitude{}; - } else { - return magnitude

{} * (magnitude{} * magnitude{}); - } - } else if constexpr (less(H2, H1)) { - return magnitude

{} * (magnitude{} * magnitude{}); - } else { - if constexpr (is_same_v) { - constexpr auto partial_product = magnitude{} * magnitude{}; - if constexpr (get_exponent(H1) + get_exponent(H2) == 0) { - return partial_product; +template +[[nodiscard]] consteval auto remove_positive_power(magnitude m); + +inline constexpr symbol_text base_multiplier(u8"× 10", "x 10"); + +template + requires detail::gt_zero +[[nodiscard]] consteval Magnitude auto mag_power_lazy(); + +template +struct magnitude_base {}; + +template +struct magnitude_base> { + template + [[nodiscard]] friend consteval Magnitude auto _multiply_impl(magnitude, magnitude) + { + if constexpr (less(H, H2)) { + if constexpr (sizeof...(T) == 0) { + // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. + return magnitude{}; } else { - // Make a new power_v with the common base of H1 and H2, whose power is their powers' sum. - constexpr auto new_head = power_v_or_T(); - - if constexpr (get_exponent(new_head) == 0) { + return magnitude{} * (magnitude{} * magnitude{}); + } + } else if constexpr (less(H2, H)) { + return magnitude

{} * (magnitude{} * magnitude{}); + } else { + if constexpr (is_same_v) { + constexpr auto partial_product = magnitude{} * magnitude{}; + if constexpr (get_exponent(H) + get_exponent(H2) == 0) { return partial_product; } else { - return magnitude{} * partial_product; + // Make a new power_v with the common base of H and H2, whose power is their powers' sum. + constexpr auto new_head = power_v_or_T(); + + if constexpr (get_exponent(new_head) == 0) { + return partial_product; + } else { + return magnitude{} * partial_product; + } } } - } else if constexpr (is_named_magnitude) { - return magnitude

{} * (magnitude{} * magnitude{}); - } else { - return magnitude

{} * (magnitude{} * magnitude{}); } } -} + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 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). + template + [[nodiscard]] friend consteval auto _common_magnitude(magnitude, magnitude) + { + using detail::remove_positive_power; + + if constexpr (detail::get_base_value(H) < detail::get_base_value(H2)) { + // When H1 has the smaller base, prepend to result from recursion. + return remove_positive_power(magnitude{}) * _common_magnitude(magnitude{}, magnitude{}); + } else if constexpr (detail::get_base_value(H2) < detail::get_base_value(H)) { + // When H2 has the smaller base, prepend to result from recursion. + return remove_positive_power(magnitude

{}) * _common_magnitude(magnitude{}, magnitude{}); + } else { + // When the bases are equal, pick whichever has the lower power. + constexpr auto common_tail = _common_magnitude(magnitude{}, magnitude{}); + if constexpr (detail::get_exponent(H) < detail::get_exponent(H2)) { + return magnitude{} * common_tail; + } else { + return magnitude

{} * common_tail; + } + } + } +}; } // namespace detail @@ -655,8 +326,7 @@ template * rational powers, and compare for equality. */ template -// requires detail::is_element_pack_valid -struct magnitude { +struct magnitude : detail::magnitude_base> { [[nodiscard]] friend consteval bool is_integral(const magnitude&) { using namespace detail; // needed for recursive case when magnitudes are in the MagnitudeSpec @@ -677,7 +347,7 @@ struct magnitude { else if constexpr (is_same_v>) return m1; else - return detail::multiply_impl(m1, m2); + return _multiply_impl(m1, m2); } [[nodiscard]] friend consteval auto operator/(magnitude l, Magnitude auto r) { return l * pow<-1>(r); } @@ -699,74 +369,102 @@ struct magnitude { constexpr T result = detail::checked_static_cast((detail::compute_base_power(Ms) * ... * T{1})); return result; } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Magnitude rational powers implementation. + template + [[nodiscard]] friend consteval auto pow(magnitude) + { + if constexpr (Num == 0) { + return magnitude<>{}; + } else { + return magnitude< + detail::power_v_or_T()...>{}; + } + } + + [[nodiscard]] friend consteval auto sqrt(magnitude m) { return pow<1, 2>(m); } + [[nodiscard]] friend consteval auto cbrt(magnitude m) { return pow<1, 3>(m); } + +private: + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Magnitude numerator and denominator implementation. + [[nodiscard]] friend consteval auto _numerator(magnitude) + { + return (detail::integer_part(magnitude{}) * ... * magnitude<>{}); + } + + [[nodiscard]] friend consteval auto _denominator(magnitude) { return _numerator(pow<-1>(magnitude{})); } + + + [[nodiscard]] friend consteval auto _remove_positive_powers(magnitude) + { + return (magnitude<>{} * ... * detail::remove_positive_power(magnitude{})); + } + + [[nodiscard]] friend consteval auto _common_magnitude_type_impl(magnitude) + { + return (std::intmax_t{} * ... * decltype(detail::get_base_value(Ms)){}); + } + + template + [[nodiscard]] friend consteval detail::ratio _get_power(T base, magnitude) + { + return ((detail::get_base_value(Ms) == base ? detail::get_exponent(Ms) : detail::ratio{0}) + ... + + detail::ratio{0}); + } + + [[nodiscard]] friend consteval std::intmax_t _extract_power_of_10(magnitude m) + { + const auto power_of_2 = _get_power(2, m); + const auto power_of_5 = _get_power(5, m); + + if ((power_of_2 * power_of_5).num <= 0) return 0; + + return detail::integer_part((detail::abs(power_of_2) < detail::abs(power_of_5)) ? power_of_2 : power_of_5); + } + + [[nodiscard]] friend consteval auto _magnitude_text(magnitude) + { + constexpr auto exp10 = _extract_power_of_10(magnitude{}); + + constexpr Magnitude auto base = magnitude{} / detail::mag_power_lazy<10, exp10>(); + constexpr Magnitude auto num = _numerator(base); + constexpr Magnitude auto den = _denominator(base); + // TODO address the below + static_assert(base == num / den, "Printing rational powers, or irrational bases, not yet supported"); + + constexpr auto num_value = get_value(num); + constexpr auto den_value = get_value(den); + + if constexpr (num_value == 1 && den_value == 1 && exp10 != 0) { + return detail::base_multiplier + detail::superscript(); + } else if constexpr (num_value != 1 || den_value != 1 || exp10 != 0) { + auto txt = symbol_text("[") + detail::regular(); + if constexpr (den_value == 1) { + if constexpr (exp10 == 0) { + return txt + symbol_text("]"); + } else { + return txt + symbol_text(" ") + detail::base_multiplier + detail::superscript() + symbol_text("]"); + } + } else { + if constexpr (exp10 == 0) { + return txt + symbol_text("/") + detail::regular() + symbol_text("]"); + } else { + return txt + symbol_text("/") + detail::regular() + symbol_text(" ") + detail::base_multiplier + + detail::superscript() + symbol_text("]"); + } + } + } else { + return symbol_text(""); + } + } }; +[[nodiscard]] consteval auto _common_magnitude(magnitude<>, Magnitude auto m) { return _remove_positive_powers(m); } +[[nodiscard]] consteval auto _common_magnitude(Magnitude auto m, magnitude<>) { return _remove_positive_powers(m); } +[[nodiscard]] consteval auto _common_magnitude(magnitude<> m, magnitude<>) { return m; } -namespace detail { - -template -void to_base_specialization_of_magnitude(const volatile magnitude*); - -template -constexpr bool is_derived_from_specialization_of_magnitude = - requires(T* type) { to_base_specialization_of_magnitude(type); }; - -template - requires is_derived_from_specialization_of_magnitude -constexpr bool is_magnitude = true; - -template -constexpr bool is_specialization_of_magnitude> = true; - -} // namespace detail - -MP_UNITS_EXPORT_BEGIN - -/** - * @brief A convenient Magnitude constant for pi, which we can manipulate like a regular number. - */ -#if MP_UNITS_COMP_CLANG - -inline constexpr struct mag_pi : magnitude}> { -} mag_pi; - -#else - -inline constexpr struct mag_pi : magnitude> { -} mag_pi; - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Magnitude rational powers implementation. - -template -[[nodiscard]] consteval auto pow(magnitude) -{ - if constexpr (Num == 0) { - return magnitude<>{}; - } else { - return magnitude< - detail::power_v_or_T()...>{}; - } -} - -template -[[nodiscard]] consteval auto sqrt(magnitude m) -{ - return pow<1, 2>(m); -} - -template -[[nodiscard]] consteval auto cbrt(magnitude m) -{ - return pow<1, 3>(m); -} - -MP_UNITS_EXPORT_END - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Magnitude numerator and denominator implementation. namespace detail { @@ -785,32 +483,6 @@ template } } -template -[[nodiscard]] consteval auto numerator(magnitude) -{ - return (detail::integer_part(magnitude{}) * ... * magnitude<>{}); -} - -[[nodiscard]] consteval auto denominator(Magnitude auto m) { return numerator(pow<-1>(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). - template [[nodiscard]] consteval auto remove_positive_power(magnitude m) { @@ -821,55 +493,9 @@ template } } -template -[[nodiscard]] consteval auto remove_positive_powers(magnitude) -{ - return (magnitude<>{} * ... * remove_positive_power(magnitude{})); -} - -// Base cases, for when either (or both) inputs are the identity. -[[nodiscard]] consteval auto common_magnitude(magnitude<>, magnitude<>) { return magnitude<>{}; } -[[nodiscard]] consteval auto common_magnitude(magnitude<>, Magnitude auto m) -{ - return detail::remove_positive_powers(m); -} -[[nodiscard]] consteval 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 -[[nodiscard]] consteval auto common_magnitude(magnitude, magnitude) -{ - using detail::remove_positive_power; - - if constexpr (detail::get_base_value(H1) < detail::get_base_value(H2)) { - // When H1 has the smaller base, prepend to result from recursion. - return remove_positive_power(magnitude

{}) * common_magnitude(magnitude{}, magnitude{}); - } else if constexpr (detail::get_base_value(H2) < detail::get_base_value(H1)) { - // When H2 has the smaller base, prepend to result from recursion. - return remove_positive_power(magnitude

{}) * common_magnitude(magnitude{}, magnitude{}); - } else { - // When the bases are equal, pick whichever has the lower power. - constexpr auto common_tail = common_magnitude(magnitude{}, magnitude{}); - if constexpr (detail::get_exponent(H1) < detail::get_exponent(H2)) { - return magnitude

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

{} * common_tail; - } - } -} - -template -[[nodiscard]] consteval auto common_magnitude_type_impl(magnitude) -{ - return (std::intmax_t{} * ... * decltype(get_base_value(Ms)){}); -} - // Returns the most precise type to express the magnitude factor template -using common_magnitude_type = decltype(common_magnitude_type_impl(M)); +using common_magnitude_type = decltype(_common_magnitude_type_impl(M)); } // namespace detail @@ -920,85 +546,59 @@ constexpr auto prime_factorization_v = prime_factorization::value; } // namespace detail -/** - * @brief Convert any positive integer to a Magnitude. - * - * This will be the main way end users create Magnitudes. They should rarely (if ever) create a magnitude<...> by - * manually adding base powers. - */ -MP_UNITS_EXPORT template - requires detail::gt_zero -constexpr Magnitude auto mag = detail::prime_factorization_v; +template +concept MagArg = std::integral || MagConstant; -MP_UNITS_EXPORT template +namespace detail { + +template +[[nodiscard]] consteval Magnitude auto make_magnitude() +{ + if constexpr (MagConstant) + return magnitude{}; + else + return prime_factorization_v; +} + +} // namespace detail + +MP_UNITS_EXPORT_BEGIN + +template + requires detail::gt_zero +constexpr Magnitude auto mag = detail::make_magnitude(); + +template requires detail::gt_zero constexpr Magnitude auto mag_ratio = detail::prime_factorization_v / detail::prime_factorization_v; /** * @brief Create a Magnitude which is some rational number raised to a rational power. */ -MP_UNITS_EXPORT template +template requires detail::gt_zero constexpr Magnitude auto mag_power = pow(mag); +/** + * @brief A convenient Magnitude constant for pi, which we can manipulate like a regular number. + */ +struct pi : mag_constant { + static constexpr auto value = std::numbers::pi_v; +}; +inline constexpr pi pi; + +MP_UNITS_EXPORT_END + namespace detail { -template -[[nodiscard]] consteval ratio get_power(T base, magnitude) +// This is introduced to break the dependency cycle between `magnitude::_magnitude_text` and `prime_factorization` +template + requires detail::gt_zero +[[nodiscard]] consteval Magnitude auto mag_power_lazy() { - return ((get_base_value(Ms) == base ? get_exponent(Ms) : ratio{0}) + ... + ratio{0}); -} - -[[nodiscard]] consteval std::intmax_t integer_part(ratio r) { return r.num / r.den; } - -[[nodiscard]] consteval std::intmax_t extract_power_of_10(Magnitude auto m) -{ - const auto power_of_2 = get_power(2, m); - const auto power_of_5 = get_power(5, m); - - if ((power_of_2 * power_of_5).num <= 0) return 0; - - return integer_part((detail::abs(power_of_2) < detail::abs(power_of_5)) ? power_of_2 : power_of_5); -} - -inline constexpr symbol_text base_multiplier(u8"× 10", "x 10"); - -template -[[nodiscard]] consteval auto magnitude_text() -{ - constexpr auto exp10 = extract_power_of_10(M); - - constexpr Magnitude auto base = M / mag_power<10, exp10>; - constexpr Magnitude auto num = numerator(base); - constexpr Magnitude auto den = denominator(base); - // TODO address the below - static_assert(base == num / den, "Printing rational powers, or irrational bases, not yet supported"); - - constexpr auto num_value = get_value(num); - constexpr auto den_value = get_value(den); - - if constexpr (num_value == 1 && den_value == 1 && exp10 != 0) { - return base_multiplier + superscript(); - } else if constexpr (num_value != 1 || den_value != 1 || exp10 != 0) { - auto txt = symbol_text("[") + regular(); - if constexpr (den_value == 1) { - if constexpr (exp10 == 0) { - return txt + symbol_text("]"); - } else { - return txt + symbol_text(" ") + base_multiplier + superscript() + symbol_text("]"); - } - } else { - if constexpr (exp10 == 0) { - return txt + symbol_text("/") + regular() + symbol_text("]"); - } else { - return txt + symbol_text("/") + regular() + symbol_text(" ") + base_multiplier + - superscript() + symbol_text("]"); - } - } - } else { - return symbol_text(""); - } + return pow(mag); } } // namespace detail + } // namespace mp_units diff --git a/src/core/include/mp-units/framework/unit.h b/src/core/include/mp-units/framework/unit.h index 332586eb..ec47afc9 100644 --- a/src/core/include/mp-units/framework/unit.h +++ b/src/core/include/mp-units/framework/unit.h @@ -654,7 +654,7 @@ template else if constexpr (is_integral(canonical_rhs.mag / canonical_lhs.mag)) return u1; else { - constexpr auto common_magnitude = detail::common_magnitude(canonical_lhs.mag, canonical_rhs.mag); + constexpr auto common_magnitude = _common_magnitude(canonical_lhs.mag, canonical_rhs.mag); return scaled_unit{}; } } @@ -741,7 +741,7 @@ constexpr Out unit_symbol_impl(Out out, const scaled_unit_impl& u, const u // no ratio/prefix return unit_symbol_impl(out, u.reference_unit, fmt, negative_power); } else { - constexpr auto mag_txt = magnitude_text(); + constexpr auto mag_txt = _magnitude_text(M); out = copy(mag_txt, fmt.encoding, out); if constexpr (space_before_unit_symbol::reference_unit>) *out++ = ' '; diff --git a/src/systems/include/mp-units/systems/angular/units.h b/src/systems/include/mp-units/systems/angular/units.h index 25709fd9..9a9a766f 100644 --- a/src/systems/include/mp-units/systems/angular/units.h +++ b/src/systems/include/mp-units/systems/angular/units.h @@ -41,7 +41,7 @@ QUANTITY_SPEC(angle, dim_angle); QUANTITY_SPEC(solid_angle, pow<2>(angle)); inline constexpr struct radian final : named_unit<"rad", kind_of> {} radian; -inline constexpr struct revolution final : named_unit<"rev", mag<2> * mag_pi * radian> {} revolution; +inline constexpr struct revolution final : named_unit<"rev", mag<2> * mag * radian> {} revolution; inline constexpr struct degree final : named_unit * revolution> {} degree; inline constexpr struct gradian final : named_unit * revolution> {} gradian; inline constexpr struct steradian final : named_unit<"sr", square(radian)> {} steradian; diff --git a/src/systems/include/mp-units/systems/si/chrono.h b/src/systems/include/mp-units/systems/si/chrono.h index 0ded9689..f809f7b5 100644 --- a/src/systems/include/mp-units/systems/si/chrono.h +++ b/src/systems/include/mp-units/systems/si/chrono.h @@ -122,7 +122,7 @@ namespace detail { [[nodiscard]] constexpr auto as_ratio(Magnitude auto m) requires(is_rational(m)) { - return std::ratio(numerator(m)), get_value(denominator(m))>{}; + return std::ratio(_numerator(m)), get_value(_denominator(m))>{}; } } // namespace detail diff --git a/src/systems/include/mp-units/systems/si/constants.h b/src/systems/include/mp-units/systems/si/constants.h index a43c871c..f032bf74 100644 --- a/src/systems/include/mp-units/systems/si/constants.h +++ b/src/systems/include/mp-units/systems/si/constants.h @@ -57,7 +57,7 @@ inline constexpr struct luminous_efficacy final : inline constexpr struct standard_gravity final : named_unit * metre / square(second)> {} standard_gravity; inline constexpr struct magnetic_constant final : - named_unit * mag_pi * mag_power<10, -7> * henry / metre> {} magnetic_constant; + named_unit * mag * mag_power<10, -7> * henry / metre> {} magnetic_constant; // clang-format on } // namespace mp_units::si diff --git a/src/systems/include/mp-units/systems/si/units.h b/src/systems/include/mp-units/systems/si/units.h index af14f2df..54130dd0 100644 --- a/src/systems/include/mp-units/systems/si/units.h +++ b/src/systems/include/mp-units/systems/si/units.h @@ -100,7 +100,7 @@ inline constexpr struct minute final : named_unit<"min", mag<60> * si::second> { inline constexpr struct hour final : named_unit<"h", mag<60> * minute> {} hour; inline constexpr struct day final : named_unit<"d", mag<24> * hour> {} day; inline constexpr struct astronomical_unit final : named_unit<"au", mag<149'597'870'700> * si::metre> {} astronomical_unit; -inline constexpr struct degree final : named_unit * si::radian> {} degree; +inline constexpr struct degree final : named_unit / mag<180> * si::radian> {} degree; inline constexpr struct arcminute final : named_unit * degree> {} arcminute; inline constexpr struct arcsecond final : named_unit * arcminute> {} arcsecond; inline constexpr struct are final : named_unit<"a", square(si::deca)> {} are; diff --git a/test/runtime/truncation_test.cpp b/test/runtime/truncation_test.cpp index 3ff10046..0944210d 100644 --- a/test/runtime/truncation_test.cpp +++ b/test/runtime/truncation_test.cpp @@ -42,11 +42,11 @@ using namespace mp_units; using namespace mp_units::angular; using namespace mp_units::angular::unit_symbols; -inline constexpr struct half_revolution final : named_unit<"hrev", mag_pi * radian> { +inline constexpr struct half_revolution final : named_unit<"hrev", mag * radian> { } half_revolution; inline constexpr auto hrev = half_revolution; -// constexpr auto revb6 = mag_ratio<1,3> * mag_pi * rad; +// constexpr auto revb6 = mag_ratio<1,3> * mag * rad; TEST_CASE("value_cast should not truncate for valid inputs", "[value_cast]") { diff --git a/test/static/magnitude_test.cpp b/test/static/magnitude_test.cpp index ab82cbfc..329ac016 100644 --- a/test/static/magnitude_test.cpp +++ b/test/static/magnitude_test.cpp @@ -216,9 +216,9 @@ static_assert(std::is_same_v{})), mag_2_> // SECTION("pi to the 1 supplies correct values") // { -// check_same_type_and_value(get_value(mag_pi), std::numbers::pi_v); -// check_same_type_and_value(get_value(mag_pi), std::numbers::pi_v); -// check_same_type_and_value(get_value(mag_pi), std::numbers::pi_v); +// check_same_type_and_value(get_value(mag), std::numbers::pi_v); +// check_same_type_and_value(get_value(mag), std::numbers::pi_v); +// check_same_type_and_value(get_value(mag), std::numbers::pi_v); // } // SECTION("pi to arbitrary power performs computations in most accurate type at compile time") diff --git a/test/static/unit_test.cpp b/test/static/unit_test.cpp index ba7ecb4c..33cf200b 100644 --- a/test/static/unit_test.cpp +++ b/test/static/unit_test.cpp @@ -74,7 +74,7 @@ inline constexpr struct degree_Celsius_ final : named_unit * second> {} minute; inline constexpr struct hour_ final : named_unit<"h", mag<60> * minute> {} hour; -inline constexpr struct degree_ final : named_unit * radian> {} degree; +inline constexpr struct degree_ final : named_unit / mag<180> * radian> {} degree; inline constexpr struct yard_ final : named_unit<"yd", mag_ratio<9'144, 10'000> * metre> {} yard; inline constexpr struct mile_ final : named_unit<"mi", mag<1760> * yard> {} mile; @@ -140,7 +140,7 @@ static_assert(get_canonical_unit(radian).mag == mag<1>); static_assert(is_of_type); static_assert(is_of_type); -static_assert(get_canonical_unit(degree).mag == mag_pi / mag<180>); +static_assert(get_canonical_unit(degree).mag == mag / mag<180>); static_assert(convertible(radian, degree)); static_assert(radian != degree);