Merge pull request #354 from chiphogg/chiphogg/num-den-conv

Support seamless interop between `ratio` and rational `Magnitude`
This commit is contained in:
Mateusz Pusz
2022-04-11 17:07:54 +02:00
committed by GitHub
4 changed files with 137 additions and 1 deletions

View File

@@ -392,7 +392,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) * ... * T{1}));
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); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.

View File

@@ -87,6 +87,24 @@ 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 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); }

View File

@@ -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")
@@ -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.
@@ -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")
{
SECTION("multiplicity")

View File

@@ -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