From 237640cad8e0ae883b2ceabc1c9f6e7c57ebcc45 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 21 Apr 2023 14:49:51 +0100 Subject: [PATCH] refactor: `ratio` refactored to use `conteval` + _ratio_maths.h_ removed --- src/core/include/mp_units/bits/ratio.h | 85 ++++++++++++------ src/core/include/mp_units/bits/ratio_maths.h | 95 -------------------- test/unit_test/static/ratio_test.cpp | 1 + 3 files changed, 58 insertions(+), 123 deletions(-) delete mode 100644 src/core/include/mp_units/bits/ratio_maths.h diff --git a/src/core/include/mp_units/bits/ratio.h b/src/core/include/mp_units/bits/ratio.h index 48e79a52..077053cb 100644 --- a/src/core/include/mp_units/bits/ratio.h +++ b/src/core/include/mp_units/bits/ratio.h @@ -22,21 +22,39 @@ #pragma once -// IWYU pragma: begin_exports -#include -#include -#include -// IWYU pragma: end_exports - #include -#include #include +#include #include namespace mp_units { -struct ratio; -constexpr ratio inverse(const ratio& r); +namespace detail { + +template +[[nodiscard]] consteval T abs(T v) noexcept +{ + return v < 0 ? -v : v; +} + +[[nodiscard]] consteval std::intmax_t safe_multiply(std::intmax_t lhs, std::intmax_t rhs) +{ + constexpr std::intmax_t c = std::uintmax_t(1) << (sizeof(std::intmax_t) * 4); + + const std::intmax_t a0 = abs(lhs) % c; + const std::intmax_t a1 = abs(lhs) / c; + const std::intmax_t b0 = abs(rhs) % c; + const std::intmax_t b1 = abs(rhs) / c; + + gsl_Assert(a1 == 0 || b1 == 0); // overflow in multiplication + gsl_Assert(a0 * b1 + b0 * a1 < (c >> 1)); // overflow in multiplication + gsl_Assert(b0 * a0 <= INTMAX_MAX); // overflow in multiplication + gsl_Assert((a0 * b1 + b0 * a1) * c <= INTMAX_MAX - b0 * a0); // overflow in multiplication + + return lhs * rhs; +} + +} // namespace detail /** * @brief Provides compile-time rational arithmetic support. @@ -48,45 +66,56 @@ struct ratio { std::intmax_t num; std::intmax_t den; - constexpr explicit(false) ratio(std::intmax_t n, std::intmax_t d = 1) : num(n), den(d) + consteval explicit(false) ratio(std::intmax_t n, std::intmax_t d = 1) : num{n}, den{d} { gsl_Expects(den != 0); - detail::normalize(num, den); + if (num == 0) + den = 1; + else { + std::intmax_t gcd = std::gcd(num, den); + num = num * (den < 0 ? -1 : 1) / gcd; + den = detail::abs(den) / gcd; + } } - [[nodiscard]] friend constexpr bool operator==(const ratio&, const ratio&) = default; + [[nodiscard]] friend consteval bool operator==(ratio, ratio) = default; + [[nodiscard]] friend consteval auto operator<=>(ratio lhs, ratio rhs) { return (lhs - rhs).num <=> 0; } - [[nodiscard]] friend constexpr auto operator<=>(const ratio& lhs, const ratio& rhs) { return (lhs - rhs).num <=> 0; } + [[nodiscard]] friend consteval ratio operator-(ratio r) { return ratio{-r.num, r.den}; } - [[nodiscard]] friend constexpr ratio operator-(const ratio& r) { return ratio(-r.num, r.den); } - - [[nodiscard]] friend constexpr ratio operator+(ratio lhs, ratio rhs) + [[nodiscard]] friend consteval ratio operator+(ratio lhs, ratio rhs) { 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); } + [[nodiscard]] friend consteval ratio operator-(ratio lhs, ratio rhs) { return lhs + (-rhs); } - [[nodiscard]] friend constexpr ratio operator*(const ratio& lhs, const ratio& rhs) + [[nodiscard]] friend consteval ratio operator*(ratio lhs, ratio rhs) { 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)); + return ratio{detail::safe_multiply(lhs.num / gcd1, rhs.num / gcd2), + 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 consteval ratio operator/(ratio lhs, ratio rhs) { return lhs* ratio{rhs.den, rhs.num}; } }; -[[nodiscard]] constexpr ratio inverse(const ratio& r) { return ratio(r.den, r.num); } +[[nodiscard]] consteval bool is_integral(ratio r) { return r.num % r.den == 0; } -[[nodiscard]] constexpr bool is_integral(const ratio& r) { return r.num % r.den == 0; } - -// common_ratio -[[nodiscard]] constexpr ratio common_ratio(const ratio& r1, const ratio& r2) +[[nodiscard]] consteval ratio common_ratio(ratio r1, ratio r2) { - const auto res = detail::gcd_frac(r1.num, r1.den, r2.num, r2.den); - return ratio(res[0], res[1]); + if (r1.num == r2.num && r1.den == r2.den) return ratio{r1.num, r1.den}; + + // gcd(a/b,c/d) = gcd(a⋅d, c⋅b) / b⋅d + gsl_Assert(std::numeric_limits::max() / r1.num > r2.den); + gsl_Assert(std::numeric_limits::max() / r2.num > r1.den); + gsl_Assert(std::numeric_limits::max() / r1.den > r2.den); + + std::intmax_t num = std::gcd(r1.num * r2.den, r2.num * r1.den); + std::intmax_t den = r1.den * r2.den; + std::intmax_t gcd = std::gcd(num, den); + return ratio{num / gcd, den / gcd}; } } // namespace mp_units diff --git a/src/core/include/mp_units/bits/ratio_maths.h b/src/core/include/mp_units/bits/ratio_maths.h deleted file mode 100644 index 64d60db2..00000000 --- a/src/core/include/mp_units/bits/ratio_maths.h +++ /dev/null @@ -1,95 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018 Mateusz Pusz, Conor Williams, Oliver Schonrock -// -// 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 - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace mp_units::detail { - -template -[[nodiscard]] constexpr T abs(T v) noexcept -{ - return v < 0 ? -v : v; -} - -// 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) { - return std::array{n1, d1}; - } - - // 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 = std::gcd(n1 * d2, n2 * d1); - - assert(std::numeric_limits::max() / d1 > d2); - - std::intmax_t den = d1 * d2; - - std::intmax_t gcd = std::gcd(num, den); - - return std::array{num / gcd, den / gcd}; -} - -constexpr void normalize(std::intmax_t& num, std::intmax_t& den) -{ - if (num == 0) { - den = 1; - return; - } - - std::intmax_t gcd = std::gcd(num, den); - num = num * (den < 0 ? -1 : 1) / gcd; - den = detail::abs(den) / gcd; -} - -[[nodiscard]] constexpr std::intmax_t safe_multiply(std::intmax_t lhs, std::intmax_t rhs) -{ - constexpr std::intmax_t c = std::uintmax_t(1) << (sizeof(std::intmax_t) * 4); - - const std::intmax_t a0 = detail::abs(lhs) % c; - const std::intmax_t a1 = detail::abs(lhs) / c; - const std::intmax_t b0 = detail::abs(rhs) % c; - const std::intmax_t b1 = detail::abs(rhs) / c; - - gsl_Expects(a1 == 0 || b1 == 0); // overflow in multiplication - gsl_Expects(a0 * b1 + b0 * a1 < (c >> 1)); // overflow in multiplication - gsl_Expects(b0 * a0 <= INTMAX_MAX); // overflow in multiplication - gsl_Expects((a0 * b1 + b0 * a1) * c <= INTMAX_MAX - b0 * a0); // overflow in multiplication - - return lhs * rhs; -} - -} // namespace mp_units::detail diff --git a/test/unit_test/static/ratio_test.cpp b/test/unit_test/static/ratio_test.cpp index 76817f7c..a9478e48 100644 --- a/test/unit_test/static/ratio_test.cpp +++ b/test/unit_test/static/ratio_test.cpp @@ -53,6 +53,7 @@ static_assert(common_ratio(ratio(1), ratio(1, 1000)) == ratio(1, 1000)); 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)); +static_assert(common_ratio(ratio(2), ratio(4)) == ratio(2)); // comparison static_assert((ratio(3, 4) <=> ratio(6, 8)) == (0 <=> 0));