diff --git a/src/core/include/units/bits/prime.h b/src/core/include/units/bits/prime.h index 5633dc4e..aaa20e4f 100644 --- a/src/core/include/units/bits/prime.h +++ b/src/core/include/units/bits/prime.h @@ -29,12 +29,14 @@ #include #include -namespace units::detail -{ +namespace units::detail { -constexpr bool is_prime_by_trial_division(std::size_t n) { +constexpr bool is_prime_by_trial_division(std::size_t n) +{ for (std::size_t f = 2; f * f <= n; f += 1 + (f % 2)) { - if (n % f == 0) { return false; } + if (n % f == 0) { + return false; + } } return true; } @@ -42,25 +44,34 @@ constexpr bool is_prime_by_trial_division(std::size_t n) { // Return the first factor of n, as long as it is either k or n. // // Precondition: no integer smaller than k evenly divides n. -constexpr std::optional first_factor_maybe(std::size_t n, std::size_t k) { - if (n % k == 0) { return k; } - if (k * k > n) { return n; } +constexpr std::optional first_factor_maybe(std::size_t n, std::size_t k) +{ + if (n % k == 0) { + return k; + } + if (k * k > n) { + return n; + } return std::nullopt; } template -constexpr std::array first_n_primes() { +constexpr std::array first_n_primes() +{ std::array primes; primes[0] = 2; for (std::size_t i = 1; i < N; ++i) { primes[i] = primes[i - 1] + 1; - while (!is_prime_by_trial_division(primes[i])) { ++primes[i]; } + while (!is_prime_by_trial_division(primes[i])) { + ++primes[i]; + } } return primes; } template -constexpr void call_for_coprimes_up_to(std::size_t n, const std::array &basis, Callable &&call) { +constexpr void call_for_coprimes_up_to(std::size_t n, const std::array& basis, Callable&& call) +{ for (std::size_t i = 0u; i < n; ++i) { if (std::apply([&i](auto... primes) { return ((i % primes != 0) && ...); }, basis)) { call(i); @@ -69,14 +80,16 @@ constexpr void call_for_coprimes_up_to(std::size_t n, const std::array -constexpr std::size_t num_coprimes_up_to(std::size_t n, const std::array &basis) { +constexpr std::size_t num_coprimes_up_to(std::size_t n, const std::array& basis) +{ std::size_t count = 0u; call_for_coprimes_up_to(n, basis, [&count](auto) { ++count; }); return count; } template -constexpr auto coprimes_up_to(std::size_t n, const std::array &basis) { +constexpr auto coprimes_up_to(std::size_t n, const std::array& basis) +{ std::array coprimes; std::size_t i = 0u; @@ -86,7 +99,8 @@ constexpr auto coprimes_up_to(std::size_t n, const std::array &b } template -constexpr std::size_t product(const std::array &values) { +constexpr std::size_t product(const std::array& values) +{ return std::accumulate(std::begin(values), std::end(values), std::size_t{1u}, std::multiplies{}); } @@ -116,29 +130,36 @@ struct WheelFactorizer { static constexpr auto coprimes_in_first_wheel = coprimes_up_to(wheel_size, basis); - static constexpr std::size_t find_first_factor(std::size_t n) { + static constexpr std::size_t find_first_factor(std::size_t n) + { for (const auto& p : basis) { - if (const auto k = first_factor_maybe(n, p)) { return *k; } + if (const auto k = first_factor_maybe(n, p)) { + return *k; + } } for (auto it = std::next(std::begin(coprimes_in_first_wheel)); it != std::end(coprimes_in_first_wheel); ++it) { - if (const auto k = first_factor_maybe(n, *it)) { return *k; } + if (const auto k = first_factor_maybe(n, *it)) { + return *k; + } } for (std::size_t wheel = wheel_size; wheel < n; wheel += wheel_size) { - if (const auto k = first_factor_maybe(n, wheel + 1)) { return *k; } + if (const auto k = first_factor_maybe(n, wheel + 1)) { + return *k; + } - for (const auto &p : coprimes_in_first_wheel) { - if (const auto k = first_factor_maybe(n, wheel + p)) { return *k; } + for (const auto& p : coprimes_in_first_wheel) { + if (const auto k = first_factor_maybe(n, wheel + p)) { + return *k; + } } } return n; } - static constexpr bool is_prime(std::size_t n) { - return (n > 1) && find_first_factor(n) == n; - } + static constexpr bool is_prime(std::size_t n) { return (n > 1) && find_first_factor(n) == n; } }; -} // namespace units::detail +} // namespace units::detail diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 5f01a0fa..3cec3b98 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -22,19 +22,19 @@ #pragma once -#include +#include #include +#include #include #include #include #include namespace units { -namespace detail -{ +namespace detail { // Higher numbers use fewer trial divisions, at the price of more storage space. using Factorizer = WheelFactorizer<4>; -} // namespace detail +} // namespace detail /** * @brief Any type which can be used as a basis vector in a BasePower. @@ -52,7 +52,8 @@ using Factorizer = WheelFactorizer<4>; * GCC 10) which don't yet permit floating point NTTPs. */ template -concept BaseRep = std::is_same_v || std::is_same_v, long double>; +concept BaseRep = std::is_same_v || std::is_same_v < std::remove_cvref_t, +long double > ; /** * @brief A basis vector in our magnitude representation, raised to some rational power. @@ -114,7 +115,7 @@ template inline constexpr bool is_base_power = false; template inline constexpr bool is_base_power> = true; -} // namespace detail +} // namespace detail /** * @brief Concept to detect whether a _type_ is a valid base power. @@ -125,34 +126,40 @@ inline constexpr bool is_base_power> = true; template concept BasePower = detail::is_base_power; -namespace detail +namespace detail { + +constexpr auto inverse(BasePower auto bp) { -constexpr auto inverse(BasePower auto bp) { bp.power.num *= -1; return bp; } // `widen_t` gives the widest arithmetic type in the same category, for intermediate computations. -template requires std::is_arithmetic_v -using widen_t = std::conditional_t< - std::is_floating_point_v, - long double, - std::conditional_t, std::intmax_t, std::uintmax_t>>; +template + requires std::is_arithmetic_v +using widen_t = std::conditional_t, long double, + std::conditional_t, std::intmax_t, std::uintmax_t>>; // Raise an arbitrary arithmetic type to a positive integer power at compile time. -template requires std::is_arithmetic_v -constexpr T int_power(T base, std::integral auto exp){ +template + requires std::is_arithmetic_v +constexpr T int_power(T base, std::integral auto exp) +{ // As this function should only be called at compile time, the exceptions herein function as // "parameter-compatible static_asserts", and should not result in exceptions at runtime. - if (exp < 0) { throw std::invalid_argument{"int_power only supports positive integer powers"}; } + if (exp < 0) { + throw std::invalid_argument{"int_power only supports positive integer powers"}; + } - constexpr auto checked_multiply = [] (auto a, auto b) { + constexpr auto checked_multiply = [](auto a, auto b) { const auto result = a * b; - if (result / a != b) { throw std::overflow_error{"Wraparound detected"}; } + if (result / a != b) { + throw std::overflow_error{"Wraparound detected"}; + } return result; }; - constexpr auto checked_square = [checked_multiply] (auto a) { return checked_multiply(a, a); }; + constexpr auto checked_square = [checked_multiply](auto a) { return checked_multiply(a, a); }; // TODO(chogg): Unify this implementation with the one in pow.h. That one takes its exponent as a // template parameter, rather than a function parameter. @@ -169,7 +176,8 @@ constexpr T int_power(T base, std::integral auto exp){ } -template requires std::is_arithmetic_v +template + requires std::is_arithmetic_v constexpr widen_t compute_base_power(BasePower auto bp) { // This utility can only handle integer powers. To compute rational powers at compile time, we'll @@ -177,8 +185,12 @@ constexpr widen_t compute_base_power(BasePower auto bp) // // Note that since this function should only be called at compile time, the point of these // exceptions is to act as "static_assert substitutes", not to throw actual exceptions at runtime. - if (bp.power.den != 1) { throw std::invalid_argument{"Rational powers not yet supported"}; } - if (bp.power.exp < 0) { throw std::invalid_argument{"Unsupported exp value"}; } + if (bp.power.den != 1) { + throw std::invalid_argument{"Rational powers not yet supported"}; + } + if (bp.power.exp < 0) { + throw std::invalid_argument{"Unsupported exp value"}; + } if (bp.power.num < 0) { if constexpr (std::is_integral_v) { @@ -197,10 +209,10 @@ constexpr widen_t compute_base_power(BasePower auto bp) // The input is the desired result, but in a (wider) intermediate type. The point of this function // is to cast to the desired type, but avoid overflow in doing so. template - requires std::is_arithmetic_v - && std::is_arithmetic_v - && (std::is_integral_v == std::is_integral_v) -constexpr To checked_static_cast(From x) { + requires std::is_arithmetic_v && std::is_arithmetic_v && + (std::is_integral_v == std::is_integral_v) +constexpr To checked_static_cast(From x) +{ // This function should only ever be called at compile time. The purpose of these exceptions is // to produce compiler errors, because we cannot `static_assert` on function arguments. if constexpr (std::is_integral_v) { @@ -215,20 +227,22 @@ constexpr To checked_static_cast(From x) { return static_cast(x); } -} // namespace detail +} // namespace detail /** * @brief Equality detection for two base powers. */ template -constexpr bool operator==(T t, U u) { +constexpr bool operator==(T t, U u) +{ return std::is_same_v && (t.get_base() == u.get_base()) && (t.power == u.power); } /** * @brief A BasePower, raised to a rational power E. */ -constexpr auto pow(BasePower auto bp, ratio p) { +constexpr auto pow(BasePower auto bp, ratio p) +{ bp.power = bp.power * p; return bp; } @@ -240,8 +254,7 @@ namespace detail { constexpr std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n) { std::intmax_t m = 0; - while (n % factor == 0) - { + while (n % factor == 0) { n /= factor; ++m; } @@ -253,20 +266,26 @@ constexpr std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n) // Undefined unless base > 1, pow >= 0, and (base ^ pow) evenly divides n. constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std::intmax_t n) { - while (pow-- > 0) { n /= base; } + while (pow-- > 0) { + n /= base; + } return n; } // A way to check whether a number is prime at compile time. -constexpr bool is_prime(std::intmax_t n) { - return (n >= 0) && Factorizer::is_prime(static_cast(n)); -} +constexpr bool is_prime(std::intmax_t n) { return (n >= 0) && Factorizer::is_prime(static_cast(n)); } -constexpr bool is_valid_base_power(const BasePower auto &bp) { - if (bp.power == 0) { return false; } +constexpr bool is_valid_base_power(const BasePower auto& bp) +{ + if (bp.power == 0) { + return false; + } - if constexpr (std::is_same_v) { return is_prime(bp.get_base()); } - else { return bp.get_base() > 0; } + if constexpr (std::is_same_v) { + return is_prime(bp.get_base()); + } else { + return bp.get_base() > 0; + } } // A function object to apply a predicate to all consecutive pairs of values in a sequence. @@ -275,16 +294,19 @@ struct pairwise_all { Predicate predicate; template - constexpr bool operator()(Ts&&... ts) const { + constexpr bool operator()(Ts&&... ts) const + { // Carefully handle different sizes, avoiding unsigned integer underflow. constexpr auto num_comparisons = [](auto num_elements) { return (num_elements > 1) ? (num_elements - 1) : 0; }(sizeof...(Ts)); // Compare zero or more pairs of neighbours as needed. - return [this](std::tuple &&t, std::index_sequence) { + return [this](std::tuple && t, std::index_sequence) + { return (predicate(std::get(t), std::get(t)) && ...); - }(std::make_tuple(std::forward(ts)...), std::make_index_sequence()); + } + (std::make_tuple(std::forward(ts)...), std::make_index_sequence()); } }; @@ -294,8 +316,9 @@ pairwise_all(T) -> pairwise_all; // Check whether a sequence of (possibly heterogeneously typed) values are strictly increasing. template - requires (std::is_signed_v && ...) -constexpr bool strictly_increasing(Ts&&... ts) { + requires(std::is_signed_v && ...) +constexpr bool strictly_increasing(Ts&&... ts) +{ return pairwise_all{std::less{}}(std::forward(ts)...); } @@ -308,14 +331,13 @@ inline constexpr bool all_bases_in_order = strictly_increasing(BPs.get_base()... template inline constexpr bool is_base_power_pack_valid = all_base_powers_valid && all_bases_in_order; -constexpr bool is_rational(BasePower auto bp) { +constexpr bool is_rational(BasePower auto bp) +{ return std::is_integral_v && (bp.power.den == 1) && (bp.power.exp >= 0); } -constexpr bool is_integral(BasePower auto bp) { - return is_rational(bp) && bp.power.num > 0; -} -} // namespace detail +constexpr bool is_integral(BasePower auto bp) { return is_rational(bp) && bp.power.num > 0; } +} // namespace detail /** * @brief A representation for positive real numbers which optimizes taking products and rational powers. @@ -339,7 +361,7 @@ template inline constexpr bool is_magnitude = false; template inline constexpr bool is_magnitude> = true; -} // namespace detail +} // namespace detail /** * @brief Concept to detect whether T is a valid Magnitude. @@ -352,7 +374,8 @@ concept Magnitude = detail::is_magnitude; */ template requires std::is_floating_point_v || (std::is_integral_v && is_integral(magnitude{})) -constexpr T get_value(const magnitude &) { +constexpr T get_value(const magnitude&) +{ // Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow. constexpr auto result = detail::checked_static_cast((detail::compute_base_power(BPs) * ...)); @@ -370,18 +393,26 @@ struct pi_base { // Magnitude equality implementation. template -constexpr bool operator==(magnitude, magnitude) { - if constexpr (sizeof...(LeftBPs) == sizeof...(RightBPs)) { return ((LeftBPs == RightBPs) && ...); } - else { return false; } +constexpr bool operator==(magnitude, magnitude) +{ + if constexpr (sizeof...(LeftBPs) == sizeof...(RightBPs)) { + return ((LeftBPs == RightBPs) && ...); + } else { + return false; + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude rational powers implementation. template -constexpr auto pow(magnitude) { - if constexpr (E == 0) { return magnitude<>{}; } - else { return magnitude{}; } +constexpr auto pow(magnitude) +{ + if constexpr (E == 0) { + return magnitude<>{}; + } else { + return magnitude{}; + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -394,9 +425,10 @@ constexpr auto operator*(Magnitude auto m, magnitude<>) { return m; } // Recursive case for the product of any two non-identity Magnitudes. template -constexpr auto operator*(magnitude, magnitude) { +constexpr auto operator*(magnitude, magnitude) +{ // Case for when H1 has the smaller base. - if constexpr(H1.get_base() < H2.get_base()){ + if constexpr (H1.get_base() < H2.get_base()) { if constexpr (sizeof...(T1) == 0) { // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. return magnitude{}; @@ -406,7 +438,7 @@ constexpr auto operator*(magnitude, magnitude) { } // Case for when H2 has the smaller base. - if constexpr(H1.get_base() > H2.get_base()){ + if constexpr (H1.get_base() > H2.get_base()) { return magnitude

