mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-04 12:54:25 +02:00
Merge pull request #323 from chiphogg/add-magnitude
Add vector space representation for magnitudes
This commit is contained in:
360
src/core/include/units/magnitude.h
Normal file
360
src/core/include/units/magnitude.h
Normal file
@@ -0,0 +1,360 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2018 Mateusz Pusz
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <units/ratio.h>
|
||||
#include <cstdint>
|
||||
#include <numbers>
|
||||
|
||||
namespace units {
|
||||
|
||||
/**
|
||||
* @brief Any type which can be used as a basis vector in a BasePower.
|
||||
*
|
||||
* We have two categories.
|
||||
*
|
||||
* The first is just an `int`. This is for prime number bases. These can always be used directly as NTTPs.
|
||||
*
|
||||
* The second category is a _custom type_, which has a static member variable of type `long double` that holds its
|
||||
* value. We choose `long double` to get the greatest degree of precision; users who need a different type can convert
|
||||
* from this at compile time. This category is for any irrational base we admit into our representation (on which, more
|
||||
* details below).
|
||||
*
|
||||
* The reason we can't hold the value directly for floating point bases is so that we can support some compilers (e.g.,
|
||||
* GCC 10) which don't yet permit floating point NTTPs.
|
||||
*/
|
||||
template<typename T>
|
||||
concept BaseRep = std::is_same_v<T, int> || std::is_same_v<std::remove_cvref_t<decltype(T::value)>, long double>;
|
||||
|
||||
/**
|
||||
* @brief A basis vector in our magnitude representation, raised to some rational power.
|
||||
*
|
||||
* The public API is that there is a `power` member variable (of type `ratio`), and a `get_base()` member function (of
|
||||
* type either `int` or `long double`, as appropriate), for any specialization.
|
||||
*
|
||||
* These types exist to be used as NTTPs for the variadic `magnitude<...>` template. We represent a magnitude (which is
|
||||
* a positive real number) as the product of rational powers of "basis vectors", where each "basis vector" is a positive
|
||||
* real number. "Addition" in this vector space corresponds to _multiplying_ two real numbers. "Scalar multiplication"
|
||||
* corresponds to _raising_ a real number to a _rational power_. Thus, this representation of positive real numbers
|
||||
* maps them onto a vector space over the rationals, supporting the operations of products and rational powers.
|
||||
*
|
||||
* (Note that this is the same representation we already use for dimensions.)
|
||||
*
|
||||
* As in any vector space, the set of basis vectors must be linearly independent: that is, no product of basis powers
|
||||
* can ever give the null vector (which in this case represents the number 1), unless all scalars (again, in this case,
|
||||
* _powers_) 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; instead, we would use `base_power{2, ratio{1, 2}}`.
|
||||
*/
|
||||
template<BaseRep T>
|
||||
struct base_power {
|
||||
// The rational power to which the base is raised.
|
||||
ratio power{1};
|
||||
|
||||
constexpr long double get_base() const { return T::value; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Specialization for prime number bases.
|
||||
*/
|
||||
template<>
|
||||
struct base_power<int> {
|
||||
// The value of the basis "vector". Must be prime to be used with `magnitude` (below).
|
||||
int base;
|
||||
|
||||
// The rational power to which the base is raised.
|
||||
ratio power{1};
|
||||
|
||||
constexpr int get_base() const { return base; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Deduction guides for base_power: only permit deducing integral bases.
|
||||
*/
|
||||
template<std::integral T, std::convertible_to<ratio> U>
|
||||
base_power(T, U) -> base_power<int>;
|
||||
template<std::integral T>
|
||||
base_power(T) -> base_power<int>;
|
||||
|
||||
// Implementation for BasePower concept (below).
|
||||
namespace detail {
|
||||
template<typename T>
|
||||
static constexpr bool is_base_power = false;
|
||||
template<BaseRep T>
|
||||
static constexpr bool is_base_power<base_power<T>> = true;
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief Concept to detect whether a _type_ is a valid base power.
|
||||
*
|
||||
* Note that this is somewhat incomplete. We must also detect whether a _value_ of that type is valid for use with
|
||||
* `magnitude<...>`. We will defer that second check to the constraints on the `magnitude` template.
|
||||
*/
|
||||
template<typename T>
|
||||
concept BasePower = detail::is_base_power<T>;
|
||||
|
||||
/**
|
||||
* @brief Equality detection for two base powers.
|
||||
*/
|
||||
template<BasePower T, BasePower U>
|
||||
constexpr bool operator==(T t, U u) {
|
||||
return std::is_same_v<T, U> && (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) {
|
||||
bp.power = bp.power * p;
|
||||
return bp;
|
||||
}
|
||||
|
||||
// A variety of implementation detail helpers.
|
||||
namespace detail {
|
||||
|
||||
// Find the smallest prime factor of `n`.
|
||||
constexpr std::intmax_t find_first_factor(std::intmax_t n)
|
||||
{
|
||||
for (std::intmax_t f = 2; f * f <= n; f += 1 + (f % 2))
|
||||
{
|
||||
if (n % f == 0) { return f; }
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// The exponent of `factor` in the prime factorization of `n`.
|
||||
constexpr std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n)
|
||||
{
|
||||
std::intmax_t m = 0;
|
||||
while (n % factor == 0)
|
||||
{
|
||||
n /= factor;
|
||||
++m;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Divide a number by a given base raised to some power.
|
||||
//
|
||||
// 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; }
|
||||
return n;
|
||||
}
|
||||
|
||||
// A way to check whether a number is prime at compile time.
|
||||
constexpr bool is_prime(std::intmax_t n) { return (n > 1) && (find_first_factor(n) == n); }
|
||||
|
||||
constexpr bool is_valid_base_power(const BasePower auto &bp) {
|
||||
if (bp.power == 0) { return false; }
|
||||
|
||||
if constexpr (std::is_same_v<decltype(bp.get_base()), int>) { 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.
|
||||
template<typename Predicate>
|
||||
struct pairwise_all {
|
||||
Predicate predicate;
|
||||
|
||||
template<typename... Ts>
|
||||
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::size_t... Is>(std::tuple<Ts...> &&t, std::index_sequence<Is...>) {
|
||||
return (predicate(std::get<Is>(t), std::get<Is + 1>(t)) && ...);
|
||||
}(std::make_tuple(std::forward<Ts>(ts)...), std::make_index_sequence<num_comparisons>());
|
||||
}
|
||||
};
|
||||
|
||||
// Deduction guide: permit constructions such as `pairwise_all{std::less{}}`.
|
||||
template<typename T>
|
||||
pairwise_all(T) -> pairwise_all<T>;
|
||||
|
||||
// Check whether a sequence of (possibly heterogeneously typed) values are strictly increasing.
|
||||
template<typename... Ts>
|
||||
requires ((std::is_signed_v<Ts> && ...))
|
||||
constexpr bool strictly_increasing(Ts&&... ts) {
|
||||
return pairwise_all{std::less{}}(std::forward<Ts>(ts)...);
|
||||
}
|
||||
|
||||
template<BasePower auto... BPs>
|
||||
static constexpr bool all_base_powers_valid = (is_valid_base_power(BPs) && ...);
|
||||
|
||||
template<BasePower auto... BPs>
|
||||
static constexpr bool all_bases_in_order = strictly_increasing(BPs.get_base()...);
|
||||
|
||||
template<BasePower auto... BPs>
|
||||
static constexpr bool is_base_power_pack_valid = all_base_powers_valid<BPs...> && all_bases_in_order<BPs...>;
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @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, raise to
|
||||
* rational powers, and compare for equality.
|
||||
*/
|
||||
template<BasePower auto... BPs>
|
||||
requires (detail::is_base_power_pack_valid<BPs...>)
|
||||
struct magnitude {};
|
||||
|
||||
// Implementation for Magnitude concept (below).
|
||||
namespace detail {
|
||||
template<typename T>
|
||||
static constexpr bool is_magnitude = false;
|
||||
template<BasePower auto... BPs>
|
||||
static constexpr bool is_magnitude<magnitude<BPs...>> = true;
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief Concept to detect whether T is a valid Magnitude.
|
||||
*/
|
||||
template<typename T>
|
||||
concept Magnitude = detail::is_magnitude<T>;
|
||||
|
||||
/**
|
||||
* @brief Convert any positive integer to a Magnitude.
|
||||
*
|
||||
* This will be the main way end users create Magnitudes. They should rarely (if ever) create a magnitude<...> by
|
||||
* manually adding base powers.
|
||||
*/
|
||||
template<ratio R>
|
||||
requires (R.num > 0)
|
||||
constexpr Magnitude auto as_magnitude();
|
||||
|
||||
/**
|
||||
* @brief A base to represent pi.
|
||||
*/
|
||||
struct pi_base {
|
||||
static constexpr long double value = std::numbers::pi_v<long double>;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Magnitude equality implementation.
|
||||
|
||||
template<BasePower auto... LeftBPs, BasePower auto... RightBPs>
|
||||
constexpr bool operator==(magnitude<LeftBPs...>, magnitude<RightBPs...>) {
|
||||
if constexpr (sizeof...(LeftBPs) == sizeof...(RightBPs)) { return ((LeftBPs == RightBPs) && ...); }
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Magnitude rational powers implementation.
|
||||
|
||||
template<ratio E, BasePower auto... BPs>
|
||||
constexpr auto pow(magnitude<BPs...>) {
|
||||
if constexpr (E == 0) { return magnitude<>{}; }
|
||||
else { return magnitude<pow(BPs, E)...>{}; }
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Magnitude product implementation.
|
||||
|
||||
// Base cases, for when either (or both) inputs are the identity.
|
||||
constexpr auto operator*(magnitude<>, magnitude<>) { return magnitude<>{}; }
|
||||
constexpr auto operator*(magnitude<>, Magnitude auto m) { return m; }
|
||||
constexpr auto operator*(Magnitude auto m, magnitude<>) { return m; }
|
||||
|
||||
// Recursive case for the product of any two non-identity Magnitudes.
|
||||
template<BasePower auto H1, BasePower auto... T1, BasePower auto H2, BasePower auto... T2>
|
||||
constexpr auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>) {
|
||||
// Case for when H1 has the smaller 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<H1, H2, T2...>{};
|
||||
} else {
|
||||
return magnitude<H1>{} * (magnitude<T1...>{} * magnitude<H2, T2...>{});
|
||||
}
|
||||
}
|
||||
|
||||
// Case for when H2 has the smaller base.
|
||||
if constexpr(H1.get_base() > H2.get_base()){
|
||||
return magnitude<H2>{} * (magnitude<H1, T1...>{} * magnitude<T2...>{});
|
||||
}
|
||||
|
||||
// "Same leading base" case.
|
||||
if constexpr (H1.get_base() == H2.get_base()) {
|
||||
constexpr auto partial_product = magnitude<T1...>{} * magnitude<T2...>{};
|
||||
|
||||
// Make a new base_power with the common base of H1 and H2, whose power is their powers' sum.
|
||||
constexpr auto new_head = [&](auto head) {
|
||||
head.power = H1.power + H2.power;
|
||||
return head;
|
||||
}(H1);
|
||||
|
||||
if constexpr (new_head.power == 0) {
|
||||
return partial_product;
|
||||
} else {
|
||||
return magnitude<new_head>{} * partial_product;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Magnitude quotient implementation.
|
||||
|
||||
constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// `as_magnitude()` implementation.
|
||||
|
||||
namespace detail {
|
||||
// Helper to perform prime factorization at compile time.
|
||||
template<std::intmax_t N>
|
||||
requires (N > 0)
|
||||
struct prime_factorization {
|
||||
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);
|
||||
|
||||
static constexpr auto value = magnitude<base_power{first_base, first_power}>{}
|
||||
* prime_factorization<remainder>::value;
|
||||
};
|
||||
|
||||
// Specialization for the prime factorization of 1 (base case).
|
||||
template<>
|
||||
struct prime_factorization<1> { static constexpr magnitude<> value{}; };
|
||||
|
||||
template<std::intmax_t N>
|
||||
static constexpr auto prime_factorization_v = prime_factorization<N>::value;
|
||||
} // namespace detail
|
||||
|
||||
template<ratio R>
|
||||
requires (R.num > 0)
|
||||
constexpr Magnitude auto as_magnitude() {
|
||||
return pow<R.exp>(detail::prime_factorization_v<10>)
|
||||
* detail::prime_factorization_v<R.num>
|
||||
/ detail::prime_factorization_v<R.den>;
|
||||
}
|
||||
|
||||
} // namespace units
|
@@ -52,7 +52,7 @@ struct ratio {
|
||||
std::intmax_t den;
|
||||
std::intmax_t exp;
|
||||
|
||||
constexpr explicit ratio(std::intmax_t n, std::intmax_t d = 1, std::intmax_t e = 0): num(n), den(d), exp(e)
|
||||
constexpr explicit(false) ratio(std::intmax_t n, std::intmax_t d = 1, std::intmax_t e = 0): num(n), den(d), exp(e)
|
||||
{
|
||||
gsl_Expects(den != 0);
|
||||
detail::normalize(num, den, exp);
|
||||
@@ -60,6 +60,27 @@ struct ratio {
|
||||
|
||||
[[nodiscard]] friend constexpr bool operator==(const ratio&, const ratio&) = default;
|
||||
|
||||
[[nodiscard]] friend constexpr ratio operator-(const ratio& r)
|
||||
{
|
||||
return ratio(-r.num, r.den, r.exp);
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr ratio operator+(ratio lhs, ratio rhs)
|
||||
{
|
||||
// First, get the inputs into a common exponent.
|
||||
const auto common_exp = std::min(lhs.exp, rhs.exp);
|
||||
auto commonify = [common_exp](ratio &r) {
|
||||
while (r.exp > common_exp) {
|
||||
r.num *= 10;
|
||||
--r.exp;
|
||||
}
|
||||
};
|
||||
commonify(lhs);
|
||||
commonify(rhs);
|
||||
|
||||
return ratio{lhs.num * rhs.den + lhs.den * rhs.num, lhs.den * rhs.den, common_exp};
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr ratio operator*(const ratio& lhs, const ratio& rhs)
|
||||
{
|
||||
const std::intmax_t gcd1 = std::gcd(lhs.num, rhs.den);
|
||||
|
@@ -27,6 +27,7 @@ find_package(Catch2 CONFIG REQUIRED)
|
||||
add_executable(unit_tests_runtime
|
||||
catch_main.cpp
|
||||
math_test.cpp
|
||||
magnitude_test.cpp
|
||||
fmt_test.cpp
|
||||
fmt_units_test.cpp
|
||||
distribution_test.cpp
|
||||
|
387
test/unit_test/runtime/magnitude_test.cpp
Normal file
387
test/unit_test/runtime/magnitude_test.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2018 Mateusz Pusz
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include <units/magnitude.h>
|
||||
#include <units/ratio.h>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
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; };
|
||||
|
||||
template<ratio Power>
|
||||
constexpr auto pi_to_the() { return magnitude<base_power<pi_base>{Power}>{}; }
|
||||
|
||||
TEST_CASE("base_power")
|
||||
{
|
||||
SECTION("base rep deducible for integral base")
|
||||
{
|
||||
CHECK(base_power{2} == base_power<int>{2, ratio{1}});
|
||||
CHECK(base_power{2, 3} == base_power<int>{2, ratio{3}});
|
||||
CHECK(base_power{2, ratio{3, 4}} == base_power<int>{2, ratio{3, 4}});
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
CHECK(base_power<noninteger_base>{}.get_base() == 1.234L);
|
||||
CHECK(base_power<noninteger_base>{2}.get_base() == 1.234L);
|
||||
CHECK(base_power<noninteger_base>{ratio{5, 8}}.get_base() == 1.234L);
|
||||
}
|
||||
|
||||
SECTION("same-base values not equal if types are different")
|
||||
{
|
||||
const auto a = base_power<noncanonical_two_base>{};
|
||||
const auto b = base_power{2};
|
||||
const auto c = base_power<other_noncanonical_two_base>{};
|
||||
|
||||
REQUIRE(a.get_base() == b.get_base());
|
||||
CHECK(a != b);
|
||||
|
||||
REQUIRE(a.get_base() == c.get_base());
|
||||
CHECK(a != c);
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
CHECK(base_power{2} != base_power{2, 2});
|
||||
CHECK(base_power<pi_base>{} != base_power<pi_base>{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>());
|
||||
};
|
||||
|
||||
check_product_with_inverse_is_identity(as_magnitude<3>());
|
||||
check_product_with_inverse_is_identity(as_magnitude<ratio{4, 17}>());
|
||||
check_product_with_inverse_is_identity(pi_to_the<ratio{-22, 7}>());
|
||||
}
|
||||
|
||||
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<pi_base>{ratio{3, 2}}, ratio{1, 3}) == base_power<pi_base>{ratio{1, 2}});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("make_ratio performs prime factorization correctly")
|
||||
{
|
||||
SECTION("Performs prime factorization when denominator is 1")
|
||||
{
|
||||
CHECK(as_magnitude<1>() == magnitude<>{});
|
||||
CHECK(as_magnitude<2>() == magnitude<base_power{2}>{});
|
||||
CHECK(as_magnitude<3>() == magnitude<base_power{3}>{});
|
||||
CHECK(as_magnitude<4>() == magnitude<base_power{2, 2}>{});
|
||||
|
||||
CHECK(as_magnitude<792>() == magnitude<base_power{2, 3}, base_power{3, 2}, base_power{11}>{});
|
||||
}
|
||||
|
||||
SECTION("Supports fractions")
|
||||
{
|
||||
CHECK(as_magnitude<ratio{5, 8}>() == magnitude<base_power{2, -3}, base_power{5}>{});
|
||||
}
|
||||
|
||||
SECTION("Supports nonzero exp")
|
||||
{
|
||||
constexpr ratio r{3, 1, 2};
|
||||
REQUIRE(r.exp == 2);
|
||||
CHECK(as_magnitude<r>() == as_magnitude<300>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Equality works for magnitudes")
|
||||
{
|
||||
SECTION("Equivalent ratios are equal")
|
||||
{
|
||||
CHECK(as_magnitude<1>() == as_magnitude<1>());
|
||||
CHECK(as_magnitude<3>() == as_magnitude<3>());
|
||||
CHECK(as_magnitude<ratio{3, 4}>() == as_magnitude<ratio{9, 12}>());
|
||||
}
|
||||
|
||||
SECTION("Different ratios are unequal")
|
||||
{
|
||||
CHECK(as_magnitude<3>() != as_magnitude<5>());
|
||||
CHECK(as_magnitude<3>() != as_magnitude<ratio{3, 2}>());
|
||||
}
|
||||
|
||||
SECTION("Supports constexpr")
|
||||
{
|
||||
constexpr auto eq = (as_magnitude<ratio{4, 5}>() == as_magnitude<ratio{4, 3}>());
|
||||
CHECK(!eq);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Multiplication works for magnitudes")
|
||||
{
|
||||
SECTION("Reciprocals reduce to null magnitude")
|
||||
{
|
||||
CHECK(as_magnitude<ratio{3, 4}>() * as_magnitude<ratio{4, 3}>() == as_magnitude<1>());
|
||||
}
|
||||
|
||||
SECTION("Products work as expected")
|
||||
{
|
||||
CHECK(as_magnitude<ratio{4, 5}>() * as_magnitude<ratio{4, 3}>() == as_magnitude<ratio{16, 15}>());
|
||||
}
|
||||
|
||||
SECTION("Products handle pi correctly")
|
||||
{
|
||||
CHECK(
|
||||
pi_to_the<1>() * as_magnitude<ratio{2, 3}>() * pi_to_the<ratio{-1, 2}>() ==
|
||||
magnitude<base_power{2}, base_power{3, -1}, base_power<pi_base>{ratio{1, 2}}>{});
|
||||
}
|
||||
|
||||
SECTION("Supports constexpr")
|
||||
{
|
||||
constexpr auto p = as_magnitude<ratio{4, 5}>() * as_magnitude<ratio{4, 3}>();
|
||||
CHECK(p == as_magnitude<ratio{16, 15}>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Division works for magnitudes")
|
||||
{
|
||||
SECTION("Dividing anything by itself reduces to null magnitude")
|
||||
{
|
||||
CHECK(as_magnitude<ratio{3, 4}>() / as_magnitude<ratio{3, 4}>() == as_magnitude<1>());
|
||||
CHECK(as_magnitude<15>() / as_magnitude<15>() == as_magnitude<1>());
|
||||
}
|
||||
|
||||
SECTION("Quotients work as expected")
|
||||
{
|
||||
CHECK(as_magnitude<ratio{4, 5}>() / as_magnitude<ratio{4, 3}>() == as_magnitude<ratio{3, 5}>());
|
||||
}
|
||||
|
||||
SECTION("Supports constexpr")
|
||||
{
|
||||
constexpr auto q = as_magnitude<ratio{4, 5}>() / as_magnitude<ratio{4, 3}>();
|
||||
CHECK(q == as_magnitude<ratio{3, 5}>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Can raise Magnitudes to rational powers")
|
||||
{
|
||||
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<ratio{3, 4}>()) == as_magnitude<1>());
|
||||
CHECK(pow<0>(pi_to_the<ratio{-1, 2}>()) == as_magnitude<1>());
|
||||
}
|
||||
|
||||
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<ratio{3, 4}>()) == as_magnitude<ratio{3, 4}>());
|
||||
CHECK(pow<1>(pi_to_the<ratio{-1, 2}>()) == pi_to_the<ratio{-1, 2}>());
|
||||
}
|
||||
|
||||
SECTION("Can raise to arbitrary rational power") {
|
||||
CHECK(pow<ratio{-8, 3}>(pi_to_the<ratio{-1, 2}>()) == pi_to_the<ratio{4, 3}>());
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
TEST_CASE("Prime helper functions")
|
||||
{
|
||||
SECTION("find_first_factor()") {
|
||||
CHECK(find_first_factor(1) == 1);
|
||||
CHECK(find_first_factor(2) == 2);
|
||||
CHECK(find_first_factor(4) == 2);
|
||||
CHECK(find_first_factor(6) == 2);
|
||||
CHECK(find_first_factor(15) == 3);
|
||||
CHECK(find_first_factor(17) == 17);
|
||||
}
|
||||
|
||||
SECTION("multiplicity") {
|
||||
CHECK(multiplicity(2, 8) == 3);
|
||||
CHECK(multiplicity(2, 1024) == 10);
|
||||
CHECK(multiplicity(11, 6655) == 3);
|
||||
}
|
||||
|
||||
SECTION("remove_power()") {
|
||||
CHECK(remove_power(17, 0, 5) == 5);
|
||||
CHECK(remove_power(2, 3, 24) == 3);
|
||||
CHECK(remove_power(11, 3, 6655) == 5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Prime factorization")
|
||||
{
|
||||
SECTION("1 factors into the null magnitude")
|
||||
{
|
||||
CHECK(prime_factorization_v<1> == magnitude<>{});
|
||||
}
|
||||
|
||||
SECTION("Prime numbers factor into themselves")
|
||||
{
|
||||
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(prime_factorization_v<41> == magnitude<base_power{41}>{});
|
||||
}
|
||||
|
||||
SECTION("Prime factorization finds factors and multiplicities")
|
||||
{
|
||||
CHECK(prime_factorization_v<792> ==
|
||||
magnitude<base_power{2, 3}, base_power{3, 2}, base_power{11}>{});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("is_prime detects primes")
|
||||
{
|
||||
SECTION("Non-positive numbers are not prime")
|
||||
{
|
||||
CHECK(!is_prime(-1328));
|
||||
CHECK(!is_prime(-1));
|
||||
CHECK(!is_prime(0));
|
||||
}
|
||||
|
||||
SECTION("1 is not prime")
|
||||
{
|
||||
CHECK(!is_prime(1));
|
||||
}
|
||||
|
||||
SECTION("Discriminates between primes and non-primes")
|
||||
{
|
||||
CHECK(is_prime(2));
|
||||
CHECK(is_prime(3));
|
||||
CHECK(!is_prime(4));
|
||||
CHECK(is_prime(5));
|
||||
CHECK(!is_prime(6));
|
||||
CHECK(is_prime(7));
|
||||
CHECK(!is_prime(8));
|
||||
CHECK(!is_prime(9));
|
||||
|
||||
CHECK(is_prime(7919));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("is_valid_base_power")
|
||||
{
|
||||
SECTION("0 power is invalid") {
|
||||
REQUIRE(is_valid_base_power(base_power{2}));
|
||||
CHECK(!is_valid_base_power(base_power{2, 0}));
|
||||
|
||||
REQUIRE(is_valid_base_power(base_power{41}));
|
||||
CHECK(!is_valid_base_power(base_power{41, 0}));
|
||||
|
||||
REQUIRE(is_valid_base_power(base_power<pi_base>{}));
|
||||
CHECK(!is_valid_base_power(base_power<pi_base>{0}));
|
||||
}
|
||||
|
||||
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}));
|
||||
|
||||
CHECK(is_valid_base_power(base_power{2}));
|
||||
CHECK(is_valid_base_power(base_power{3}));
|
||||
|
||||
CHECK(!is_valid_base_power(base_power{4}));
|
||||
}
|
||||
|
||||
SECTION("non-positive floating point bases are invalid") {
|
||||
CHECK(!is_valid_base_power(base_power<invalid_zero_base>{}));
|
||||
CHECK(!is_valid_base_power(base_power<invalid_negative_base>{}));
|
||||
}
|
||||
}
|
||||
|
||||
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_increasing = pairwise_all{std::less{}};
|
||||
|
||||
SECTION("always true for empty tuples")
|
||||
{
|
||||
CHECK(all_pairs_return_true());
|
||||
CHECK(all_pairs_return_false());
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
CHECK(all_increasing(1, 1.5));
|
||||
CHECK(all_increasing(1, 1.5, 2));
|
||||
|
||||
CHECK(!all_increasing(1, 2.0, 2));
|
||||
CHECK(!all_increasing(1, 2.5, 2));
|
||||
|
||||
CHECK(all_pairs_return_true('c', 1, 8.9, 42u));
|
||||
CHECK(!all_pairs_return_false('c', 1, 8.9, 42u));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("strictly_increasing")
|
||||
{
|
||||
SECTION("Empty input is sorted")
|
||||
{
|
||||
CHECK(strictly_increasing());
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
CHECK(strictly_increasing(3, 3.14));
|
||||
CHECK(!strictly_increasing(3, 3.0));
|
||||
CHECK(!strictly_increasing(4, 3.0));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace units
|
@@ -40,6 +40,13 @@ static_assert(ratio(4) * ratio(1, 2) == ratio(2));
|
||||
static_assert(ratio(1, 8) * ratio(2) == ratio(1, 4));
|
||||
static_assert(ratio(1, 2) * ratio(8) == ratio(4));
|
||||
|
||||
// ratio negation
|
||||
static_assert(-ratio(3, 8) == ratio(-3, 8));
|
||||
|
||||
// ratio addition
|
||||
static_assert(ratio(1, 2) + ratio(1, 3) == ratio(5, 6));
|
||||
static_assert(ratio(1, 3, 2) + ratio(11, 6) == ratio(211, 6)); // 100/3 + 11/6
|
||||
|
||||
// multiply with exponents
|
||||
static_assert(ratio(1, 8, 2) * ratio(2, 1, 4) == ratio(1, 4, 6));
|
||||
static_assert(ratio(1, 2, -4) * ratio(8, 1, 3) == ratio(4, 1, -1));
|
||||
|
Reference in New Issue
Block a user