From a80378cae86d818c15634dba265ddba789b29adc Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 07:51:16 -0500 Subject: [PATCH 01/42] Support addition and negation in ratio --- src/core/include/units/ratio.h | 21 +++++++++++++++++++++ test/unit_test/static/ratio_test.cpp | 7 +++++++ 2 files changed, 28 insertions(+) diff --git a/src/core/include/units/ratio.h b/src/core/include/units/ratio.h index dcb4956b..923f2355 100644 --- a/src/core/include/units/ratio.h +++ b/src/core/include/units/ratio.h @@ -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); diff --git a/test/unit_test/static/ratio_test.cpp b/test/unit_test/static/ratio_test.cpp index 5067d3fa..32de5f10 100644 --- a/test/unit_test/static/ratio_test.cpp +++ b/test/unit_test/static/ratio_test.cpp @@ -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)); From 0c5ae082d5417c4f14cbf7d2ccfdd687a7b54d23 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 07:51:44 -0500 Subject: [PATCH 02/42] Add magnitude with product, inverse, quotient For now, all templates are unconstrained; we hope to clean that up later. --- src/core/include/units/magnitude.h | 161 ++++++++++++++++++++++ test/unit_test/runtime/CMakeLists.txt | 1 + test/unit_test/runtime/magnitude_test.cpp | 130 +++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 src/core/include/units/magnitude.h create mode 100644 test/unit_test/runtime/magnitude_test.cpp diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h new file mode 100644 index 00000000..060b1642 --- /dev/null +++ b/src/core/include/units/magnitude.h @@ -0,0 +1,161 @@ +// 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 +#include + +namespace units::mag +{ + +// A `magnitude` represents a positive real number in a format which optimizes taking products and +// rational powers. +template +struct magnitude; + +template +struct inverse; +template +using inverse_t = typename inverse::type; + +template +struct product; +template +using product_t = typename product::type; + +template +using quotient_t = product_t>; + +namespace detail +{ +template +struct prime_factorization; +template +using prime_factorization_t = typename prime_factorization::type; +} // namespace detail + +template +struct magnitude { + bool operator==(magnitude) const { return true; } + + template + bool operator==(magnitude) const { return false; } +}; + +template +using ratio_t = quotient_t, detail::prime_factorization_t>; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation details below. +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Base powers. + +template +struct base_power; + +template +struct int_base : std::integral_constant {}; +template +using int_base_power = base_power, ratio{num, den}>; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Inverse implementation. + +template +struct inverse...>> { + using type = magnitude...>; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// 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 +struct prepend_base; +template +using prepend_base_t = typename prepend_base::type; +template +struct prepend_base> { + using type = magnitude; +}; + +// Nullary case. +template <> +struct product<> { using type = magnitude<>; }; + +// Unary case. +template +struct product { using type = mag_t; }; + +// Binary case, where right argument is null magnitude. +template +struct product> { using type = mag_t; }; + +// Binary case, where left argument is null magnitude, and right is non-null. +template +struct product, magnitude> { using type = magnitude; }; + +// Binary case, with distinct heads. +template < + typename base1, + ratio pow1, + typename... tail1, + typename base2, + ratio pow2, + typename... tail2> +struct product, tail1...>, + magnitude, tail2...>> +{ + using mag1 = magnitude, tail1...>; + using mag2 = magnitude, tail2...>; + + using type = std::conditional_t< + (base1::value < base2::value), + prepend_base_t, product_t, mag2>>, + prepend_base_t, product_t>>>; +}; + +// Binary case, same head. +template +struct product, tail1...>, + magnitude, tail2...>> +{ + using tail_product = product_t, magnitude>; + using type = std::conditional_t< + ((pow1 + pow2).num == 0), + tail_product, + prepend_base_t, tail_product>>; +}; + +// N-ary case (N > 2). +template +struct product +{ + using type = product_t, tail...>; +}; + +} // namespace units::mag diff --git a/test/unit_test/runtime/CMakeLists.txt b/test/unit_test/runtime/CMakeLists.txt index 4cbb821c..4ed735e7 100644 --- a/test/unit_test/runtime/CMakeLists.txt +++ b/test/unit_test/runtime/CMakeLists.txt @@ -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 diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp new file mode 100644 index 00000000..a21a4741 --- /dev/null +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -0,0 +1,130 @@ +// 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 +#include +#include +#include + +using namespace units; +using namespace units::mag; + +TEST_CASE("Magnitude is invertible") +{ + CHECK(std::is_same_v>, magnitude<>>); + CHECK(std::is_same_v< + inverse_t>>, magnitude>>); + CHECK(std::is_same_v< + inverse_t, int_base_power<11, -5>>>, + magnitude, int_base_power<11, 5>>>); +} + +TEST_CASE("Magnitude supports products") +{ + SECTION ("The nullary product gives the unit magnitude") { + CHECK(std::is_same_v, magnitude<>>); + } + + SECTION ("The unary product is the identity operation") { + CHECK(std::is_same_v< + product_t>>, + magnitude>>); + + CHECK(std::is_same_v< + product_t, int_base_power<13, -2>>>, + magnitude, int_base_power<13, -2>>>); + } + + SECTION ("Binary product with null magnitude is identity") { + using arbitrary_mag = magnitude>; + CHECK(std::is_same_v, magnitude<>>, magnitude<>>); + CHECK(std::is_same_v>, arbitrary_mag>); + CHECK(std::is_same_v, arbitrary_mag>, arbitrary_mag>); + } + + SECTION ("Binary product with distinct bases maintains sorted order") { + CHECK(std::is_same_v< + product_t< + magnitude, int_base_power<7, -2>>, + magnitude, 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>, + magnitude>>, + magnitude>>); + CHECK(std::is_same_v< + product_t< + magnitude, int_base_power<3, -1, 3>>, + magnitude, int_base_power<5, 4>>>, + magnitude, 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>, magnitude>>, + magnitude<>>); + CHECK(std::is_same_v< + product_t< + magnitude, int_base_power<7, -2>>, + magnitude, int_base_power<5, 5>>>, + magnitude, int_base_power<7, -2>>>); + CHECK(std::is_same_v< + product_t< + magnitude, int_base_power<3, -2>, int_base_power<7, -2>>, + magnitude, int_base_power<5, 5>, int_base_power<7, 2>>>, + magnitude, int_base_power<5, 5>>>); + } + + SECTION ("N-ary products recurse") { + CHECK(std::is_same_v< + product_t< + magnitude>, + magnitude>, + magnitude>, + magnitude>, + magnitude>>, + magnitude, int_base_power<5>>>); + } +} + +// TEST_CASE("Ratio shortcut performs prime factorization") +// { +// // CHECK(std::is_same_v, magnitude<>>); +// // CHECK(std::is_same_v, magnitude>>); +// } +// +// TEST_CASE("Equality works for ratios") +// { +// CHECK(ratio<>{} == ratio<>{}); +// CHECK(ratio<3>{} == ratio<3>{}); +// +// // CHECK(ratio<3>{} != ratio<4>{}); +// } From 0566158e91579494ae7e5cbf2fcd7288f6381d87 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 11:38:56 -0500 Subject: [PATCH 03/42] Add concept for magnitude --- src/core/include/units/magnitude.h | 61 ++++++++++++++++++----- test/unit_test/runtime/magnitude_test.cpp | 59 ++++++++++++++++++++++ 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 060b1642..41d03e6a 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -24,6 +24,7 @@ #include #include +#include namespace units::mag { @@ -33,6 +34,21 @@ namespace units::mag template struct magnitude; +template +struct base_power; + +template +struct is_magnitude; +template +inline constexpr bool is_magnitude_v = is_magnitude::value; +template +concept Magnitude = is_magnitude_v; + +template +struct int_base : std::integral_constant {}; +template +using int_base_power = base_power, ratio{num, den}>; + template struct inverse; template @@ -54,6 +70,17 @@ template using prime_factorization_t = typename prime_factorization::type; } // namespace detail +template +using ratio_t = quotient_t, detail::prime_factorization_t>; + +struct pi { + static inline constexpr long double value = std::numbers::pi_v; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation details below. +//////////////////////////////////////////////////////////////////////////////////////////////////// + template struct magnitude { bool operator==(magnitude) const { return true; } @@ -62,23 +89,31 @@ struct magnitude { bool operator==(magnitude) const { return false; } }; -template -using ratio_t = quotient_t, detail::prime_factorization_t>; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Implementation details below. //////////////////////////////////////////////////////////////////////////////////////////////////// +// Magnitude concept implementation. -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Base powers. +template +struct is_magnitude: std::false_type {}; -template -struct base_power; +// Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. +template +constexpr bool strictly_increasing(const std::tuple &ts) { + // 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)); -template -struct int_base : std::integral_constant {}; -template -using int_base_power = base_power, ratio{num, den}>; + // Compare zero or more pairs of neighbours as needed. + return [&ts](std::integer_sequence) { + return ((std::get(ts) < std::get(ts)) && ...); + }(std::make_index_sequence()); +} + +template +struct is_magnitude...>> + : std::bool_constant<( + strictly_increasing(std::make_tuple(bases::value...)) + && ((powers.num != 0) && ...))> {}; //////////////////////////////////////////////////////////////////////////////////////////////////// // Inverse implementation. diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index a21a4741..8553fd73 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -115,6 +115,65 @@ TEST_CASE("Magnitude supports products") } } +TEST_CASE("is_magnitude detects well formed magnitudes") +{ + SECTION ("Arbitrary other types are not magnitudes") + { + CHECK(!is_magnitude_v); + CHECK(!is_magnitude_v); + CHECK(!is_magnitude_v>); + } + + SECTION ("Null magnitude is magnitude") + { + CHECK(is_magnitude_v>); + } + + SECTION ("Single-base magnitude is magnitude") + { + CHECK(is_magnitude_v>>); + } + + SECTION ("Out-of-order bases disqualify magnitudes") + { + CHECK(!is_magnitude_v, int_base_power<2>>>); + } + + SECTION ("Repeated bases disqualify magnitudes") + { + CHECK(!is_magnitude_v, int_base_power<2, 2>>>); + } + + SECTION ("Mixed base types are magnitudes if sorted") + { + CHECK(is_magnitude_v, base_power>>); + CHECK(is_magnitude_v, base_power>>); + CHECK(!is_magnitude_v, base_power>>); + } +} + +TEST_CASE("strictly_increasing") +{ + SECTION ("Empty tuple is sorted") + { + CHECK(strictly_increasing(std::make_tuple())); + } + + SECTION ("Single-element tuple is sorted") + { + CHECK(strictly_increasing(std::make_tuple(3))); + CHECK(strictly_increasing(std::make_tuple(15.42))); + CHECK(strictly_increasing(std::make_tuple('c'))); + } + + SECTION ("Multi-element tuples compare correctly") + { + CHECK(strictly_increasing(std::make_tuple(3, 3.14))); + CHECK(!strictly_increasing(std::make_tuple(3, 3.0))); + CHECK(!strictly_increasing(std::make_tuple(4, 3.0))); + } +} + // TEST_CASE("Ratio shortcut performs prime factorization") // { // // CHECK(std::is_same_v, magnitude<>>); From 4f759f95e809859f9c71f9a22752ee3956b6f304 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 11:44:22 -0500 Subject: [PATCH 04/42] Use Magnitude concept where appropriate --- src/core/include/units/magnitude.h | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 41d03e6a..9f6ab618 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -49,18 +49,18 @@ struct int_base : std::integral_constant {}; template using int_base_power = base_power, ratio{num, den}>; -template +template struct inverse; -template -using inverse_t = typename inverse::type; +template +using inverse_t = typename inverse::type; -template +template struct product; -template -using product_t = typename product::type; +template +using product_t = typename product::type; -template -using quotient_t = product_t>; +template +using quotient_t = product_t>; namespace detail { @@ -129,10 +129,10 @@ struct inverse...>> { // 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 +template struct prepend_base; -template -using prepend_base_t = typename prepend_base::type; +template +using prepend_base_t = typename prepend_base::type; template struct prepend_base> { using type = magnitude; @@ -143,12 +143,12 @@ template <> struct product<> { using type = magnitude<>; }; // Unary case. -template -struct product { using type = mag_t; }; +template +struct product { using type = M; }; // Binary case, where right argument is null magnitude. -template -struct product> { using type = mag_t; }; +template +struct product> { using type = M; }; // Binary case, where left argument is null magnitude, and right is non-null. template @@ -187,10 +187,10 @@ struct product, tail1...>, }; // N-ary case (N > 2). -template -struct product +template +struct product { - using type = product_t, tail...>; + using type = product_t, tail...>; }; } // namespace units::mag From 2c229710e730df83b9ad3dda91707d3a80333d0e Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 19:24:24 -0500 Subject: [PATCH 05/42] Create and use BasePower concept --- src/core/include/units/magnitude.h | 108 ++++++++++++---------- test/unit_test/runtime/magnitude_test.cpp | 37 ++++++++ 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 9f6ab618..38ded5e1 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -29,14 +29,24 @@ namespace units::mag { +template +struct base_power { + using base = base_; + static inline constexpr ratio power = power_; +}; + +template +struct is_base_power; +template +inline constexpr bool is_base_power_v = is_base_power::value; +template +concept BasePower = is_base_power_v; + // A `magnitude` represents a positive real number in a format which optimizes taking products and // rational powers. -template +template struct magnitude; -template -struct base_power; - template struct is_magnitude; template @@ -81,14 +91,25 @@ struct pi { // Implementation details below. //////////////////////////////////////////////////////////////////////////////////////////////////// -template +template struct magnitude { bool operator==(magnitude) const { return true; } - template - bool operator==(magnitude) const { return false; } + template + bool operator==(magnitude) const { return false; } }; +//////////////////////////////////////////////////////////////////////////////////////////////////// +// BasePower concept implementation. + +template +struct is_base_power: std::false_type {}; + +template + requires requires() { B::value; } +struct is_base_power> + : std::bool_constant<(B::value > 0)> {}; + //////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude concept implementation. @@ -109,18 +130,21 @@ constexpr bool strictly_increasing(const std::tuple &ts) { }(std::make_index_sequence()); } -template -struct is_magnitude...>> +template +struct is_magnitude> : std::bool_constant<( - strictly_increasing(std::make_tuple(bases::value...)) - && ((powers.num != 0) && ...))> {}; + strictly_increasing(std::make_tuple(Bs::base::value...)) + && ((Bs::power.num != 0) && ...))> {}; //////////////////////////////////////////////////////////////////////////////////////////////////// // Inverse implementation. -template -struct inverse...>> { - using type = magnitude...>; +template +using base_power_inverse = base_power; + +template +struct inverse> { + using type = magnitude...>; }; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -129,13 +153,13 @@ struct inverse...>> { // 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 +template struct prepend_base; -template -using prepend_base_t = typename prepend_base::type; -template -struct prepend_base> { - using type = magnitude; +template +using prepend_base_t = typename prepend_base::type; +template +struct prepend_base> { + using type = magnitude; }; // Nullary case. @@ -151,46 +175,36 @@ template struct product> { using type = M; }; // Binary case, where left argument is null magnitude, and right is non-null. -template -struct product, magnitude> { using type = magnitude; }; +template +struct product, magnitude> { using type = magnitude; }; -// Binary case, with distinct heads. -template < - typename base1, - ratio pow1, - typename... tail1, - typename base2, - ratio pow2, - typename... tail2> -struct product, tail1...>, - magnitude, tail2...>> +// Binary case, with distinct and non-null heads. +template +struct product, magnitude> { - using mag1 = magnitude, tail1...>; - using mag2 = magnitude, tail2...>; - using type = std::conditional_t< - (base1::value < base2::value), - prepend_base_t, product_t, mag2>>, - prepend_base_t, product_t>>>; + (Head1::base::value < Head2::base::value), + prepend_base_t, magnitude>>, + prepend_base_t, magnitude>>>; }; // Binary case, same head. -template -struct product, tail1...>, - magnitude, tail2...>> +template +struct product, Tail1...>, + magnitude, Tail2...>> { - using tail_product = product_t, magnitude>; + using tail_product = product_t, magnitude>; using type = std::conditional_t< - ((pow1 + pow2).num == 0), + ((Pow1 + Pow2).num == 0), tail_product, - prepend_base_t, tail_product>>; + prepend_base_t, tail_product>>; }; // N-ary case (N > 2). -template -struct product +template +struct product { - using type = product_t, tail...>; + using type = product_t, Tail...>; }; } // namespace units::mag diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 8553fd73..a3561561 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -115,6 +115,43 @@ TEST_CASE("Magnitude supports products") } } +TEST_CASE("is_base_power detects well formed base powers") +{ + SECTION ("Arbitrary other types are not base powers") + { + CHECK(!is_base_power_v); + CHECK(!is_base_power_v); + CHECK(!is_base_power_v>>); + } + + SECTION ("int_base_power forms base powers") + { + CHECK(is_base_power_v>); + CHECK(is_base_power_v>); + CHECK(is_base_power_v>); + } + + SECTION ("base_power forms base powers with pi and ratio") + { + CHECK(is_base_power_v>); + CHECK(is_base_power_v>); + CHECK(is_base_power_v>); + } + + SECTION ("base_power disqualified by negative or zero base") + { + CHECK(!is_base_power_v>); + CHECK(!is_base_power_v>); + } + + SECTION ("base_power disqualified by base without value") + { + CHECK(!is_base_power_v>); + CHECK(!is_base_power_v>); + CHECK(!is_base_power_v>); + } +} + TEST_CASE("is_magnitude detects well formed magnitudes") { SECTION ("Arbitrary other types are not magnitudes") From a86cac87059bd1f77f7e52ad9188dfa345c71e9c Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 20:53:55 -0500 Subject: [PATCH 06/42] Capitalize more template arguments --- src/core/include/units/magnitude.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 38ded5e1..1e414485 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -54,10 +54,10 @@ inline constexpr bool is_magnitude_v = is_magnitude::value; template concept Magnitude = is_magnitude_v; -template -struct int_base : std::integral_constant {}; -template -using int_base_power = base_power, ratio{num, den}>; +template +struct int_base : std::integral_constant {}; +template +using int_base_power = base_power, ratio{num, den}>; template struct inverse; @@ -74,10 +74,10 @@ using quotient_t = product_t>; namespace detail { -template +template struct prime_factorization; -template -using prime_factorization_t = typename prime_factorization::type; +template +using prime_factorization_t = typename prime_factorization::type; } // namespace detail template From 8a1ed3adcbab6d052c86204395d8ccc8511d1632 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 20:56:10 -0500 Subject: [PATCH 07/42] Add make_ratio() helper and prime factorization --- src/core/include/units/magnitude.h | 64 ++++++++++++++++- test/unit_test/runtime/magnitude_test.cpp | 88 +++++++++++++++++++---- 2 files changed, 135 insertions(+), 17 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 1e414485..5c7963a2 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -80,8 +80,10 @@ template using prime_factorization_t = typename prime_factorization::type; } // namespace detail -template -using ratio_t = quotient_t, detail::prime_factorization_t>; +template +auto make_ratio() { + return quotient_t, detail::prime_factorization_t>{}; +} struct pi { static inline constexpr long double value = std::numbers::pi_v; @@ -207,4 +209,62 @@ struct product using type = product_t, Tail...>; }; +namespace detail +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Prime factorization implementation. + +// 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; +} + +// Specialization for the prime factorization of 1 (base case). +template <> +struct prime_factorization<1> { using type = magnitude<>; }; + +// Specialization for the prime factorization of larger numbers (recursive case). +template +struct prime_factorization { + static_assert(N > 1, "Can only factor positive integers."); + + static inline constexpr std::intmax_t first_base = find_first_factor(N); + static inline constexpr std::intmax_t first_power = multiplicity(first_base, N); + static inline constexpr std::intmax_t remainder = remove_power(first_base, first_power, N); + + using type = product_t< + magnitude>, prime_factorization_t>; +}; + +} // namespace detail } // namespace units::mag diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index a3561561..d0a51b1c 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -25,8 +25,8 @@ #include #include -using namespace units; -using namespace units::mag; +namespace units::mag +{ TEST_CASE("Magnitude is invertible") { @@ -211,16 +211,74 @@ TEST_CASE("strictly_increasing") } } -// TEST_CASE("Ratio shortcut performs prime factorization") -// { -// // CHECK(std::is_same_v, magnitude<>>); -// // CHECK(std::is_same_v, magnitude>>); -// } -// -// TEST_CASE("Equality works for ratios") -// { -// CHECK(ratio<>{} == ratio<>{}); -// CHECK(ratio<3>{} == ratio<3>{}); -// -// // CHECK(ratio<3>{} != ratio<4>{}); -// } +TEST_CASE("make_ratio performs prime factorization correctly") +{ + SECTION("Performs prime factorization when denominator is 1") + { + CHECK(std::is_same_v()), magnitude<>>); + CHECK(std::is_same_v()), magnitude>>); + CHECK(std::is_same_v()), magnitude>>); + CHECK(std::is_same_v()), magnitude>>); + + CHECK(std::is_same_v< + decltype(make_ratio<792>()), + magnitude, int_base_power<3, 2>, int_base_power<11>>>); + } + + SECTION("Reduces fractions to lowest terms") + { + CHECK(std::is_same_v()), magnitude<>>); + CHECK(std::is_same_v< + decltype(make_ratio<50, 80>()), + magnitude, int_base_power<5>>>); + } +} + +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>()); + } +} + +namespace detail +{ + +TEST_CASE("Prime factorization") +{ + SECTION ("1 factors into the null magnitude") + { + CHECK(std::is_same_v, magnitude<>>); + } + + SECTION ("Prime numbers factor into themselves") + { + CHECK(std::is_same_v, magnitude>>); + CHECK(std::is_same_v, magnitude>>); + CHECK(std::is_same_v, magnitude>>); + CHECK(std::is_same_v, magnitude>>); + CHECK(std::is_same_v, magnitude>>); + + CHECK(std::is_same_v, magnitude>>); + } + + SECTION("Prime factorization finds factors and multiplicities") + { + CHECK(std::is_same_v< + prime_factorization_t<792>, + magnitude, int_base_power<3, 2>, int_base_power<11>>>); + } +} + +} // namespace detail + +} // namespace units::mag From c75eb02ea7da8900167438061c8afb5cbb12ef66 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 21:10:29 -0500 Subject: [PATCH 08/42] Finish fleshing out make_ratio --- src/core/include/units/magnitude.h | 12 ++++-- test/unit_test/runtime/magnitude_test.cpp | 45 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 5c7963a2..d411766a 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -81,7 +81,7 @@ using prime_factorization_t = typename prime_factorization::type; } // namespace detail template -auto make_ratio() { +constexpr auto make_ratio() { return quotient_t, detail::prime_factorization_t>{}; } @@ -95,10 +95,14 @@ struct pi { template struct magnitude { - bool operator==(magnitude) const { return true; } + template + constexpr bool operator==(M) const { return std::is_same_v; } - template - bool operator==(magnitude) const { return false; } + template + constexpr friend auto operator*(magnitude, M) { return product_t{}; } + + template + constexpr friend auto operator/(magnitude, M) { return quotient_t{}; } }; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index d0a51b1c..e0f5a00f 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -248,6 +248,51 @@ TEST_CASE("Equality works for magnitudes") 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); + } +} + +TEST_CASE("Multiplication works for magnitudes") +{ + SECTION("Reciprocals reduce to null magnitude") + { + CHECK(make_ratio<3, 4>() * make_ratio<4, 3>() == make_ratio<1>()); + } + + SECTION("Products work as expected") + { + CHECK(make_ratio<4, 5>() * make_ratio<4, 3>() == make_ratio<16, 15>()); + } + + SECTION("Supports constexpr") + { + constexpr auto p = make_ratio<4, 5>() * make_ratio<4, 3>(); + CHECK(p == make_ratio<16, 15>()); + } +} + +TEST_CASE("Division works for magnitudes") +{ + SECTION("Dividing anything by itself reduces to null magnitude") + { + CHECK(make_ratio<3, 4>() / make_ratio<3, 4>() == make_ratio<1>()); + CHECK(make_ratio<15>() / make_ratio<15>() == make_ratio<1>()); + } + + SECTION("Quotients work as expected") + { + CHECK(make_ratio<4, 5>() / make_ratio<4, 3>() == make_ratio<3, 5>()); + } + + SECTION("Supports constexpr") + { + constexpr auto q = make_ratio<4, 5>() / make_ratio<4, 3>(); + CHECK(q == make_ratio<3, 5>()); + } } namespace detail From 05934c8b7206aaa991f2d71289cf51ddb2fba33d Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 29 Dec 2021 21:37:06 -0500 Subject: [PATCH 09/42] Add make_base_power() helper --- src/core/include/units/magnitude.h | 5 +++++ test/unit_test/runtime/magnitude_test.cpp | 24 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index d411766a..7e766b7e 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -85,6 +85,11 @@ constexpr auto make_ratio() { return quotient_t, detail::prime_factorization_t>{}; } +template +constexpr auto make_base_power() { + return magnitude>{}; +} + struct pi { static inline constexpr long double value = std::numbers::pi_v; }; diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index e0f5a00f..f95c37f9 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -234,6 +234,22 @@ TEST_CASE("make_ratio performs prime factorization correctly") } } +TEST_CASE("make_magnitude handles arbitrary bases") +{ + SECTION("Equivalent to std::integral_constant for integer bases") + { + CHECK(make_base_power>() == make_ratio<2>()); + CHECK(make_base_power>() == make_ratio<7>()); + } + + SECTION("Handles non-integer bases") + { + CHECK(make_base_power() == magnitude>{}); + CHECK(make_base_power() == magnitude>{}); + CHECK(make_base_power() == magnitude>{}); + } +} + TEST_CASE("Equality works for magnitudes") { SECTION("Equivalent ratios are equal") @@ -268,6 +284,14 @@ TEST_CASE("Multiplication works for magnitudes") CHECK(make_ratio<4, 5>() * make_ratio<4, 3>() == make_ratio<16, 15>()); } + SECTION("Products handle pi correctly") + { + CHECK( + make_base_power() * make_ratio<2, 3>() * make_base_power() == + magnitude, int_base_power<3, -1>, base_power>{}); + + } + SECTION("Supports constexpr") { constexpr auto p = make_ratio<4, 5>() * make_ratio<4, 3>(); From 626c7bfe390eed7ffbf1d63f85ae0e17b351517b Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 30 Dec 2021 10:47:01 -0500 Subject: [PATCH 10/42] Fix up for review - 120 line limit - uppercase template params - doxygen comments for public APIs - `int_base` -> `prime_base` --- src/core/include/units/magnitude.h | 198 ++++++++++++++-------- test/unit_test/runtime/magnitude_test.cpp | 42 ++++- 2 files changed, 159 insertions(+), 81 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 7e766b7e..88d231de 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -29,106 +29,165 @@ namespace units::mag { -template +/** + * @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 struct base_power { - using base = base_; - static inline constexpr ratio power = power_; + using base = Base; + static inline constexpr ratio power = Power; }; -template +/** + * @brief A type trait and concept to detect whether something is a valid "base power". + */ +template struct is_base_power; -template +template inline constexpr bool is_base_power_v = is_base_power::value; -template +template concept BasePower = is_base_power_v; -// A `magnitude` represents a positive real number in a format which optimizes taking products and -// rational powers. -template +/** + * @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 struct magnitude; -template +/** + * @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 struct is_magnitude; -template +template inline constexpr bool is_magnitude_v = is_magnitude::value; -template +template concept Magnitude = is_magnitude_v; -template -struct int_base : std::integral_constant {}; -template -using int_base_power = base_power, ratio{num, den}>; - -template +/** + * @brief Compute the inverse of a Magnitude. + */ +template struct inverse; -template +template using inverse_t = typename inverse::type; -template +/** + * @brief Compute the product of 0 or more Magnitudes. + */ +template struct product; -template +template using product_t = typename product::type; -template +/** + * @brief Compute the quotient of 2 Magnitudes. + */ +template using quotient_t = product_t>; namespace detail { -template +// Helpers to perform prime factorization at compile time. +template struct prime_factorization; -template +template using prime_factorization_t = typename prime_factorization::type; + +// A way to check whether a number is prime at compile time. +constexpr bool is_prime(std::intmax_t n); } // namespace detail -template +/** + * @brief A template to represent prime number bases. + */ +template +struct prime_base : std::integral_constant { + static_assert(detail::is_prime(N)); +}; + +/** + * @brief Make a Magnitude that is a rational number. + * + * This will be the main way end users create Magnitudes. They should rarely (if ever) create a magnitude<...> by + * manually adding base powers. + */ +template constexpr auto make_ratio() { return quotient_t, detail::prime_factorization_t>{}; } -template +/** + * @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()` to represent pi + * - `make_base_power, 1, 2>()` to represent sqrt(2) + */ +template constexpr auto make_base_power() { return magnitude>{}; } +/** + * @brief A base to represent pi. + */ struct pi { static inline constexpr long double value = std::numbers::pi_v; }; -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation details below. -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template +template struct magnitude { - template + template constexpr bool operator==(M) const { return std::is_same_v; } - template + template constexpr friend auto operator*(magnitude, M) { return product_t{}; } - template + template constexpr friend auto operator/(magnitude, M) { return quotient_t{}; } }; -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BasePower concept implementation. -template +// Default implementation: most things are not base powers. +template struct is_base_power: std::false_type {}; -template +// To be a valid base power, one must be a base_power, where B has a static value member which is positive. +template requires requires() { B::value; } struct is_base_power> : std::bool_constant<(B::value > 0)> {}; -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude concept implementation. -template +// Default implementation: most things are not magnitudes. +template struct is_magnitude: std::false_type {}; // Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. -template +template constexpr bool strictly_increasing(const std::tuple &ts) { // Carefully handle different sizes, avoiding unsigned integer underflow. constexpr auto num_comparisons = [](auto num_elements) { @@ -141,56 +200,53 @@ constexpr bool strictly_increasing(const std::tuple &ts) { }(std::make_index_sequence()); } -template +// To be a valid magnitude, one must be a magnitude<...> of BasePowers with nonzero exponents, sorted by increasing base +// value. +template struct is_magnitude> - : std::bool_constant<( - strictly_increasing(std::make_tuple(Bs::base::value...)) - && ((Bs::power.num != 0) && ...))> {}; + : 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 using base_power_inverse = base_power; -template -struct inverse> { - using type = magnitude...>; -}; +template +struct inverse> { using type = magnitude...>; }; -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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 +template struct prepend_base; -template +template using prepend_base_t = typename prepend_base::type; -template -struct prepend_base> { - using type = magnitude; -}; +template +struct prepend_base> { using type = magnitude; }; // Nullary case. -template <> +template<> struct product<> { using type = magnitude<>; }; // Unary case. -template +template struct product { using type = M; }; // Binary case, where right argument is null magnitude. -template +template struct product> { using type = M; }; // Binary case, where left argument is null magnitude, and right is non-null. -template +template struct product, magnitude> { using type = magnitude; }; // Binary case, with distinct and non-null heads. -template +template struct product, magnitude> { using type = std::conditional_t< @@ -200,7 +256,7 @@ struct product, magnitude> }; // Binary case, same head. -template +template struct product, Tail1...>, magnitude, Tail2...>> { @@ -212,16 +268,13 @@ struct product, Tail1...>, }; // N-ary case (N > 2). -template -struct product -{ - using type = product_t, Tail...>; -}; +template +struct product { using type = product_t, Tail...>; }; namespace detail { -//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Prime factorization implementation. // Find the smallest prime factor of `n`. @@ -251,19 +304,16 @@ 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; } // Specialization for the prime factorization of 1 (base case). -template <> +template<> struct prime_factorization<1> { using type = magnitude<>; }; // Specialization for the prime factorization of larger numbers (recursive case). -template +template struct prime_factorization { static_assert(N > 1, "Can only factor positive integers."); @@ -272,8 +322,10 @@ struct prime_factorization { static inline constexpr std::intmax_t remainder = remove_power(first_base, first_power, N); using type = product_t< - magnitude>, prime_factorization_t>; + magnitude, ratio{first_power}>>, prime_factorization_t>; }; +constexpr bool is_prime(std::intmax_t n) { return (n > 1) && (find_first_factor(n) == n); } + } // namespace detail } // namespace units::mag diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index f95c37f9..61115a4f 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,6 +28,9 @@ namespace units::mag { +template +using int_base_power = base_power, ratio{Num, Den}>; + TEST_CASE("Magnitude is invertible") { CHECK(std::is_same_v>, magnitude<>>); @@ -138,12 +141,6 @@ TEST_CASE("is_base_power detects well formed base powers") CHECK(is_base_power_v>); } - SECTION ("base_power disqualified by negative or zero base") - { - CHECK(!is_base_power_v>); - CHECK(!is_base_power_v>); - } - SECTION ("base_power disqualified by base without value") { CHECK(!is_base_power_v>); @@ -238,8 +235,8 @@ TEST_CASE("make_magnitude handles arbitrary bases") { SECTION("Equivalent to std::integral_constant for integer bases") { - CHECK(make_base_power>() == make_ratio<2>()); - CHECK(make_base_power>() == make_ratio<7>()); + CHECK(make_base_power>() == make_ratio<2>()); + CHECK(make_base_power>() == make_ratio<7>()); } SECTION("Handles non-integer bases") @@ -348,6 +345,35 @@ TEST_CASE("Prime factorization") } } +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)); + } +} + } // namespace detail } // namespace units::mag From 998266e780edda307f429490b61859b1f2f8eb77 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Fri, 31 Dec 2021 09:35:30 -0500 Subject: [PATCH 11/42] Try fixing MSVC 14.2 I don't know how to run the build directly, but it seems like this should work. --- src/core/include/units/magnitude.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 88d231de..943cab2e 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -261,10 +261,11 @@ struct product, Tail1...>, magnitude, Tail2...>> { using tail_product = product_t, magnitude>; + static inline constexpr auto Pow = Pow1 + Pow2; using type = std::conditional_t< - ((Pow1 + Pow2).num == 0), + Pow.num == 0, tail_product, - prepend_base_t, tail_product>>; + prepend_base_t, tail_product>>; }; // N-ary case (N > 2). From cef01ba67cef1e64ad98a5d2cb5c0fbd9868aeb7 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Fri, 31 Dec 2021 09:54:10 -0500 Subject: [PATCH 12/42] Remove redundant `inline` specifiers `static constexpr` implies `inline`; see: https://stackoverflow.com/a/57407675 --- src/core/include/units/magnitude.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 943cab2e..25931150 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -42,7 +42,7 @@ namespace units::mag template struct base_power { using base = Base; - static inline constexpr ratio power = Power; + static constexpr ratio power = Power; }; /** @@ -147,7 +147,7 @@ constexpr auto make_base_power() { * @brief A base to represent pi. */ struct pi { - static inline constexpr long double value = std::numbers::pi_v; + static constexpr long double value = std::numbers::pi_v; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -261,7 +261,7 @@ struct product, Tail1...>, magnitude, Tail2...>> { using tail_product = product_t, magnitude>; - static inline constexpr auto Pow = Pow1 + Pow2; + static constexpr auto Pow = Pow1 + Pow2; using type = std::conditional_t< Pow.num == 0, tail_product, @@ -318,9 +318,9 @@ template struct prime_factorization { static_assert(N > 1, "Can only factor positive integers."); - static inline constexpr std::intmax_t first_base = find_first_factor(N); - static inline constexpr std::intmax_t first_power = multiplicity(first_base, N); - static inline constexpr std::intmax_t remainder = remove_power(first_base, first_power, N); + static constexpr std::intmax_t 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, ratio{first_power}>>, prime_factorization_t>; From bb4fe5040dfb0197522c29d57072e60be6e8af74 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Mon, 3 Jan 2022 15:20:05 -0500 Subject: [PATCH 13/42] Factor out logic into `pairwise_all()` helper We could move this somewhere more generic if we want. --- src/core/include/units/magnitude.h | 15 +++++++++----- test/unit_test/runtime/magnitude_test.cpp | 24 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 25931150..0cbfd4d5 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -186,20 +186,25 @@ struct is_base_power> template struct is_magnitude: std::false_type {}; -// Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. -template -constexpr bool strictly_increasing(const std::tuple &ts) { +template +constexpr bool pairwise_all(const std::tuple &ts, const Predicate &pred) { // 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 [&ts](std::integer_sequence) { - return ((std::get(ts) < std::get(ts)) && ...); + return [&ts, &pred](std::integer_sequence) { + return (pred(std::get(ts), std::get(ts)) && ...); }(std::make_index_sequence()); } +// Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. +template +constexpr bool strictly_increasing(const std::tuple &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 diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 61115a4f..475d97f7 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -374,6 +374,30 @@ TEST_CASE("is_prime detects primes") } } +TEST_CASE("pairwise_all evaluates all pairs") +{ + SECTION("always true for empty tuples") + { + CHECK(pairwise_all(std::make_tuple(), [](auto a, auto b){ return true; })); + CHECK(pairwise_all(std::make_tuple(), [](auto a, auto b){ return false; })); + } + + SECTION("always true for single-element tuples") + { + CHECK(pairwise_all(std::make_tuple(1), [](auto a, auto b){ return true; })); + CHECK(pairwise_all(std::make_tuple(3.14), [](auto a, auto b){ return false; })); + CHECK(pairwise_all(std::make_tuple('x'), [](auto a, auto b){ return true; })); + } + + SECTION("true for longer tuples iff true for all neighbouring pairs") + { + CHECK(pairwise_all(std::make_tuple(1, 1.5), std::less{})); + CHECK(pairwise_all(std::make_tuple(1, 1.5, 2), std::less{})); + CHECK(!pairwise_all(std::make_tuple(1, 2.0, 2), std::less{})); + CHECK(!pairwise_all(std::make_tuple(1, 2.5, 2), std::less{})); + } +} + } // namespace detail } // namespace units::mag From 4246f2db2aa67e9a1dc32ebc2ca003af3b3c7f80 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 4 Jan 2022 14:01:42 -0500 Subject: [PATCH 14/42] Remove unused variable names Should fix clang builds --- test/unit_test/runtime/magnitude_test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 475d97f7..5a866db2 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -378,15 +378,15 @@ TEST_CASE("pairwise_all evaluates all pairs") { SECTION("always true for empty tuples") { - CHECK(pairwise_all(std::make_tuple(), [](auto a, auto b){ return true; })); - CHECK(pairwise_all(std::make_tuple(), [](auto a, auto b){ return false; })); + CHECK(pairwise_all(std::make_tuple(), [](auto, auto){ return true; })); + CHECK(pairwise_all(std::make_tuple(), [](auto, auto){ return false; })); } SECTION("always true for single-element tuples") { - CHECK(pairwise_all(std::make_tuple(1), [](auto a, auto b){ return true; })); - CHECK(pairwise_all(std::make_tuple(3.14), [](auto a, auto b){ return false; })); - CHECK(pairwise_all(std::make_tuple('x'), [](auto a, auto b){ return true; })); + CHECK(pairwise_all(std::make_tuple(1), [](auto, auto){ return true; })); + CHECK(pairwise_all(std::make_tuple(3.14), [](auto, auto){ return false; })); + CHECK(pairwise_all(std::make_tuple('x'), [](auto, auto){ return true; })); } SECTION("true for longer tuples iff true for all neighbouring pairs") From 63910131fff784fb652d3ad39f204afcfaf3c787 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 4 Jan 2022 14:14:22 -0500 Subject: [PATCH 15/42] Try fixing MSVC 14 bug I don't know how to test this locally. --- test/unit_test/runtime/magnitude_test.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 5a866db2..afbaf62f 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,8 +28,19 @@ namespace units::mag { +// Convenience utility to create an integral base power. For unit tests only. +template +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, power>; +}; template -using int_base_power = base_power, ratio{Num, Den}>; +using int_base_power = typename IntBasePower::type; TEST_CASE("Magnitude is invertible") { From 14d8d96b3c0251404281881fa3cc7355c2b74d32 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 4 Jan 2022 20:06:41 -0500 Subject: [PATCH 16/42] Replace `static_assert` with template parameter constraints --- src/core/include/units/magnitude.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 0cbfd4d5..aa3ed580 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -103,8 +103,10 @@ namespace detail { // Helpers to perform prime factorization at compile time. template + requires requires { N > 0; } struct prime_factorization; template + requires requires { N > 0; } using prime_factorization_t = typename prime_factorization::type; // A way to check whether a number is prime at compile time. @@ -115,9 +117,8 @@ constexpr bool is_prime(std::intmax_t n); * @brief A template to represent prime number bases. */ template -struct prime_base : std::integral_constant { - static_assert(detail::is_prime(N)); -}; + requires requires { detail::is_prime(N); } +struct prime_base : std::integral_constant {}; /** * @brief Make a Magnitude that is a rational number. @@ -320,9 +321,8 @@ struct prime_factorization<1> { using type = magnitude<>; }; // Specialization for the prime factorization of larger numbers (recursive case). template + requires requires { N > 0; } struct prime_factorization { - static_assert(N > 1, "Can only factor positive integers."); - static constexpr std::intmax_t 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); From 6033f962679c69cdd0a3613993972183e817218b Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 14:03:52 -0500 Subject: [PATCH 17/42] Enable implicit ratio construction --- src/core/include/units/ratio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/include/units/ratio.h b/src/core/include/units/ratio.h index 923f2355..caf2571f 100644 --- a/src/core/include/units/ratio.h +++ b/src/core/include/units/ratio.h @@ -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); From 92b7a15d6f567854209dd2ab33cc15b04bb62caf Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 14:04:19 -0500 Subject: [PATCH 18/42] (partial) Switch base_power to NTTP Apparently, gcc-10 does not support floating point NTTPs, so we'll need to reimagine the approach. --- src/core/include/units/magnitude.h | 317 ++++++++-------------- test/unit_test/runtime/magnitude_test.cpp | 304 ++++----------------- 2 files changed, 177 insertions(+), 444 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index aa3ed580..9e73fd20 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -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 -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 -struct is_base_power; -template -inline constexpr bool is_base_power_v = is_base_power::value; -template -concept BasePower = is_base_power_v; - -/** - * @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 -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 -struct is_magnitude; -template -inline constexpr bool is_magnitude_v = is_magnitude::value; -template -concept Magnitude = is_magnitude_v; - -/** - * @brief Compute the inverse of a Magnitude. - */ -template -struct inverse; -template -using inverse_t = typename inverse::type; - -/** - * @brief Compute the product of 0 or more Magnitudes. - */ -template -struct product; -template -using product_t = typename product::type; - -/** - * @brief Compute the quotient of 2 Magnitudes. - */ -template -using quotient_t = product_t>; - namespace detail { // Helpers to perform prime factorization at compile time. @@ -106,19 +36,119 @@ template requires requires { N > 0; } struct prime_factorization; template - requires requires { N > 0; } -using prime_factorization_t = typename prime_factorization::type; +static constexpr auto prime_factorization_v = prime_factorization::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 +concept BaseRep = std::is_same_v || std::is_same_v; + /** - * @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 - requires requires { detail::is_prime(N); } -struct prime_base : std::integral_constant {}; +template +struct base_power { + // The value of the basis vector. + T base; + + // The rational power to which the base is raised. + ratio power{1}; +}; + +template U> +base_power(T, U) -> base_power; + +template +base_power(T) -> base_power; + +template +constexpr bool operator==(base_power t, base_power u) { + return std::is_same_v && (t.base == u.base) && (t.power == u.power); +} + +template +constexpr auto inverse(base_power bp) { + bp.power = -bp.power; + return bp; +} + +namespace detail +{ +template +constexpr bool is_valid_base_power(const base_power &bp) { + if (bp.power == 0) { return false; } + + if constexpr (std::is_same_v) { return is_prime(bp.base); } + else if constexpr (std::is_same_v) { return bp.base > 0; } + else { return false; } // Unreachable. +} +} // namespace detail + +template +concept BasePower = std::is_same_v> || std::is_same_v>; + +/** + * @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 + requires requires { + // (is_valid_base_power(BasePowers) && ... && strictly_increasing(std::make_tuple(BasePowers.base...))); + (detail::is_valid_base_power(BasePowers) && ...); + } +struct magnitude {}; + +template +constexpr bool operator==(magnitude, magnitude) { return ((LeftBPs == RightBPs) && ...); } + +template +constexpr auto inverse(magnitude) { return magnitude{}; } + +constexpr auto operator*(magnitude<>, magnitude<>) { return magnitude<>{}; } + +template +constexpr auto operator*(magnitude<>, magnitude m) { return m; } + +template +constexpr auto operator*(magnitude m, magnitude<>) { return m; } + +template +constexpr auto operator*(magnitude, magnitude) { + // 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{}; } + + if constexpr (H1.base == H2.base) { + constexpr auto partial_product = magnitude{} * magnitude{}; + constexpr base_power new_head{H1.base, (H1.power + H2.power)}; + + if constexpr (new_head.power == 0) { + return partial_product; + } else { + return magnitude{} * partial_product; + } + } else if constexpr(H1.base < H2.base){ + return magnitude

