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>{}); +// }