From 6a9dcb30def15d28b499c65c6dccca08a9be78ba Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 7 Jul 2022 19:00:38 +0000 Subject: [PATCH 1/4] Migrate explicit-`exp` inputs --- example/custom_systems.cpp | 2 +- .../si-hep/include/units/isq/si/hep/area.h | 2 +- .../si-hep/include/units/isq/si/hep/mass.h | 15 +++++++++++---- .../si-hep/include/units/isq/si/hep/momentum.h | 3 ++- .../si-iau/include/units/isq/si/iau/length.h | 2 +- .../include/units/isq/si/international/length.h | 2 +- .../include/units/isq/si/typographic/length.h | 13 +++++++++---- .../si/include/units/isq/si/catalytic_activity.h | 3 ++- src/systems/si/include/units/isq/si/energy.h | 3 ++- src/systems/si/include/units/isq/si/mass.h | 4 +++- test/unit_test/static/quantity_test.cpp | 7 +++---- test/unit_test/static/unit_test.cpp | 6 +++--- 12 files changed, 39 insertions(+), 23 deletions(-) diff --git a/example/custom_systems.cpp b/example/custom_systems.cpp index b2c3419d..4604be9e 100644 --- a/example/custom_systems.cpp +++ b/example/custom_systems.cpp @@ -54,7 +54,7 @@ using length = quantity; namespace fps { -struct foot : named_scaled_unit(), metre> {}; +struct foot : named_scaled_unit(), metre> {}; struct yard : named_scaled_unit(), foot> {}; struct dim_length : base_dimension<"L", foot> {}; diff --git a/src/systems/si-hep/include/units/isq/si/hep/area.h b/src/systems/si-hep/include/units/isq/si/hep/area.h index e08b59eb..658a12a8 100644 --- a/src/systems/si-hep/include/units/isq/si/hep/area.h +++ b/src/systems/si-hep/include/units/isq/si/hep/area.h @@ -37,7 +37,7 @@ namespace units::isq::si::hep { // effective cross-sectional area according to EU council directive 80/181/EEC // https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:01980L0181-20090527#page=10 // https://www.fedlex.admin.ch/eli/cc/1994/3109_3109_3109/de -struct barn : named_scaled_unit(), square_metre> {}; +struct barn : named_scaled_unit(as_magnitude<10>()), square_metre> {}; struct yocto_barn : prefixed_unit {}; struct zepto_barn : prefixed_unit {}; struct atto_barn : prefixed_unit {}; diff --git a/src/systems/si-hep/include/units/isq/si/hep/mass.h b/src/systems/si-hep/include/units/isq/si/hep/mass.h index e4a895e0..754c2a61 100644 --- a/src/systems/si-hep/include/units/isq/si/hep/mass.h +++ b/src/systems/si-hep/include/units/isq/si/hep/mass.h @@ -44,7 +44,8 @@ namespace units::isq::si::hep { struct eV_per_c2 : named_scaled_unit(), kilogram> {}; + as_magnitude() * pow<-35>(as_magnitude<10>()), + kilogram> {}; struct feV_per_c2 : prefixed_unit {}; struct peV_per_c2 : prefixed_unit {}; struct neV_per_c2 : prefixed_unit {}; @@ -60,11 +61,17 @@ struct PeV_per_c2 : prefixed_unit {}; struct EeV_per_c2 : prefixed_unit {}; struct YeV_per_c2 : prefixed_unit {}; struct electron_mass : - named_scaled_unit(), kilogram> {}; + named_scaled_unit() * pow<-31>(as_magnitude<10>()), + kilogram> {}; struct proton_mass : - named_scaled_unit(), kilogram> {}; + named_scaled_unit() * pow<-27>(as_magnitude<10>()), + kilogram> {}; struct neutron_mass : - named_scaled_unit(), kilogram> {}; + named_scaled_unit() * pow<-27>(as_magnitude<10>()), + kilogram> {}; struct dim_mass : isq::dim_mass {}; diff --git a/src/systems/si-hep/include/units/isq/si/hep/momentum.h b/src/systems/si-hep/include/units/isq/si/hep/momentum.h index c6791501..ab87fb4a 100644 --- a/src/systems/si-hep/include/units/isq/si/hep/momentum.h +++ b/src/systems/si-hep/include/units/isq/si/hep/momentum.h @@ -41,7 +41,8 @@ namespace units::isq::si::hep { struct kilogram_metre_per_second : derived_unit {}; struct eV_per_c : - named_scaled_unit(), + named_scaled_unit() * pow<-35>(as_magnitude<10>()), kilogram_metre_per_second> {}; struct feV_per_c : prefixed_unit {}; struct peV_per_c : prefixed_unit {}; diff --git a/src/systems/si-iau/include/units/isq/si/iau/length.h b/src/systems/si-iau/include/units/isq/si/iau/length.h index d2e184a1..e4066edb 100644 --- a/src/systems/si-iau/include/units/isq/si/iau/length.h +++ b/src/systems/si-iau/include/units/isq/si/iau/length.h @@ -42,7 +42,7 @@ struct light_year : named_scaled_unit(), si::metre> {}; // https://en.wikipedia.org/wiki/Angstrom -struct angstrom : named_scaled_unit(), si::metre> {}; +struct angstrom : named_scaled_unit(as_magnitude<10>()), si::metre> {}; #ifndef UNITS_NO_LITERALS diff --git a/src/systems/si-international/include/units/isq/si/international/length.h b/src/systems/si-international/include/units/isq/si/international/length.h index b61bd2cf..5e3bab57 100644 --- a/src/systems/si-international/include/units/isq/si/international/length.h +++ b/src/systems/si-international/include/units/isq/si/international/length.h @@ -37,7 +37,7 @@ namespace units::isq::si::international { // si::international yard // https://en.wikipedia.org/wiki/International_yard_and_pound -struct yard : named_scaled_unit(), si::metre> {}; +struct yard : named_scaled_unit(), si::metre> {}; // si::international foot // https://en.wikipedia.org/wiki/Foot_(unit)#International_foot diff --git a/src/systems/si-typographic/include/units/isq/si/typographic/length.h b/src/systems/si-typographic/include/units/isq/si/typographic/length.h index e69c31c6..ecd56afd 100644 --- a/src/systems/si-typographic/include/units/isq/si/typographic/length.h +++ b/src/systems/si-typographic/include/units/isq/si/typographic/length.h @@ -38,11 +38,16 @@ namespace units::isq::si::typographic { // TODO Conflicts with (https://en.wikipedia.org/wiki/Pica_(typography)), verify correctness of below conversion factors // and provide hyperlinks to definitions struct pica_comp : - named_scaled_unit(), si::metre> {}; -struct pica_prn : named_scaled_unit(), si::metre> {}; + named_scaled_unit() * pow<-9>(as_magnitude<10>()), si::metre> {}; +struct pica_prn : + named_scaled_unit() * pow<-3>(as_magnitude<10>()), + si::metre> {}; struct point_comp : - named_scaled_unit(), si::metre> {}; -struct point_prn : named_scaled_unit(), si::metre> {}; + named_scaled_unit() * pow<-4>(as_magnitude<10>()), + si::metre> {}; +struct point_prn : + named_scaled_unit() * pow<-4>(as_magnitude<10>()), + si::metre> {}; #ifndef UNITS_NO_LITERALS diff --git a/src/systems/si/include/units/isq/si/catalytic_activity.h b/src/systems/si/include/units/isq/si/catalytic_activity.h index 07ca426f..562eb0fa 100644 --- a/src/systems/si/include/units/isq/si/catalytic_activity.h +++ b/src/systems/si/include/units/isq/si/catalytic_activity.h @@ -58,7 +58,8 @@ struct exakatal : prefixed_unit {}; struct zettakatal : prefixed_unit {}; struct yottakatal : prefixed_unit {}; -struct enzyme_unit : named_scaled_unit(), katal> {}; +struct enzyme_unit : + named_scaled_unit() * pow<-6>(as_magnitude<10>()), katal> {}; struct dim_catalytic_activity : isq::dim_catalytic_activity {}; diff --git a/src/systems/si/include/units/isq/si/energy.h b/src/systems/si/include/units/isq/si/energy.h index cf0eba26..c9eca1cb 100644 --- a/src/systems/si/include/units/isq/si/energy.h +++ b/src/systems/si/include/units/isq/si/energy.h @@ -56,7 +56,8 @@ struct yottajoule : prefixed_unit {}; // N.B. electron charge (and eV) is an exact constant: // https://www.bipm.org/documents/20126/41483022/SI-Brochure-9.pdf#page=147 struct electronvolt : - named_scaled_unit(), joule> {}; + named_scaled_unit() * pow<-19>(as_magnitude<10>()), joule> {}; struct gigaelectronvolt : prefixed_unit {}; struct dim_energy : isq::dim_energy {}; diff --git a/src/systems/si/include/units/isq/si/mass.h b/src/systems/si/include/units/isq/si/mass.h index 53a574f4..e4d935d1 100644 --- a/src/systems/si/include/units/isq/si/mass.h +++ b/src/systems/si/include/units/isq/si/mass.h @@ -79,7 +79,9 @@ struct zettatonne : prefixed_unit {}; struct yottatonne : prefixed_unit {}; struct dalton : - named_scaled_unit(), kilogram> {}; + named_scaled_unit() * pow<-27>(as_magnitude<10>()), + kilogram> {}; struct dim_mass : isq::dim_mass {}; diff --git a/test/unit_test/static/quantity_test.cpp b/test/unit_test/static/quantity_test.cpp index cf346a27..a47c5782 100644 --- a/test/unit_test/static/quantity_test.cpp +++ b/test/unit_test/static/quantity_test.cpp @@ -579,8 +579,7 @@ static_assert(is_same_v>); -static_assert( - compare(), metre>, std::int64_t>>); +static_assert(compare(), metre>, std::int64_t>>); static_assert( compare, exponent>, scaled_unit(), unknown_coherent_unit>, std::int64_t>>); @@ -590,7 +589,7 @@ static_assert( static_assert(compare>); static_assert(compare>, - scaled_unit(), unknown_coherent_unit>, std::int64_t>>); + scaled_unit(), unknown_coherent_unit>, std::int64_t>>); static_assert(compare(), one>, std::int64_t>>); static_assert(compare>); static_assert( @@ -889,7 +888,7 @@ static_assert(is_same_v, units::exponent>, scaled_unit(), unknown_coherent_unit>, std::int64_t>>); static_assert( - is_same_v(), metre>, std::int64_t>>); + is_same_v(), metre>, std::int64_t>>); #else diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index 128fac3d..c4d4baf6 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -36,12 +36,12 @@ using namespace units::isq; struct metre : named_unit {}; struct centimetre : prefixed_unit {}; struct kilometre : prefixed_unit {}; -struct yard : named_scaled_unit(), metre> {}; +struct yard : named_scaled_unit(), metre> {}; struct foot : named_scaled_unit(), yard> {}; struct dim_length : base_dimension<"length", metre> {}; struct second : named_unit {}; -struct hour : named_scaled_unit(), second> {}; +struct hour : named_scaled_unit(), second> {}; struct dim_time : base_dimension<"time", second> {}; struct kelvin : named_unit {}; @@ -60,7 +60,7 @@ struct kilometre_per_hour : derived_scaled_unit); static_assert(equivalent); static_assert(compare(), metre>>, metre>); -static_assert(compare(), metre>>, centimetre>); +static_assert(compare(), metre>>, centimetre>); static_assert(compare>, yard>); static_assert(compare(), metre>>, foot>); static_assert(compare>, kilometre_per_hour>); From 7fd6913b732d6226ff50f86a7a9269213465b611 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 7 Jul 2022 19:19:08 +0000 Subject: [PATCH 2/4] Replace `exp` with `0` everywhere, and remove it This lets us remove a ton of special-casing throughout the codebase, and just generally makes things a lot simpler. We also remove the ability to take rational powers of `ratio`, including `sqrt` and `cbrt` helpers, because these are intrinsically ill-defined. Fixes #369. --- example/custom_systems.cpp | 5 +- src/core/include/units/bits/ratio_maths.h | 112 ++------------------- src/core/include/units/chrono.h | 7 +- src/core/include/units/magnitude.h | 8 +- src/core/include/units/ratio.h | 117 ++++------------------ test/unit_test/runtime/magnitude_test.cpp | 7 -- test/unit_test/static/ratio_test.cpp | 42 -------- 7 files changed, 28 insertions(+), 270 deletions(-) diff --git a/example/custom_systems.cpp b/example/custom_systems.cpp index 4604be9e..90a63dae 100644 --- a/example/custom_systems.cpp +++ b/example/custom_systems.cpp @@ -94,10 +94,7 @@ void unknown_dimensions() std::cout << si_fps_area << "\n"; } -std::ostream& operator<<(std::ostream& os, const ratio& r) -{ - return os << "ratio{" << r.num << ", " << r.den << ", " << r.exp << "}"; -} +std::ostream& operator<<(std::ostream& os, const ratio& r) { return os << "ratio{" << r.num << ", " << r.den << "}"; } template std::ostream& operator<<(std::ostream& os, const U& u) diff --git a/src/core/include/units/bits/ratio_maths.h b/src/core/include/units/bits/ratio_maths.h index d9a6494a..a6cbea4c 100644 --- a/src/core/include/units/bits/ratio_maths.h +++ b/src/core/include/units/bits/ratio_maths.h @@ -39,108 +39,20 @@ template return v < 0 ? -v : v; } -// the following functions enable gcd and related computations on ratios -// with exponents. They avoid overflow. Further information here: -// https://github.com/mpusz/units/issues/62#issuecomment-588152833 - -// Computes (a * b) mod m relies on unsigned integer arithmetic, should not -// overflow -[[nodiscard]] constexpr std::uint64_t mulmod(std::uint64_t a, std::uint64_t b, std::uint64_t m) -{ - std::uint64_t res = 0; - - if (b >= m) { - if (m > UINT64_MAX / 2u) { - b -= m; - } else { - b %= m; - } - } - - while (a != 0) { - if (a & 1) { - if (b >= m - res) { - res -= m; - } - res += b; - } - a >>= 1; - - std::uint64_t temp_b = b; - if (b >= m - b) { - temp_b -= m; - } - b += temp_b; - } - - return res; -} - -// Calculates (a ^ e) mod m , should not overflow. -[[nodiscard]] constexpr std::uint64_t modpow(std::uint64_t a, std::uint64_t e, std::uint64_t m) -{ - a %= m; - std::uint64_t result = 1; - - while (e > 0) { - if (e & 1) { - result = mulmod(result, a, m); - } - a = mulmod(a, a, m); - e >>= 1; - } - return result; -} - -// gcd(a * 10 ^ e, b), should not overflow -[[nodiscard]] constexpr std::intmax_t gcdpow(std::intmax_t a, std::intmax_t e, std::intmax_t b) noexcept -{ - assert(a > 0); - assert(e >= 0); - assert(b > 0); - - // gcd(i, j) = gcd(j, i mod j) for j != 0 Euclid; - // - // gcd(a 10^e, b) = gcd(b, a 10^e mod b) - // - // (a 10^e) mod b -> [ (a mod b) (10^e mod b) ] mod b - - return std::gcd( - b, static_cast(mulmod(static_cast(a % b), - modpow(10, static_cast(e), static_cast(b)), - static_cast(b)))); -} - -constexpr void cwap(std::intmax_t& lhs, std::intmax_t& rhs) -{ - std::intmax_t tmp = lhs; - lhs = rhs; - rhs = tmp; -} - -// Computes the rational gcd of n1/d1 x 10^e1 and n2/d2 x 10^e2 -[[nodiscard]] constexpr auto gcd_frac(std::intmax_t n1, std::intmax_t d1, std::intmax_t e1, std::intmax_t n2, - std::intmax_t d2, std::intmax_t e2) noexcept +// Computes the rational gcd of n1/d1 and n2/d2 +[[nodiscard]] constexpr auto gcd_frac(std::intmax_t n1, std::intmax_t d1, std::intmax_t n2, std::intmax_t d2) noexcept { // Short cut for equal ratios - if (n1 == n2 && d1 == d2 && e1 == e2) { - return std::array{n1, d1, e1}; + if (n1 == n2 && d1 == d2) { + return std::array{n1, d1}; } - if (e2 > e1) { - detail::cwap(n1, n2); - detail::cwap(d1, d2); - detail::cwap(e1, e2); - } - - std::intmax_t exp = e2; // minimum - // gcd(a/b,c/d) = gcd(a⋅d, c⋅b) / b⋅d assert(std::numeric_limits::max() / n1 > d2); assert(std::numeric_limits::max() / n2 > d1); - std::intmax_t num = detail::gcdpow(n1 * d2, e1 - e2, n2 * d1); + std::intmax_t num = std::gcd(n1 * d2, n2 * d1); assert(std::numeric_limits::max() / d1 > d2); @@ -148,29 +60,19 @@ constexpr void cwap(std::intmax_t& lhs, std::intmax_t& rhs) std::intmax_t gcd = std::gcd(num, den); - return std::array{num / gcd, den / gcd, exp}; + return std::array{num / gcd, den / gcd}; } -constexpr void normalize(std::intmax_t& num, std::intmax_t& den, std::intmax_t& exp) +constexpr void normalize(std::intmax_t& num, std::intmax_t& den) { if (num == 0) { den = 1; - exp = 0; return; } std::intmax_t gcd = std::gcd(num, den); num = num * (den < 0 ? -1 : 1) / gcd; den = detail::abs(den) / gcd; - - while (num % 10 == 0) { - num /= 10; - ++exp; - } - while (den % 10 == 0) { - den /= 10; - --exp; - } } [[nodiscard]] constexpr std::intmax_t safe_multiply(std::intmax_t lhs, std::intmax_t rhs) diff --git a/src/core/include/units/chrono.h b/src/core/include/units/chrono.h index 91b4ab6b..d7c9fc91 100644 --- a/src/core/include/units/chrono.h +++ b/src/core/include/units/chrono.h @@ -75,12 +75,7 @@ constexpr std::intmax_t pow_10(std::intmax_t v) template constexpr auto to_std_ratio_impl() { - if constexpr (R.exp == 0) - return std::ratio{}; - else if constexpr (R.exp > 0) - return std::ratio{}; - else - return std::ratio{}; + return std::ratio{}; } } // namespace detail diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 1257e132..47bb1aee 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -189,9 +189,6 @@ constexpr widen_t compute_base_power(BasePower auto bp) if (bp.power.den != 1) { throw std::invalid_argument{"Rational powers not yet supported"}; } - if (bp.power.exp < 0) { - throw std::invalid_argument{"Unsupported exp value"}; - } if (bp.power.num < 0) { if constexpr (std::is_integral_v) { @@ -344,7 +341,7 @@ inline constexpr bool is_base_power_pack_valid = all_base_powers_valid & constexpr bool is_rational(BasePower auto bp) { - return std::is_integral_v && (bp.power.den == 1) && (bp.power.exp >= 0); + return std::is_integral_v && (bp.power.den == 1); } constexpr bool is_integral(BasePower auto bp) { return is_rational(bp) && bp.power.num > 0; } @@ -652,8 +649,7 @@ template requires(R.num > 0) constexpr Magnitude auto as_magnitude() { - return pow(detail::prime_factorization_v<10>) * detail::prime_factorization_v / - detail::prime_factorization_v; + return detail::prime_factorization_v / detail::prime_factorization_v; } namespace detail { diff --git a/src/core/include/units/ratio.h b/src/core/include/units/ratio.h index b57ce06d..a469d4c2 100644 --- a/src/core/include/units/ratio.h +++ b/src/core/include/units/ratio.h @@ -43,42 +43,28 @@ constexpr ratio inverse(const ratio& r); /** * @brief Provides compile-time rational arithmetic support. * - * This class is really similar to @c std::ratio but gets an additional `Exp` - * template parameter that defines the exponent of the ratio. Another important - * difference is the fact that the objects of that class are used as class NTTPs - * rather then a type template parameter kind. + * This class is really similar to @c std::ratio. An important difference is the fact that the objects of that class + * are used as class NTTPs rather then a type template parameter kind. */ struct ratio { std::intmax_t num; std::intmax_t den; - std::intmax_t exp; - constexpr explicit(false) ratio(std::intmax_t n, std::intmax_t d = 1, std::intmax_t e = 0) : num(n), den(d), exp(e) + constexpr explicit(false) ratio(std::intmax_t n, std::intmax_t d = 1) : num(n), den(d) { gsl_Expects(den != 0); - detail::normalize(num, den, exp); + detail::normalize(num, den); } [[nodiscard]] friend constexpr bool operator==(const ratio&, const ratio&) = default; [[nodiscard]] friend constexpr auto operator<=>(const ratio& lhs, const ratio& rhs) { return (lhs - rhs).num <=> 0; } - [[nodiscard]] friend constexpr ratio operator-(const ratio& r) { return ratio(-r.num, r.den, r.exp); } + [[nodiscard]] friend constexpr ratio operator-(const ratio& r) { return ratio(-r.num, r.den); } [[nodiscard]] friend constexpr ratio operator+(ratio lhs, ratio rhs) { - // First, get the inputs into a common exponent. - const auto common_exp = std::min(lhs.exp, rhs.exp); - auto commonify = [common_exp](ratio& r) { - while (r.exp > common_exp) { - r.num *= 10; - --r.exp; - } - }; - commonify(lhs); - commonify(rhs); - - return ratio{lhs.num * rhs.den + lhs.den * rhs.num, lhs.den * rhs.den, common_exp}; + return ratio{lhs.num * rhs.den + lhs.den * rhs.num, lhs.den * rhs.den}; } [[nodiscard]] friend constexpr ratio operator-(const ratio& lhs, const ratio& rhs) { return lhs + (-rhs); } @@ -88,96 +74,31 @@ struct ratio { const std::intmax_t gcd1 = std::gcd(lhs.num, rhs.den); const std::intmax_t gcd2 = std::gcd(rhs.num, lhs.den); return ratio(detail::safe_multiply(lhs.num / gcd1, rhs.num / gcd2), - detail::safe_multiply(lhs.den / gcd2, rhs.den / gcd1), lhs.exp + rhs.exp); + detail::safe_multiply(lhs.den / gcd2, rhs.den / gcd1)); } [[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 numerator(const ratio& r) { return r.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]] friend constexpr std::intmax_t denominator(const ratio& r) { return r.den; } }; -[[nodiscard]] constexpr ratio inverse(const ratio& r) { return ratio(r.den, r.num, -r.exp); } +[[nodiscard]] constexpr ratio inverse(const ratio& r) { return ratio(r.den, r.num); } -[[nodiscard]] constexpr bool is_integral(const ratio& r) -{ - if (r.exp < 0) { - return false; - } else { - return detail::gcdpow(r.num, r.exp, r.den) == r.den; - } -} +[[nodiscard]] constexpr bool is_integral(const ratio& r) { return r.num % r.den == 0; } -namespace detail { - -[[nodiscard]] constexpr auto make_exp_align(const ratio& r, std::intmax_t alignment) -{ - gsl_Expects(alignment > 0); - const std::intmax_t rem = r.exp % alignment; - - if (rem == 0) { // already aligned - return std::array{r.num, r.den, r.exp}; - } - - if (r.exp > 0) { // remainder is positive - return std::array{r.num * ipow10(rem), r.den, r.exp - rem}; - } - - // remainder is negative - return std::array{r.num, r.den * ipow10(-rem), r.exp - rem}; -} - -template - requires gt_zero -[[nodiscard]] constexpr ratio root(const ratio& r) -{ - if constexpr (N == 1) { - return r; - } else { - if (r.num == 0) { - return ratio(0); - } - - const auto aligned = make_exp_align(r, N); - return ratio(iroot(aligned[0]), iroot(aligned[1]), aligned[2] / N); - } -} - -} // namespace detail - -template - requires detail::non_zero +template [[nodiscard]] constexpr ratio pow(const ratio& r) { if constexpr (Num == 0) { return ratio(1); - } else if constexpr (Num == Den) { + } else if constexpr (Num == 1) { return r; } else { - // simplify factors first and compute power for positive exponent - constexpr std::intmax_t gcd = std::gcd(Num, Den); - constexpr std::intmax_t num = detail::abs(Num / gcd); - constexpr std::intmax_t den = detail::abs(Den / gcd); + const ratio result = detail::pow_impl(r); - // integer root loses precision so do pow first - const ratio result = detail::root(detail::pow_impl(r)); - - if constexpr (Num * Den < 0) { // account for negative exponent + if constexpr (Num < 0) { // account for negative exponent return inverse(result); } else { return result; @@ -185,15 +106,11 @@ template } } -[[nodiscard]] constexpr ratio sqrt(const ratio& r) { return pow<1, 2>(r); } - -[[nodiscard]] constexpr ratio cbrt(const ratio& r) { return pow<1, 3>(r); } - // common_ratio [[nodiscard]] constexpr ratio common_ratio(const ratio& r1, const ratio& r2) { - const auto res = detail::gcd_frac(r1.num, r1.den, r1.exp, r2.num, r2.den, r2.exp); - return ratio(res[0], res[1], res[2]); + const auto res = detail::gcd_frac(r1.num, r1.den, r2.num, r2.den); + return ratio(res[0], res[1]); } } // namespace units diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 9fa0aa98..8b80a60d 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -154,13 +154,6 @@ TEST_CASE("make_ratio performs prime factorization correctly") SECTION("Supports fractions") { CHECK(as_magnitude() == magnitude{}); } - SECTION("Supports nonzero exp") - { - constexpr ratio r{3, 1, 2}; - REQUIRE(r.exp == 2); - CHECK(as_magnitude() == as_magnitude<300>()); - } - SECTION("Can handle prime factor which would be large enough to overflow int") { // This was taken from a case which failed when we used `int` for our base to store prime numbers. diff --git a/test/unit_test/static/ratio_test.cpp b/test/unit_test/static/ratio_test.cpp index ad966950..87d36443 100644 --- a/test/unit_test/static/ratio_test.cpp +++ b/test/unit_test/static/ratio_test.cpp @@ -28,11 +28,6 @@ using namespace units; static_assert(ratio(2, 4) == ratio(1, 2)); -// basic exponents tests -static_assert(ratio(2, 40, 1) == ratio(1, 20, 1)); -static_assert(ratio(20, 4, -1) == ratio(10, 2, -1)); -static_assert(ratio(200, 5) == ratio(20'000, 50, -1)); - static_assert(ratio(1) * ratio(3, 8) == ratio(3, 8)); static_assert(ratio(3, 8) * ratio(1) == ratio(3, 8)); static_assert(ratio(4) * ratio(1, 8) == ratio(1, 2)); @@ -45,21 +40,12 @@ static_assert(-ratio(3, 8) == ratio(-3, 8)); // ratio addition static_assert(ratio(1, 2) + ratio(1, 3) == ratio(5, 6)); -static_assert(ratio(1, 3, 2) + ratio(11, 6) == ratio(211, 6)); // 100/3 + 11/6 - -// multiply with exponents -static_assert(ratio(1, 8, 2) * ratio(2, 1, 4) == ratio(1, 4, 6)); -static_assert(ratio(1, 2, -4) * ratio(8, 1, 3) == ratio(4, 1, -1)); static_assert(ratio(4) / ratio(2) == ratio(2)); static_assert(ratio(2) / ratio(8) == ratio(1, 4)); static_assert(ratio(1, 8) / ratio(2) == ratio(1, 16)); static_assert(ratio(6) / ratio(3) == ratio(2)); -// divide with exponents -static_assert(ratio(1, 8, -6) / ratio(2, 1, -8) == ratio(1, 16, 2)); -static_assert(ratio(6, 1, 4) / ratio(3) == ratio(2, 1, 4)); - static_assert(pow<0>(ratio(2)) == ratio(1)); static_assert(pow<1>(ratio(2)) == ratio(2)); static_assert(pow<2>(ratio(2)) == ratio(4)); @@ -69,27 +55,6 @@ static_assert(pow<1>(ratio(1, 2)) == ratio(1, 2)); static_assert(pow<2>(ratio(1, 2)) == ratio(1, 4)); static_assert(pow<3>(ratio(1, 2)) == ratio(1, 8)); -// pow with exponents -static_assert(pow<2>(ratio(1, 2, 3)) == ratio(1, 4, 6)); -static_assert(pow<4, 2>(ratio(1, 2, 3)) == ratio(1, 4, 6)); -static_assert(pow<3>(ratio(1, 2, -6)) == ratio(1, 8, -18)); - -static_assert(sqrt(ratio(9)) == ratio(3)); -static_assert(cbrt(ratio(27)) == ratio(3)); -static_assert(sqrt(ratio(4)) == ratio(2)); -static_assert(cbrt(ratio(8)) == ratio(2)); -static_assert(sqrt(ratio(1)) == ratio(1)); -static_assert(cbrt(ratio(1)) == ratio(1)); -static_assert(sqrt(ratio(0)) == ratio(0)); -static_assert(cbrt(ratio(0)) == ratio(0)); -static_assert(sqrt(ratio(1, 4)) == ratio(1, 2)); -static_assert(cbrt(ratio(1, 8)) == ratio(1, 2)); - -// sqrt with exponents -static_assert(sqrt(ratio(9, 1, 2)) == ratio(3, 1, 1)); -static_assert(cbrt(ratio(27, 1, 3)) == ratio(3, 1, 1)); -static_assert(cbrt(ratio(27, 1, 2)) == ratio(13, 1, 0)); - // common_ratio static_assert(common_ratio(ratio(1), ratio(1000)) == ratio(1)); static_assert(common_ratio(ratio(1000), ratio(1)) == ratio(1)); @@ -98,20 +63,13 @@ static_assert(common_ratio(ratio(1, 1000), ratio(1)) == ratio(1, 1000)); static_assert(common_ratio(ratio(100, 1), ratio(10, 1)) == ratio(10, 1)); static_assert(common_ratio(ratio(100, 1), ratio(1, 10)) == ratio(1, 10)); -// common ratio with exponents -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); // comparison static_assert((ratio(3, 4) <=> ratio(6, 8)) == (0 <=> 0)); static_assert((ratio(3, 4) <=> ratio(-3, 4)) == (0 <=> -1)); static_assert((ratio(-3, 4) <=> ratio(3, -4)) == (0 <=> 0)); -static_assert((ratio(1, 1, 1) <=> ratio(10)) == (0 <=> 0)); } // namespace From 7e591115fa4cc0d8573f5df94ca5faebd5c93626 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 7 Jul 2022 19:22:26 +0000 Subject: [PATCH 3/4] Remove ratio's `numerator()` and `denominator()` These have now become trivial. --- src/core/include/units/magnitude.h | 10 +++++----- src/core/include/units/ratio.h | 4 ---- test/unit_test/static/ratio_test.cpp | 4 ---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 47bb1aee..3e1e7a19 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -198,7 +198,7 @@ constexpr widen_t compute_base_power(BasePower auto bp) } } - auto power = numerator(bp.power); + auto power = bp.power.num; return int_power(static_cast>(bp.get_base()), power); } @@ -495,8 +495,8 @@ namespace detail { template constexpr auto integer_part(magnitude) { - constexpr auto power_num = numerator(BP.power); - constexpr auto power_den = denominator(BP.power); + constexpr auto power_num = BP.power.num; + constexpr auto power_den = BP.power.den; if constexpr (std::is_integral_v && (power_num >= power_den)) { constexpr auto largest_integer_power = [=](BasePower auto bp) { @@ -553,7 +553,7 @@ namespace detail { template constexpr auto remove_positive_power(magnitude m) { - if constexpr (numerator(BP.power) < 0) { + if constexpr (BP.power.num < 0) { return m; } else { return magnitude<>{}; @@ -659,7 +659,7 @@ constexpr ratio get_power(T base, magnitude) return ((BPs.get_base() == base ? BPs.power : ratio{0}) + ... + ratio{0}); } -constexpr std::intmax_t integer_part(ratio r) { return numerator(r) / denominator(r); } +constexpr std::intmax_t integer_part(ratio r) { return r.num / r.den; } constexpr std::intmax_t extract_power_of_10(Magnitude auto m) { diff --git a/src/core/include/units/ratio.h b/src/core/include/units/ratio.h index a469d4c2..d0bd8ec3 100644 --- a/src/core/include/units/ratio.h +++ b/src/core/include/units/ratio.h @@ -78,10 +78,6 @@ 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) { return r.num; } - - [[nodiscard]] friend constexpr std::intmax_t denominator(const ratio& r) { return r.den; } }; [[nodiscard]] constexpr ratio inverse(const ratio& r) { return ratio(r.den, r.num); } diff --git a/test/unit_test/static/ratio_test.cpp b/test/unit_test/static/ratio_test.cpp index 87d36443..02828115 100644 --- a/test/unit_test/static/ratio_test.cpp +++ b/test/unit_test/static/ratio_test.cpp @@ -63,10 +63,6 @@ static_assert(common_ratio(ratio(1, 1000), ratio(1)) == ratio(1, 1000)); static_assert(common_ratio(ratio(100, 1), ratio(10, 1)) == ratio(10, 1)); static_assert(common_ratio(ratio(100, 1), ratio(1, 10)) == ratio(1, 10)); -// numerator and denominator -static_assert(numerator(ratio(3, 4)) == 3); -static_assert(denominator(ratio(3, 4)) == 4); - // comparison static_assert((ratio(3, 4) <=> ratio(6, 8)) == (0 <=> 0)); static_assert((ratio(3, 4) <=> ratio(-3, 4)) == (0 <=> -1)); From 0adee83866f4dd1d85ff60078e3c025c58703fd3 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 27 Jul 2022 14:30:38 +0000 Subject: [PATCH 4/4] Use `mag_power` to make callsites more concise --- src/core/include/units/magnitude.h | 10 ++++++++++ src/systems/si-hep/include/units/isq/si/hep/area.h | 2 +- src/systems/si-hep/include/units/isq/si/hep/mass.h | 12 ++++-------- .../si-hep/include/units/isq/si/hep/momentum.h | 2 +- src/systems/si-iau/include/units/isq/si/iau/length.h | 2 +- .../include/units/isq/si/typographic/length.h | 10 +++++----- .../si/include/units/isq/si/catalytic_activity.h | 3 +-- src/systems/si/include/units/isq/si/energy.h | 4 ++-- src/systems/si/include/units/isq/si/mass.h | 4 ++-- 9 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 3e1e7a19..d3ab7181 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -652,6 +652,16 @@ constexpr Magnitude auto as_magnitude() return detail::prime_factorization_v / detail::prime_factorization_v; } +/** + * @brief Create a Magnitude which is some rational number raised to a rational power. + */ +template + requires(Base.num > 0) +constexpr Magnitude auto mag_power() +{ + return pow(as_magnitude()); +} + namespace detail { template constexpr ratio get_power(T base, magnitude) diff --git a/src/systems/si-hep/include/units/isq/si/hep/area.h b/src/systems/si-hep/include/units/isq/si/hep/area.h index 658a12a8..65130f8a 100644 --- a/src/systems/si-hep/include/units/isq/si/hep/area.h +++ b/src/systems/si-hep/include/units/isq/si/hep/area.h @@ -37,7 +37,7 @@ namespace units::isq::si::hep { // effective cross-sectional area according to EU council directive 80/181/EEC // https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:01980L0181-20090527#page=10 // https://www.fedlex.admin.ch/eli/cc/1994/3109_3109_3109/de -struct barn : named_scaled_unit(as_magnitude<10>()), square_metre> {}; +struct barn : named_scaled_unit(), square_metre> {}; struct yocto_barn : prefixed_unit {}; struct zepto_barn : prefixed_unit {}; struct atto_barn : prefixed_unit {}; diff --git a/src/systems/si-hep/include/units/isq/si/hep/mass.h b/src/systems/si-hep/include/units/isq/si/hep/mass.h index 754c2a61..c3b3bd99 100644 --- a/src/systems/si-hep/include/units/isq/si/hep/mass.h +++ b/src/systems/si-hep/include/units/isq/si/hep/mass.h @@ -44,8 +44,7 @@ namespace units::isq::si::hep { struct eV_per_c2 : named_scaled_unit() * pow<-35>(as_magnitude<10>()), - kilogram> {}; + as_magnitude() * mag_power<10, -35>(), kilogram> {}; struct feV_per_c2 : prefixed_unit {}; struct peV_per_c2 : prefixed_unit {}; struct neV_per_c2 : prefixed_unit {}; @@ -62,16 +61,13 @@ struct EeV_per_c2 : prefixed_unit {}; struct YeV_per_c2 : prefixed_unit {}; struct electron_mass : named_scaled_unit() * pow<-31>(as_magnitude<10>()), - kilogram> {}; + as_magnitude() * mag_power<10, -31>(), kilogram> {}; struct proton_mass : named_scaled_unit() * pow<-27>(as_magnitude<10>()), - kilogram> {}; + as_magnitude() * mag_power<10, -27>(), kilogram> {}; struct neutron_mass : named_scaled_unit() * pow<-27>(as_magnitude<10>()), - kilogram> {}; + as_magnitude() * mag_power<10, -27>(), kilogram> {}; struct dim_mass : isq::dim_mass {}; diff --git a/src/systems/si-hep/include/units/isq/si/hep/momentum.h b/src/systems/si-hep/include/units/isq/si/hep/momentum.h index ab87fb4a..dbc66c57 100644 --- a/src/systems/si-hep/include/units/isq/si/hep/momentum.h +++ b/src/systems/si-hep/include/units/isq/si/hep/momentum.h @@ -42,7 +42,7 @@ struct kilogram_metre_per_second : derived_unit {}; struct eV_per_c : named_scaled_unit() * pow<-35>(as_magnitude<10>()), + as_magnitude() * mag_power<10, -35>(), kilogram_metre_per_second> {}; struct feV_per_c : prefixed_unit {}; struct peV_per_c : prefixed_unit {}; diff --git a/src/systems/si-iau/include/units/isq/si/iau/length.h b/src/systems/si-iau/include/units/isq/si/iau/length.h index e4066edb..bf0226c3 100644 --- a/src/systems/si-iau/include/units/isq/si/iau/length.h +++ b/src/systems/si-iau/include/units/isq/si/iau/length.h @@ -42,7 +42,7 @@ struct light_year : named_scaled_unit(), si::metre> {}; // https://en.wikipedia.org/wiki/Angstrom -struct angstrom : named_scaled_unit(as_magnitude<10>()), si::metre> {}; +struct angstrom : named_scaled_unit(), si::metre> {}; #ifndef UNITS_NO_LITERALS diff --git a/src/systems/si-typographic/include/units/isq/si/typographic/length.h b/src/systems/si-typographic/include/units/isq/si/typographic/length.h index ecd56afd..899bd4e6 100644 --- a/src/systems/si-typographic/include/units/isq/si/typographic/length.h +++ b/src/systems/si-typographic/include/units/isq/si/typographic/length.h @@ -38,15 +38,15 @@ namespace units::isq::si::typographic { // TODO Conflicts with (https://en.wikipedia.org/wiki/Pica_(typography)), verify correctness of below conversion factors // and provide hyperlinks to definitions struct pica_comp : - named_scaled_unit() * pow<-9>(as_magnitude<10>()), si::metre> {}; + named_scaled_unit() * mag_power<10, -9>(), si::metre> {}; struct pica_prn : - named_scaled_unit() * pow<-3>(as_magnitude<10>()), - si::metre> {}; + named_scaled_unit() * mag_power<10, -3>(), si::metre> { +}; struct point_comp : - named_scaled_unit() * pow<-4>(as_magnitude<10>()), + named_scaled_unit() * mag_power<10, -4>(), si::metre> {}; struct point_prn : - named_scaled_unit() * pow<-4>(as_magnitude<10>()), + named_scaled_unit() * mag_power<10, -4>(), si::metre> {}; #ifndef UNITS_NO_LITERALS diff --git a/src/systems/si/include/units/isq/si/catalytic_activity.h b/src/systems/si/include/units/isq/si/catalytic_activity.h index 562eb0fa..397de5c4 100644 --- a/src/systems/si/include/units/isq/si/catalytic_activity.h +++ b/src/systems/si/include/units/isq/si/catalytic_activity.h @@ -58,8 +58,7 @@ struct exakatal : prefixed_unit {}; struct zettakatal : prefixed_unit {}; struct yottakatal : prefixed_unit {}; -struct enzyme_unit : - named_scaled_unit() * pow<-6>(as_magnitude<10>()), katal> {}; +struct enzyme_unit : named_scaled_unit() * mag_power<10, -6>(), katal> {}; struct dim_catalytic_activity : isq::dim_catalytic_activity {}; diff --git a/src/systems/si/include/units/isq/si/energy.h b/src/systems/si/include/units/isq/si/energy.h index c9eca1cb..d5edf645 100644 --- a/src/systems/si/include/units/isq/si/energy.h +++ b/src/systems/si/include/units/isq/si/energy.h @@ -56,8 +56,8 @@ struct yottajoule : prefixed_unit {}; // N.B. electron charge (and eV) is an exact constant: // https://www.bipm.org/documents/20126/41483022/SI-Brochure-9.pdf#page=147 struct electronvolt : - named_scaled_unit() * pow<-19>(as_magnitude<10>()), joule> {}; + named_scaled_unit() * mag_power<10, -19>(), + joule> {}; struct gigaelectronvolt : prefixed_unit {}; struct dim_energy : isq::dim_energy {}; diff --git a/src/systems/si/include/units/isq/si/mass.h b/src/systems/si/include/units/isq/si/mass.h index e4d935d1..e57d2c0b 100644 --- a/src/systems/si/include/units/isq/si/mass.h +++ b/src/systems/si/include/units/isq/si/mass.h @@ -80,8 +80,8 @@ struct yottatonne : prefixed_unit {}; struct dalton : named_scaled_unit() * pow<-27>(as_magnitude<10>()), - kilogram> {}; + as_magnitude() * mag_power<10, -27>(), kilogram> { +}; struct dim_mass : isq::dim_mass {};