{} * (magnitude{} * magnitude{}); + } else { // We know H2.base < H1.base + return magnitude

{} * (magnitude{} * magnitude{}); + } +} + +template +constexpr auto operator/(magnitude l, magnitude r) { return l * inverse(r); } /** * @brief Make a Magnitude that is a rational number. @@ -127,66 +157,25 @@ struct prime_base : std::integral_constant {}; * manually adding base powers. */ template -constexpr auto make_ratio() { - return quotient_t, detail::prime_factorization_t>{}; -} - -/** - * @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()` to represent pi - * - `make_base_power, 1, 2>()` to represent sqrt(2) - */ -template -constexpr auto make_base_power() { - return magnitude>{}; -} +constexpr auto make_ratio() { return detail::prime_factorization_v / detail::prime_factorization_v; } /** * @brief A base to represent pi. */ -struct pi { - static constexpr long double value = std::numbers::pi_v; -}; +template + requires requires { Power != 0; } +constexpr auto pi_power() { return base_power{std::numbers::pi_v, Power}; } + +template +constexpr auto pi_to_the() { return magnitude()>{}; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation details below. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template -struct magnitude { - template - constexpr bool operator==(M) const { return std::is_same_v; } - - template - constexpr friend auto operator*(magnitude, M) { return product_t{}; } - - template - constexpr friend auto operator/(magnitude, M) { return quotient_t{}; } -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// BasePower concept implementation. - -// Default implementation: most things are not base powers. -template -struct is_base_power: std::false_type {}; - -// To be a valid base power, one must be a base_power, where B has a static value member which is positive. -template - requires requires() { B::value; } -struct is_base_power> - : std::bool_constant<(B::value > 0)> {}; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude concept implementation. -// Default implementation: most things are not magnitudes. -template -struct is_magnitude: std::false_type {}; - template constexpr bool pairwise_all(const std::tuple &ts, const Predicate &pred) { // Carefully handle different sizes, avoiding unsigned integer underflow. @@ -206,78 +195,6 @@ constexpr bool strictly_increasing(const std::tuple &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 -struct is_magnitude> - : 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 -using base_power_inverse = base_power; - -template -struct inverse> { using type = magnitude...>; }; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// 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 -struct prepend_base; -template -using prepend_base_t = typename prepend_base::type; -template -struct prepend_base> { using type = magnitude; }; - -// Nullary case. -template<> -struct product<> { using type = magnitude<>; }; - -// Unary case. -template -struct product { using type = M; }; - -// Binary case, where right argument is null magnitude. -template -struct product> { using type = M; }; - -// Binary case, where left argument is null magnitude, and right is non-null. -template -struct product, magnitude> { using type = magnitude; }; - -// Binary case, with distinct and non-null heads. -template -struct product, magnitude> -{ - using type = std::conditional_t< - (Head1::base::value < Head2::base::value), - prepend_base_t, magnitude>>, - prepend_base_t, magnitude>>>; -}; - -// Binary case, same head. -template -struct product, Tail1...>, - magnitude, Tail2...>> -{ - using tail_product = product_t, magnitude>; - static constexpr auto Pow = Pow1 + Pow2; - using type = std::conditional_t< - Pow.num == 0, - tail_product, - prepend_base_t, tail_product>>; -}; - -// N-ary case (N > 2). -template -struct product { using type = product_t, 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 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, ratio{first_power}>>, prime_factorization_t>; + static constexpr auto value = magnitude{} + * prime_factorization_v; }; constexpr bool is_prime(std::intmax_t n) { return (n > 1) && (find_first_factor(n) == n); } diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index afbaf62f..9cd54835 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,175 +28,6 @@ namespace units::mag { -// Convenience utility to create an integral base power. For unit tests only. -template -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, power>; -}; -template -using int_base_power = typename IntBasePower::type; - -TEST_CASE("Magnitude is invertible") -{ - CHECK(std::is_same_v>, magnitude<>>); - CHECK(std::is_same_v< - inverse_t>>, magnitude>>); - CHECK(std::is_same_v< - inverse_t, int_base_power<11, -5>>>, - magnitude, int_base_power<11, 5>>>); -} - -TEST_CASE("Magnitude supports products") -{ - SECTION ("The nullary product gives the unit magnitude") { - CHECK(std::is_same_v, magnitude<>>); - } - - SECTION ("The unary product is the identity operation") { - CHECK(std::is_same_v< - product_t>>, - magnitude>>); - - CHECK(std::is_same_v< - product_t, int_base_power<13, -2>>>, - magnitude, int_base_power<13, -2>>>); - } - - SECTION ("Binary product with null magnitude is identity") { - using arbitrary_mag = magnitude>; - CHECK(std::is_same_v, magnitude<>>, magnitude<>>); - CHECK(std::is_same_v>, arbitrary_mag>); - CHECK(std::is_same_v, arbitrary_mag>, arbitrary_mag>); - } - - SECTION ("Binary product with distinct bases maintains sorted order") { - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<7, -2>>, - magnitude, 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>, - magnitude>>, - magnitude>>); - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<3, -1, 3>>, - magnitude, int_base_power<5, 4>>>, - magnitude, 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>, magnitude>>, - magnitude<>>); - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<7, -2>>, - magnitude, int_base_power<5, 5>>>, - magnitude, int_base_power<7, -2>>>); - CHECK(std::is_same_v< - product_t< - magnitude, int_base_power<3, -2>, int_base_power<7, -2>>, - magnitude, int_base_power<5, 5>, int_base_power<7, 2>>>, - magnitude, int_base_power<5, 5>>>); - } - - SECTION ("N-ary products recurse") { - CHECK(std::is_same_v< - product_t< - magnitude>, - magnitude>, - magnitude>, - magnitude>, - magnitude>>, - magnitude, 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); - CHECK(!is_base_power_v); - CHECK(!is_base_power_v>>); - } - - SECTION ("int_base_power forms base powers") - { - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - } - - SECTION ("base_power forms base powers with pi and ratio") - { - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - CHECK(is_base_power_v>); - } - - SECTION ("base_power disqualified by base without value") - { - CHECK(!is_base_power_v>); - CHECK(!is_base_power_v>); - CHECK(!is_base_power_v>); - } -} - -TEST_CASE("is_magnitude detects well formed magnitudes") -{ - SECTION ("Arbitrary other types are not magnitudes") - { - CHECK(!is_magnitude_v); - CHECK(!is_magnitude_v); - CHECK(!is_magnitude_v>); - } - - SECTION ("Null magnitude is magnitude") - { - CHECK(is_magnitude_v>); - } - - SECTION ("Single-base magnitude is magnitude") - { - CHECK(is_magnitude_v>>); - } - - SECTION ("Out-of-order bases disqualify magnitudes") - { - CHECK(!is_magnitude_v, int_base_power<2>>>); - } - - SECTION ("Repeated bases disqualify magnitudes") - { - CHECK(!is_magnitude_v, int_base_power<2, 2>>>); - } - - SECTION ("Mixed base types are magnitudes if sorted") - { - CHECK(is_magnitude_v, base_power>>); - CHECK(is_magnitude_v, base_power>>); - CHECK(!is_magnitude_v, base_power>>); - } -} - 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()), magnitude<>>); - CHECK(std::is_same_v()), magnitude>>); - CHECK(std::is_same_v()), magnitude>>); - CHECK(std::is_same_v()), magnitude>>); +// TEST_CASE("make_ratio performs prime factorization correctly") +// { +// SECTION("Performs prime factorization when denominator is 1") +// { +// CHECK(std::is_same_v()), magnitude<>>); +// CHECK(std::is_same_v()), magnitude>); +// CHECK(std::is_same_v()), magnitude>); +// CHECK(std::is_same_v()), magnitude>); +// +// CHECK(std::is_same_v< +// decltype(make_ratio<792>()), +// magnitude>); +// } +// +// SECTION("Reduces fractions to lowest terms") +// { +// CHECK(std::is_same_v()), magnitude<>>); +// CHECK(std::is_same_v< +// decltype(make_ratio<50, 80>()), magnitude>); +// } +// } - CHECK(std::is_same_v< - decltype(make_ratio<792>()), - magnitude, 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()), magnitude<>>); - CHECK(std::is_same_v< - decltype(make_ratio<50, 80>()), - magnitude, int_base_power<5>>>); - } -} - -TEST_CASE("make_magnitude handles arbitrary bases") -{ - SECTION("Equivalent to std::integral_constant for integer bases") - { - CHECK(make_base_power>() == make_ratio<2>()); - CHECK(make_base_power>() == make_ratio<7>()); - } - - SECTION("Handles non-integer bases") - { - CHECK(make_base_power() == magnitude>{}); - CHECK(make_base_power() == magnitude>{}); - CHECK(make_base_power() == magnitude>{}); - } -} - -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 +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() * make_ratio<2, 3>() * make_base_power() == - magnitude, int_base_power<3, -1>, base_power>{}); - - } + //SECTION("Products handle pi correctly") + //{ + // CHECK( + // pi_to_the<1>() * make_ratio<2, 3>() * pi_to_the() == + // magnitude()>{}); + //} SECTION("Supports constexpr") { @@ -334,25 +151,24 @@ TEST_CASE("Prime factorization") { SECTION ("1 factors into the null magnitude") { - CHECK(std::is_same_v, magnitude<>>); + CHECK(prime_factorization_v<1> == magnitude<>{}); } SECTION ("Prime numbers factor into themselves") { - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); - CHECK(std::is_same_v, magnitude>>); + CHECK(prime_factorization_v<2> == magnitude{}); + CHECK(prime_factorization_v<3> == magnitude{}); + CHECK(prime_factorization_v<5> == magnitude{}); + CHECK(prime_factorization_v<7> == magnitude{}); + CHECK(prime_factorization_v<11> == magnitude{}); - CHECK(std::is_same_v, magnitude>>); + CHECK(prime_factorization_v<41> == magnitude{}); } SECTION("Prime factorization finds factors and multiplicities") { - CHECK(std::is_same_v< - prime_factorization_t<792>, - magnitude, int_base_power<3, 2>, int_base_power<11>>>); + CHECK(prime_factorization_v<792> == + magnitude{}); } } From 3063aebe5a73f71b6d01ecb582633494b1f0f22c Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 14:21:13 -0500 Subject: [PATCH 19/42] Clean up pairwise_all() and strictly_increasing() --- src/core/include/units/magnitude.h | 34 +++++++++++------- test/unit_test/runtime/magnitude_test.cpp | 42 ++++++++++------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 9e73fd20..09343c86 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -176,23 +176,31 @@ constexpr auto pi_to_the() { return magnitude()>{}; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude concept implementation. -template -constexpr bool pairwise_all(const std::tuple &ts, const Predicate &pred) { - // 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)); +template +struct pairwise_all { + Predicate predicate; - // Compare zero or more pairs of neighbours as needed. - return [&ts, &pred](std::integer_sequence) { - return (pred(std::get(ts), std::get(ts)) && ...); - }(std::make_index_sequence()); -} + template + 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 (predicate(std::get(t), std::get(t)) && ...); + }(std::make_tuple(std::forward(ts)...), std::make_index_sequence()); + } +}; + +template +pairwise_all(T) -> pairwise_all; // Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. template -constexpr bool strictly_increasing(const std::tuple &ts) { - return pairwise_all(ts, std::less{}); +constexpr bool strictly_increasing(Ts&&... ts) { + return pairwise_all{std::less{}}(std::forward(ts)...); } namespace detail diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 9cd54835..ff66d952 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -30,23 +30,23 @@ namespace units::mag TEST_CASE("strictly_increasing") { - SECTION ("Empty tuple is sorted") + SECTION ("Empty input is sorted") { - CHECK(strictly_increasing(std::make_tuple())); + CHECK(strictly_increasing()); } - SECTION ("Single-element tuple is sorted") + SECTION ("Single-element input is sorted") { - CHECK(strictly_increasing(std::make_tuple(3))); - CHECK(strictly_increasing(std::make_tuple(15.42))); - CHECK(strictly_increasing(std::make_tuple('c'))); + CHECK(strictly_increasing(3)); + CHECK(strictly_increasing(15.42)); + CHECK(strictly_increasing('c')); } - SECTION ("Multi-element tuples compare correctly") + SECTION ("Multi-value inputs compare correctly") { - CHECK(strictly_increasing(std::make_tuple(3, 3.14))); - CHECK(!strictly_increasing(std::make_tuple(3, 3.0))); - CHECK(!strictly_increasing(std::make_tuple(4, 3.0))); + CHECK(strictly_increasing(3, 3.14)); + CHECK(!strictly_increasing(3, 3.0)); + CHECK(!strictly_increasing(4, 3.0)); } } @@ -94,9 +94,6 @@ TEST_CASE("strictly_increasing") // } // } -template -using double_v = 2 * x; - TEST_CASE("Multiplication works for magnitudes") { SECTION("Reciprocals reduce to null magnitude") @@ -107,7 +104,6 @@ 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") @@ -205,23 +201,23 @@ TEST_CASE("pairwise_all evaluates all pairs") { SECTION("always true for empty tuples") { - CHECK(pairwise_all(std::make_tuple(), [](auto, auto){ return true; })); - CHECK(pairwise_all(std::make_tuple(), [](auto, auto){ return false; })); + CHECK(pairwise_all{[](auto, auto){ return true; }}()); + CHECK(pairwise_all{[](auto, auto){ return false; }}()); } SECTION("always true for single-element tuples") { - CHECK(pairwise_all(std::make_tuple(1), [](auto, auto){ return true; })); - CHECK(pairwise_all(std::make_tuple(3.14), [](auto, auto){ return false; })); - CHECK(pairwise_all(std::make_tuple('x'), [](auto, auto){ return true; })); + CHECK(pairwise_all{[](auto, auto){ return true; }}(1)); + CHECK(pairwise_all{[](auto, auto){ return false; }}(3.14)); + CHECK(pairwise_all{[](auto, auto){ return true; }}('x')); } SECTION("true for longer tuples iff true for all neighbouring pairs") { - CHECK(pairwise_all(std::make_tuple(1, 1.5), std::less{})); - CHECK(pairwise_all(std::make_tuple(1, 1.5, 2), std::less{})); - CHECK(!pairwise_all(std::make_tuple(1, 2.0, 2), std::less{})); - CHECK(!pairwise_all(std::make_tuple(1, 2.5, 2), std::less{})); + CHECK(pairwise_all{std::less{}}(1, 1.5)); + CHECK(pairwise_all{std::less{}}(1, 1.5, 2)); + CHECK(!pairwise_all{std::less{}}(1, 2.0, 2)); + CHECK(!pairwise_all{std::less{}}(1, 2.5, 2)); } } From 9d48e6983a2ba62b5666ffd203a6581bdf25c82c Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 14:40:28 -0500 Subject: [PATCH 20/42] Refactor BasePower struct and concept Instead of templating on `long double`, we template on "anything whose value member is `long double`". This is how we work around gcc-10's lack of support for floating point NTTPs. To access the base value, we provide `.get_base()` member functions. The clearest and most direct way I know to express "is this a base power" is via a type trait, which is true only for `base_power`. --- src/core/include/units/magnitude.h | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 09343c86..4e35a4fb 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -44,7 +44,7 @@ constexpr bool is_prime(std::intmax_t n); // Integer rep is for prime numbers; long double is for any irrational base we permit. template -concept BaseRep = std::is_same_v || std::is_same_v; +concept BaseRep = std::is_same_v || std::is_same_v; /** * @brief A basis vector in our magnitude representation, raised to some rational power. @@ -60,11 +60,21 @@ concept BaseRep = std::is_same_v || std::is_same_v; */ template struct base_power { - // The value of the basis vector. - T base; + // The rational power to which the base is raised. + ratio power{1}; + + constexpr long double get_base() const { return T::value; } +}; + +template<> +struct base_power { + // The value of the basis "vector". + int base; // The rational power to which the base is raised. ratio power{1}; + + constexpr int get_base() const { return base; } }; template U> @@ -94,10 +104,15 @@ constexpr bool is_valid_base_power(const base_power &bp) { else if constexpr (std::is_same_v) { return bp.base > 0; } else { return false; } // Unreachable. } + +template +struct is_base_power : std::false_type {}; +template +struct is_base_power> : std::true_type {}; } // namespace detail template -concept BasePower = std::is_same_v> || std::is_same_v>; +concept BasePower = detail::is_base_power::value; /** * @brief A representation for positive real numbers which optimizes taking products and rational powers. @@ -107,7 +122,7 @@ concept BasePower = std::is_same_v> || std::is_same_v requires requires { - // (is_valid_base_power(BasePowers) && ... && strictly_increasing(std::make_tuple(BasePowers.base...))); + // (is_valid_base_power(BasePowers) && ... && strictly_increasing(BasePowers.base...)); (detail::is_valid_base_power(BasePowers) && ...); } struct magnitude {}; From 838b132a61f02ccf35d3bfe16a1ba6d1c894013f Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 15:08:05 -0500 Subject: [PATCH 21/42] Finish migrating base_power to NTTP --- src/core/include/units/magnitude.h | 48 +++++++----- test/unit_test/runtime/magnitude_test.cpp | 93 +++++++++++------------ 2 files changed, 74 insertions(+), 67 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 4e35a4fb..a2d33c08 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -44,7 +44,7 @@ constexpr bool is_prime(std::intmax_t n); // Integer rep is for prime numbers; long double is for any irrational base we permit. template -concept BaseRep = std::is_same_v || std::is_same_v; +concept BaseRep = std::is_same_v || std::is_same_v, long double>; /** * @brief A basis vector in our magnitude representation, raised to some rational power. @@ -85,7 +85,7 @@ base_power(T) -> base_power; template constexpr bool operator==(base_power t, base_power u) { - return std::is_same_v && (t.base == u.base) && (t.power == u.power); + return std::is_same_v && (t.get_base() == u.get_base()) && (t.power == u.power); } template @@ -100,8 +100,8 @@ template constexpr bool is_valid_base_power(const base_power &bp) { if (bp.power == 0) { return false; } - if constexpr (std::is_same_v) { return is_prime(bp.base); } - else if constexpr (std::is_same_v) { return bp.base > 0; } + if constexpr (std::is_same_v) { return is_prime(bp.get_base()); } + else if constexpr (std::is_same_v) { return bp.get_base() > 0; } else { return false; } // Unreachable. } @@ -114,24 +114,29 @@ struct is_base_power> : std::true_type {}; template concept BasePower = detail::is_base_power::value; +template +constexpr bool strictly_increasing(Ts&&... ts); + /** * @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 +template requires requires { - // (is_valid_base_power(BasePowers) && ... && strictly_increasing(BasePowers.base...)); - (detail::is_valid_base_power(BasePowers) && ...); + (detail::is_valid_base_power(BPs) && ... && strictly_increasing(BPs.get_base()...)); } struct magnitude {}; template -constexpr bool operator==(magnitude, magnitude) { return ((LeftBPs == RightBPs) && ...); } +constexpr bool operator==(magnitude, magnitude) { + if constexpr (sizeof...(LeftBPs) == sizeof...(RightBPs)) { return ((LeftBPs == RightBPs) && ...); } + else { return false; } +} -template -constexpr auto inverse(magnitude) { return magnitude{}; } +template +constexpr auto inverse(magnitude) { return magnitude{}; } constexpr auto operator*(magnitude<>, magnitude<>) { return magnitude<>{}; } @@ -144,20 +149,25 @@ constexpr auto operator*(magnitude m, magnitude<>) { return m; } template constexpr auto operator*(magnitude, magnitude) { // 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{}; } + if constexpr ((sizeof...(T1) == 0) && H1.get_base() < H2.get_base()) { return magnitude{}; } - if constexpr (H1.base == H2.base) { + if constexpr (H1.get_base() == H2.get_base()) { constexpr auto partial_product = magnitude{} * magnitude{}; - constexpr base_power new_head{H1.base, (H1.power + H2.power)}; + + // 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{} * partial_product; } - } else if constexpr(H1.base < H2.base){ + } else if constexpr(H1.get_base() < H2.get_base()){ return magnitude

{} * (magnitude{} * magnitude{}); - } else { // We know H2.base < H1.base + } else { // We know H2.get_base() < H1.get_base() return magnitude

{} * (magnitude{} * magnitude{}); } } @@ -177,12 +187,12 @@ constexpr auto make_ratio() { return detail::prime_factorization_v / detail:: /** * @brief A base to represent pi. */ -template - requires requires { Power != 0; } -constexpr auto pi_power() { return base_power{std::numbers::pi_v, Power}; } +struct pi_base { + static constexpr long double value = std::numbers::pi_v; +}; template -constexpr auto pi_to_the() { return magnitude()>{}; } +constexpr auto pi_to_the() { return magnitude{Power}>{}; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation details below. diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index ff66d952..a4a5e74e 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -50,49 +50,46 @@ 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()), magnitude<>>); -// CHECK(std::is_same_v()), magnitude>); -// CHECK(std::is_same_v()), magnitude>); -// CHECK(std::is_same_v()), magnitude>); -// -// CHECK(std::is_same_v< -// decltype(make_ratio<792>()), -// magnitude>); -// } -// -// SECTION("Reduces fractions to lowest terms") -// { -// CHECK(std::is_same_v()), magnitude<>>); -// CHECK(std::is_same_v< -// decltype(make_ratio<50, 80>()), magnitude>); -// } -// } +TEST_CASE("make_ratio performs prime factorization correctly") +{ + SECTION("Performs prime factorization when denominator is 1") + { + CHECK(make_ratio<1>() == magnitude<>{}); + CHECK(make_ratio<2>() == magnitude{}); + CHECK(make_ratio<3>() == magnitude{}); + CHECK(make_ratio<4>() == magnitude{}); -// 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); -// } -// } + CHECK(make_ratio<792>() == magnitude{}); + } + + SECTION("Reduces fractions to lowest terms") + { + CHECK(make_ratio<8, 8>() == magnitude<>{}); + CHECK(make_ratio<50, 80>() == magnitude{}); + } +} + +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); + } +} TEST_CASE("Multiplication works for magnitudes") { @@ -106,12 +103,12 @@ TEST_CASE("Multiplication works for magnitudes") CHECK(make_ratio<4, 5>() * make_ratio<4, 3>() == make_ratio<16, 15>()); } - //SECTION("Products handle pi correctly") - //{ - // CHECK( - // pi_to_the<1>() * make_ratio<2, 3>() * pi_to_the() == - // magnitude()>{}); - //} + SECTION("Products handle pi correctly") + { + CHECK( + pi_to_the<1>() * make_ratio<2, 3>() * pi_to_the() == + magnitude{ratio{1, 2}}>{}); + } SECTION("Supports constexpr") { From ef6f9374602bb8202f52273d45ef2b76f38991d6 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 17:18:33 -0500 Subject: [PATCH 22/42] Clean up and reorder file --- src/core/include/units/magnitude.h | 342 +++++++++++++--------- test/unit_test/runtime/magnitude_test.cpp | 44 +-- 2 files changed, 232 insertions(+), 154 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index a2d33c08..f757f020 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -26,37 +26,49 @@ #include #include -namespace units::mag -{ +namespace units::mag { -namespace detail -{ -// Helpers to perform prime factorization at compile time. -template - requires requires { N > 0; } -struct prime_factorization; -template -static constexpr auto prime_factorization_v = prime_factorization::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. +/** + * @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 concept BaseRep = std::is_same_v || std::is_same_v, long double>; /** * @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. + * 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 + * _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 struct base_power { @@ -66,9 +78,12 @@ struct base_power { constexpr long double get_base() const { return T::value; } }; +/** + * @brief Specialization for prime number bases. + */ template<> struct base_power { - // The value of the basis "vector". + // 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. @@ -77,103 +92,88 @@ struct base_power { constexpr int get_base() const { return base; } }; -template U> -base_power(T, U) -> base_power; - -template -base_power(T) -> base_power; - -template -constexpr bool operator==(base_power t, base_power u) { - return std::is_same_v && (t.get_base() == u.get_base()) && (t.power == u.power); -} - -template -constexpr auto inverse(base_power bp) { - bp.power = -bp.power; - return bp; -} - -namespace detail -{ -template -constexpr bool is_valid_base_power(const base_power &bp) { - if (bp.power == 0) { return false; } - - if constexpr (std::is_same_v) { return is_prime(bp.get_base()); } - else if constexpr (std::is_same_v) { return bp.get_base() > 0; } - else { return false; } // Unreachable. -} +/** + * @brief Deduction guides for base_power: only permit deducing integral bases. + */ +template U> +base_power(T, U) -> base_power; +template +base_power(T) -> base_power; +// Implementation for BasePower concept (below). +namespace detail { template struct is_base_power : std::false_type {}; template struct is_base_power> : std::true_type {}; } // 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 concept BasePower = detail::is_base_power::value; +/** + * @brief Equality detection for two base powers. + */ +template +constexpr bool operator==(T t, U u) { + return std::is_same_v && (t.get_base() == u.get_base()) && (t.power == u.power); +} + +/** + * @brief The (multiplicative) inverse of a BasePower. + */ +template +constexpr auto inverse(BP bp) { + bp.power = -bp.power; + return bp; +} + +// Implementation helpers for `magnitude<...>` constraints (below). +namespace detail { +constexpr bool is_valid_base_power(const BasePower auto &bp); + template constexpr bool strictly_increasing(Ts&&... ts); +} // 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, and compare - * for equality using this value API. + * for equality. */ template requires requires { - (detail::is_valid_base_power(BPs) && ... && strictly_increasing(BPs.get_base()...)); + (detail::is_valid_base_power(BPs) && ... && detail::strictly_increasing(BPs.get_base()...)); } struct magnitude {}; -template -constexpr bool operator==(magnitude, magnitude) { - if constexpr (sizeof...(LeftBPs) == sizeof...(RightBPs)) { return ((LeftBPs == RightBPs) && ...); } - else { return false; } -} - +// Implementation for Magnitude concept (below). +namespace detail { +template +struct is_magnitude : std::false_type {}; template -constexpr auto inverse(magnitude) { return magnitude{}; } +struct is_magnitude> : std::true_type {}; +} // namespace detail -constexpr auto operator*(magnitude<>, magnitude<>) { return magnitude<>{}; } +/** + * @brief Concept to detect whether T is a valid Magnitude. + */ +template +concept Magnitude = detail::is_magnitude::value; -template -constexpr auto operator*(magnitude<>, magnitude m) { return m; } - -template -constexpr auto operator*(magnitude m, magnitude<>) { return m; } - -template -constexpr auto operator*(magnitude, magnitude) { - // Shortcut for prepending, which makes it easier to implement some of the other cases. - if constexpr ((sizeof...(T1) == 0) && H1.get_base() < H2.get_base()) { return magnitude{}; } - - if constexpr (H1.get_base() == H2.get_base()) { - constexpr auto partial_product = magnitude{} * magnitude{}; - - // 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{} * partial_product; - } - } else if constexpr(H1.get_base() < H2.get_base()){ - return magnitude

