Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions au/magnitude.hh
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ constexpr const auto &mag_label(MagT = MagT{});
template <std::uintmax_t N>
constexpr auto mag();

// Check whether a Magnitude is representable in type T.
template <typename T, typename... BPs>
constexpr bool representable_in(Magnitude<BPs...> m);

// A base type for prime numbers.
template <std::uintmax_t N>
struct Prime {
Expand Down Expand Up @@ -200,6 +204,51 @@ struct IsRational
template <typename MagT>
struct IsInteger : std::is_same<MagT, IntegerPart<MagT>> {};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Validation utilities for rational Magnitude arithmetic operations.
//
// Many common mathematical operations (comparison, addition, etc.) are not feasible in _general_
// for magnitudes. However, we _can_ support them for _specific subsets_ of magnitudes, the
// simplest being purely rational magnitudes, whose absolute numerator and denominator fit in a
// 64-bit integer. We have decided to provide these operations for _this subset only_, as it
// satisfies many practical use cases.

namespace detail {
template <typename MagT>
struct IsMagnitudeU64RationalCompatibleHelper {
static constexpr bool is_rational() { return IsRational<MagT>::value; }
static constexpr bool numerator_fits() {
return representable_in<std::uint64_t>(Abs<Numerator<MagT>>{});
}
static constexpr bool denominator_fits() {
return representable_in<std::uint64_t>(Denominator<MagT>{});
}
};
template <>
struct IsMagnitudeU64RationalCompatibleHelper<Zero> {
static constexpr bool is_rational() { return true; }
static constexpr bool numerator_fits() { return true; }
static constexpr bool denominator_fits() { return true; }
};

template <typename MagT>
struct IsMagnitudeU64RationalCompatible : IsMagnitudeU64RationalCompatibleHelper<MagT> {
using IsMagnitudeU64RationalCompatibleHelper<MagT>::is_rational;
using IsMagnitudeU64RationalCompatibleHelper<MagT>::numerator_fits;
using IsMagnitudeU64RationalCompatibleHelper<MagT>::denominator_fits;
};

// Instantiating this struct will produce clear compiler errors if the Magnitude doesn't meet the
// requirements for arithmetic operations.
template <typename MagT>
struct AssertMagnitudeU64RationalCompatible {
using Check = IsMagnitudeU64RationalCompatible<MagT>;
static_assert(Check::is_rational(), "Mag must be purely rational");
static_assert(Check::numerator_fits(), "Mag numerator too large to fit in uint64_t");
static_assert(Check::denominator_fits(), "Mag denominator too large to fit in uint64_t");
};
} // namespace detail

// The "common magnitude" of two Magnitudes is the largest Magnitude that evenly divides both.
//
// This is possible only if the quotient of the inputs is rational. If it's not, then the "common
Expand Down
53 changes: 53 additions & 0 deletions au/magnitude_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -600,5 +600,58 @@ TEST(DenominatorPart, OmitsSignForNegativeNumbers) {
StaticAssertTypeEq<DenominatorPart<decltype(-mag<3>() / mag<7>())>, decltype(mag<7>())>();
}

template <typename T>
constexpr bool is_magnitude_u64_rational_compatible(T) {
return detail::IsMagnitudeU64RationalCompatibleHelper<T>::is_rational() &&
detail::IsMagnitudeU64RationalCompatibleHelper<T>::numerator_fits() &&
detail::IsMagnitudeU64RationalCompatibleHelper<T>::denominator_fits();
}

TEST(IsMagnitudeU64RationalCompatible, TrueForReasonablySizedIntegers) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(mag<1>()), IsTrue());
EXPECT_THAT(is_magnitude_u64_rational_compatible(mag<1000>()), IsTrue());
EXPECT_THAT(is_magnitude_u64_rational_compatible(pow<63>(mag<2>())), IsTrue());
}

TEST(IsMagnitudeU64RationalCompatible, FalseForTooLargeIntegers) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(pow<64>(mag<2>())), IsFalse());
}

TEST(IsMagnitudeU64RationalCompatible, TrueForRationalsWithReasonablySizedNumeratorAndDenominator) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(mag<5>() / mag<7>()), IsTrue());
EXPECT_THAT(is_magnitude_u64_rational_compatible(mag<1>() / mag<1000>()), IsTrue());
EXPECT_THAT(is_magnitude_u64_rational_compatible(pow<32>(mag<2>() / mag<3>())), IsTrue());
}

TEST(IsMagnitudeU64RationalCompatible, FalseWhenDenominatorTooLarge) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(mag<5>() / pow<64>(mag<2>())), IsFalse());
}

TEST(IsMagnitudeU64RationalCompatible, TrueForNegatives) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(-mag<5>()), IsTrue());
EXPECT_THAT(is_magnitude_u64_rational_compatible(-mag<5>() / mag<7>()), IsTrue());
EXPECT_THAT(is_magnitude_u64_rational_compatible(-pow<63>(mag<2>()) / pow<32>(mag<3>())),
IsTrue());
}

TEST(IsMagnitudeU64RationalCompatible, TrueForZero) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(ZERO), IsTrue());
}

TEST(IsMagnitudeU64RationalCompatible, FalseForIrrationals) {
EXPECT_THAT(is_magnitude_u64_rational_compatible(Magnitude<Pi>{}), IsFalse());
EXPECT_THAT(is_magnitude_u64_rational_compatible(sqrt(mag<2>())), IsFalse());
}

TEST(AssertMagnitudeU64RationalCompatible, NoCompilerErrorForKnownValidInput) {
(void)AssertMagnitudeU64RationalCompatible<decltype(mag<1>())>{};
(void)AssertMagnitudeU64RationalCompatible<decltype(mag<1000>())>{};
(void)AssertMagnitudeU64RationalCompatible<decltype(pow<63>(mag<2>()))>{};
(void)AssertMagnitudeU64RationalCompatible<decltype(mag<5>() / mag<7>())>{};
(void)AssertMagnitudeU64RationalCompatible<decltype(pow<32>(mag<2>() / mag<3>()))>{};
(void)AssertMagnitudeU64RationalCompatible<decltype(-mag<5>())>{};
(void)AssertMagnitudeU64RationalCompatible<decltype(-pow<63>(mag<2>()) / pow<32>(mag<3>()))>{};
(void)AssertMagnitudeU64RationalCompatible<Zero>{};
}
} // namespace detail
} // namespace au