mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-03 20:34:26 +02:00
Merge pull request #354 from chiphogg/chiphogg/num-den-conv
Support seamless interop between `ratio` and rational `Magnitude`
This commit is contained in:
@@ -392,7 +392,7 @@ template<typename T, BasePower auto... BPs>
|
|||||||
constexpr T get_value(const magnitude<BPs...>&)
|
constexpr T get_value(const magnitude<BPs...>&)
|
||||||
{
|
{
|
||||||
// Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow.
|
// 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) * ... * T{1}));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -480,6 +480,51 @@ constexpr auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>)
|
|||||||
|
|
||||||
constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); }
|
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 = [=](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 conversion to ratio goes here, because it needs `numerator()` and `denominator()`.
|
||||||
|
constexpr ratio as_ratio(Magnitude auto m)
|
||||||
|
requires(is_rational(decltype(m){}))
|
||||||
|
{
|
||||||
|
return ratio{
|
||||||
|
get_value<std::intmax_t>(numerator(m)),
|
||||||
|
get_value<std::intmax_t>(denominator(m)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// `as_magnitude()` implementation.
|
// `as_magnitude()` implementation.
|
||||||
|
|
||||||
|
@@ -87,6 +87,24 @@ struct ratio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] friend constexpr ratio operator/(const ratio& lhs, const ratio& rhs) { return lhs * inverse(rhs); }
|
[[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 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]] 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, -r.exp); }
|
||||||
|
@@ -63,6 +63,18 @@ void check_same_type_and_value(T actual, U expected)
|
|||||||
CHECK(actual == 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")
|
TEST_CASE("base_power")
|
||||||
{
|
{
|
||||||
SECTION("base rep deducible for integral base")
|
SECTION("base rep deducible for integral base")
|
||||||
@@ -351,6 +363,33 @@ 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 converts to ratio")
|
||||||
|
{
|
||||||
|
constexpr ratio r = as_ratio(as_magnitude<ratio{22, 7}>());
|
||||||
|
CHECK(r == ratio{22, 7});
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Irrational magnitude does not convert to ratio")
|
||||||
|
{
|
||||||
|
// The following code should not compile.
|
||||||
|
// as_ratio(pow<ratio{1, 2}>(as_magnitude<2>()));
|
||||||
|
|
||||||
|
// The following code should not compile.
|
||||||
|
// as_ratio(as_magnitude<180>() / pi_to_the<1>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Detail function tests below.
|
// Detail function tests below.
|
||||||
|
|
||||||
@@ -374,6 +413,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")
|
TEST_CASE("Prime helper functions")
|
||||||
{
|
{
|
||||||
SECTION("multiplicity")
|
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(1), ratio(1, 1, 3)) == ratio(1));
|
||||||
static_assert(common_ratio(ratio(10, 1, -1), ratio(1, 1, -3)) == ratio(1, 1, -3));
|
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
|
} // namespace
|
||||||
|
Reference in New Issue
Block a user