{} * (magnitude{} * magnitude{}); - } else { // We know H2.get_base() < H1.get_base() - return magnitude

{} * (magnitude{} * magnitude{}); - } -} - -template -constexpr auto operator/(magnitude l, magnitude r) { return l * inverse(r); } +/** + * @brief Convert any positive integer to a Magnitude. + */ +template + requires requires { N > 0; } +constexpr Magnitude auto as_magnitude(); /** * @brief Make a Magnitude that is a rational number. @@ -182,7 +182,7 @@ constexpr auto operator/(magnitude l, magnitude r) { re * manually adding base powers. */ template -constexpr auto make_ratio() { return detail::prime_factorization_v / detail::prime_factorization_v; } +constexpr auto make_ratio() { return as_magnitude() / as_magnitude(); } /** * @brief A base to represent pi. @@ -191,6 +191,9 @@ struct pi_base { static constexpr long double value = std::numbers::pi_v; }; +/** + * @brief A simple way to create a Magnitude representing a rational power of pi. + */ template constexpr auto pi_to_the() { return magnitude{Power}>{}; } @@ -198,42 +201,9 @@ constexpr auto pi_to_the() { return magnitude{Power}>{}; } // Implementation details below. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Magnitude concept implementation. - -template -struct pairwise_all { - Predicate predicate; - - template - 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 (predicate(std::get(t), std::get(t)) && ...); - }(std::make_tuple(std::forward(ts)...), std::make_index_sequence()); - } -}; - -template -pairwise_all(T) -> pairwise_all; - -// Check whether a tuple of (possibly heterogeneously typed) values are strictly increasing. -template -constexpr bool strictly_increasing(Ts&&... ts) { - return pairwise_all{std::less{}}(std::forward(ts)...); -} - namespace detail { -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Prime factorization implementation. - // Find the smallest prime factor of `n`. constexpr std::intmax_t find_first_factor(std::intmax_t n) { @@ -265,11 +235,7 @@ constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std: return n; } -// Specialization for the prime factorization of 1 (base case). -template<> -struct prime_factorization<1> { static constexpr magnitude<> value{}; }; - -// Specialization for the prime factorization of larger numbers (recursive case). +// Helpers to perform prime factorization at compile time. template requires requires { N > 0; } struct prime_factorization { @@ -278,10 +244,122 @@ struct prime_factorization { static constexpr std::intmax_t remainder = remove_power(first_base, first_power, N); static constexpr auto value = magnitude{} - * prime_factorization_v; + * prime_factorization::value; }; +template +static constexpr auto prime_factorization_v = prime_factorization::value; + +// Specialization for the prime factorization of 1 (base case). +template<> +struct prime_factorization<1> { static constexpr magnitude<> value{}; }; + +// 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) { 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 +struct pairwise_all { + Predicate predicate; + + template + 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 (predicate(std::get(t), std::get(t)) && ...); + }(std::make_tuple(std::forward(ts)...), std::make_index_sequence()); + } +}; + +// Deduction guide: permit constructions such as `pairwise_all{std::less{}}`. +template +pairwise_all(T) -> pairwise_all; + +// Check whether a sequence of (possibly heterogeneously typed) values are strictly increasing. +template +constexpr bool strictly_increasing(Ts&&... ts) { + return pairwise_all{std::less{}}(std::forward(ts)...); +} + } // namespace detail + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Magnitude equality implementation. + +template +constexpr bool operator==(magnitude, magnitude) { + if constexpr (sizeof...(LeftBPs) == sizeof...(RightBPs)) { return ((LeftBPs == RightBPs) && ...); } + else { return false; } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Magnitude inverse implementation. + +template +constexpr auto inverse(magnitude) { return magnitude{}; } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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 +constexpr auto operator*(magnitude, magnitude) { + // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. + if constexpr ((sizeof...(T1) == 0) && H1.get_base() < H2.get_base()) { return magnitude{}; } + + // "Same leading base" case. + if constexpr (H1.get_base() == H2.get_base()) { + constexpr auto partial_product = magnitude{} * magnitude{}; + + // 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{} * partial_product; + } + } + // Case for when H1 has the smaller base. + else if constexpr(H1.get_base() < H2.get_base()){ + return magnitude

