(partial) Switch base_power to NTTP

Apparently, gcc-10 does not support floating point NTTPs, so we'll need
to reimagine the approach.
This commit is contained in:
Chip Hogg
2022-01-08 14:04:19 -05:00
parent 6033f96267
commit 92b7a15d6f
2 changed files with 177 additions and 444 deletions

View File

@@ -29,76 +29,6 @@
namespace units::mag
{
/**
* @brief A basis vector in our magnitude representation, raised to some rational power.
*
* The set of basis vectors must be linearly independent: that is, no product of basis powers can ever equal 1, unless
* all exponents are zero. To achieve this, we use arbitrarily many prime numbers as basis vectors, and also various
* irrational numbers such as pi.
*
* @tparam Base A type representing the basis vector. Must have a numeric static `value` member.
* @tparam Power The rational power to which the base is raised.
*/
template<typename Base, ratio Power = ratio{1}>
struct base_power {
using base = Base;
static constexpr ratio power = Power;
};
/**
* @brief A type trait and concept to detect whether something is a valid "base power".
*/
template<typename T>
struct is_base_power;
template<typename T>
inline constexpr bool is_base_power_v = is_base_power<T>::value;
template<typename T>
concept BasePower = is_base_power_v<T>;
/**
* @brief A representation for positive real numbers which optimizes taking products and rational powers.
*
* Magnitudes can be treated as values. Each type encodes exactly one value. Users can multiply, divide, and compare
* for equality using this value API.
*/
template<BasePower... BasePowers>
struct magnitude;
/**
* @brief A type trait and concept to detect whether something is a valid magnitude.
*
* In particular, these traits check for canonicalized forms: the base powers must be sorted by increasing base value,
* and all exponents must be nonzero.
*/
template<typename T>
struct is_magnitude;
template<typename T>
inline constexpr bool is_magnitude_v = is_magnitude<T>::value;
template<typename T>
concept Magnitude = is_magnitude_v<T>;
/**
* @brief Compute the inverse of a Magnitude.
*/
template<Magnitude M>
struct inverse;
template<Magnitude M>
using inverse_t = typename inverse<M>::type;
/**
* @brief Compute the product of 0 or more Magnitudes.
*/
template<Magnitude... Mags>
struct product;
template<Magnitude... Mags>
using product_t = typename product<Mags...>::type;
/**
* @brief Compute the quotient of 2 Magnitudes.
*/
template<Magnitude T, Magnitude U>
using quotient_t = product_t<T, inverse_t<U>>;
namespace detail
{
// Helpers to perform prime factorization at compile time.
@@ -106,19 +36,119 @@ template<std::intmax_t N>
requires requires { N > 0; }
struct prime_factorization;
template<std::intmax_t N>
requires requires { N > 0; }
using prime_factorization_t = typename prime_factorization<N>::type;
static constexpr auto prime_factorization_v = prime_factorization<N>::value;
// A way to check whether a number is prime at compile time.
constexpr bool is_prime(std::intmax_t n);
} // namespace detail
// Integer rep is for prime numbers; long double is for any irrational base we permit.
template<typename T>
concept BaseRep = std::is_same_v<T, int> || std::is_same_v<T, long double>;
/**
* @brief A template to represent prime number bases.
* @brief A basis vector in our magnitude representation, raised to some rational power.
*
* The set of basis vectors must be linearly independent: that is, no product of basis powers can ever equal 1, unless
* all exponents are zero. To achieve this, we use the following kinds of basis vectors.
* - Prime numbers. (These are the only allowable values for `int` base.)
* - Certain selected irrational numbers, such as pi.
*
* Before allowing a new irrational base, make sure that it _cannot_ be represented as the product of rational powers of
* _existing_ bases, including both prime numbers and any other irrational bases. For example, even though sqrt(2) is
* irrational, we must not ever use it as a base
*/
template<std::intmax_t N>
requires requires { detail::is_prime(N); }
struct prime_base : std::integral_constant<std::intmax_t, N> {};
template<BaseRep T>
struct base_power {
// The value of the basis vector.
T base;
// The rational power to which the base is raised.
ratio power{1};
};
template<BaseRep T, std::convertible_to<ratio> U>
base_power(T, U) -> base_power<T>;
template<BaseRep T>
base_power(T) -> base_power<T>;
template<typename T, typename U>
constexpr bool operator==(base_power<T> t, base_power<U> u) {
return std::is_same_v<T, U> && (t.base == u.base) && (t.power == u.power);
}
template<BaseRep T>
constexpr auto inverse(base_power<T> bp) {
bp.power = -bp.power;
return bp;
}
namespace detail
{
template<BaseRep T>
constexpr bool is_valid_base_power(const base_power<T> &bp) {
if (bp.power == 0) { return false; }
if constexpr (std::is_same_v<T, int>) { return is_prime(bp.base); }
else if constexpr (std::is_same_v<T, long double>) { return bp.base > 0; }
else { return false; } // Unreachable.
}
} // namespace detail
template<typename T>
concept BasePower = std::is_same_v<T, base_power<int>> || std::is_same_v<T, base_power<long double>>;
/**
* @brief A representation for positive real numbers which optimizes taking products and rational powers.
*
* Magnitudes can be treated as values. Each type encodes exactly one value. Users can multiply, divide, and compare
* for equality using this value API.
*/
template<BasePower auto... BasePowers>
requires requires {
// (is_valid_base_power(BasePowers) && ... && strictly_increasing(std::make_tuple(BasePowers.base...)));
(detail::is_valid_base_power(BasePowers) && ...);
}
struct magnitude {};
template<BasePower auto... LeftBPs, BasePower auto... RightBPs>
constexpr bool operator==(magnitude<LeftBPs...>, magnitude<RightBPs...>) { return ((LeftBPs == RightBPs) && ...); }
template<BasePower auto... BasePowers>
constexpr auto inverse(magnitude<BasePowers...>) { return magnitude<inverse(BasePowers)...>{}; }
constexpr auto operator*(magnitude<>, magnitude<>) { return magnitude<>{}; }
template<BasePower auto... BPs>
constexpr auto operator*(magnitude<>, magnitude<BPs...> m) { return m; }
template<BasePower auto... BPs>
constexpr auto operator*(magnitude<BPs...> m, magnitude<>) { return m; }
template<BasePower auto H1, BasePower auto... T1, BasePower auto H2, BasePower auto... T2>
constexpr auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>) {
// Shortcut for prepending, which makes it easier to implement some of the other cases.
if constexpr ((sizeof...(T1) == 0) && H1.base < H2.base) { return magnitude<H1, H2, T2...>{}; }
if constexpr (H1.base == H2.base) {
constexpr auto partial_product = magnitude<T1...>{} * magnitude<T2...>{};
constexpr base_power new_head{H1.base, (H1.power + H2.power)};
if constexpr (new_head.power == 0) {
return partial_product;
} else {
return magnitude<new_head>{} * partial_product;
}
} else if constexpr(H1.base < H2.base){
return magnitude<H1>{} * (magnitude<T1...>{} * magnitude<H2, T2...>{});
} else { // We know H2.base < H1.base
return magnitude<H2>{} * (magnitude<H1, T1...>{} * magnitude<T2...>{});
}
}
template<BasePower auto... LeftBPs, BasePower auto... RightBPs>
constexpr auto operator/(magnitude<LeftBPs...> l, magnitude<RightBPs...> r) { return l * inverse(r); }
/**
* @brief Make a Magnitude that is a rational number.
@@ -127,66 +157,25 @@ struct prime_base : std::integral_constant<std::intmax_t, N> {};
* manually adding base powers.
*/
template<std::intmax_t N, std::intmax_t D = 1>
constexpr auto make_ratio() {
return quotient_t<detail::prime_factorization_t<N>, detail::prime_factorization_t<D>>{};
}
/**
* @brief Make a Magnitude from a single base raised to a particular power.
*
* This should handle all of the remaining use cases which can't be captured by make_ratio(), i.e., any irrational
* magnitudes. For example:
* - `make_base_power<pi>()` to represent pi
* - `make_base_power<prime_base<2>, 1, 2>()` to represent sqrt(2)
*/
template<typename T, std::intmax_t N = 1, std::intmax_t D = 1>
constexpr auto make_base_power() {
return magnitude<base_power<T, ratio{N, D}>>{};
}
constexpr auto make_ratio() { return detail::prime_factorization_v<N> / detail::prime_factorization_v<D>; }
/**
* @brief A base to represent pi.
*/
struct pi {
static constexpr long double value = std::numbers::pi_v<long double>;
};
template<ratio Power>
requires requires { Power != 0; }
constexpr auto pi_power() { return base_power{std::numbers::pi_v<long double>, Power}; }
template<ratio Power>
constexpr auto pi_to_the() { return magnitude<pi_power<Power>()>{}; }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation details below.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<BasePower... Bs>
struct magnitude {
template<Magnitude M>
constexpr bool operator==(M) const { return std::is_same_v<magnitude, M>; }
template<Magnitude M>
constexpr friend auto operator*(magnitude, M) { return product_t<magnitude, M>{}; }
template<Magnitude M>
constexpr friend auto operator/(magnitude, M) { return quotient_t<magnitude, M>{}; }
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BasePower concept implementation.
// Default implementation: most things are not base powers.
template<typename T>
struct is_base_power: std::false_type {};
// To be a valid base power, one must be a base_power<B, E>, where B has a static value member which is positive.
template<typename B, ratio E>
requires requires() { B::value; }
struct is_base_power<base_power<B, E>>
: std::bool_constant<(B::value > 0)> {};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Magnitude concept implementation.
// Default implementation: most things are not magnitudes.
template<typename T>
struct is_magnitude: std::false_type {};
template<typename Predicate, typename... Ts>
constexpr bool pairwise_all(const std::tuple<Ts...> &ts, const Predicate &pred) {
// Carefully handle different sizes, avoiding unsigned integer underflow.
@@ -206,78 +195,6 @@ constexpr bool strictly_increasing(const std::tuple<Ts...> &ts) {
return pairwise_all(ts, std::less{});
}
// To be a valid magnitude, one must be a magnitude<...> of BasePowers with nonzero exponents, sorted by increasing base
// value.
template<BasePower... Bs>
struct is_magnitude<magnitude<Bs...>>
: std::bool_constant<(strictly_increasing(std::make_tuple(Bs::base::value...)) && ((Bs::power.num != 0) && ...))> {};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Inverse implementation.
// To invert a BasePower, negate all exponents.
template<BasePower B>
using base_power_inverse = base_power<typename B::base, -B::power>;
template<BasePower... Bs>
struct inverse<magnitude<Bs...>> { using type = magnitude<base_power_inverse<Bs>...>; };
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Product implementation.
// Convenience utility to prepend a base_power to a magnitude.
//
// Assumes that the prepended power has a smaller base than every base power in the magnitude.
template<BasePower B, Magnitude M>
struct prepend_base;
template<BasePower B, Magnitude M>
using prepend_base_t = typename prepend_base<B, M>::type;
template<BasePower B, BasePower... Bs>
struct prepend_base<B, magnitude<Bs...>> { using type = magnitude<B, Bs...>; };
// Nullary case.
template<>
struct product<> { using type = magnitude<>; };
// Unary case.
template<Magnitude M>
struct product<M> { using type = M; };
// Binary case, where right argument is null magnitude.
template<Magnitude M>
struct product<M, magnitude<>> { using type = M; };
// Binary case, where left argument is null magnitude, and right is non-null.
template<BasePower B, BasePower... Bs>
struct product<magnitude<>, magnitude<B, Bs...>> { using type = magnitude<B, Bs...>; };
// Binary case, with distinct and non-null heads.
template<BasePower Head1, BasePower... Tail1, BasePower Head2, BasePower... Tail2>
struct product<magnitude<Head1, Tail1...>, magnitude<Head2, Tail2...>>
{
using type = std::conditional_t<
(Head1::base::value < Head2::base::value),
prepend_base_t<Head1, product_t<magnitude<Tail1...>, magnitude<Head2, Tail2...>>>,
prepend_base_t<Head2, product_t<magnitude<Head1, Tail1...>, magnitude<Tail2...>>>>;
};
// Binary case, same head.
template<typename Base, ratio Pow1, ratio Pow2, BasePower... Tail1, BasePower... Tail2>
struct product<magnitude<base_power<Base, Pow1>, Tail1...>,
magnitude<base_power<Base, Pow2>, Tail2...>>
{
using tail_product = product_t<magnitude<Tail1...>, magnitude<Tail2...>>;
static constexpr auto Pow = Pow1 + Pow2;
using type = std::conditional_t<
Pow.num == 0,
tail_product,
prepend_base_t<base_power<Base, Pow>, tail_product>>;
};
// N-ary case (N > 2).
template<Magnitude T, Magnitude U, Magnitude... Tail>
struct product<T, U, Tail...> { using type = product_t<product_t<T, U>, Tail...>; };
namespace detail
{
@@ -317,18 +234,18 @@ constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std:
// Specialization for the prime factorization of 1 (base case).
template<>
struct prime_factorization<1> { using type = magnitude<>; };
struct prime_factorization<1> { static constexpr magnitude<> value{}; };
// Specialization for the prime factorization of larger numbers (recursive case).
template<std::intmax_t N>
requires requires { N > 0; }
struct prime_factorization {
static constexpr std::intmax_t first_base = find_first_factor(N);
static constexpr int first_base = find_first_factor(N);
static constexpr std::intmax_t first_power = multiplicity(first_base, N);
static constexpr std::intmax_t remainder = remove_power(first_base, first_power, N);
using type = product_t<
magnitude<base_power<prime_base<first_base>, ratio{first_power}>>, prime_factorization_t<remainder>>;
static constexpr auto value = magnitude<base_power{first_base, first_power}>{}
* prime_factorization_v<remainder>;
};
constexpr bool is_prime(std::intmax_t n) { return (n > 1) && (find_first_factor(n) == n); }

View File

@@ -28,175 +28,6 @@
namespace units::mag
{
// Convenience utility to create an integral base power. For unit tests only.
template<std::intmax_t N, std::intmax_t Num, std::intmax_t Den>
struct IntBasePower
{
// This setup is more complicated than might appear necessary in the hopes of appeasing a
// (possibly spurious) MSVC 14 compiler error.
static constexpr std::intmax_t num = Num;
static constexpr std::intmax_t den = Den;
static constexpr ratio power = ratio{num, den};
using type = base_power<prime_base<N>, power>;
};
template<std::intmax_t N, std::intmax_t Num = 1, std::intmax_t Den = 1>
using int_base_power = typename IntBasePower<N, Num, Den>::type;
TEST_CASE("Magnitude is invertible")
{
CHECK(std::is_same_v<inverse_t<magnitude<>>, magnitude<>>);
CHECK(std::is_same_v<
inverse_t<magnitude<int_base_power<2>>>, magnitude<int_base_power<2, -1>>>);
CHECK(std::is_same_v<
inverse_t<magnitude<int_base_power<3, 1, 2>, int_base_power<11, -5>>>,
magnitude<int_base_power<3, -1, 2>, int_base_power<11, 5>>>);
}
TEST_CASE("Magnitude supports products")
{
SECTION ("The nullary product gives the unit magnitude") {
CHECK(std::is_same_v<product_t<>, magnitude<>>);
}
SECTION ("The unary product is the identity operation") {
CHECK(std::is_same_v<
product_t<magnitude<int_base_power<3, 4>>>,
magnitude<int_base_power<3, 4>>>);
CHECK(std::is_same_v<
product_t<magnitude<int_base_power<2, -1, 3>, int_base_power<13, -2>>>,
magnitude<int_base_power<2, -1, 3>, int_base_power<13, -2>>>);
}
SECTION ("Binary product with null magnitude is identity") {
using arbitrary_mag = magnitude<int_base_power<11, 3, 2>>;
CHECK(std::is_same_v<product_t<magnitude<>, magnitude<>>, magnitude<>>);
CHECK(std::is_same_v<product_t<arbitrary_mag, magnitude<>>, arbitrary_mag>);
CHECK(std::is_same_v<product_t<magnitude<>, arbitrary_mag>, arbitrary_mag>);
}
SECTION ("Binary product with distinct bases maintains sorted order") {
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 1, 3>, int_base_power<7, -2>>,
magnitude<int_base_power<3>, int_base_power<5, 5>>>,
magnitude<
int_base_power<2, 1, 3>,
int_base_power<3>,
int_base_power<5, 5>,
int_base_power<7, -2>>>);
}
SECTION ("Binary products add exponents for same bases") {
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 3>>,
magnitude<int_base_power<2, -5>>>,
magnitude<int_base_power<2, -2>>>);
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 3>, int_base_power<3, -1, 3>>,
magnitude<int_base_power<2, -5>, int_base_power<5, 4>>>,
magnitude<int_base_power<2, -2>, int_base_power<3, -1, 3>, int_base_power<5, 4>>>);
}
SECTION ("Binary products omit bases whose exponents cancel") {
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 1, 3>>, magnitude<int_base_power<2, -1, 3>>>,
magnitude<>>);
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 1, 3>, int_base_power<7, -2>>,
magnitude<int_base_power<2, -1, 3>, int_base_power<5, 5>>>,
magnitude<int_base_power<5, 5>, int_base_power<7, -2>>>);
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 1, 3>, int_base_power<3, -2>, int_base_power<7, -2>>,
magnitude<int_base_power<2, -1, 3>, int_base_power<5, 5>, int_base_power<7, 2>>>,
magnitude<int_base_power<3, -2>, int_base_power<5, 5>>>);
}
SECTION ("N-ary products recurse") {
CHECK(std::is_same_v<
product_t<
magnitude<int_base_power<2, 1, 3>>,
magnitude<int_base_power<2, 2, 3>>,
magnitude<int_base_power<3, -4>>,
magnitude<int_base_power<5>>,
magnitude<int_base_power<2, -1>>>,
magnitude<int_base_power<3, -4>, int_base_power<5>>>);
}
}
TEST_CASE("is_base_power detects well formed base powers")
{
SECTION ("Arbitrary other types are not base powers")
{
CHECK(!is_base_power_v<void>);
CHECK(!is_base_power_v<int>);
CHECK(!is_base_power_v<magnitude<int_base_power<3, 1, 4>>>);
}
SECTION ("int_base_power forms base powers")
{
CHECK(is_base_power_v<int_base_power<2>>);
CHECK(is_base_power_v<int_base_power<2, -1>>);
CHECK(is_base_power_v<int_base_power<2, -1, 8>>);
}
SECTION ("base_power forms base powers with pi and ratio")
{
CHECK(is_base_power_v<base_power<pi>>);
CHECK(is_base_power_v<base_power<pi, ratio{2}>>);
CHECK(is_base_power_v<base_power<pi, ratio{-2, 3}>>);
}
SECTION ("base_power disqualified by base without value")
{
CHECK(!is_base_power_v<base_power<int>>);
CHECK(!is_base_power_v<base_power<int, ratio{2}>>);
CHECK(!is_base_power_v<base_power<int, ratio{-2, 3}>>);
}
}
TEST_CASE("is_magnitude detects well formed magnitudes")
{
SECTION ("Arbitrary other types are not magnitudes")
{
CHECK(!is_magnitude_v<void>);
CHECK(!is_magnitude_v<int>);
CHECK(!is_magnitude_v<int_base_power<3, 1, 4>>);
}
SECTION ("Null magnitude is magnitude")
{
CHECK(is_magnitude_v<magnitude<>>);
}
SECTION ("Single-base magnitude is magnitude")
{
CHECK(is_magnitude_v<magnitude<int_base_power<3, 1, 4>>>);
}
SECTION ("Out-of-order bases disqualify magnitudes")
{
CHECK(!is_magnitude_v<magnitude<int_base_power<3>, int_base_power<2>>>);
}
SECTION ("Repeated bases disqualify magnitudes")
{
CHECK(!is_magnitude_v<magnitude<int_base_power<2, 1>, int_base_power<2, 2>>>);
}
SECTION ("Mixed base types are magnitudes if sorted")
{
CHECK(is_magnitude_v<magnitude<int_base_power<2>, base_power<pi>>>);
CHECK(is_magnitude_v<magnitude<int_base_power<3>, base_power<pi>>>);
CHECK(!is_magnitude_v<magnitude<int_base_power<5>, base_power<pi>>>);
}
}
TEST_CASE("strictly_increasing")
{
SECTION ("Empty tuple is sorted")
@@ -219,66 +50,52 @@ TEST_CASE("strictly_increasing")
}
}
TEST_CASE("make_ratio performs prime factorization correctly")
{
SECTION("Performs prime factorization when denominator is 1")
{
CHECK(std::is_same_v<decltype(make_ratio<1>()), magnitude<>>);
CHECK(std::is_same_v<decltype(make_ratio<2>()), magnitude<int_base_power<2>>>);
CHECK(std::is_same_v<decltype(make_ratio<3>()), magnitude<int_base_power<3>>>);
CHECK(std::is_same_v<decltype(make_ratio<4>()), magnitude<int_base_power<2, 2>>>);
// TEST_CASE("make_ratio performs prime factorization correctly")
// {
// SECTION("Performs prime factorization when denominator is 1")
// {
// CHECK(std::is_same_v<decltype(make_ratio<1>()), magnitude<>>);
// CHECK(std::is_same_v<decltype(make_ratio<2>()), magnitude<base_power{2}>>);
// CHECK(std::is_same_v<decltype(make_ratio<3>()), magnitude<base_power{3}>>);
// CHECK(std::is_same_v<decltype(make_ratio<4>()), magnitude<base_power{2, 2}>>);
//
// CHECK(std::is_same_v<
// decltype(make_ratio<792>()),
// magnitude<base_power{2, 3}, base_power{3, 2}, base_power{11}>>);
// }
//
// SECTION("Reduces fractions to lowest terms")
// {
// CHECK(std::is_same_v<decltype(make_ratio<8, 8>()), magnitude<>>);
// CHECK(std::is_same_v<
// decltype(make_ratio<50, 80>()), magnitude<base_power{2, -3}, base_power{5}>>);
// }
// }
CHECK(std::is_same_v<
decltype(make_ratio<792>()),
magnitude<int_base_power<2, 3>, int_base_power<3, 2>, int_base_power<11>>>);
}
// TEST_CASE("Equality works for magnitudes")
// {
// SECTION("Equivalent ratios are equal")
// {
// CHECK(make_ratio<1>() == make_ratio<1>());
// CHECK(make_ratio<3>() == make_ratio<3>());
// CHECK(make_ratio<3, 4>() == make_ratio<9, 12>());
// }
//
// SECTION("Different ratios are unequal")
// {
// CHECK(make_ratio<3>() != make_ratio<5>());
// CHECK(make_ratio<3>() != make_ratio<3, 2>());
// }
//
// SECTION("Supports constexpr")
// {
// constexpr auto eq = (make_ratio<4, 5>() == make_ratio<4, 3>());
// CHECK(!eq);
// }
// }
SECTION("Reduces fractions to lowest terms")
{
CHECK(std::is_same_v<decltype(make_ratio<8, 8>()), magnitude<>>);
CHECK(std::is_same_v<
decltype(make_ratio<50, 80>()),
magnitude<int_base_power<2, -3>, int_base_power<5>>>);
}
}
TEST_CASE("make_magnitude handles arbitrary bases")
{
SECTION("Equivalent to std::integral_constant for integer bases")
{
CHECK(make_base_power<prime_base<2>>() == make_ratio<2>());
CHECK(make_base_power<prime_base<7>>() == make_ratio<7>());
}
SECTION("Handles non-integer bases")
{
CHECK(make_base_power<pi>() == magnitude<base_power<pi>>{});
CHECK(make_base_power<pi, -3>() == magnitude<base_power<pi, ratio{-3}>>{});
CHECK(make_base_power<pi, -3, 7>() == magnitude<base_power<pi, ratio{-3, 7}>>{});
}
}
TEST_CASE("Equality works for magnitudes")
{
SECTION("Equivalent ratios are equal")
{
CHECK(make_ratio<1>() == make_ratio<1>());
CHECK(make_ratio<3>() == make_ratio<3>());
CHECK(make_ratio<3, 4>() == make_ratio<9, 12>());
}
SECTION("Different ratios are unequal")
{
CHECK(make_ratio<3>() != make_ratio<5>());
CHECK(make_ratio<3>() != make_ratio<3, 2>());
}
SECTION("Supports constexpr")
{
constexpr auto eq = make_ratio<4, 5>() == make_ratio<4, 3>();
CHECK(!eq);
}
}
template<double x>
using double_v = 2 * x;
TEST_CASE("Multiplication works for magnitudes")
{
@@ -290,15 +107,15 @@ TEST_CASE("Multiplication works for magnitudes")
SECTION("Products work as expected")
{
CHECK(make_ratio<4, 5>() * make_ratio<4, 3>() == make_ratio<16, 15>());
CHECK(double_v<1.5> == 3.0);
}
SECTION("Products handle pi correctly")
{
CHECK(
make_base_power<pi>() * make_ratio<2, 3>() * make_base_power<pi, -1, 2>() ==
magnitude<int_base_power<2>, int_base_power<3, -1>, base_power<pi, ratio{1, 2}>>{});
}
//SECTION("Products handle pi correctly")
//{
// CHECK(
// pi_to_the<1>() * make_ratio<2, 3>() * pi_to_the<ratio{-1, 2}>() ==
// magnitude<base_power{2}, base_power{3, -1}, pi_power<ratio{1, 2}>()>{});
//}
SECTION("Supports constexpr")
{
@@ -334,25 +151,24 @@ TEST_CASE("Prime factorization")
{
SECTION ("1 factors into the null magnitude")
{
CHECK(std::is_same_v<prime_factorization_t<1>, magnitude<>>);
CHECK(prime_factorization_v<1> == magnitude<>{});
}
SECTION ("Prime numbers factor into themselves")
{
CHECK(std::is_same_v<prime_factorization_t<2>, magnitude<int_base_power<2>>>);
CHECK(std::is_same_v<prime_factorization_t<3>, magnitude<int_base_power<3>>>);
CHECK(std::is_same_v<prime_factorization_t<5>, magnitude<int_base_power<5>>>);
CHECK(std::is_same_v<prime_factorization_t<7>, magnitude<int_base_power<7>>>);
CHECK(std::is_same_v<prime_factorization_t<11>, magnitude<int_base_power<11>>>);
CHECK(prime_factorization_v<2> == magnitude<base_power{2}>{});
CHECK(prime_factorization_v<3> == magnitude<base_power{3}>{});
CHECK(prime_factorization_v<5> == magnitude<base_power{5}>{});
CHECK(prime_factorization_v<7> == magnitude<base_power{7}>{});
CHECK(prime_factorization_v<11> == magnitude<base_power{11}>{});
CHECK(std::is_same_v<prime_factorization_t<41>, magnitude<int_base_power<41>>>);
CHECK(prime_factorization_v<41> == magnitude<base_power{41}>{});
}
SECTION("Prime factorization finds factors and multiplicities")
{
CHECK(std::is_same_v<
prime_factorization_t<792>,
magnitude<int_base_power<2, 3>, int_base_power<3, 2>, int_base_power<11>>>);
CHECK(prime_factorization_v<792> ==
magnitude<base_power{2, 3}, base_power{3, 2}, base_power{11}>{});
}
}