mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-04 20:54:28 +02:00
Support seamless interop between ratio
and rational Magnitude
We provide two new functions, `numerator(m)` and `denominator(m)`, for a Magnitude `m`. They fulfill the following conditions: 1. `numerator(m)` and `denominator(m)` are always integer Magnitudes. 2. If `m` is rational, then `m == numerator(m) / denominator(m)`. If `m` is _not_ rational, then the numerator and denominator are not especially meaningful (there is no uniquely defined "leftover irrational part"). However, we choose a convention that matches how humans would write a mixed number. For example, sqrt(27/16) would have a numerator of 3, denominator of 4, and a "leftover part" of sqrt(3), matching the "human" way of writing this as [(3 * sqrt(3)) / 4]. This has no use yet, but it may later be useful in printing the Magnitude of an anonymous Unit for end users. To further reduce friction for the upcoming migration, we provide an implicit conversion from a Magnitude to a `ratio`. We restrict this operation to rational Magnitudes, and guard this with a `static_assert`.
This commit is contained in:
@@ -368,6 +368,9 @@ struct magnitude {
|
||||
|
||||
// Whether this magnitude represents a rational number.
|
||||
friend constexpr bool is_rational(const magnitude&) { return (detail::is_rational(BPs) && ...); }
|
||||
|
||||
// Implicit conversion to ratio.
|
||||
constexpr explicit(false) operator ratio() const;
|
||||
};
|
||||
|
||||
// Implementation for Magnitude concept (below).
|
||||
@@ -392,7 +395,7 @@ template<typename T, BasePower auto... BPs>
|
||||
constexpr T get_value(const magnitude<BPs...>&)
|
||||
{
|
||||
// Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow.
|
||||
constexpr auto result = detail::checked_static_cast<T>((detail::compute_base_power<T>(BPs) * ...));
|
||||
constexpr auto result = detail::checked_static_cast<T>((detail::compute_base_power<T>(BPs) * ... * 1));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -480,6 +483,53 @@ constexpr auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>)
|
||||
|
||||
constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Magnitude numerator and denominator implementation.
|
||||
|
||||
namespace detail {
|
||||
|
||||
// The largest integer which can be extracted from any magnitude with only a single basis vector.
|
||||
template<BasePower auto BP>
|
||||
constexpr auto integer_part(magnitude<BP>)
|
||||
{
|
||||
constexpr auto power_num = numerator(BP.power);
|
||||
constexpr auto power_den = denominator(BP.power);
|
||||
|
||||
if constexpr (std::is_integral_v<decltype(BP.get_base())> && (power_num >= power_den)) {
|
||||
constexpr auto largest_integer_power = [power_num, power_den](BasePower auto bp) {
|
||||
bp.power = (power_num / power_den); // Note: integer division intended.
|
||||
return bp;
|
||||
}(BP); // Note: lambda is immediately invoked.
|
||||
|
||||
return magnitude<largest_integer_power>{};
|
||||
} else {
|
||||
return magnitude<>{};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<BasePower auto... BPs>
|
||||
constexpr auto numerator(magnitude<BPs...>)
|
||||
{
|
||||
return (detail::integer_part(magnitude<BPs>{}) * ... * magnitude<>{});
|
||||
}
|
||||
|
||||
constexpr auto denominator(Magnitude auto m) { return numerator(pow<-1>(m)); }
|
||||
|
||||
// Implementation of implicit conversion to ratio goes here, because it needs `numerator()` and `denominator()`.
|
||||
template<BasePower auto... BPs>
|
||||
constexpr magnitude<BPs...>::operator ratio() const
|
||||
{
|
||||
static_assert(is_rational(magnitude<BPs...>{}));
|
||||
|
||||
return ratio{
|
||||
get_value<std::intmax_t>(numerator(*this)),
|
||||
get_value<std::intmax_t>(denominator(*this)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// `as_magnitude()` implementation.
|
||||
|
||||
|
@@ -87,8 +87,27 @@ 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)
|
||||
{
|
||||
std::intmax_t num = r.num;
|
||||
for (auto i = r.exp; i > 0; --i) {
|
||||
num *= 10;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr std::intmax_t denominator(const ratio& r)
|
||||
{
|
||||
std::intmax_t den = r.den;
|
||||
for (auto i = r.exp; i < 0; ++i) {
|
||||
den *= 10;
|
||||
}
|
||||
return den;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
[[nodiscard]] constexpr ratio inverse(const ratio& r) { return ratio(r.den, r.num, -r.exp); }
|
||||
|
||||
[[nodiscard]] constexpr bool is_integral(const ratio& r)
|
||||
|
@@ -63,6 +63,18 @@ void check_same_type_and_value(T actual, U expected)
|
||||
CHECK(actual == expected);
|
||||
}
|
||||
|
||||
template<ratio R>
|
||||
void check_ratio_round_trip_is_identity()
|
||||
{
|
||||
constexpr Magnitude auto m = as_magnitude<R>();
|
||||
constexpr ratio round_trip = ratio{
|
||||
get_value<std::intmax_t>(numerator(m)),
|
||||
get_value<std::intmax_t>(denominator(m)),
|
||||
};
|
||||
CHECK(round_trip == R);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("base_power")
|
||||
{
|
||||
SECTION("base rep deducible for integral base")
|
||||
@@ -156,6 +168,15 @@ TEST_CASE("make_ratio performs prime factorization correctly")
|
||||
as_magnitude<ratio(16'605'390'666'050, 10'000'000'000'000)>();
|
||||
}
|
||||
|
||||
SECTION("Can handle prime number which would exceed GCC iteration limit")
|
||||
{
|
||||
// GCC 10 has a constexpr loop iteration limit of 262144. A naive algorithm, which performs trial division on 2 and
|
||||
// all odd numbers up to sqrt(N), will exceed this limit for the following prime. Thus, for this test to pass, we
|
||||
// need to be using a more efficient algorithm. (We could increase the limit, but we don't want users to have to
|
||||
// mess with compiler flags just to compile the code.)
|
||||
as_magnitude<334'524'384'739>();
|
||||
}
|
||||
|
||||
SECTION("Can bypass computing primes by providing known_first_factor<N>")
|
||||
{
|
||||
// Sometimes, even wheel factorization isn't enough to handle the compilers' limits on constexpr steps and/or
|
||||
@@ -351,6 +372,35 @@ TEST_CASE("can distinguish integral, rational, and irrational magnitudes")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Constructing ratio from rational magnitude")
|
||||
{
|
||||
SECTION("Round trip is identity")
|
||||
{
|
||||
// Note that not every Magnitude can be represented as a ratio. However, if we _start_ with a
|
||||
// ratio, we must guarantee to recover the same ratio in a round trip.
|
||||
check_ratio_round_trip_is_identity<1>();
|
||||
check_ratio_round_trip_is_identity<9>();
|
||||
check_ratio_round_trip_is_identity<ratio{5, 8}>();
|
||||
}
|
||||
|
||||
SECTION("Rational magnitude implicitly converts to ratio")
|
||||
{
|
||||
constexpr ratio r = as_magnitude<ratio{22, 7}>();
|
||||
CHECK(r == ratio{22, 7});
|
||||
}
|
||||
|
||||
SECTION("Irrational magnitude does not convert to ratio")
|
||||
{
|
||||
// The following code should not compile.
|
||||
// constexpr ratio radical = pow<ratio{1, 2}>(as_magnitude<2>());
|
||||
// (void)radical;
|
||||
|
||||
// The following code should not compile.
|
||||
// constexpr ratio degrees_per_radian = as_magnitude<180>() / pi_to_the<1>();
|
||||
// (void)degrees_per_radian;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Detail function tests below.
|
||||
|
||||
@@ -374,6 +424,34 @@ TEST_CASE("int_power computes integer powers")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("integer_part picks out integer part of single-basis magnitude")
|
||||
{
|
||||
SECTION("integer_part of non-integer base is identity magnitude")
|
||||
{
|
||||
CHECK(integer_part(pi_to_the<1>()) == magnitude<>{});
|
||||
CHECK(integer_part(pi_to_the<-8>()) == magnitude<>{});
|
||||
CHECK(integer_part(pi_to_the<ratio{3, 4}>()) == magnitude<>{});
|
||||
}
|
||||
|
||||
SECTION("integer_part of integer base to negative power is identity magnitude")
|
||||
{
|
||||
CHECK(integer_part(magnitude<base_power{2, -8}>{}) == magnitude<>{});
|
||||
CHECK(integer_part(magnitude<base_power{11, -1}>{}) == magnitude<>{});
|
||||
}
|
||||
|
||||
SECTION("integer_part of integer base to fractional power is identity magnitude")
|
||||
{
|
||||
CHECK(integer_part(magnitude<base_power{2, ratio{1, 2}}>{}) == magnitude<>{});
|
||||
}
|
||||
|
||||
SECTION("integer_part of integer base to power at least one takes integer part")
|
||||
{
|
||||
CHECK(integer_part(magnitude<base_power{2, 1}>{}) == magnitude<base_power{2, 1}>{});
|
||||
CHECK(integer_part(magnitude<base_power{2, ratio{19, 10}}>{}) == magnitude<base_power{2, 1}>{});
|
||||
CHECK(integer_part(magnitude<base_power{11, ratio{97, 9}}>{}) == magnitude<base_power{11, 10}>{});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Prime helper functions")
|
||||
{
|
||||
SECTION("multiplicity")
|
||||
|
@@ -102,4 +102,10 @@ static_assert(common_ratio(ratio(100, 1), ratio(1, 10)) == ratio(1, 10));
|
||||
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);
|
||||
|
||||
} // namespace
|
||||
|
Reference in New Issue
Block a user