{} * (magnitude{} * magnitude{}); + } + // Case for when H2 has the smaller base. + else { + return magnitude

{} * (magnitude{} * magnitude{}); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Magnitude quotient implementation. + +constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * inverse(r); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// `as_magnitude()` implementation. + +template + requires requires { N > 0; } +constexpr Magnitude auto as_magnitude() { return detail::prime_factorization_v; } + } // namespace units::mag diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index a4a5e74e..cf6f5ecd 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,28 +28,6 @@ namespace units::mag { -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)); - } -} - TEST_CASE("make_ratio performs prime factorization correctly") { SECTION("Performs prime factorization when denominator is 1") @@ -218,6 +196,28 @@ TEST_CASE("pairwise_all evaluates all pairs") } } +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::mag From 0a470f2617984a7997f00c5388cafb6d7765a77f Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 17:29:40 -0500 Subject: [PATCH 23/42] Replace make_ratio with as_magnitude It's cleaner to just have the one way. --- src/core/include/units/magnitude.h | 20 ++++------ test/unit_test/runtime/magnitude_test.cpp | 47 +++++++++++------------ 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index f757f020..a46c336e 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -170,19 +170,13 @@ concept Magnitude = detail::is_magnitude::value; /** * @brief Convert any positive integer to a Magnitude. - */ -template - requires requires { N > 0; } -constexpr Magnitude auto as_magnitude(); - -/** - * @brief Make a Magnitude that is a rational number. * * This will be the main way end users create Magnitudes. They should rarely (if ever) create a magnitude<...> by * manually adding base powers. */ -template -constexpr auto make_ratio() { return as_magnitude() / as_magnitude(); } +template + requires requires { R.num > 0; } +constexpr Magnitude auto as_magnitude(); /** * @brief A base to represent pi. @@ -358,8 +352,10 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * invers //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // `as_magnitude()` implementation. -template - requires requires { N > 0; } -constexpr Magnitude auto as_magnitude() { return detail::prime_factorization_v; } +template + requires requires { R.num > 0; } +constexpr Magnitude auto as_magnitude() { + return detail::prime_factorization_v / detail::prime_factorization_v; +} } // namespace units::mag diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index cf6f5ecd..edf0dbb1 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -32,18 +32,17 @@ TEST_CASE("make_ratio performs prime factorization correctly") { SECTION("Performs prime factorization when denominator is 1") { - CHECK(make_ratio<1>() == magnitude<>{}); - CHECK(make_ratio<2>() == magnitude{}); - CHECK(make_ratio<3>() == magnitude{}); - CHECK(make_ratio<4>() == magnitude{}); + CHECK(as_magnitude<1>() == magnitude<>{}); + CHECK(as_magnitude<2>() == magnitude{}); + CHECK(as_magnitude<3>() == magnitude{}); + CHECK(as_magnitude<4>() == magnitude{}); - CHECK(make_ratio<792>() == magnitude{}); + CHECK(as_magnitude<792>() == magnitude{}); } - SECTION("Reduces fractions to lowest terms") + SECTION("Supports fractions") { - CHECK(make_ratio<8, 8>() == magnitude<>{}); - CHECK(make_ratio<50, 80>() == magnitude{}); + CHECK(as_magnitude() == magnitude{}); } } @@ -51,20 +50,20 @@ 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>()); + CHECK(as_magnitude<1>() == as_magnitude<1>()); + CHECK(as_magnitude<3>() == as_magnitude<3>()); + CHECK(as_magnitude() == as_magnitude()); } SECTION("Different ratios are unequal") { - CHECK(make_ratio<3>() != make_ratio<5>()); - CHECK(make_ratio<3>() != make_ratio<3, 2>()); + CHECK(as_magnitude<3>() != as_magnitude<5>()); + CHECK(as_magnitude<3>() != as_magnitude()); } SECTION("Supports constexpr") { - constexpr auto eq = (make_ratio<4, 5>() == make_ratio<4, 3>()); + constexpr auto eq = (as_magnitude() == as_magnitude()); CHECK(!eq); } } @@ -73,25 +72,25 @@ TEST_CASE("Multiplication works for magnitudes") { SECTION("Reciprocals reduce to null magnitude") { - CHECK(make_ratio<3, 4>() * make_ratio<4, 3>() == make_ratio<1>()); + CHECK(as_magnitude() * as_magnitude() == as_magnitude<1>()); } SECTION("Products work as expected") { - CHECK(make_ratio<4, 5>() * make_ratio<4, 3>() == make_ratio<16, 15>()); + CHECK(as_magnitude() * as_magnitude() == as_magnitude()); } SECTION("Products handle pi correctly") { CHECK( - pi_to_the<1>() * make_ratio<2, 3>() * pi_to_the() == + pi_to_the<1>() * as_magnitude() * pi_to_the() == magnitude{ratio{1, 2}}>{}); } SECTION("Supports constexpr") { - constexpr auto p = make_ratio<4, 5>() * make_ratio<4, 3>(); - CHECK(p == make_ratio<16, 15>()); + constexpr auto p = as_magnitude() * as_magnitude(); + CHECK(p == as_magnitude()); } } @@ -99,19 +98,19 @@ TEST_CASE("Division works for magnitudes") { SECTION("Dividing anything by itself reduces to null magnitude") { - CHECK(make_ratio<3, 4>() / make_ratio<3, 4>() == make_ratio<1>()); - CHECK(make_ratio<15>() / make_ratio<15>() == make_ratio<1>()); + CHECK(as_magnitude() / as_magnitude() == as_magnitude<1>()); + CHECK(as_magnitude<15>() / as_magnitude<15>() == as_magnitude<1>()); } SECTION("Quotients work as expected") { - CHECK(make_ratio<4, 5>() / make_ratio<4, 3>() == make_ratio<3, 5>()); + CHECK(as_magnitude() / as_magnitude() == as_magnitude()); } SECTION("Supports constexpr") { - constexpr auto q = make_ratio<4, 5>() / make_ratio<4, 3>(); - CHECK(q == make_ratio<3, 5>()); + constexpr auto q = as_magnitude() / as_magnitude(); + CHECK(q == as_magnitude()); } } From f12fde6204297a4f499146e0d5ca492bc0efe0c0 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 17:57:10 -0500 Subject: [PATCH 24/42] Add some tests --- test/unit_test/runtime/magnitude_test.cpp | 65 +++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index edf0dbb1..0d0389d7 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -28,6 +28,71 @@ namespace units::mag { +// 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; }; + +TEST_CASE("base_power") +{ + 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") + { + 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{}.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") + { + const auto a = base_power{}; + const auto b = base_power{2}; + const auto c = base_power{}; + + 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{} != base_power{ratio{1, 3}}); + } + + SECTION("product with inverse equals identity") + { + auto check_product_with_inverse_is_identity = [] (auto x) { + CHECK(x * inverse(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()); + } +} + TEST_CASE("make_ratio performs prime factorization correctly") { SECTION("Performs prime factorization when denominator is 1") From 41995464f01652da3a9efe88565f92396e055d64 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 18:13:48 -0500 Subject: [PATCH 25/42] Fix magnitude constraints I got my requires expressions/clauses mixed up. Unfortunately, this means we can't just shove all the "implementation details" down to the bottom of the file. Oh well. --- src/core/include/units/magnitude.h | 142 ++++++++++++++--------------- 1 file changed, 68 insertions(+), 74 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index a46c336e..4eb640fb 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -134,69 +134,8 @@ constexpr auto inverse(BP bp) { return bp; } -// Implementation helpers for `magnitude<...>` constraints (below). +// A variety of implementation detail helpers. namespace detail { -constexpr bool is_valid_base_power(const BasePower auto &bp); - -template -constexpr bool strictly_increasing(Ts&&... ts); -} // 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, and compare - * for equality. - */ -template - requires requires { - (detail::is_valid_base_power(BPs) && ... && detail::strictly_increasing(BPs.get_base()...)); - } -struct magnitude {}; - -// Implementation for Magnitude concept (below). -namespace detail { -template -struct is_magnitude : std::false_type {}; -template -struct is_magnitude> : std::true_type {}; -} // namespace detail - -/** - * @brief Concept to detect whether T is a valid Magnitude. - */ -template -concept Magnitude = detail::is_magnitude::value; - -/** - * @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 - requires 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; -}; - -/** - * @brief A simple way to create a Magnitude representing a rational power of pi. - */ -template -constexpr auto pi_to_the() { return magnitude{Power}>{}; } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Implementation details below. -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -namespace detail -{ // Find the smallest prime factor of `n`. constexpr std::intmax_t find_first_factor(std::intmax_t n) @@ -232,22 +171,11 @@ constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std: // Helpers to perform prime factorization at compile time. template requires 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{} - * prime_factorization::value; -}; +struct prime_factorization; template static constexpr auto prime_factorization_v = prime_factorization::value; -// Specialization for the prime factorization of 1 (base case). -template<> -struct prime_factorization<1> { static constexpr magnitude<> value{}; }; - // 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); } @@ -289,6 +217,53 @@ constexpr bool strictly_increasing(Ts&&... ts) { } // 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, and compare + * for equality. + */ +template + requires ((detail::is_valid_base_power(BPs) && ... && detail::strictly_increasing(BPs.get_base()...))) +struct magnitude {}; + +// Implementation for Magnitude concept (below). +namespace detail { +template +struct is_magnitude : std::false_type {}; +template +struct is_magnitude> : std::true_type {}; +} // namespace detail + +/** + * @brief Concept to detect whether T is a valid Magnitude. + */ +template +concept Magnitude = detail::is_magnitude::value; + +/** + * @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 + requires 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; +}; + +/** + * @brief A simple way to create a Magnitude representing a rational power of pi. + */ +template +constexpr auto pi_to_the() { return magnitude{Power}>{}; } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude equality implementation. @@ -358,4 +333,23 @@ constexpr Magnitude auto as_magnitude() { return detail::prime_factorization_v / detail::prime_factorization_v; } +namespace detail +{ +// Default implementation. +template + requires 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{} + * prime_factorization::value; +}; + +// Specialization for the prime factorization of 1 (base case). +template<> +struct prime_factorization<1> { static constexpr magnitude<> value{}; }; +} // namespace detail + } // namespace units::mag From 325cbc8fa9dc0e1a6c66f595795927de2a5561ea Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 18:14:22 -0500 Subject: [PATCH 26/42] Finish adding tests --- test/unit_test/runtime/magnitude_test.cpp | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 0d0389d7..0750a4b5 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -32,6 +32,8 @@ namespace units::mag 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; }; TEST_CASE("base_power") { @@ -182,6 +184,30 @@ TEST_CASE("Division works for magnitudes") 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") @@ -236,6 +262,36 @@ TEST_CASE("is_prime detects primes") } } +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{})); + CHECK(!is_valid_base_power(base_power{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{})); + CHECK(!is_valid_base_power(base_power{})); + } +} + TEST_CASE("pairwise_all evaluates all pairs") { SECTION("always true for empty tuples") From ba458d479e16ea87631722c19966432139ef7670 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 18:19:40 -0500 Subject: [PATCH 27/42] Fix remaining constraints --- src/core/include/units/magnitude.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 4eb640fb..4e622e27 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -170,7 +170,7 @@ constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std: // Helpers to perform prime factorization at compile time. template - requires requires { N > 0; } + requires (N > 0) struct prime_factorization; template @@ -248,7 +248,7 @@ concept Magnitude = detail::is_magnitude::value; * manually adding base powers. */ template - requires requires { R.num > 0; } + requires (R.num > 0) constexpr Magnitude auto as_magnitude(); /** @@ -328,7 +328,7 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * invers // `as_magnitude()` implementation. template - requires requires { R.num > 0; } + requires (R.num > 0) constexpr Magnitude auto as_magnitude() { return detail::prime_factorization_v / detail::prime_factorization_v; } @@ -337,7 +337,7 @@ namespace detail { // Default implementation. template - requires requires { N > 0; } + 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); From 053de8d539c00a6d2787936bb7ed5b6c7d02063d Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 18:34:14 -0500 Subject: [PATCH 28/42] Replace structs with variables --- src/core/include/units/magnitude.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 4e622e27..f50ca775 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -103,9 +103,9 @@ base_power(T) -> base_power; // Implementation for BasePower concept (below). namespace detail { template -struct is_base_power : std::false_type {}; +static constexpr bool is_base_power = false; template -struct is_base_power> : std::true_type {}; +static constexpr bool is_base_power> = true; } // namespace detail /** @@ -115,7 +115,7 @@ struct is_base_power> : std::true_type {}; * `magnitude<...>`. We will defer that second check to the constraints on the `magnitude` template. */ template -concept BasePower = detail::is_base_power::value; +concept BasePower = detail::is_base_power; /** * @brief Equality detection for two base powers. @@ -230,16 +230,16 @@ struct magnitude {}; // Implementation for Magnitude concept (below). namespace detail { template -struct is_magnitude : std::false_type {}; +static constexpr bool is_magnitude = false; template -struct is_magnitude> : std::true_type {}; +static constexpr bool is_magnitude> = true; } // namespace detail /** * @brief Concept to detect whether T is a valid Magnitude. */ template -concept Magnitude = detail::is_magnitude::value; +concept Magnitude = detail::is_magnitude; /** * @brief Convert any positive integer to a Magnitude. From 409aaf463665f8f3945fef456e9dac2db2861732 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 19:34:21 -0500 Subject: [PATCH 29/42] Generalize inverse() to pow<...>() --- src/core/include/units/magnitude.h | 23 +++++++++++++---------- test/unit_test/runtime/magnitude_test.cpp | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index f50ca775..c4964337 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -126,11 +126,11 @@ constexpr bool operator==(T t, U u) { } /** - * @brief The (multiplicative) inverse of a BasePower. + * @brief A BasePower, raised to a rational power E. */ -template -constexpr auto inverse(BP bp) { - bp.power = -bp.power; +template +constexpr auto pow(BasePower auto bp) { + bp.power = bp.power * E; return bp; } @@ -220,8 +220,8 @@ constexpr bool strictly_increasing(Ts&&... ts) { /** * @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. + * 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 requires ((detail::is_valid_base_power(BPs) && ... && detail::strictly_increasing(BPs.get_base()...))) @@ -274,10 +274,13 @@ constexpr bool operator==(magnitude, magnitude) { } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Magnitude inverse implementation. +// Magnitude rational powers implementation. -template -constexpr auto inverse(magnitude) { return magnitude{}; } +template +constexpr auto pow(magnitude) { + if constexpr (E == 0) { return magnitude<>{}; } + else { return magnitude(BPs)...>{}; } +} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude product implementation. @@ -322,7 +325,7 @@ constexpr auto operator*(magnitude, magnitude) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude quotient implementation. -constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * inverse(r); } +constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // `as_magnitude()` implementation. diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 0750a4b5..b32d2928 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -181,6 +181,23 @@ TEST_CASE("Division works for magnitudes") } } +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()) == as_magnitude<1>()); + CHECK(pow<0>(pi_to_the()) == 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()) == as_magnitude()); + CHECK(pow<1>(pi_to_the()) == pi_to_the()); + } +} + namespace detail { From f783d7f274bf6fcbe3978a8414423cf1cb82ee04 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Sat, 8 Jan 2022 19:34:42 -0500 Subject: [PATCH 30/42] Handle `exp` explicitly in as_magnitude() The new test actually passed without modifying the code. However, that might be implementation-dependent (presumably based on canonicalization of the `ratio` template parameter), so I wanted a more obviously correct implementation. --- src/core/include/units/magnitude.h | 4 +++- test/unit_test/runtime/magnitude_test.cpp | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index c4964337..bc333a43 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -333,7 +333,9 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1 template requires (R.num > 0) constexpr Magnitude auto as_magnitude() { - return detail::prime_factorization_v / detail::prime_factorization_v; + return pow(detail::prime_factorization_v<10>) + * detail::prime_factorization_v + / detail::prime_factorization_v; } namespace detail diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index b32d2928..c0c8cb2e 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -86,13 +86,20 @@ TEST_CASE("base_power") SECTION("product with inverse equals identity") { auto check_product_with_inverse_is_identity = [] (auto x) { - CHECK(x * inverse(x) == as_magnitude<1>()); + 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") + { + CHECK(pow<0>(base_power{2}) == base_power{2, 0}); + CHECK(pow(base_power{2, 3}) == base_power{2, ratio{-3, 2}}); + CHECK(pow(base_power{ratio{3, 2}}) == base_power{ratio{1, 2}}); + } } TEST_CASE("make_ratio performs prime factorization correctly") @@ -111,6 +118,13 @@ TEST_CASE("make_ratio performs prime factorization correctly") { CHECK(as_magnitude() == magnitude{}); } + + SECTION("Supports nonzero exp") + { + constexpr ratio r{3, 1, 2}; + REQUIRE(r.exp == 2); + CHECK(as_magnitude() == as_magnitude<300>()); + } } TEST_CASE("Equality works for magnitudes") @@ -196,6 +210,10 @@ TEST_CASE("Can raise Magnitudes to rational powers") CHECK(pow<1>(as_magnitude()) == as_magnitude()); CHECK(pow<1>(pi_to_the()) == pi_to_the()); } + + SECTION("Can raise to arbitrary rational power") { + CHECK(pow(pi_to_the()) == pi_to_the()); + } } namespace detail From c64f392dc35f6a25f05c6be54a429409467dbbcb Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Mon, 10 Jan 2022 14:23:44 -0500 Subject: [PATCH 31/42] Remove some extra spaces --- test/unit_test/runtime/magnitude_test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index c0c8cb2e..536f8fed 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -245,12 +245,12 @@ 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{}); @@ -353,19 +353,19 @@ 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)); From 4f90302dd7f5980bc356760358e877e872d1d7e4 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Mon, 10 Jan 2022 14:27:04 -0500 Subject: [PATCH 32/42] Constrain strictly_increasing to signed values --- src/core/include/units/magnitude.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index bc333a43..6443cfbe 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -211,6 +211,7 @@ 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) { return pairwise_all{std::less{}}(std::forward(ts)...); } From cee4be2b67dc1b17995f419ad7e34d13cb68a0be Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Mon, 10 Jan 2022 14:28:02 -0500 Subject: [PATCH 33/42] Simplify namespaces and canonicalize formatting The motivation for the `mag` sub-namespace was to distinguish something like `mag::product_t<...>` from `dim::product_t<...>`, based on the idioms of Aurora Units. However, we have no need for a `product_t` type trait, since we can just use `operator*()`, so we can eliminate this sub-namespace. --- src/core/include/units/magnitude.h | 7 +++---- test/unit_test/runtime/magnitude_test.cpp | 8 +++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 6443cfbe..4ce8f137 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -26,7 +26,7 @@ #include #include -namespace units::mag { +namespace units { /** * @brief Any type which can be used as a basis vector in a BasePower. @@ -339,8 +339,7 @@ constexpr Magnitude auto as_magnitude() { / detail::prime_factorization_v; } -namespace detail -{ +namespace detail { // Default implementation. template requires (N > 0) @@ -358,4 +357,4 @@ template<> struct prime_factorization<1> { static constexpr magnitude<> value{}; }; } // namespace detail -} // namespace units::mag +} // namespace units diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index 536f8fed..df1afee7 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -25,8 +25,7 @@ #include #include -namespace units::mag -{ +namespace units { // A set of non-standard bases for testing purposes. struct noninteger_base { static constexpr long double value = 1.234L; }; @@ -216,8 +215,7 @@ TEST_CASE("Can raise Magnitudes to rational powers") } } -namespace detail -{ +namespace detail { TEST_CASE("Prime helper functions") { @@ -375,4 +373,4 @@ TEST_CASE("strictly_increasing") } // namespace detail -} // namespace units::mag +} // namespace units From 3042135eb905c6cbf6046d5ceacb1432bd9e44b7 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 11 Jan 2022 12:56:14 -0500 Subject: [PATCH 34/42] Try fixing clang build failures --- src/core/include/units/magnitude.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 4ce8f137..8a000fca 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -331,14 +331,6 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // `as_magnitude()` implementation. -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; -} - namespace detail { // Default implementation. template @@ -357,4 +349,12 @@ template<> struct prime_factorization<1> { static constexpr magnitude<> value{}; }; } // namespace detail +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; +} + } // namespace units From a8d6c890a55f61d65f568d2e4ffeb2f34115985e Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 11 Jan 2022 19:12:27 -0500 Subject: [PATCH 35/42] Move `pi_to_the` to the test file --- src/core/include/units/magnitude.h | 6 ------ test/unit_test/runtime/magnitude_test.cpp | 3 +++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 8a000fca..7427e9dd 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -259,12 +259,6 @@ struct pi_base { static constexpr long double value = std::numbers::pi_v; }; -/** - * @brief A simple way to create a Magnitude representing a rational power of pi. - */ -template -constexpr auto pi_to_the() { return magnitude{Power}>{}; } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude equality implementation. diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index df1afee7..f37a5cee 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -34,6 +34,9 @@ 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}>{}; } + TEST_CASE("base_power") { SECTION("base rep deducible for integral base") From 08818ac6a8292dfa234b48434cd2b7b11f77c1ee Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 11 Jan 2022 19:19:25 -0500 Subject: [PATCH 36/42] Make pow()'s argument a simple (non-template) parameter --- src/core/include/units/magnitude.h | 7 +++---- test/unit_test/runtime/magnitude_test.cpp | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 7427e9dd..63b510aa 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -128,9 +128,8 @@ constexpr bool operator==(T t, U u) { /** * @brief A BasePower, raised to a rational power E. */ -template -constexpr auto pow(BasePower auto bp) { - bp.power = bp.power * E; +constexpr auto pow(BasePower auto bp, ratio p) { + bp.power = bp.power * p; return bp; } @@ -274,7 +273,7 @@ constexpr bool operator==(magnitude, magnitude) { template constexpr auto pow(magnitude) { if constexpr (E == 0) { return magnitude<>{}; } - else { return magnitude(BPs)...>{}; } + else { return magnitude{}; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index f37a5cee..f5eaf0e3 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -98,9 +98,9 @@ TEST_CASE("base_power") SECTION("pow() multiplies exponent") { - CHECK(pow<0>(base_power{2}) == base_power{2, 0}); - CHECK(pow(base_power{2, 3}) == base_power{2, ratio{-3, 2}}); - CHECK(pow(base_power{ratio{3, 2}}) == base_power{ratio{1, 2}}); + 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}}); } } From bdf488db0430fd05a71bed06d514c9966db7e1f9 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 11 Jan 2022 19:34:57 -0500 Subject: [PATCH 37/42] Remove unnecessary forward declaration --- src/core/include/units/magnitude.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 63b510aa..6becff3d 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -167,14 +167,6 @@ constexpr std::intmax_t remove_power(std::intmax_t base, std::intmax_t pow, std: return n; } -// Helpers to perform prime factorization at compile time. -template - requires (N > 0) -struct prime_factorization; - -template -static constexpr auto prime_factorization_v = prime_factorization::value; - // 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); } @@ -325,7 +317,7 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1 // `as_magnitude()` implementation. namespace detail { -// Default implementation. +// Helper to perform prime factorization at compile time. template requires (N > 0) struct prime_factorization { @@ -340,6 +332,9 @@ struct prime_factorization { // Specialization for the prime factorization of 1 (base case). template<> struct prime_factorization<1> { static constexpr magnitude<> value{}; }; + +template +static constexpr auto prime_factorization_v = prime_factorization::value; } // namespace detail template From a306472a6b1ac57a47b4df3b2909d092072bea9e Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 11 Jan 2022 19:44:10 -0500 Subject: [PATCH 38/42] Reuse callables in pairwise_all tests --- test/unit_test/runtime/magnitude_test.cpp | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/unit_test/runtime/magnitude_test.cpp b/test/unit_test/runtime/magnitude_test.cpp index f5eaf0e3..653a6ef8 100644 --- a/test/unit_test/runtime/magnitude_test.cpp +++ b/test/unit_test/runtime/magnitude_test.cpp @@ -330,25 +330,33 @@ 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_increasing = pairwise_all{std::less{}}; + SECTION("always true for empty tuples") { - CHECK(pairwise_all{[](auto, auto){ return true; }}()); - CHECK(pairwise_all{[](auto, auto){ return false; }}()); + CHECK(all_pairs_return_true()); + CHECK(all_pairs_return_false()); } SECTION("always true for single-element tuples") { - CHECK(pairwise_all{[](auto, auto){ return true; }}(1)); - CHECK(pairwise_all{[](auto, auto){ return false; }}(3.14)); - CHECK(pairwise_all{[](auto, auto){ return true; }}('x')); + 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(pairwise_all{std::less{}}(1, 1.5)); - CHECK(pairwise_all{std::less{}}(1, 1.5, 2)); - CHECK(!pairwise_all{std::less{}}(1, 2.0, 2)); - CHECK(!pairwise_all{std::less{}}(1, 2.5, 2)); + 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)); } } From d554fb28cc577ebf6373fc62b9afc9caf218f0e4 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Tue, 11 Jan 2022 20:33:48 -0500 Subject: [PATCH 39/42] Try fixing MSVC internal compiler error I'm breaking apart the requirements into simpler pieces, but I don't really know if that will help. --- src/core/include/units/magnitude.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 6becff3d..df2b5507 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -207,6 +207,14 @@ constexpr bool strictly_increasing(Ts&&... ts) { return pairwise_all{std::less{}}(std::forward(ts)...); } +template +static constexpr bool all_base_powers_valid = (is_valid_base_power(BPs) && ...); + +template +static constexpr bool all_bases_in_order = strictly_increasing(BPs.get_base()...); + +template +static constexpr bool is_base_power_pack_valid = all_base_powers_valid && all_bases_in_order; } // namespace detail /** @@ -216,7 +224,7 @@ constexpr bool strictly_increasing(Ts&&... ts) { * rational powers, and compare for equality. */ template - requires ((detail::is_valid_base_power(BPs) && ... && detail::strictly_increasing(BPs.get_base()...))) + requires (detail::is_base_power_pack_valid) struct magnitude {}; // Implementation for Magnitude concept (below). From fb602a449731fcdd6734646c5446a4f95eb78fcd Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 12 Jan 2022 09:47:40 -0500 Subject: [PATCH 40/42] Try fixing "unreachable code" warning on MSVC 14.2 This doesn't show up on any other compiler, including MSVC 14.3, so I think it's just a compiler bug. Cursory googling suggests perhaps that some older versions of MSVC have immature support for `if constexpr`. --- src/core/include/units/magnitude.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index df2b5507..c9013bd1 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -306,12 +306,14 @@ constexpr auto operator*(magnitude, magnitude) { return magnitude{} * partial_product; } } + // Case for when H1 has the smaller base. - else if constexpr(H1.get_base() < H2.get_base()){ + if constexpr(H1.get_base() < H2.get_base()){ return magnitude