{} * (magnitude{} * magnitude{}); } @@ -439,23 +471,25 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1 namespace detail { // Helper to perform prime factorization at compile time. template - requires (N > 0) + requires(N > 0) struct prime_factorization { static constexpr std::intmax_t first_base = static_cast(Factorizer::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); - static constexpr auto value = magnitude{} - * prime_factorization::value; + static constexpr auto value = + magnitude{} * prime_factorization::value; }; // Specialization for the prime factorization of 1 (base case). template<> -struct prime_factorization<1> { static constexpr magnitude<> value{}; }; +struct prime_factorization<1> { + static constexpr magnitude<> value{}; +}; template inline constexpr auto prime_factorization_v = prime_factorization::value; -} // namespace detail +} // namespace detail /** * @brief Convert any positive integer to a Magnitude. @@ -464,11 +498,11 @@ inline constexpr auto prime_factorization_v = prime_factorization::value; * manually adding base powers. */ template - requires (R.num > 0) -constexpr Magnitude auto as_magnitude() { - return pow(detail::prime_factorization_v<10>) - * detail::prime_factorization_v - / detail::prime_factorization_v; + requires(R.num > 0) +constexpr Magnitude auto as_magnitude() +{ + return pow(detail::prime_factorization_v<10>) * detail::prime_factorization_v / + detail::prime_factorization_v; } -} // namespace units +} // namespace units diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 667adfc3..e9b359f2 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -20,54 +20,64 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#include #include #include -#include #include namespace units { // A set of non-standard bases for testing purposes. -struct noninteger_base { static constexpr long double value = 1.234L; }; -struct noncanonical_two_base { static constexpr long double value = 2.0L; }; -struct other_noncanonical_two_base { static constexpr long double value = 2.0L; }; -struct invalid_zero_base { static constexpr long double value = 0.0L; }; -struct invalid_negative_base { static constexpr long double value = -1.234L; }; +struct noninteger_base { + static constexpr long double value = 1.234L; +}; +struct noncanonical_two_base { + static constexpr long double value = 2.0L; +}; +struct other_noncanonical_two_base { + static constexpr long double value = 2.0L; +}; +struct invalid_zero_base { + static constexpr long double value = 0.0L; +}; +struct invalid_negative_base { + static constexpr long double value = -1.234L; +}; template -constexpr auto pi_to_the() { return magnitude{Power}>{}; } +constexpr auto pi_to_the() +{ + return magnitude{Power}>{}; +} template -void check_same_type_and_value(T actual, U expected) { +void check_same_type_and_value(T actual, U expected) +{ CHECK(std::is_same_v); CHECK(actual == expected); } TEST_CASE("base_power") { - SECTION("base rep deducible for integral base") - { + SECTION ("base rep deducible for integral base") { CHECK(base_power{2} == base_power{2, ratio{1}}); CHECK(base_power{2, 3} == base_power{2, ratio{3}}); CHECK(base_power{2, ratio{3, 4}} == base_power{2, ratio{3, 4}}); } - SECTION("get_base retrieves base for integral base") - { + SECTION ("get_base retrieves base for integral base") { CHECK(base_power{2}.get_base() == 2); CHECK(base_power{3, 5}.get_base() == 3); CHECK(base_power{5, ratio{1, 3}}.get_base() == 5); } - SECTION("get_base retrieves member value for non-integer base") - { + SECTION ("get_base retrieves member value for non-integer base") { CHECK(base_power{}.get_base() == 1.234L); CHECK(base_power{2}.get_base() == 1.234L); CHECK(base_power{ratio{5, 8}}.get_base() == 1.234L); } - SECTION("same-base values not equal if types are different") - { + SECTION ("same-base values not equal if types are different") { const auto a = base_power{}; const auto b = base_power{2}; const auto c = base_power{}; @@ -79,31 +89,25 @@ TEST_CASE("base_power") CHECK(a != c); } - SECTION("same-type values not equal if bases are different") - { + SECTION ("same-type values not equal if bases are different") { CHECK(base_power{2} != base_power{3}); CHECK(base_power{2, ratio{5, 4}} != base_power{3, ratio{5, 4}}); } - SECTION("same-type, same-base values not equal if powers are different") - { + SECTION ("same-type, same-base values not equal if powers are different") { CHECK(base_power{2} != base_power{2, 2}); CHECK(base_power{} != base_power{ratio{1, 3}}); } - SECTION("product with inverse equals identity") - { - auto check_product_with_inverse_is_identity = [] (auto x) { - CHECK(x * pow<-1>(x) == as_magnitude<1>()); - }; + SECTION ("product with inverse equals identity") { + auto check_product_with_inverse_is_identity = [](auto x) { CHECK(x * pow<-1>(x) == as_magnitude<1>()); }; check_product_with_inverse_is_identity(as_magnitude<3>()); check_product_with_inverse_is_identity(as_magnitude()); check_product_with_inverse_is_identity(pi_to_the()); } - SECTION("pow() multiplies exponent") - { + SECTION ("pow() multiplies exponent") { CHECK(pow(base_power{2}, 0) == base_power{2, 0}); CHECK(pow(base_power{2, 3}, ratio{-1, 2}) == base_power{2, ratio{-3, 2}}); CHECK(pow(base_power{ratio{3, 2}}, ratio{1, 3}) == base_power{ratio{1, 2}}); @@ -112,8 +116,7 @@ TEST_CASE("base_power") TEST_CASE("make_ratio performs prime factorization correctly") { - SECTION("Performs prime factorization when denominator is 1") - { + SECTION ("Performs prime factorization when denominator is 1") { CHECK(as_magnitude<1>() == magnitude<>{}); CHECK(as_magnitude<2>() == magnitude{}); CHECK(as_magnitude<3>() == magnitude{}); @@ -122,27 +125,23 @@ TEST_CASE("make_ratio performs prime factorization correctly") CHECK(as_magnitude<792>() == magnitude{}); } - SECTION("Supports fractions") - { + SECTION ("Supports fractions") { CHECK(as_magnitude() == magnitude{}); } - SECTION("Supports nonzero exp") - { + SECTION ("Supports nonzero exp") { constexpr ratio r{3, 1, 2}; REQUIRE(r.exp == 2); CHECK(as_magnitude() == as_magnitude<300>()); } - SECTION("Can handle prime factor which would be large enough to overflow int") - { + SECTION ("Can handle prime factor which would be large enough to overflow int") { // This was taken from a case which failed when we used `int` for our base to store prime numbers. // The failure was due to a prime factor which is larger than 2^31. as_magnitude(); } - SECTION("Can handle prime number which would exceed GCC iteration limit") - { + 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 @@ -153,8 +152,7 @@ TEST_CASE("make_ratio performs prime factorization correctly") TEST_CASE("magnitude converts to numerical value") { - SECTION("Positive integer powers of integer bases give integer values") - { + SECTION ("Positive integer powers of integer bases give integer values") { constexpr auto mag_412 = as_magnitude<412>(); check_same_type_and_value(get_value(mag_412), 412); check_same_type_and_value(get_value(mag_412), std::size_t{412}); @@ -162,23 +160,20 @@ TEST_CASE("magnitude converts to numerical value") check_same_type_and_value(get_value(mag_412), 412.0); } - SECTION("Negative integer powers of integer bases compute correct values") - { + SECTION ("Negative integer powers of integer bases compute correct values") { constexpr auto mag_0p125 = as_magnitude(); check_same_type_and_value(get_value(mag_0p125), 0.125f); check_same_type_and_value(get_value(mag_0p125), 0.125); } - SECTION("pi to the 1 supplies correct values") - { + SECTION ("pi to the 1 supplies correct values") { constexpr auto pi = pi_to_the<1>(); check_same_type_and_value(get_value(pi), std::numbers::pi_v); check_same_type_and_value(get_value(pi), std::numbers::pi_v); check_same_type_and_value(get_value(pi), std::numbers::pi_v); } - SECTION("pi to arbitrary power performs computations in most accurate type at compile time") - { + SECTION ("pi to arbitrary power performs computations in most accurate type at compile time") { if constexpr (sizeof(float) < sizeof(long double)) { constexpr auto pi_cubed = pi_to_the<3>(); @@ -192,8 +187,7 @@ TEST_CASE("magnitude converts to numerical value") } } - SECTION("Impossible requests are prevented at compile time") - { + SECTION ("Impossible requests are prevented at compile time") { // Naturally, we cannot actually write a test to verify a compiler error. But any of these can // be uncommented if desired to verify that it breaks the build. @@ -205,7 +199,7 @@ TEST_CASE("magnitude converts to numerical value") // Would work for pow<63>: // get_value(pow<64>(as_magnitude<2>())); - get_value(pow<308>(as_magnitude<10>())); // Compiles, correctly. + get_value(pow<308>(as_magnitude<10>())); // Compiles, correctly. // get_value(pow<309>(as_magnitude<10>())); // get_value(pow<3099>(as_magnitude<10>())); // get_value(pow<3099999>(as_magnitude<10>())); @@ -218,21 +212,18 @@ TEST_CASE("magnitude converts to numerical value") TEST_CASE("Equality works for magnitudes") { - SECTION("Equivalent ratios are equal") - { + SECTION ("Equivalent ratios are equal") { CHECK(as_magnitude<1>() == as_magnitude<1>()); CHECK(as_magnitude<3>() == as_magnitude<3>()); CHECK(as_magnitude() == as_magnitude()); } - SECTION("Different ratios are unequal") - { + SECTION ("Different ratios are unequal") { CHECK(as_magnitude<3>() != as_magnitude<5>()); CHECK(as_magnitude<3>() != as_magnitude()); } - SECTION("Supports constexpr") - { + SECTION ("Supports constexpr") { constexpr auto eq = (as_magnitude() == as_magnitude()); CHECK(!eq); } @@ -240,25 +231,20 @@ TEST_CASE("Equality works for magnitudes") TEST_CASE("Multiplication works for magnitudes") { - SECTION("Reciprocals reduce to null magnitude") - { + SECTION ("Reciprocals reduce to null magnitude") { CHECK(as_magnitude() * as_magnitude() == as_magnitude<1>()); } - SECTION("Products work as expected") - { + SECTION ("Products work as expected") { CHECK(as_magnitude() * as_magnitude() == as_magnitude()); } - SECTION("Products handle pi correctly") - { - CHECK( - pi_to_the<1>() * as_magnitude() * pi_to_the() == - magnitude{ratio{1, 2}}>{}); + SECTION ("Products handle pi correctly") { + CHECK(pi_to_the<1>() * as_magnitude() * pi_to_the() == + magnitude{ratio{1, 2}}>{}); } - SECTION("Supports constexpr") - { + SECTION ("Supports constexpr") { constexpr auto p = as_magnitude() * as_magnitude(); CHECK(p == as_magnitude()); } @@ -266,19 +252,16 @@ TEST_CASE("Multiplication works for magnitudes") TEST_CASE("Division works for magnitudes") { - SECTION("Dividing anything by itself reduces to null magnitude") - { + SECTION ("Dividing anything by itself reduces to null magnitude") { CHECK(as_magnitude() / as_magnitude() == as_magnitude<1>()); CHECK(as_magnitude<15>() / as_magnitude<15>() == as_magnitude<1>()); } - SECTION("Quotients work as expected") - { + SECTION ("Quotients work as expected") { CHECK(as_magnitude() / as_magnitude() == as_magnitude()); } - SECTION("Supports constexpr") - { + SECTION ("Supports constexpr") { constexpr auto q = as_magnitude() / as_magnitude(); CHECK(q == as_magnitude()); } @@ -286,29 +269,28 @@ TEST_CASE("Division works for magnitudes") TEST_CASE("Can raise Magnitudes to rational powers") { - SECTION("Anything to the 0 is 1") { + SECTION ("Anything to the 0 is 1") { CHECK(pow<0>(as_magnitude<1>()) == as_magnitude<1>()); CHECK(pow<0>(as_magnitude<123>()) == as_magnitude<1>()); CHECK(pow<0>(as_magnitude()) == as_magnitude<1>()); CHECK(pow<0>(pi_to_the()) == as_magnitude<1>()); } - SECTION("Anything to the 1 is itself") { + SECTION ("Anything to the 1 is itself") { CHECK(pow<1>(as_magnitude<1>()) == as_magnitude<1>()); CHECK(pow<1>(as_magnitude<123>()) == as_magnitude<123>()); CHECK(pow<1>(as_magnitude()) == as_magnitude()); CHECK(pow<1>(pi_to_the()) == pi_to_the()); } - SECTION("Can raise to arbitrary rational power") { + SECTION ("Can raise to arbitrary rational power") { CHECK(pow(pi_to_the()) == pi_to_the()); } } TEST_CASE("can distinguish integral, rational, and irrational magnitudes") { - SECTION("Integer magnitudes are integral and rational") - { + SECTION ("Integer magnitudes are integral and rational") { auto check_rational_and_integral = [](Magnitude auto m) { CHECK(is_integral(m)); CHECK(is_rational(m)); @@ -321,8 +303,7 @@ TEST_CASE("can distinguish integral, rational, and irrational magnitudes") check_rational_and_integral(as_magnitude()); } - SECTION("Fractional magnitudes are rational, but not integral") - { + SECTION ("Fractional magnitudes are rational, but not integral") { auto check_rational_but_not_integral = [](Magnitude auto m) { CHECK(!is_integral(m)); CHECK(is_rational(m)); @@ -334,8 +315,9 @@ TEST_CASE("can distinguish integral, rational, and irrational magnitudes") namespace detail { -TEST_CASE("int_power computes integer powers") { - SECTION("handles floating point") { +TEST_CASE("int_power computes integer powers") +{ + SECTION ("handles floating point") { check_same_type_and_value(int_power(0.123L, 0), 1.0L); check_same_type_and_value(int_power(0.246f, 1), 0.246f); check_same_type_and_value(int_power(0.5f, 3), 0.125f); @@ -344,7 +326,7 @@ TEST_CASE("int_power computes integer powers") { CHECK(std::is_same_v(base_power{10, 20}))>); } - SECTION("handles integral") { + SECTION ("handles integral") { check_same_type_and_value(int_power(8, 0), 1); check_same_type_and_value(int_power(9L, 1), 9L); check_same_type_and_value(int_power(2, 10), 1024); @@ -353,13 +335,13 @@ TEST_CASE("int_power computes integer powers") { TEST_CASE("Prime helper functions") { - SECTION("multiplicity") { + SECTION ("multiplicity") { CHECK(multiplicity(2, 8) == 3); CHECK(multiplicity(2, 1024) == 10); CHECK(multiplicity(11, 6655) == 3); } - SECTION("remove_power()") { + SECTION ("remove_power()") { CHECK(remove_power(17, 0, 5) == 5); CHECK(remove_power(2, 3, 24) == 3); CHECK(remove_power(11, 3, 6655) == 5); @@ -368,13 +350,11 @@ TEST_CASE("Prime helper functions") TEST_CASE("Prime factorization") { - SECTION("1 factors into the null magnitude") - { + SECTION ("1 factors into the null magnitude") { CHECK(prime_factorization_v<1> == magnitude<>{}); } - SECTION("Prime numbers factor into themselves") - { + SECTION ("Prime numbers factor into themselves") { CHECK(prime_factorization_v<2> == magnitude{}); CHECK(prime_factorization_v<3> == magnitude{}); CHECK(prime_factorization_v<5> == magnitude{}); @@ -384,29 +364,24 @@ TEST_CASE("Prime factorization") CHECK(prime_factorization_v<41> == magnitude{}); } - SECTION("Prime factorization finds factors and multiplicities") - { - CHECK(prime_factorization_v<792> == - magnitude{}); + SECTION ("Prime factorization finds factors and multiplicities") { + CHECK(prime_factorization_v<792> == magnitude{}); } } TEST_CASE("is_prime detects primes") { - SECTION("Non-positive numbers are not prime") - { + SECTION ("Non-positive numbers are not prime") { CHECK(!is_prime(-1328)); CHECK(!is_prime(-1)); CHECK(!is_prime(0)); } - SECTION("1 is not prime") - { + SECTION ("1 is not prime") { CHECK(!is_prime(1)); } - SECTION("Discriminates between primes and non-primes") - { + SECTION ("Discriminates between primes and non-primes") { CHECK(is_prime(2)); CHECK(is_prime(3)); CHECK(!is_prime(4)); @@ -422,7 +397,7 @@ TEST_CASE("is_prime detects primes") TEST_CASE("is_valid_base_power") { - SECTION("0 power is invalid") { + SECTION ("0 power is invalid") { REQUIRE(is_valid_base_power(base_power{2})); CHECK(!is_valid_base_power(base_power{2, 0})); @@ -433,7 +408,7 @@ TEST_CASE("is_valid_base_power") CHECK(!is_valid_base_power(base_power{0})); } - SECTION("non-prime integers are invalid") { + SECTION ("non-prime integers are invalid") { CHECK(!is_valid_base_power(base_power{-8})); CHECK(!is_valid_base_power(base_power{0})); CHECK(!is_valid_base_power(base_power{1})); @@ -444,7 +419,7 @@ TEST_CASE("is_valid_base_power") CHECK(!is_valid_base_power(base_power{4})); } - SECTION("non-positive floating point bases are invalid") { + SECTION ("non-positive floating point bases are invalid") { CHECK(!is_valid_base_power(base_power{})); CHECK(!is_valid_base_power(base_power{})); } @@ -452,25 +427,22 @@ TEST_CASE("is_valid_base_power") TEST_CASE("pairwise_all evaluates all pairs") { - const auto all_pairs_return_true = pairwise_all{[](auto, auto){ return true; }}; - const auto all_pairs_return_false = pairwise_all{[](auto, auto){ return false; }}; + const auto all_pairs_return_true = pairwise_all{[](auto, auto) { return true; }}; + const auto all_pairs_return_false = pairwise_all{[](auto, auto) { return false; }}; const auto all_increasing = pairwise_all{std::less{}}; - SECTION("always true for empty tuples") - { + SECTION ("always true for empty tuples") { CHECK(all_pairs_return_true()); CHECK(all_pairs_return_false()); } - SECTION("always true for single-element tuples") - { + SECTION ("always true for single-element tuples") { CHECK(all_pairs_return_true(1)); CHECK(all_pairs_return_false(3.14)); CHECK(all_pairs_return_true('x')); } - SECTION("true for longer tuples iff true for all neighbouring pairs") - { + SECTION ("true for longer tuples iff true for all neighbouring pairs") { CHECK(all_increasing(1, 1.5)); CHECK(all_increasing(1, 1.5, 2)); @@ -484,26 +456,23 @@ TEST_CASE("pairwise_all evaluates all pairs") TEST_CASE("strictly_increasing") { - SECTION("Empty input is sorted") - { + SECTION ("Empty input is sorted") { CHECK(strictly_increasing()); } - SECTION("Single-element input is sorted") - { + SECTION ("Single-element input is sorted") { CHECK(strictly_increasing(3)); CHECK(strictly_increasing(15.42)); CHECK(strictly_increasing('c')); } - SECTION("Multi-value inputs compare correctly") - { + SECTION ("Multi-value inputs compare correctly") { CHECK(strictly_increasing(3, 3.14)); CHECK(!strictly_increasing(3, 3.0)); CHECK(!strictly_increasing(4, 3.0)); } } -} // namespace detail +} // namespace detail -} // namespace units +} // namespace units diff --git a/test/unit_test/static/prime_test.cpp b/test/unit_test/static/prime_test.cpp index ab852974..26e9478d 100644 --- a/test/unit_test/static/prime_test.cpp +++ b/test/unit_test/static/prime_test.cpp @@ -21,15 +21,14 @@ // SOFTWARE. #include - #include #include -namespace units::detail -{ +namespace units::detail { template -constexpr bool check_primes(std::index_sequence) { +constexpr bool check_primes(std::index_sequence) +{ return ((Is < 2 || WheelFactorizer::is_prime(Is) == is_prime_by_trial_division(Is)) && ...); } @@ -72,4 +71,4 @@ static_assert(!WheelFactorizer<3>::is_prime(0)); static_assert(!WheelFactorizer<3>::is_prime(1)); static_assert(WheelFactorizer<3>::is_prime(2)); -} // namespace units::detail +} // namespace units::detail