{} * (magnitude{} * magnitude{}); } + // Case for when H2 has the smaller base. - else { + if constexpr(H1.get_base() > H2.get_base()){ return magnitude

{} * (magnitude{} * magnitude{}); } } From 038616c901dae4f76063b36672bd6ec85e057e42 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 12 Jan 2022 10:11:50 -0500 Subject: [PATCH 41/42] Try rearranging the order Maybe this will help me understand where the problem lies. --- src/core/include/units/magnitude.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index c9013bd1..58ae46df 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -290,6 +290,16 @@ constexpr auto operator*(magnitude, magnitude) { // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. if constexpr ((sizeof...(T1) == 0) && H1.get_base() < H2.get_base()) { return magnitude{}; } + // Case for when H1 has the smaller base. + if constexpr(H1.get_base() < H2.get_base()){ + return magnitude

{} * (magnitude{} * magnitude{}); + } + + // Case for when H2 has the smaller base. + if constexpr(H1.get_base() > H2.get_base()){ + return magnitude

{} * (magnitude{} * magnitude{}); + } + // "Same leading base" case. if constexpr (H1.get_base() == H2.get_base()) { constexpr auto partial_product = magnitude{} * magnitude{}; @@ -306,16 +316,6 @@ constexpr auto operator*(magnitude, magnitude) { return magnitude{} * partial_product; } } - - // Case for when H1 has the smaller base. - if constexpr(H1.get_base() < H2.get_base()){ - return magnitude

{} * (magnitude{} * magnitude{}); - } - - // Case for when H2 has the smaller base. - if constexpr(H1.get_base() > H2.get_base()){ - return magnitude

{} * (magnitude{} * magnitude{}); - } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 1c70b187091b5a2d149ea663a1d1700e035281f9 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Wed, 12 Jan 2022 10:47:59 -0500 Subject: [PATCH 42/42] Unify "H1 base smaller" test cases This might actually fix the error! --- src/core/include/units/magnitude.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index 58ae46df..8e9c46ca 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -287,12 +287,14 @@ 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) { - // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. - if constexpr ((sizeof...(T1) == 0) && H1.get_base() < H2.get_base()) { return magnitude{}; } - // Case for when H1 has the smaller base. if constexpr(H1.get_base() < H2.get_base()){ - return magnitude

{} * (magnitude{} * magnitude{}); + if constexpr (sizeof...(T1) == 0) { + // Shortcut for the "pure prepend" case, which makes it easier to implement some of the other cases. + return magnitude{}; + } else { + return magnitude

{} * (magnitude{} * magnitude{}); + } } // Case for when H2 has the smaller base.