From cf9c05f0ef8094e4024b0a8dbb887971f71007d4 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 15 Sep 2024 18:23:23 +0200 Subject: [PATCH 01/34] wip --- src/core/include/mp-units/bits/fixed_point.h | 224 +++++++++++++++++++ src/core/include/mp-units/bits/sudo_cast.h | 109 +++++---- test/static/international_test.cpp | 5 + 3 files changed, 298 insertions(+), 40 deletions(-) create mode 100644 src/core/include/mp-units/bits/fixed_point.h diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h new file mode 100644 index 000000000..6a96e7a33 --- /dev/null +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -0,0 +1,224 @@ +// 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 + +// IWYU pragma: private, include +#include + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#include +#include +#endif +#endif + +namespace mp_units { +namespace detail { + +// this class synthesizes a double-width integer from two base-width integers. +template +struct double_width_int { + static constexpr bool is_signed = std::is_signed_v; + static constexpr std::size_t base_width = std::numeric_limits>::digits; + static constexpr std::size_t width = 2 * base_width; + + using Th = T; + using Tl = std::make_unsigned_t; + + constexpr double_width_int() = default; + constexpr double_width_int(const double_width_int&) = default; + + constexpr double_width_int& operator=(const double_width_int&) = default; + + constexpr double_width_int(Th hi, Tl lo) : hi(hi), lo(lo) {} + + explicit(true) constexpr double_width_int(long double v) + { + constexpr auto scale = int_power(2, base_width); + constexpr auto iscale = 1.l / scale; + hi = static_cast(v * iscale); + lo = static_cast(v - (hi * scale)); + } + template + requires(is_signed || !std::is_signed_v) + explicit(false) constexpr double_width_int(U v) + { + if constexpr (is_signed) { + hi = v < 0 ? Th{-1} : Th{0}; + } else { + hi = 0; + } + lo = static_cast(v); + } + + explicit(true) constexpr operator Th() const { return static_cast(lo); } + + constexpr auto operator<=>(const double_width_int&) const = default; + + // calculates the double-width product of two base-size integers + static constexpr double_width_int wide_product_of(Th lhs, Tl rhs) + { + static constexpr std::size_t half_width = base_width / 2; + static constexpr Tl msk = Tl(1) << half_width; + Th l1 = lhs >> half_width; + Tl l0 = static_cast(lhs) & msk; + Tl r1 = rhs >> half_width; + Tl r0 = rhs & msk; + Tl t00 = l0 * r0; + Tl t01 = l0 * r1; + Th t10 = l1 * r0; + Th t11 = l1 * r1; + Tl m = (t01 & msk) + (static_cast(t10) & msk) + (t00 >> half_width); + Th o1 = t11 + (m >> half_width) + (t10 >> half_width) + static_cast(t01 >> half_width); + Tl o0 = (t00 & msk) | ((m & msk) << half_width); + return {o1, o0}; + } + + template + friend constexpr auto operator*(Lhs lhs, const double_width_int& rhs) + { + using ret_t = double_width_int>; + auto ret = ret_t::wide_product_of(lhs, rhs.lo); + ret.hi += lhs * rhs.hi; + return ret; + }; + + + constexpr double_width_int operator+(Tl rhs) const + { + auto ret = lo + rhs; + return {hi + (ret < lo ? 1 : 0), ret}; + } + + constexpr double_width_int operator>>(unsigned n) const + { + if (n >= base_width) { + return {0, hi >> (n - base_width)}; + } + return {hi >> n, (static_cast(hi) << (base_width - n)) | (lo >> n)}; + } + constexpr double_width_int operator<<(unsigned n) const + { + if (n >= base_width) { + return {static_cast(lo >> (2 * base_width - n)), 0}; + } + return {(hi << n) + static_cast(lo >> (base_width - n)), lo << n}; + } + + static constexpr double_width_int max() { return {std::numeric_limits::max(), std::numeric_limits::max()}; } + + Th hi; + Tl lo; +}; + +#ifdef __SIZEOF_INT128__ +using int128_t = __int128; +using uint128_t = unsigned __int128; +inline constexpr std::size_t max_native_width = 128; +#else +using int128_t = double_width_int; +using uint128_t = double_width_int; +inline constexpr std::size_t max_native_width = 64; +#endif + +template +inline constexpr std::size_t integer_rep_width_v = std::numeric_limits>::digits; +template +inline constexpr std::size_t integer_rep_width_v> = double_width_int::width; + +template +inline constexpr bool is_signed_v = std::is_signed_v; +template +inline constexpr bool is_signed_v> = double_width_int::is_signed; + +template +using make_signed_t = std::make_signed_t; + +template +using min_width_uint_t = + std::tuple_element_t (1u << (std::bit_width(N) - 1)) ? 1 : 0), + std::tuple>; + +static_assert(std::is_same_v, std::uint32_t>); +static_assert(std::is_same_v, std::uint32_t>); +static_assert(std::is_same_v, std::uint64_t>); + +template +using min_width_int_t = make_signed_t>; + +template +using double_width_int_for_t = std::conditional_t, min_width_int_t * 2>, + min_width_uint_t * 2>>; + +template +constexpr auto wide_product_of(Lhs lhs, Rhs rhs) +{ + if constexpr (integer_rep_width_v + integer_rep_width_v <= max_native_width) { + using T = std::common_type_t, double_width_int_for_t>; + return static_cast(lhs) * static_cast(rhs); + } else { + using T = double_width_int>; + return T::wide_product_of(lhs, rhs); + } +} + +// This class represents rational numbers using a fixed-point representation, with a symmetric number of digits (bits) +// on either side of the decimal point. The template argument `T` specifies the range of the integral part, +// thus this class uses twice as many bits as the provided type, but is able to precisely store exactly all integers +// from the declared type, as well as efficiently describe all rational factors that can be applied to that type +// and neither always cause underflow or overflow. +template +struct fixed_point { + using repr_t = double_width_int_for_t; + static constexpr std::size_t fractional_bits = integer_rep_width_v; + + constexpr fixed_point() = default; + constexpr fixed_point(const fixed_point&) = default; + + constexpr fixed_point& operator=(const fixed_point&) = default; + + explicit constexpr fixed_point(long double v) : + int_repr_is_an_implementation_detail_(static_cast(v * int_power(2, fractional_bits))) + { + } + + template + requires(integer_rep_width_v <= integer_rep_width_v) + auto scale(U v) const + { + auto res = v * int_repr_is_an_implementation_detail_; + return static_cast, std::make_signed_t, U>>(res >> + fractional_bits); + } + + repr_t int_repr_is_an_implementation_detail_; +}; + +} // namespace detail +} // namespace mp_units diff --git a/src/core/include/mp-units/bits/sudo_cast.h b/src/core/include/mp-units/bits/sudo_cast.h index 845761b65..5a817886f 100644 --- a/src/core/include/mp-units/bits/sudo_cast.h +++ b/src/core/include/mp-units/bits/sudo_cast.h @@ -22,6 +22,7 @@ #pragma once +#include #include #include #include @@ -53,14 +54,15 @@ template struct conversion_type_traits { using c_rep_type = maybe_common_type; using c_mag_type = common_magnitude_type; - using multiplier_type = conditional< - treat_as_floating_point, - // ensure that the multiplier is also floating-point - conditional>, - // reuse user's type if possible - std::common_type_t>, std::common_type_t>, - c_mag_type>; - using c_type = maybe_common_type; + /* using multiplier_type = conditional< + treat_as_floating_point, + // ensure that the multiplier is also floating-point + conditional>, + // reuse user's type if possible + std::common_type_t>, std::common_type_t>, + c_mag_type>;*/ + using c_type = conditional>, value_type_t, + std::common_type_t>; }; /** @@ -79,10 +81,56 @@ struct conversion_value_traits { static constexpr Magnitude auto num = numerator(M); static constexpr Magnitude auto den = denominator(M); static constexpr Magnitude auto irr = M * (den / num); + static constexpr auto ratio = [] { + if constexpr (std::is_integral_v) { + using U = long double; + return detail::fixed_point{get_value(num) / get_value(den) * get_value(irr)}; + } else { + return get_value(num) / get_value(den) * get_value(irr); + } + }(); + static constexpr bool value_increases = ratio >= T{1}; + + template + static constexpr auto scale(V value) + { + if constexpr (std::is_integral_v) { + return ratio.scale(value); + } else { + return value * ratio; + } + } +}; + +template + requires(is_integral(M)) +struct conversion_value_traits { + static constexpr Magnitude auto num = numerator(M); static constexpr T num_mult = get_value(num); - static constexpr T den_mult = get_value(den); - static constexpr T irr_mult = get_value(irr); - static constexpr T ratio = num_mult / den_mult * irr_mult; + static constexpr bool value_increases = true; + + static_assert(get_value(denominator(M)) == 1); + static_assert(is_integral(M)); + + template + static constexpr auto scale(V value) + { + return value * num_mult; + } +}; + +template + requires(is_integral(pow<-1>(M)) && !is_integral(M)) +struct conversion_value_traits { + static constexpr Magnitude auto den = denominator(M); + static constexpr T den_div = get_value(den); + static constexpr bool value_increases = false; + + template + static constexpr auto scale(V value) + { + return value / den_div; + } }; @@ -110,30 +158,11 @@ template; - using multiplier_type = typename type_traits::multiplier_type; - // TODO the below crashed nearly every compiler I tried it on - // auto scale = [&](std::invocable auto func) { - auto scale = [&](auto func) { - auto res = - static_cast(func(static_cast(q.numerical_value_is_an_implementation_detail_))); - return To{res, To::reference}; - }; - - // scale the number - if constexpr (is_integral(c_mag)) - return scale([&](auto value) { return value * get_value(numerator(c_mag)); }); - else if constexpr (is_integral(pow<-1>(c_mag))) - return scale([&](auto value) { return value / get_value(denominator(c_mag)); }); - else { - using value_traits = conversion_value_traits; - if constexpr (std::is_floating_point_v) - // this results in great assembly - return scale([](auto value) { return value * value_traits::ratio; }); - else - // this is slower but allows conversions like 2000 m -> 2 km without loosing data - return scale( - [](auto value) { return value * value_traits::num_mult / value_traits::den_mult * value_traits::irr_mult; }); - } + using value_traits = conversion_value_traits; + + auto res = static_cast( + value_traits::scale(static_cast(q.numerical_value_is_an_implementation_detail_))); + return To{res, To::reference}; } } @@ -172,21 +201,21 @@ template; - using value_traits = conversion_value_traits; - using c_rep_type = typename type_traits::c_rep_type; - if constexpr (value_traits::num_mult * value_traits::irr_mult > value_traits::den_mult) { + using c_type = type_traits::c_type; + using value_traits = conversion_value_traits; + if constexpr (value_traits::value_increases) { // original unit had a larger unit magnitude; if we first convert to the common representation but retain the // unit, we obtain the largest possible range while not causing truncation of fractional values. This is optimal // for the offset computation. return sudo_cast( - sudo_cast>(std::forward(qp)) + sudo_cast>(std::forward(qp)) .point_for(ToQP::point_origin)); } else { // new unit may have a larger unit magnitude; we first need to convert to the new unit (potentially causing // truncation, but no more than if we did the conversion later), but make sure we keep the larger of the two // representation types. Then, we can perform the offset computation. return sudo_cast( - sudo_cast>( + sudo_cast>( std::forward(qp)) .point_for(ToQP::point_origin)); } diff --git a/test/static/international_test.cpp b/test/static/international_test.cpp index 755eea73c..55ee3274e 100644 --- a/test/static/international_test.cpp +++ b/test/static/international_test.cpp @@ -37,6 +37,11 @@ using namespace mp_units::international; using namespace mp_units::international::unit_symbols; // Mass +constexpr Magnitude auto c_mag = get_canonical_unit(lb).mag / get_canonical_unit(si::kilogram).mag; +static_assert(get_value(detail::numerator(c_mag)) == 45'359'237); +static_assert(get_value(detail::denominator(c_mag)) == 100'000'000); +static_assert(!is_integral(c_mag)); + static_assert(100'000'000 * isq::mass[lb] == 45'359'237 * isq::mass[si::kilogram]); static_assert(1 * isq::mass[lb] == 16 * isq::mass[oz]); static_assert(1 * isq::mass[oz] == 16 * isq::mass[dr]); From 3635260f0825a9e2d60a545e3824278be50106a8 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 15 Sep 2024 20:38:26 +0200 Subject: [PATCH 02/34] disabled two tests which now trigger #614 --- test/static/international_test.cpp | 8 ++------ test/static/usc_test.cpp | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/test/static/international_test.cpp b/test/static/international_test.cpp index 55ee3274e..5e88606b3 100644 --- a/test/static/international_test.cpp +++ b/test/static/international_test.cpp @@ -37,12 +37,8 @@ using namespace mp_units::international; using namespace mp_units::international::unit_symbols; // Mass -constexpr Magnitude auto c_mag = get_canonical_unit(lb).mag / get_canonical_unit(si::kilogram).mag; -static_assert(get_value(detail::numerator(c_mag)) == 45'359'237); -static_assert(get_value(detail::denominator(c_mag)) == 100'000'000); -static_assert(!is_integral(c_mag)); - -static_assert(100'000'000 * isq::mass[lb] == 45'359'237 * isq::mass[si::kilogram]); +// static_assert(100'000'000 * isq::mass[lb] == 45'359'237 * isq::mass[si::kilogram]); +// the previous test is currently disabled; it surfaced #614 static_assert(1 * isq::mass[lb] == 16 * isq::mass[oz]); static_assert(1 * isq::mass[oz] == 16 * isq::mass[dr]); static_assert(7'000 * isq::mass[gr] == 1 * isq::mass[lb]); diff --git a/test/static/usc_test.cpp b/test/static/usc_test.cpp index 10273d5bc..7f195fafa 100644 --- a/test/static/usc_test.cpp +++ b/test/static/usc_test.cpp @@ -123,7 +123,8 @@ static_assert(isq::mass(1 * oz_t) == isq::mass(20 * dwt)); static_assert(isq::mass(1 * lb_t) == isq::mass(12 * oz_t)); // Pressure -static_assert(isq::pressure(1'000 * inHg) == isq::pressure(3'386'389 * si::pascal)); +// the next test is currently disabled; it surfaced #614 +// static_assert(isq::pressure(1'000 * inHg) == isq::pressure(3'386'389 * si::pascal)); // Temperature static_assert(delta(9) == From 74f30dad3eafb48f96b8a88b06e6ba0564790f96 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 15 Sep 2024 21:18:02 +0200 Subject: [PATCH 03/34] again; fix C++20 compatibility --- src/core/include/mp-units/bits/fixed_point.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 6a96e7a33..cba0ee8b5 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -56,7 +56,7 @@ struct double_width_int { constexpr double_width_int& operator=(const double_width_int&) = default; - constexpr double_width_int(Th hi, Tl lo) : hi(hi), lo(lo) {} + constexpr double_width_int(Th hi_, Tl lo_) : hi(hi_), lo(lo_) {} explicit(true) constexpr double_width_int(long double v) { @@ -84,8 +84,8 @@ struct double_width_int { // calculates the double-width product of two base-size integers static constexpr double_width_int wide_product_of(Th lhs, Tl rhs) { - static constexpr std::size_t half_width = base_width / 2; - static constexpr Tl msk = Tl(1) << half_width; + constexpr std::size_t half_width = base_width / 2; + constexpr Tl msk = Tl(1) << half_width; Th l1 = lhs >> half_width; Tl l0 = static_cast(lhs) & msk; Tl r1 = rhs >> half_width; @@ -137,7 +137,7 @@ struct double_width_int { Tl lo; }; -#ifdef __SIZEOF_INT128__ +#if false && defined(__SIZEOF_INT128__) using int128_t = __int128; using uint128_t = unsigned __int128; inline constexpr std::size_t max_native_width = 128; @@ -162,9 +162,13 @@ using make_signed_t = std::make_signed_t; template using min_width_uint_t = - std::tuple_element_t (1u << (std::bit_width(N) - 1)) ? 1 : 0), + std::tuple_element_t(4u, std::bit_width(N) + (std::has_single_bit(N) ? 0u : 1u)) - 4u, std::tuple>; +static_assert(std::is_same_v, std::uint8_t>); +static_assert(std::is_same_v, std::uint8_t>); +static_assert(std::is_same_v, std::uint8_t>); +static_assert(std::is_same_v, std::uint16_t>); static_assert(std::is_same_v, std::uint32_t>); static_assert(std::is_same_v, std::uint32_t>); static_assert(std::is_same_v, std::uint64_t>); From 2f54c76fb340c9b36db92c505a6d663b047f2f76 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 16 Sep 2024 11:36:03 +0200 Subject: [PATCH 04/34] addessed most review concerns, fixed CI failure --- src/core/CMakeLists.txt | 1 + src/core/include/mp-units/bits/fixed_point.h | 64 +++++++++++++++----- src/core/include/mp-units/bits/sudo_cast.h | 10 --- test/runtime/almost_equals.h | 9 +-- test/runtime/math_test.cpp | 4 +- 5 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ce9db06be..8dafb9b4b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -32,6 +32,7 @@ endfunction() add_mp_units_module( core mp-units-core HEADERS include/mp-units/bits/core_gmf.h + include/mp-units/bits/fixed_point.h include/mp-units/bits/get_associated_quantity.h include/mp-units/bits/hacks.h include/mp-units/bits/math_concepts.h diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index cba0ee8b5..2eb75ede7 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -100,20 +100,52 @@ struct double_width_int { return {o1, o0}; } - template - friend constexpr auto operator*(Lhs lhs, const double_width_int& rhs) + template + friend constexpr auto operator*(const double_width_int& lhs, Rhs rhs) { - using ret_t = double_width_int>; - auto ret = ret_t::wide_product_of(lhs, rhs.lo); - ret.hi += lhs * rhs.hi; + using ret_t = double_width_int>; + auto ret = ret_t::wide_product_of(lhs.lo, rhs); + ret.hi += lhs.hi * rhs; return ret; }; - - - constexpr double_width_int operator+(Tl rhs) const + template + friend constexpr auto operator*(Lhs lhs, const double_width_int& rhs) + { + return rhs * lhs; + } + template + requires(std::numeric_limits::digits <= base_width) + friend constexpr double_width_int operator+(const double_width_int& lhs, Rhs rhs) + { + // this follows the usual (but somewhat dangerous) rules in C++; we "win", as we are the larger type. + // -> signed computation only of both types are signed + if constexpr (is_signed, std::is_signed_v) { + if (rhs < 0) return lhs - static_cast>(-rhs); + } + Tl ret = lhs.lo + static_cast(rhs); + return {lhs.hi + Th{ret < lhs.lo ? 1 : 0}, ret}; + } + template + friend constexpr double_width_int operator+(Lhs lhs, const double_width_int& rhs) + { + return rhs + lhs; + } + template + requires(std::numeric_limits::digits <= base_width) + friend constexpr double_width_int operator-(const double_width_int& lhs, Rhs rhs) + { + // this follows the usual (but somewhat dangerous) rules in C++; we "win", as we are the larger type. + // -> signed computation only of both types are signed + if constexpr (is_signed, std::is_signed_v) { + if (rhs < 0) return lhs + static_cast>(-rhs); + } + Tl ret = lhs.lo - static_cast(rhs); + return {lhs.hi - Th{ret > lhs.lo ? 1 : 0}, ret}; + } + template + friend constexpr double_width_int operator-(Lhs lhs, const double_width_int& rhs) { - auto ret = lo + rhs; - return {hi + (ret < lo ? 1 : 0), ret}; + return rhs + lhs; } constexpr double_width_int operator>>(unsigned n) const @@ -144,18 +176,18 @@ inline constexpr std::size_t max_native_width = 128; #else using int128_t = double_width_int; using uint128_t = double_width_int; -inline constexpr std::size_t max_native_width = 64; +constexpr std::size_t max_native_width = 64; #endif template -inline constexpr std::size_t integer_rep_width_v = std::numeric_limits>::digits; +constexpr std::size_t integer_rep_width_v = std::numeric_limits>::digits; template -inline constexpr std::size_t integer_rep_width_v> = double_width_int::width; +constexpr std::size_t integer_rep_width_v> = double_width_int::width; template -inline constexpr bool is_signed_v = std::is_signed_v; +constexpr bool is_signed_v = std::is_signed_v; template -inline constexpr bool is_signed_v> = double_width_int::is_signed; +constexpr bool is_signed_v> = double_width_int::is_signed; template using make_signed_t = std::make_signed_t; @@ -214,7 +246,7 @@ struct fixed_point { template requires(integer_rep_width_v <= integer_rep_width_v) - auto scale(U v) const + constexpr auto scale(U v) const { auto res = v * int_repr_is_an_implementation_detail_; return static_cast, std::make_signed_t, U>>(res >> diff --git a/src/core/include/mp-units/bits/sudo_cast.h b/src/core/include/mp-units/bits/sudo_cast.h index 5a817886f..04af25d7c 100644 --- a/src/core/include/mp-units/bits/sudo_cast.h +++ b/src/core/include/mp-units/bits/sudo_cast.h @@ -54,13 +54,6 @@ template struct conversion_type_traits { using c_rep_type = maybe_common_type; using c_mag_type = common_magnitude_type; - /* using multiplier_type = conditional< - treat_as_floating_point, - // ensure that the multiplier is also floating-point - conditional>, - // reuse user's type if possible - std::common_type_t>, std::common_type_t>, - c_mag_type>;*/ using c_type = conditional>, value_type_t, std::common_type_t>; }; @@ -109,9 +102,6 @@ struct conversion_value_traits { static constexpr T num_mult = get_value(num); static constexpr bool value_increases = true; - static_assert(get_value(denominator(M)) == 1); - static_assert(is_integral(M)); - template static constexpr auto scale(V value) { diff --git a/test/runtime/almost_equals.h b/test/runtime/almost_equals.h index 0cc03e8eb..4c2153a0b 100644 --- a/test/runtime/almost_equals.h +++ b/test/runtime/almost_equals.h @@ -35,7 +35,7 @@ namespace mp_units { template struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { - explicit AlmostEqualsMatcher(const T& target) : target_{target} {} + explicit AlmostEqualsMatcher(const T& target, int n_eps) : target_{target}, n_eps_{n_eps} {} template U> requires std::same_as @@ -48,7 +48,7 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { const auto y = common(other).numerical_value_in(common::unit); if constexpr (treat_as_floating_point) { const auto maxXYOne = std::max({rep{1}, abs(x), abs(y)}); - return abs(x - y) <= std::numeric_limits::epsilon() * maxXYOne; + return abs(x - y) <= (n_eps_ * std::numeric_limits::epsilon()) * maxXYOne; } else { if (x >= 0) { return x - 1 <= y && y - 1 <= x; @@ -71,12 +71,13 @@ struct AlmostEqualsMatcher : Catch::Matchers::MatcherGenericBase { private: const T& target_; + int n_eps_; }; template -AlmostEqualsMatcher AlmostEquals(const T& target) +AlmostEqualsMatcher AlmostEquals(const T& target, int n_eps = 1) { - return AlmostEqualsMatcher{target}; + return AlmostEqualsMatcher{target, n_eps}; } diff --git a/test/runtime/math_test.cpp b/test/runtime/math_test.cpp index 2a59135b8..79018bff8 100644 --- a/test/runtime/math_test.cpp +++ b/test/runtime/math_test.cpp @@ -448,7 +448,7 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]") REQUIRE_THAT(sin(0 * angle[grad]), AlmostEquals(0. * one)); REQUIRE_THAT(sin(100 * angle[grad]), AlmostEquals(1. * one)); - REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(sin(200 * angle[grad]), AlmostEquals(0. * one, 2)); REQUIRE_THAT(sin(300 * angle[grad]), AlmostEquals(-1. * one)); } @@ -475,7 +475,7 @@ TEST_CASE("Angle trigonometric functions", "[trig][angle]") REQUIRE_THAT(tan(0 * angle[grad]), AlmostEquals(0. * one)); REQUIRE_THAT(tan(50 * angle[grad]), AlmostEquals(1. * one)); REQUIRE_THAT(tan(150 * angle[grad]), AlmostEquals(-1. * one)); - REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one)); + REQUIRE_THAT(tan(200 * angle[grad]), AlmostEquals(0. * one, 2)); } } From e003a587970929ef385c3b5dc8468dd8365dcbbb Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 16 Sep 2024 14:15:46 +0200 Subject: [PATCH 05/34] fixed and expanded double_width_int implemenation, tried to fix a bug encountered in CI --- src/core/include/mp-units/bits/fixed_point.h | 82 ++++++++++++++++---- test/static/CMakeLists.txt | 1 + test/static/fixed_point.cpp | 47 +++++++++++ 3 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 test/static/fixed_point.cpp diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 2eb75ede7..66071dd1d 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -81,21 +81,22 @@ struct double_width_int { constexpr auto operator<=>(const double_width_int&) const = default; - // calculates the double-width product of two base-size integers + // calculates the double-width product of two base-size integers; this implementation requires at least one of them to + // be unsigned static constexpr double_width_int wide_product_of(Th lhs, Tl rhs) { constexpr std::size_t half_width = base_width / 2; - constexpr Tl msk = Tl(1) << half_width; + constexpr Tl msk = (Tl(1) << half_width) - 1u; Th l1 = lhs >> half_width; Tl l0 = static_cast(lhs) & msk; Tl r1 = rhs >> half_width; Tl r0 = rhs & msk; Tl t00 = l0 * r0; Tl t01 = l0 * r1; - Th t10 = l1 * r0; - Th t11 = l1 * r1; + Th t10 = l1 * static_cast(r0); + Th t11 = l1 * static_cast(r1); Tl m = (t01 & msk) + (static_cast(t10) & msk) + (t00 >> half_width); - Th o1 = t11 + (m >> half_width) + (t10 >> half_width) + static_cast(t01 >> half_width); + Th o1 = t11 + static_cast(m >> half_width) + (t10 >> half_width) + static_cast(t01 >> half_width); Tl o0 = (t00 & msk) | ((m & msk) << half_width); return {o1, o0}; } @@ -103,8 +104,9 @@ struct double_width_int { template friend constexpr auto operator*(const double_width_int& lhs, Rhs rhs) { - using ret_t = double_width_int>; - auto ret = ret_t::wide_product_of(lhs.lo, rhs); + // Normal C++ rules; with respect to signedness, the bigger type always wins. + using ret_t = double_width_int; + auto ret = ret_t::wide_product_of(rhs, lhs.lo); ret.hi += lhs.hi * rhs; return ret; }; @@ -113,6 +115,47 @@ struct double_width_int { { return rhs * lhs; } + template + friend constexpr double_width_int operator/(const double_width_int& lhs, Rhs rhs) + { + // Normal C++ rules; with respect to signedness, the bigger type always wins. + using ret_t = double_width_int; + if constexpr (std::is_signed_v) { + if (rhs < 0) { + return (-lhs) / static_cast(-rhs); + } else { + return lhs / static_cast(rhs); + } + } else if constexpr (is_signed) { + if (lhs.hi < 0) { + return -((-lhs) / rhs); + } else { + using unsigned_t = double_width_int; + auto tmp = unsigned_t{static_cast(lhs.hi), lhs.lo} / rhs; + return ret_t{static_cast(tmp.hi), tmp.lo}; + } + } else { + Th res_hi = lhs.hi / rhs; + // unfortunately, wide division is hard: https://en.wikipedia.org/wiki/Division_algorithm. + // Here, we just provide a somewhat naive implementation of long division. + Tl rem_hi = lhs.hi % rhs; + Tl rem_lo = lhs.lo; + Tl res_lo = 0; + for (std::size_t i = 0; i < base_width; ++i) { + // shift in one bit + rem_hi = (rem_hi << 1u) | (rem_lo >> (base_width - 1)); + rem_lo <<= 1u; + res_lo <<= 1u; + // perform one bit of long division + if (rem_hi >= rhs) { + rem_hi -= rhs; + res_lo |= 1u; + } + } + return ret_t{res_hi, res_lo}; + } + }; + template requires(std::numeric_limits::digits <= base_width) friend constexpr double_width_int operator+(const double_width_int& lhs, Rhs rhs) @@ -148,17 +191,19 @@ struct double_width_int { return rhs + lhs; } + constexpr double_width_int operator-() const { return {(lo > 0 ? -1 : 0) - hi, -lo}; } + constexpr double_width_int operator>>(unsigned n) const { if (n >= base_width) { - return {0, hi >> (n - base_width)}; + return {static_cast(hi < 0 ? -1 : 0), static_cast(hi >> (n - base_width))}; } return {hi >> n, (static_cast(hi) << (base_width - n)) | (lo >> n)}; } constexpr double_width_int operator<<(unsigned n) const { if (n >= base_width) { - return {static_cast(lo >> (2 * base_width - n)), 0}; + return {static_cast(lo << (n - base_width)), 0}; } return {(hi << n) + static_cast(lo >> (base_width - n)), lo << n}; } @@ -192,18 +237,19 @@ constexpr bool is_signed_v> = double_width_int::is_signed template using make_signed_t = std::make_signed_t; +#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L template using min_width_uint_t = std::tuple_element_t(4u, std::bit_width(N) + (std::has_single_bit(N) ? 0u : 1u)) - 4u, std::tuple>; - -static_assert(std::is_same_v, std::uint8_t>); -static_assert(std::is_same_v, std::uint8_t>); -static_assert(std::is_same_v, std::uint8_t>); -static_assert(std::is_same_v, std::uint16_t>); -static_assert(std::is_same_v, std::uint32_t>); -static_assert(std::is_same_v, std::uint32_t>); -static_assert(std::is_same_v, std::uint64_t>); +#else +template +using min_width_uint_t = + std::tuple_element_t( + 4u, std::numeric_limits::digits - std::countl_zero(N) + (N & (N - 1) ? 1u : 0u)) - + 4u, + std::tuple>; +#endif template using min_width_int_t = make_signed_t>; @@ -239,6 +285,8 @@ struct fixed_point { constexpr fixed_point& operator=(const fixed_point&) = default; + explicit constexpr fixed_point(repr_t v) : int_repr_is_an_implementation_detail_(v) {} + explicit constexpr fixed_point(long double v) : int_repr_is_an_implementation_detail_(static_cast(v * int_power(2, fractional_bits))) { diff --git a/test/static/CMakeLists.txt b/test/static/CMakeLists.txt index 2d8b5f67a..9abc62c5d 100644 --- a/test/static/CMakeLists.txt +++ b/test/static/CMakeLists.txt @@ -36,6 +36,7 @@ add_library( custom_rep_test_min_impl.cpp dimension_test.cpp dimension_symbol_test.cpp + fixed_point.cpp fixed_string_test.cpp hep_test.cpp iau_test.cpp diff --git a/test/static/fixed_point.cpp b/test/static/fixed_point.cpp new file mode 100644 index 000000000..db3cd2b7e --- /dev/null +++ b/test/static/fixed_point.cpp @@ -0,0 +1,47 @@ +// 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 +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif + +using namespace mp_units; + +namespace { + +static_assert(std::is_same_v, std::uint8_t>); +static_assert(std::is_same_v, std::uint8_t>); +static_assert(std::is_same_v, std::uint8_t>); +static_assert(std::is_same_v, std::uint16_t>); +static_assert(std::is_same_v, std::uint32_t>); +static_assert(std::is_same_v, std::uint32_t>); +static_assert(std::is_same_v, std::uint64_t>); + +using i128 = detail::double_width_int; +using u128 = detail::double_width_int; + +static_assert((((83 * 79 * 73) * (i128{97} << 64u) / 89) >> 64u) == (83 * 79 * 73 * 97) / 89); + +} // namespace From 60c94da658cd6f16074f3c78e4de979c0afd3cd7 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 16 Sep 2024 17:08:38 +0200 Subject: [PATCH 06/34] one more try --- src/core/include/mp-units/bits/fixed_point.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 66071dd1d..1a009c4f2 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -237,7 +237,7 @@ constexpr bool is_signed_v> = double_width_int::is_signed template using make_signed_t = std::make_signed_t; -#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L +#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L && false template using min_width_uint_t = std::tuple_element_t(4u, std::bit_width(N) + (std::has_single_bit(N) ? 0u : 1u)) - 4u, From d00b3306487271144472341872407f3440c815a5 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 16 Sep 2024 20:43:32 +0200 Subject: [PATCH 07/34] fixed pedantic error --- src/core/include/mp-units/bits/fixed_point.h | 4 ++-- test/static/CMakeLists.txt | 2 +- test/static/{fixed_point.cpp => fixed_point_test.cpp} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename test/static/{fixed_point.cpp => fixed_point_test.cpp} (100%) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 1a009c4f2..cc8590651 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -109,7 +109,7 @@ struct double_width_int { auto ret = ret_t::wide_product_of(rhs, lhs.lo); ret.hi += lhs.hi * rhs; return ret; - }; + } template friend constexpr auto operator*(Lhs lhs, const double_width_int& rhs) { @@ -154,7 +154,7 @@ struct double_width_int { } return ret_t{res_hi, res_lo}; } - }; + } template requires(std::numeric_limits::digits <= base_width) diff --git a/test/static/CMakeLists.txt b/test/static/CMakeLists.txt index 9abc62c5d..8f851abe0 100644 --- a/test/static/CMakeLists.txt +++ b/test/static/CMakeLists.txt @@ -36,7 +36,7 @@ add_library( custom_rep_test_min_impl.cpp dimension_test.cpp dimension_symbol_test.cpp - fixed_point.cpp + fixed_point_test.cpp fixed_string_test.cpp hep_test.cpp iau_test.cpp diff --git a/test/static/fixed_point.cpp b/test/static/fixed_point_test.cpp similarity index 100% rename from test/static/fixed_point.cpp rename to test/static/fixed_point_test.cpp From 653d3d2ab23fa7eda6a3f96caf0b1344c3ef63ef Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 5 Nov 2024 13:53:40 +0100 Subject: [PATCH 08/34] fix formatting issues --- src/core/include/mp-units/bits/fixed_point.h | 7 ++++--- src/core/include/mp-units/bits/sudo_cast.h | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 2605fce44..8433b82a3 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -65,7 +65,7 @@ struct double_width_int { auto scaled = v * iscale; hi = static_cast(scaled); auto resid = (scaled - static_cast(hi)); - if(resid<0) { + if (resid < 0) { --hi; resid += 1; } @@ -241,9 +241,10 @@ template constexpr bool is_signed_v> = double_width_int::is_signed; template -using make_signed_t = std::conditional_t,std::make_signed,std::type_identity>::type; +using make_signed_t = + std::conditional_t, std::make_signed, std::type_identity>::type; -#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L && false +#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L /*&& false*/ template using min_width_uint_t = std::tuple_element_t(4u, std::bit_width(N) + (std::has_single_bit(N) ? 0u : 1u)) - 4u, diff --git a/src/core/include/mp-units/bits/sudo_cast.h b/src/core/include/mp-units/bits/sudo_cast.h index 1cb54708b..a194308e9 100644 --- a/src/core/include/mp-units/bits/sudo_cast.h +++ b/src/core/include/mp-units/bits/sudo_cast.h @@ -168,7 +168,8 @@ template> requires(castable(FromQP::quantity_spec, ToQP::quantity_spec)) && (detail::same_absolute_point_origins(ToQP::point_origin, FromQP::point_origin)) && - ((equivalent(FromQP::unit, ToQP::unit) && std::constructible_from) || + ((equivalent(FromQP::unit, ToQP::unit) && + std::constructible_from) || (!equivalent(FromQP::unit, ToQP::unit))) [[nodiscard]] constexpr QuantityPoint auto sudo_cast(FwdFromQP&& qp) { From ed2574f4a90312b1db82e1fac98f7bc8a70edf37 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 5 Nov 2024 13:55:29 +0100 Subject: [PATCH 09/34] allow use of __(u)int128, and always use std::bit_width and friends --- src/core/include/mp-units/bits/fixed_point.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 8433b82a3..d128e528d 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -220,7 +220,7 @@ struct double_width_int { Tl lo; }; -#if false && defined(__SIZEOF_INT128__) +#if defined(__SIZEOF_INT128__) using int128_t = __int128; using uint128_t = unsigned __int128; inline constexpr std::size_t max_native_width = 128; @@ -244,7 +244,7 @@ template using make_signed_t = std::conditional_t, std::make_signed, std::type_identity>::type; -#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L /*&& false*/ +#if true /* defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L */ template using min_width_uint_t = std::tuple_element_t(4u, std::bit_width(N) + (std::has_single_bit(N) ? 0u : 1u)) - 4u, From e688ffc73eeebd8204195a7e8aea47a0747f23b6 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 5 Nov 2024 14:22:10 +0100 Subject: [PATCH 10/34] silence pedantic warning about __int128 --- src/core/include/mp-units/bits/fixed_point.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index d128e528d..11b2a02a1 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -221,8 +221,16 @@ struct double_width_int { }; #if defined(__SIZEOF_INT128__) +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" using int128_t = __int128; using uint128_t = unsigned __int128; +#pragma GCC diagnostic pop +#else +using int128_t = __int128; +using uint128_t = unsigned __int128; +#endif inline constexpr std::size_t max_native_width = 128; #else using int128_t = double_width_int; From 55d8fd6495efd84b9bb091ccada6543b46ab905b Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 5 Nov 2024 20:05:49 +0100 Subject: [PATCH 11/34] cross-platform silencing of pedantic warning --- src/core/include/mp-units/bits/core_gmf.h | 1 + src/core/include/mp-units/bits/fixed_point.h | 11 +++-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/core/include/mp-units/bits/core_gmf.h b/src/core/include/mp-units/bits/core_gmf.h index 5566bc923..7655e6f79 100644 --- a/src/core/include/mp-units/bits/core_gmf.h +++ b/src/core/include/mp-units/bits/core_gmf.h @@ -30,6 +30,7 @@ #ifndef MP_UNITS_IMPORT_STD #include +#include #include #include #include diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 11b2a02a1..9acf22d62 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -221,16 +221,11 @@ struct double_width_int { }; #if defined(__SIZEOF_INT128__) -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpedantic" +MP_UNITS_DIAGNOSTIC_PUSH +MP_UNITS_DIAGNOSTIC_IGNORE("-Wpedantic") using int128_t = __int128; using uint128_t = unsigned __int128; -#pragma GCC diagnostic pop -#else -using int128_t = __int128; -using uint128_t = unsigned __int128; -#endif +MP_UNITS_DIAGNOSTIC_POP inline constexpr std::size_t max_native_width = 128; #else using int128_t = double_width_int; From ad76149364ed5bf4d1694340b2a4101afb64f039 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Wed, 6 Nov 2024 16:27:02 +0100 Subject: [PATCH 12/34] Apply suggestions from code review Co-authored-by: Mateusz Pusz --- src/core/include/mp-units/bits/fixed_point.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 5e9a0b36a..39e52f01d 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -38,8 +38,7 @@ import std; #endif #endif -namespace mp_units { -namespace detail { +namespace mp_units::detail { // this class synthesizes a double-width integer from two base-width integers. template @@ -58,7 +57,7 @@ struct double_width_int { constexpr double_width_int(Th hi_, Tl lo_) : hi(hi_), lo(lo_) {} - explicit(true) constexpr double_width_int(long double v) + explicit constexpr double_width_int(long double v) { constexpr auto scale = int_power(2, base_width); constexpr auto iscale = 1.l / scale; @@ -83,9 +82,9 @@ struct double_width_int { lo = static_cast(v); } - explicit(true) constexpr operator Th() const { return static_cast(lo); } + explicit constexpr operator Th() const { return static_cast(lo); } - constexpr auto operator<=>(const double_width_int&) const = default; + [[nodiscard]] constexpr auto operator<=>(const double_width_int&) const = default; // calculates the double-width product of two base-size integers; this implementation requires at least one of them to // be unsigned From 95cc9f376df0c2709fd4b2c6e689d73caf359db8 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Wed, 6 Nov 2024 16:44:18 +0100 Subject: [PATCH 13/34] more review-requested changes, good test-coverage of double_width_int, and fixes for issues identified by those tests --- src/core/include/mp-units/bits/fixed_point.h | 120 ++++++---- src/core/include/mp-units/bits/hacks.h | 4 + test/runtime/CMakeLists.txt | 1 + test/runtime/fixed_point_test.cpp | 230 +++++++++++++++++++ 4 files changed, 310 insertions(+), 45 deletions(-) create mode 100644 test/runtime/fixed_point_test.cpp diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 39e52f01d..362ada46c 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -51,9 +51,6 @@ struct double_width_int { using Tl = std::make_unsigned_t; constexpr double_width_int() = default; - constexpr double_width_int(const double_width_int&) = default; - - constexpr double_width_int& operator=(const double_width_int&) = default; constexpr double_width_int(Th hi_, Tl lo_) : hi(hi_), lo(lo_) {} @@ -107,21 +104,24 @@ struct double_width_int { } template - friend constexpr auto operator*(const double_width_int& lhs, Rhs rhs) + requires(std::numeric_limits::digits <= base_width) + [[nodiscard]] friend constexpr auto operator*(const double_width_int& lhs, Rhs rhs) { - // Normal C++ rules; with respect to signedness, the bigger type always wins. - using ret_t = double_width_int; - auto ret = ret_t::wide_product_of(rhs, lhs.lo); - ret.hi += lhs.hi * rhs; - return ret; + using RT = std::conditional_t, std::make_signed_t, Tl>; + auto lo_prod = double_width_int::wide_product_of(rhs, lhs.lo); + // Normal C++ rules; with respect to signedness, the wider type always wins. + using ret_t = double_width_int; + return ret_t{static_cast(lo_prod.hi) + lhs.hi * static_cast(rhs), lo_prod.lo}; } template - friend constexpr auto operator*(Lhs lhs, const double_width_int& rhs) + requires(std::numeric_limits::digits <= base_width) + [[nodiscard]] friend constexpr auto operator*(Lhs lhs, const double_width_int& rhs) { return rhs * lhs; } template - friend constexpr double_width_int operator/(const double_width_int& lhs, Rhs rhs) + requires(std::numeric_limits::digits <= base_width) + [[nodiscard]] friend constexpr double_width_int operator/(const double_width_int& lhs, Rhs rhs) { // Normal C++ rules; with respect to signedness, the bigger type always wins. using ret_t = double_width_int; @@ -163,49 +163,75 @@ struct double_width_int { template requires(std::numeric_limits::digits <= base_width) - friend constexpr double_width_int operator+(const double_width_int& lhs, Rhs rhs) + [[nodiscard]] friend constexpr double_width_int operator+(const double_width_int& lhs, Rhs rhs) { - // this follows the usual (but somewhat dangerous) rules in C++; we "win", as we are the larger type. - // -> signed computation only of both types are signed - if constexpr (is_signed, std::is_signed_v) { - if (rhs < 0) return lhs - static_cast>(-rhs); + Th rhi = lhs.hi; + Tl rlo = lhs.lo; + if constexpr (std::is_signed_v) { + // sign extension; no matter if lhs is signed, negative rhs sign extend + if (rhs < 0) --rhi; + } + rlo += static_cast(rhs); + if (rlo < lhs.lo) { + // carry bit + ++rhi; } - Tl ret = lhs.lo + static_cast(rhs); - return {lhs.hi + Th{ret < lhs.lo ? 1 : 0}, ret}; + return {rhi, rlo}; } template - friend constexpr double_width_int operator+(Lhs lhs, const double_width_int& rhs) + [[nodiscard]] friend constexpr double_width_int operator+(Lhs lhs, const double_width_int& rhs) { return rhs + lhs; } template requires(std::numeric_limits::digits <= base_width) - friend constexpr double_width_int operator-(const double_width_int& lhs, Rhs rhs) + [[nodiscard]] friend constexpr double_width_int operator-(const double_width_int& lhs, Rhs rhs) { - // this follows the usual (but somewhat dangerous) rules in C++; we "win", as we are the larger type. - // -> signed computation only of both types are signed - if constexpr (is_signed, std::is_signed_v) { - if (rhs < 0) return lhs + static_cast>(-rhs); + Th rhi = lhs.hi; + Tl rlo = lhs.lo; + if constexpr (std::is_signed_v) { + // sign extension; no matter if lhs is signed, negative rhs sign extend + if (rhs < 0) ++rhi; } - Tl ret = lhs.lo - static_cast(rhs); - return {lhs.hi - Th{ret > lhs.lo ? 1 : 0}, ret}; + rlo -= static_cast(rhs); + if (rlo > lhs.lo) { + // carry bit + --rhi; + } + return {rhi, rlo}; } + template - friend constexpr double_width_int operator-(Lhs lhs, const double_width_int& rhs) + [[nodiscard]] friend constexpr double_width_int operator-(Lhs lhs, const double_width_int& rhs) { - return rhs + lhs; + Th rhi = 0; + Tl rlo = static_cast(lhs); + if constexpr (std::is_signed_v) { + // sign extension; no matter if rhs is signed, negative lhs sign extend + if (lhs < 0) --rhi; + } + rhi -= rhs.hi; + if (rhs.lo > rlo) { + // carry bit + --rhi; + } + rlo -= rhs.lo; + return {rhi, rlo}; } - constexpr double_width_int operator-() const { return {(lo > 0 ? -1 : 0) - hi, -lo}; } + [[nodiscard]] constexpr double_width_int operator-() const + { + return {(lo > 0 ? static_cast(-1) : Th{0}) - hi, -lo}; + } - constexpr double_width_int operator>>(unsigned n) const + [[nodiscard]] constexpr double_width_int operator>>(unsigned n) const { if (n >= base_width) { return {static_cast(hi < 0 ? -1 : 0), static_cast(hi >> (n - base_width))}; } return {hi >> n, (static_cast(hi) << (base_width - n)) | (lo >> n)}; } - constexpr double_width_int operator<<(unsigned n) const + [[nodiscard]] constexpr double_width_int operator<<(unsigned n) const { if (n >= base_width) { return {static_cast(lo << (n - base_width)), 0}; @@ -221,7 +247,7 @@ struct double_width_int { #if defined(__SIZEOF_INT128__) MP_UNITS_DIAGNOSTIC_PUSH -MP_UNITS_DIAGNOSTIC_IGNORE("-Wpedantic") +MP_UNITS_DIAGNOSTIC_IGNORE_PEDANTIC using int128_t = __int128; using uint128_t = unsigned __int128; MP_UNITS_DIAGNOSTIC_POP @@ -277,32 +303,36 @@ constexpr auto wide_product_of(Lhs lhs, Rhs rhs) // and neither always cause underflow or overflow. template struct fixed_point { - using repr_t = double_width_int_for_t; + using value_type = double_width_int_for_t; static constexpr std::size_t fractional_bits = integer_rep_width_v; constexpr fixed_point() = default; - constexpr fixed_point(const fixed_point&) = default; - constexpr fixed_point& operator=(const fixed_point&) = default; + explicit constexpr fixed_point(value_type v) : int_repr_(v) {} - explicit constexpr fixed_point(repr_t v) : int_repr_is_an_implementation_detail_(v) {} - - explicit constexpr fixed_point(long double v) : - int_repr_is_an_implementation_detail_(static_cast(v * int_power(2, fractional_bits))) + explicit constexpr fixed_point(long double v) { + long double scaled = v * int_power(2, fractional_bits); + int_repr_ = static_cast(scaled); + // round away from zero; scaling will truncate towards zero, so we need to do the opposite to prevent + // double rounding. + if (int_repr_ >= 0) { + if (scaled > static_cast(int_repr_)) int_repr_++; + } else { + if (scaled < static_cast(int_repr_)) int_repr_--; + } } template requires(integer_rep_width_v <= integer_rep_width_v) - constexpr auto scale(U v) const + [[nodiscard]] constexpr auto scale(U v) const { - auto res = v * int_repr_is_an_implementation_detail_; + auto res = v * int_repr_; return static_cast, std::make_signed_t, U>>(res >> fractional_bits); } - - repr_t int_repr_is_an_implementation_detail_; +private: + value_type int_repr_; }; -} // namespace detail -} // namespace mp_units +} // namespace mp_units::detail diff --git a/src/core/include/mp-units/bits/hacks.h b/src/core/include/mp-units/bits/hacks.h index 87d7c47db..aa6ddab4b 100644 --- a/src/core/include/mp-units/bits/hacks.h +++ b/src/core/include/mp-units/bits/hacks.h @@ -58,6 +58,8 @@ MP_UNITS_DIAGNOSTIC_IGNORE("-Wzero-as-nullpointer-constant") #define MP_UNITS_DIAGNOSTIC_IGNORE_DEPRECATED MP_UNITS_DIAGNOSTIC_IGNORE("-Wdeprecated-declarations") #define MP_UNITS_DIAGNOSTIC_IGNORE_BUILTIN_MACRO_REDEFINED MP_UNITS_DIAGNOSTIC_IGNORE("-Wbuiltin-macro-redefined") +#define MP_UNITS_DIAGNOSTIC_IGNORE_PEDANTIC MP_UNITS_DIAGNOSTIC_IGNORE("-Wpedantic") +#define MP_UNITS_DIAGNOSTIC_IGNORE_SIGN_CONVERSION MP_UNITS_DIAGNOSTIC_IGNORE("-Wsign-conversion") #else #define MP_UNITS_DIAGNOSTIC_PUSH MP_UNITS_PRAGMA(warning(push)) #define MP_UNITS_DIAGNOSTIC_POP MP_UNITS_PRAGMA(warning(pop)) @@ -72,6 +74,8 @@ #define MP_UNITS_DIAGNOSTIC_IGNORE_UNREACHABLE MP_UNITS_DIAGNOSTIC_IGNORE(4702) #define MP_UNITS_DIAGNOSTIC_IGNORE_ZERO_AS_NULLPOINTER_CONSTANT #define MP_UNITS_DIAGNOSTIC_IGNORE_DEPRECATED +#define MP_UNITS_DIAGNOSTIC_IGNORE_PEDANTIC +#define MP_UNITS_DIAGNOSTIC_IGNORE_SIGN_CONVERSION #endif #if !defined MP_UNITS_HOSTED && defined __STDC_HOSTED__ diff --git a/test/runtime/CMakeLists.txt b/test/runtime/CMakeLists.txt index d79ed54b3..2dc624fee 100644 --- a/test/runtime/CMakeLists.txt +++ b/test/runtime/CMakeLists.txt @@ -25,6 +25,7 @@ find_package(Catch2 3 REQUIRED) add_executable( unit_tests_runtime distribution_test.cpp + fixed_point_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp diff --git a/test/runtime/fixed_point_test.cpp b/test/runtime/fixed_point_test.cpp new file mode 100644 index 000000000..2aa621984 --- /dev/null +++ b/test/runtime/fixed_point_test.cpp @@ -0,0 +1,230 @@ +// 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 +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#endif + +using namespace mp_units; +using namespace mp_units::detail; + +template + requires(N == sizeof...(T) && N == sizeof...(I)) +std::tuple at(const std::array& idx, std::integer_sequence, + const std::vector&... src) +{ + return {src[idx[I]]...}; +} + +template +std::vector> cartesian_product(const std::vector&... src) +{ + std::vector> ret; + constexpr std::size_t N = sizeof...(src); + std::array sizes; + { + std::size_t n = 1; + std::size_t k = 0; + for (std::size_t s : {src.size()...}) { + sizes[k++] = s; + n *= s; + } + ret.reserve(n); + } + std::array idx = {}; + bool done = false; + while (!done) { + ret.push_back(at(idx, std::make_index_sequence{}, src...)); + for (std::size_t k = 0; k < idx.size(); ++k) { + if (++idx[k] < sizes[k]) break; + if (k + 1 >= idx.size()) { + done = true; + break; + } + idx[k] = 0; + } + } + return ret; +} + + +template +using half_width_int_t = std::conditional_t, min_width_int_t / 2>, + min_width_uint_t / 2>>; +template +using double_width_int_t = std::conditional_t, min_width_int_t * 2>, + min_width_uint_t * 2>>; + +template + requires(integer_rep_width_v == integer_rep_width_v) +auto combine_bits(Hi hi, Lo lo) +{ + using ret_t = double_width_int_t; + return (static_cast(hi) << integer_rep_width_v)+static_cast(lo); +} + +template +void check(double_width_int value, V&& visitor) +{ + auto as_standard_int = combine_bits(value.hi, value.lo); + auto expected = visitor(as_standard_int); + auto actual = visitor(value); + auto actual_as_standard = combine_bits(actual.hi, actual.lo); + REQUIRE(actual_as_standard == expected); +} + +template +std::vector test_values() +{ + std::vector ret; + for (int msb : {0, 1, 2, 3}) { + auto ref = static_cast(msb) << (integer_rep_width_v - 2); + for (int lsb_corr : {-2, -1, 0, 1, 2}) { + auto corr = static_cast(lsb_corr); + T value = ref + corr; + ret.push_back(value); + } + } + return ret; +} + +using u32 = std::uint32_t; +using i32 = std::int32_t; +using u64 = std::uint64_t; +using i64 = std::int64_t; +using du32 = double_width_int; +using di32 = double_width_int; + +MP_UNITS_DIAGNOSTIC_PUSH +// double_width_int implements the same sign-conversion rules as the standard int types, and we want to verify that; +// even if those sign-conversion rules are frowned upon. +MP_UNITS_DIAGNOSTIC_IGNORE_SIGN_CONVERSION + +TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") +{ + SECTION("u32x2 +/- u32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v + rhs; }); + check(lhs, [&](auto v) { return v - rhs; }); + check(lhs, [&](auto v) { return rhs - v; }); + } + } + SECTION("u32x2 +/- i32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v + rhs; }); + check(lhs, [&](auto v) { return v - rhs; }); + check(lhs, [&](auto v) { return rhs - v; }); + } + } + SECTION("i32x2 +/- u32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v + rhs; }); + check(lhs, [&](auto v) { return v - rhs; }); + check(lhs, [&](auto v) { return rhs - v; }); + } + } + SECTION("i32x2 +/- i32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v + rhs; }); + check(lhs, [&](auto v) { return v - rhs; }); + check(lhs, [&](auto v) { return rhs - v; }); + } + } +} + +TEST_CASE("double_width_int multiplication", "[double_width_int]") +{ + SECTION("u32 * u32") + { + for (auto [lhs, rhs] : cartesian_product(test_values(), test_values())) { + CAPTURE(lhs, rhs); + u64 expected = u64{lhs} * u64{rhs}; + auto actual = double_width_int::wide_product_of(lhs, rhs); + u64 actual_as_std = combine_bits(actual.hi, actual.lo); + REQUIRE(actual_as_std == expected); + } + } + SECTION("i32 * u32") + { + for (auto [lhs, rhs] : cartesian_product(test_values(), test_values())) { + CAPTURE(lhs, rhs); + i64 expected = i64{lhs} * i64{rhs}; + auto actual = double_width_int::wide_product_of(lhs, rhs); + i64 actual_as_std = combine_bits(actual.hi, actual.lo); + REQUIRE(actual_as_std == expected); + } + } + SECTION("u32x2 * u32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v * rhs; }); + } + } + SECTION("u32x2 * i32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v * rhs; }); + } + } + SECTION("i32x2 * u32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v * rhs; }); + } + } + SECTION("i32x2 * i32") + { + for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { + CAPTURE(lhi, llo, rhs); + double_width_int lhs{lhi, llo}; + check(lhs, [&](auto v) { return v * rhs; }); + } + } +} + +MP_UNITS_DIAGNOSTIC_POP From 5f8eb5cbf8523c49f0af6a841a2af3adabbba8cb Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Wed, 6 Nov 2024 17:04:50 +0100 Subject: [PATCH 14/34] made hi_ and lo_ private members of double_width_int --- src/core/include/mp-units/bits/fixed_point.h | 90 ++++++++++++-------- test/runtime/fixed_point_test.cpp | 25 +++--- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 362ada46c..f0ab86188 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -40,11 +40,15 @@ import std; namespace mp_units::detail { +template +constexpr std::size_t integer_rep_width_v = std::numeric_limits>::digits; + + // this class synthesizes a double-width integer from two base-width integers. template struct double_width_int { static constexpr bool is_signed = std::is_signed_v; - static constexpr std::size_t base_width = std::numeric_limits>::digits; + static constexpr std::size_t base_width = integer_rep_width_v; static constexpr std::size_t width = 2 * base_width; using Th = T; @@ -52,34 +56,49 @@ struct double_width_int { constexpr double_width_int() = default; - constexpr double_width_int(Th hi_, Tl lo_) : hi(hi_), lo(lo_) {} +private: + constexpr double_width_int(Th hi, Tl lo) : hi_(hi), lo_(lo) {} + + template + friend struct double_width_int; + +public: + static constexpr double_width_int from_hi_lo(Th hi, Tl lo) { return {hi, lo}; } explicit constexpr double_width_int(long double v) { constexpr auto scale = int_power(2, base_width); constexpr auto iscale = 1.l / scale; auto scaled = v * iscale; - hi = static_cast(scaled); - auto resid = (scaled - static_cast(hi)); + hi_ = static_cast(scaled); + auto resid = (scaled - static_cast(hi_)); if (resid < 0) { - --hi; + --hi_; resid += 1; } - lo = static_cast(resid * scale); + lo_ = static_cast(resid * scale); } template requires(is_signed || !std::is_signed_v) explicit(false) constexpr double_width_int(U v) { if constexpr (is_signed) { - hi = v < 0 ? Th{-1} : Th{0}; + hi_ = v < 0 ? Th{-1} : Th{0}; } else { - hi = 0; + hi_ = 0; } - lo = static_cast(v); + lo_ = static_cast(v); } - explicit constexpr operator Th() const { return static_cast(lo); } + template + explicit constexpr operator U() const + { + if constexpr (integer_rep_width_v > base_width) { + return (static_cast(hi_) << base_width) + static_cast(lo_); + } else { + return static_cast(lo_); + } + } [[nodiscard]] constexpr auto operator<=>(const double_width_int&) const = default; @@ -108,10 +127,10 @@ struct double_width_int { [[nodiscard]] friend constexpr auto operator*(const double_width_int& lhs, Rhs rhs) { using RT = std::conditional_t, std::make_signed_t, Tl>; - auto lo_prod = double_width_int::wide_product_of(rhs, lhs.lo); + auto lo_prod = double_width_int::wide_product_of(rhs, lhs.lo_); // Normal C++ rules; with respect to signedness, the wider type always wins. using ret_t = double_width_int; - return ret_t{static_cast(lo_prod.hi) + lhs.hi * static_cast(rhs), lo_prod.lo}; + return ret_t{static_cast(lo_prod.hi_) + lhs.hi_ * static_cast(rhs), lo_prod.lo_}; } template requires(std::numeric_limits::digits <= base_width) @@ -132,19 +151,19 @@ struct double_width_int { return lhs / static_cast(rhs); } } else if constexpr (is_signed) { - if (lhs.hi < 0) { + if (lhs.hi_ < 0) { return -((-lhs) / rhs); } else { using unsigned_t = double_width_int; - auto tmp = unsigned_t{static_cast(lhs.hi), lhs.lo} / rhs; - return ret_t{static_cast(tmp.hi), tmp.lo}; + auto tmp = unsigned_t{static_cast(lhs.hi_), lhs.lo_} / rhs; + return ret_t{static_cast(tmp.hi_), tmp.lo_}; } } else { - Th res_hi = lhs.hi / rhs; + Th res_hi = lhs.hi_ / rhs; // unfortunately, wide division is hard: https://en.wikipedia.org/wiki/Division_algorithm. // Here, we just provide a somewhat naive implementation of long division. - Tl rem_hi = lhs.hi % rhs; - Tl rem_lo = lhs.lo; + Tl rem_hi = lhs.hi_ % rhs; + Tl rem_lo = lhs.lo_; Tl res_lo = 0; for (std::size_t i = 0; i < base_width; ++i) { // shift in one bit @@ -165,14 +184,14 @@ struct double_width_int { requires(std::numeric_limits::digits <= base_width) [[nodiscard]] friend constexpr double_width_int operator+(const double_width_int& lhs, Rhs rhs) { - Th rhi = lhs.hi; - Tl rlo = lhs.lo; + Th rhi = lhs.hi_; + Tl rlo = lhs.lo_; if constexpr (std::is_signed_v) { // sign extension; no matter if lhs is signed, negative rhs sign extend if (rhs < 0) --rhi; } rlo += static_cast(rhs); - if (rlo < lhs.lo) { + if (rlo < lhs.lo_) { // carry bit ++rhi; } @@ -187,14 +206,14 @@ struct double_width_int { requires(std::numeric_limits::digits <= base_width) [[nodiscard]] friend constexpr double_width_int operator-(const double_width_int& lhs, Rhs rhs) { - Th rhi = lhs.hi; - Tl rlo = lhs.lo; + Th rhi = lhs.hi_; + Tl rlo = lhs.lo_; if constexpr (std::is_signed_v) { // sign extension; no matter if lhs is signed, negative rhs sign extend if (rhs < 0) ++rhi; } rlo -= static_cast(rhs); - if (rlo > lhs.lo) { + if (rlo > lhs.lo_) { // carry bit --rhi; } @@ -210,39 +229,40 @@ struct double_width_int { // sign extension; no matter if rhs is signed, negative lhs sign extend if (lhs < 0) --rhi; } - rhi -= rhs.hi; - if (rhs.lo > rlo) { + rhi -= rhs.hi_; + if (rhs.lo_ > rlo) { // carry bit --rhi; } - rlo -= rhs.lo; + rlo -= rhs.lo_; return {rhi, rlo}; } [[nodiscard]] constexpr double_width_int operator-() const { - return {(lo > 0 ? static_cast(-1) : Th{0}) - hi, -lo}; + return {(lo_ > 0 ? static_cast(-1) : Th{0}) - hi_, -lo_}; } [[nodiscard]] constexpr double_width_int operator>>(unsigned n) const { if (n >= base_width) { - return {static_cast(hi < 0 ? -1 : 0), static_cast(hi >> (n - base_width))}; + return {static_cast(hi_ < 0 ? -1 : 0), static_cast(hi_ >> (n - base_width))}; } - return {hi >> n, (static_cast(hi) << (base_width - n)) | (lo >> n)}; + return {hi_ >> n, (static_cast(hi_) << (base_width - n)) | (lo_ >> n)}; } [[nodiscard]] constexpr double_width_int operator<<(unsigned n) const { if (n >= base_width) { - return {static_cast(lo << (n - base_width)), 0}; + return {static_cast(lo_ << (n - base_width)), 0}; } - return {(hi << n) + static_cast(lo >> (base_width - n)), lo << n}; + return {(hi_ << n) + static_cast(lo_ >> (base_width - n)), lo_ << n}; } static constexpr double_width_int max() { return {std::numeric_limits::max(), std::numeric_limits::max()}; } - Th hi; - Tl lo; +private: + Th hi_; + Tl lo_; }; #if defined(__SIZEOF_INT128__) @@ -258,8 +278,6 @@ using uint128_t = double_width_int; constexpr std::size_t max_native_width = 64; #endif -template -constexpr std::size_t integer_rep_width_v = std::numeric_limits>::digits; template constexpr std::size_t integer_rep_width_v> = double_width_int::width; diff --git a/test/runtime/fixed_point_test.cpp b/test/runtime/fixed_point_test.cpp index 2aa621984..c24928fa2 100644 --- a/test/runtime/fixed_point_test.cpp +++ b/test/runtime/fixed_point_test.cpp @@ -93,10 +93,11 @@ auto combine_bits(Hi hi, Lo lo) template void check(double_width_int value, V&& visitor) { - auto as_standard_int = combine_bits(value.hi, value.lo); + using DT = double_width_int_t; + auto as_standard_int = static_cast
(value); auto expected = visitor(as_standard_int); auto actual = visitor(value); - auto actual_as_standard = combine_bits(actual.hi, actual.lo); + auto actual_as_standard = static_cast
(actual); REQUIRE(actual_as_standard == expected); } @@ -133,7 +134,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v + rhs; }); check(lhs, [&](auto v) { return v - rhs; }); check(lhs, [&](auto v) { return rhs - v; }); @@ -143,7 +144,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v + rhs; }); check(lhs, [&](auto v) { return v - rhs; }); check(lhs, [&](auto v) { return rhs - v; }); @@ -153,7 +154,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v + rhs; }); check(lhs, [&](auto v) { return v - rhs; }); check(lhs, [&](auto v) { return rhs - v; }); @@ -163,7 +164,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v + rhs; }); check(lhs, [&](auto v) { return v - rhs; }); check(lhs, [&](auto v) { return rhs - v; }); @@ -179,7 +180,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") CAPTURE(lhs, rhs); u64 expected = u64{lhs} * u64{rhs}; auto actual = double_width_int::wide_product_of(lhs, rhs); - u64 actual_as_std = combine_bits(actual.hi, actual.lo); + auto actual_as_std = static_cast(actual); REQUIRE(actual_as_std == expected); } } @@ -189,7 +190,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") CAPTURE(lhs, rhs); i64 expected = i64{lhs} * i64{rhs}; auto actual = double_width_int::wide_product_of(lhs, rhs); - i64 actual_as_std = combine_bits(actual.hi, actual.lo); + auto actual_as_std = static_cast(actual); REQUIRE(actual_as_std == expected); } } @@ -197,7 +198,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v * rhs; }); } } @@ -205,7 +206,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v * rhs; }); } } @@ -213,7 +214,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v * rhs; }); } } @@ -221,7 +222,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") { for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); - double_width_int lhs{lhi, llo}; + auto lhs = double_width_int::from_hi_lo(lhi, llo); check(lhs, [&](auto v) { return v * rhs; }); } } From 1b574049ff7a4bbf1fe3ebb7a7d8ae78f82b22e8 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Wed, 6 Nov 2024 19:30:46 +0100 Subject: [PATCH 15/34] attempt to fix tests on apple clang --- test/runtime/fixed_point_test.cpp | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/runtime/fixed_point_test.cpp b/test/runtime/fixed_point_test.cpp index c24928fa2..43974dac5 100644 --- a/test/runtime/fixed_point_test.cpp +++ b/test/runtime/fixed_point_test.cpp @@ -135,9 +135,9 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v + rhs; }); - check(lhs, [&](auto v) { return v - rhs; }); - check(lhs, [&](auto v) { return rhs - v; }); + check(lhs, [r = rhs](auto v) { return v + r; }); + check(lhs, [r = rhs](auto v) { return v - r; }); + check(lhs, [r = rhs](auto v) { return r - v; }); } } SECTION("u32x2 +/- i32") @@ -145,9 +145,9 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v + rhs; }); - check(lhs, [&](auto v) { return v - rhs; }); - check(lhs, [&](auto v) { return rhs - v; }); + check(lhs, [r = rhs](auto v) { return v + r; }); + check(lhs, [r = rhs](auto v) { return v - r; }); + check(lhs, [r = rhs](auto v) { return r - v; }); } } SECTION("i32x2 +/- u32") @@ -155,9 +155,9 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v + rhs; }); - check(lhs, [&](auto v) { return v - rhs; }); - check(lhs, [&](auto v) { return rhs - v; }); + check(lhs, [r = rhs](auto v) { return v + r; }); + check(lhs, [r = rhs](auto v) { return v - r; }); + check(lhs, [r = rhs](auto v) { return r - v; }); } } SECTION("i32x2 +/- i32") @@ -165,9 +165,9 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v + rhs; }); - check(lhs, [&](auto v) { return v - rhs; }); - check(lhs, [&](auto v) { return rhs - v; }); + check(lhs, [r = rhs](auto v) { return v + r; }); + check(lhs, [r = rhs](auto v) { return v - r; }); + check(lhs, [r = rhs](auto v) { return r - v; }); } } } @@ -199,7 +199,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v * rhs; }); + check(lhs, [r = rhs](auto v) { return v * r; }); } } SECTION("u32x2 * i32") @@ -207,7 +207,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v * rhs; }); + check(lhs, [r = rhs](auto v) { return v * r; }); } } SECTION("i32x2 * u32") @@ -215,7 +215,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v * rhs; }); + check(lhs, [r = rhs](auto v) { return v * r; }); } } SECTION("i32x2 * i32") @@ -223,7 +223,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]") for (auto [lhi, llo, rhs] : cartesian_product(test_values(), test_values(), test_values())) { CAPTURE(lhi, llo, rhs); auto lhs = double_width_int::from_hi_lo(lhi, llo); - check(lhs, [&](auto v) { return v * rhs; }); + check(lhs, [r = rhs](auto v) { return v * r; }); } } } From f673619d6bc70b3b579947f4946efa6a651abd0c Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Wed, 6 Nov 2024 22:01:24 +0100 Subject: [PATCH 16/34] try to work around issues around friend instantiations of double_width_int --- src/core/include/mp-units/bits/fixed_point.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index f0ab86188..9ccc907fb 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -59,8 +59,7 @@ struct double_width_int { private: constexpr double_width_int(Th hi, Tl lo) : hi_(hi), lo_(lo) {} - template - friend struct double_width_int; + friend struct double_width_int, std::make_signed_t>>; public: static constexpr double_width_int from_hi_lo(Th hi, Tl lo) { return {hi, lo}; } From f642d37035d051debe7f6f9ec61b0f35c023f5bd Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Sat, 9 Nov 2024 23:22:34 +0100 Subject: [PATCH 17/34] fix: gcc-12 friend compilation issue workaround --- src/core/include/mp-units/bits/fixed_point.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 9ccc907fb..3ef8b2ea7 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -56,7 +56,9 @@ struct double_width_int { constexpr double_width_int() = default; +#if !MP_UNITS_COMP_GCC || MP_UNITS_COMP_GCC > 12 private: +#endif constexpr double_width_int(Th hi, Tl lo) : hi_(hi), lo_(lo) {} friend struct double_width_int, std::make_signed_t>>; @@ -259,7 +261,9 @@ struct double_width_int { static constexpr double_width_int max() { return {std::numeric_limits::max(), std::numeric_limits::max()}; } +#if !MP_UNITS_COMP_GCC || MP_UNITS_COMP_GCC > 12 private: +#endif Th hi_; Tl lo_; }; From b6a67524f8ab499c855fd907304329d3c5450474 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 10 Nov 2024 22:18:27 +0100 Subject: [PATCH 18/34] implement dedicated facilities to customise scaling of numbers with magnitudes --- example/measurement.cpp | 34 ++- src/core/CMakeLists.txt | 1 + src/core/include/mp-units/bits/scaling.h | 238 ++++++++++++++++++ src/core/include/mp-units/bits/sudo_cast.h | 79 +----- .../mp-units/framework/customization_points.h | 36 +++ .../framework/representation_concepts.h | 62 +++-- 6 files changed, 346 insertions(+), 104 deletions(-) create mode 100644 src/core/include/mp-units/bits/scaling.h diff --git a/example/measurement.cpp b/example/measurement.cpp index 596361132..6eaeaef4e 100644 --- a/example/measurement.cpp +++ b/example/measurement.cpp @@ -70,15 +70,37 @@ class measurement { [[nodiscard]] friend constexpr measurement operator+(const measurement& lhs, const measurement& rhs) { using namespace std; - return measurement(lhs.value() + rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())); + return measurement{std::in_place, lhs.value() + rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; } [[nodiscard]] friend constexpr measurement operator-(const measurement& lhs, const measurement& rhs) { using namespace std; - return measurement(lhs.value() - rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())); + return measurement{std::in_place, lhs.value() - rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; } + template + [[nodiscard]] constexpr measurement scale(std::type_identity>, M scaling_factor) const + { + constexpr std::type_identity to_value_type; + return measurement{ + std::in_place, + mp_units::scale(to_value_type, scaling_factor, value()), + mp_units::scale(to_value_type, scaling_factor, value()), + }; + } + + template + [[nodiscard]] constexpr auto scale(M scaling_factor) const + { + return measurement{ + std::in_place, + mp_units::scale(scaling_factor, value()), + mp_units::scale(scaling_factor, value()), + }; + } + + [[nodiscard]] friend constexpr measurement operator*(const measurement& lhs, const measurement& rhs) { const auto val = lhs.value() * rhs.value(); @@ -127,15 +149,23 @@ class measurement { private: value_type value_{}; value_type uncertainty_{}; + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + constexpr measurement(std::in_place_t, value_type val, value_type err) : + value_(std::move(val)), uncertainty_(std::move(err)) + { + } }; } // namespace + template constexpr bool mp_units::is_scalar> = true; template constexpr bool mp_units::is_vector> = true; +static_assert(mp_units::RepresentationOf, mp_units::quantity_character::scalar>); static_assert(mp_units::RepresentationOf, mp_units::quantity_character::scalar>); namespace { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3eaef43c0..59f557906 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,6 +40,7 @@ add_mp_units_module( include/mp-units/bits/module_macros.h include/mp-units/bits/quantity_spec_hierarchy.h include/mp-units/bits/ratio.h + include/mp-units/bits/scaling.h include/mp-units/bits/sudo_cast.h include/mp-units/bits/text_tools.h include/mp-units/bits/type_list.h diff --git a/src/core/include/mp-units/bits/scaling.h b/src/core/include/mp-units/bits/scaling.h new file mode 100644 index 000000000..de629a6cd --- /dev/null +++ b/src/core/include/mp-units/bits/scaling.h @@ -0,0 +1,238 @@ +// 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 + +// IWYU pragma: private, include +#include +#include + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif +#endif + + +namespace mp_units { + +namespace detail { + +template +using minimal_floating_point_type = + std::conditional_t<(std::numeric_limits::digits >= std::numeric_limits::digits), A, B>; + +template +constexpr auto cast_integral(const T& value) +{ + if constexpr (std::is_integral_v) { + return static_cast(value); + } else { + return value; + } +} + +template +struct floating_point_scaling_factor_type { + // fallback implementation for types with a `value_type` nested type + using type = + std::enable_if_t, T>, floating_point_scaling_factor_type>>::type; +}; + +template +struct floating_point_scaling_factor_type : std::type_identity {}; + +template +struct floating_point_scaling_factor_type : std::common_type {}; + +template + requires requires { typename scaling_traits::floating_point_scaling_factor_type; } +struct floating_point_scaling_factor_type : + std::type_identity::floating_point_scaling_factor_type> {}; + + +template +struct floating_point_scaling_impl { + static constexpr Magnitude auto num = _numerator(M); + static constexpr Magnitude auto den = _denominator(M); + static constexpr Magnitude auto irr = M * (den / num); + template + static constexpr T ratio = [] { + using U = long double; + return static_cast(_get_value(M)); + }(); + + template + static constexpr To scale(const From& value) + { + using U = minimal_floating_point_type::type, + typename floating_point_scaling_factor_type::type>; + if constexpr (_is_integral(M)) { + return static_cast(cast_integral(value) * _get_value(num)); + } else if constexpr (_is_integral(_pow<-1>(M))) { + return static_cast(cast_integral(value) / _get_value(den)); + } else { + return static_cast(cast_integral(value) * ratio); + } + } +}; + +/** + * @brief Default implementation of `scaling_traits` for "floating-point like" types + * + * This class implements scaling by either multiplying or dividing the value with + * a floating-point representation of the scaling factor; the floating-point representation + * is chosen such that it is of comparable precision as the representation type, + * + * @note This is a low-level facility. Neither the `From`, nor the `To` types of the scaling + * operation are actually constrained to be floating-point or even "floating-point like" types. + * All it represents is the scaling operation as implemented by multiplication with a floating-point + * representation of the scaling factor. This is also used whenever simultaneously scaling and + * converting between integer and floating-point types. + * + * @tparam Rep Representation type + */ +template +struct floating_point_scaling_traits { + template + static constexpr Rep scale_from(M, const From& value) + { + return floating_point_scaling_impl::template scale(value); + }; + + template + static constexpr auto scale(M, const Rep& value) + { + // for standard floating-point types, the result respresentation is always the same + return floating_point_scaling_impl::template scale(value); + } +}; + + +template +struct fixed_point_scaling_impl { + static constexpr Magnitude auto num = _numerator(M); + static constexpr Magnitude auto den = _denominator(M); + template + static constexpr auto ratio = [] { + using U = long double; + return detail::fixed_point(_get_value(M)); + }(); + + template + requires std::integral> && std::integral> + static constexpr To scale(const From& value) + { + using U = std::common_type_t, value_type_t>; + if constexpr (_is_integral(M)) { + return static_cast(static_cast>(value) * _get_value(num)); + } else if constexpr (_is_integral(_pow<-1>(M))) { + return static_cast(static_cast>(value) / _get_value(den)); + } else { + return static_cast(ratio.scale(static_cast>(value))); + } + } +}; + + +template +struct fixed_point_scaling_traits { + template + static constexpr Rep scale_from(M, const From& value) + { + return fixed_point_scaling_impl::template scale(value); + }; + + template + static constexpr auto scale(M, const Rep& value) + { + // for standard integer types, the result respresentation is always the same + return fixed_point_scaling_impl::template scale(value); + } +}; + + +template +inline constexpr auto select_scaling_traits = [] { + if constexpr (requires { + // we only check if the traits class is complete, not it's members; we do not want to fall-back + // depending on the argument types + { sizeof(mp_units::scaling_traits) } -> std::integral; + }) { + // traits class is defined; use that + return mp_units::scaling_traits{}; + } else { + // undefined traits class; fall-back to default handling + if constexpr (mp_units::treat_as_floating_point || mp_units::treat_as_floating_point) { + return floating_point_scaling_traits{}; + } else if constexpr (std::is_integral_v || + (std::is_integral_v> && std::convertible_to, T> && + std::convertible_to>)) { + return fixed_point_scaling_traits>{}; + } else { + // placeholder to report failure + return std::false_type{}; + } + } +}(); + +template +concept HasScalingTraits = !std::convertible_to), std::false_type>; + +static_assert(HasScalingTraits); +static_assert(HasScalingTraits); + +} // namespace detail + +template + requires detail::HasScalingTraits || + requires(const From& value) { value.scale(std::type_identity{}, M{}); } +constexpr To scale(std::type_identity to_type, M scaling_factor, const From& value) +{ + if constexpr (requires { + { value.scale(to_type, scaling_factor) } -> std::convertible_to; + }) { + return value.scale(to_type, scaling_factor); + } else { + return detail::select_scaling_traits.scale_from(scaling_factor, value); + } +} + +template + requires detail::HasScalingTraits || requires(const From& value) { value.scale(M{}); } +constexpr auto scale(M scaling_factor, const From& value) +{ + if constexpr (requires { value.scale(scaling_factor); }) { + return value.scale(scaling_factor); + } else { + return detail::select_scaling_traits.scale(scaling_factor, value); + } +} + +static_assert(_is_integral(mag<299'792'458>)); +static_assert(_get_value(mag<299'792'458>) == 299'792'458); +static_assert(scale(mag<299'792'458>, 1) == 299'792'458); +static_assert(scale(std::type_identity{}, mag<299'792'458>, 1) == 299'792'458); + +} // namespace mp_units diff --git a/src/core/include/mp-units/bits/sudo_cast.h b/src/core/include/mp-units/bits/sudo_cast.h index a194308e9..3cce14ff7 100644 --- a/src/core/include/mp-units/bits/sudo_cast.h +++ b/src/core/include/mp-units/bits/sudo_cast.h @@ -53,77 +53,9 @@ using maybe_common_type = template struct conversion_type_traits { using c_rep_type = maybe_common_type; - using c_mag_type = common_magnitude_type; - using c_type = conditional>, value_type_t, - std::common_type_t>; + using c_type = conditional>, value_type_t, double>; }; -/** - * @brief Value-related details about the conversion from one quantity to another - * - * This trait provide ingredients to calculate the conversion factor that needs to be applied - * to a number, in order to convert from one quantity to another. - * - * @note This is a low-level facility. - * - * @tparam M common magnitude between the two quantities - * @tparam T common multiplier representation type - */ -template -struct conversion_value_traits { - static constexpr Magnitude auto num = _numerator(M); - static constexpr Magnitude auto den = _denominator(M); - static constexpr Magnitude auto irr = M * (den / num); - static constexpr auto ratio = [] { - if constexpr (std::is_integral_v) { - using U = long double; - return detail::fixed_point{_get_value(num) / _get_value(den) * _get_value(irr)}; - } else { - return _get_value(num) / _get_value(den) * _get_value(irr); - } - }(); - static constexpr bool value_increases = ratio >= T{1}; - - template - static constexpr auto scale(V value) - { - if constexpr (std::is_integral_v) { - return ratio.scale(value); - } else { - return value * ratio; - } - } -}; - -template - requires(_is_integral(M)) -struct conversion_value_traits { - static constexpr Magnitude auto num = _numerator(M); - static constexpr T num_mult = _get_value(num); - static constexpr bool value_increases = true; - - template - static constexpr auto scale(V value) - { - return value * num_mult; - } -}; - -template - requires(_is_integral(_pow<-1>(M)) && !_is_integral(M)) -struct conversion_value_traits { - static constexpr Magnitude auto den = _denominator(M); - static constexpr T den_div = _get_value(den); - static constexpr bool value_increases = false; - - template - static constexpr auto scale(V value) - { - return value / den_div; - } -}; - - /** * @brief Explicit cast between different quantity types * @@ -147,11 +79,9 @@ template; - using value_traits = conversion_value_traits; - auto res = static_cast( - value_traits::scale(static_cast(q.numerical_value_is_an_implementation_detail_))); + typename To::rep res = + scale(std::type_identity{}, c_mag, q.numerical_value_is_an_implementation_detail_); return To{res, To::reference}; } } @@ -193,8 +123,7 @@ template; using c_type = type_traits::c_type; - using value_traits = conversion_value_traits; - if constexpr (value_traits::value_increases) { + if constexpr (_get_value(c_mag) > 1.) { // original unit had a larger unit magnitude; if we first convert to the common representation but retain the // unit, we obtain the largest possible range while not causing truncation of fractional values. This is optimal // for the offset computation. diff --git a/src/core/include/mp-units/framework/customization_points.h b/src/core/include/mp-units/framework/customization_points.h index 29dcda6ab..004b0a423 100644 --- a/src/core/include/mp-units/framework/customization_points.h +++ b/src/core/include/mp-units/framework/customization_points.h @@ -149,6 +149,42 @@ struct quantity_values { } }; + +/** + * @brief A type trait that defines the behavior of scaling a value using a magnitude + * + * Whereas C++ numeric types usually represent a (fixed) subset of the real numbers + * (or another vector-space over the field of the real numbers), + * the magnitude concept fundamentally can represent any real number. + * Thus, in general, the result of a scaling operation is not exactly representable, + * and some form of approximation may be needed. That approximation is not + * part of the semantics of a physical quantitiy, but of its representation + * in C++. Therefore, the approximation semantics are dictatet by the + * representation type, which can be customised for user-types through + * this type-trait. + * + * In the following, $\mathcal{V}$ shall denote the vector-space represented by all representation + * types involved in the following discussion. + * + * A specialisation @c scaling_traits for a type @c Rep shall provide the following + * template static method members: + * - `template static constexpr Rep scale_from(M scaling_factor, const From &value)`: + * Given an element of $\mathcal{V}$ represented by @c value and, a real number represented by @c scaling_factor, + * return an instance of @c Rep that approximates `scaling_factor * value`, another element of $\mathcal{V}$. + * This needs to be defined at least for `From = Rep`, as well as any other representation + * types for which interoperability is desired. + * - `template static constexpr auto scale(M scaling_factor, const Rep &value)`: + * Given an element of $\mathcal{V}$ represented by @c value and, a real number represented by @c scaling_factor, + * return an approximation of `scaling_factor * value`, another element of $\mathcal{V}$. + * Contrary to the `scale_from` case, here, the result representation is unspecified. + * Because the @c scaling_factor encodes the represented real value in the type, + * a representation may even depend on the actual scaling factor. + * + * @tparam Rep a representation type for which a type trait is defined + */ +template +struct scaling_traits; + /** * @brief Provides support for external quantity-like types * diff --git a/src/core/include/mp-units/framework/representation_concepts.h b/src/core/include/mp-units/framework/representation_concepts.h index d802163d7..e30ead8d4 100644 --- a/src/core/include/mp-units/framework/representation_concepts.h +++ b/src/core/include/mp-units/framework/representation_concepts.h @@ -24,7 +24,9 @@ // IWYU pragma: private, include #include +#include #include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE #ifdef MP_UNITS_IMPORT_STD @@ -39,6 +41,7 @@ import std; namespace mp_units { + /** * @brief Quantity character * @@ -66,6 +69,26 @@ namespace detail { template concept WeaklyRegular = std::copyable && std::equality_comparable; +/** + * @brief MagnitudeScalable + * + * Physical quantities can be represented as a product of a "number" $n$ times a unit $u$. + * Because the units $u$ of each physical dimension form a (mathematical) vector space over + * the field of the real numbers, and each such unit admits an equivalent representation + * of the same physical quantitiy, the "number" will have to embed the structure of the + * same (mathematical) vector space over the real numbers. That is, + * for some scaled unit $u' = f \cdot u$ ($f \in \mathcal{R}$), the two representations + * $(n \cdot f) \times u$ and $n \times u'$ are equivalent, and thus the mathematical space + * embedding $n$ must admit scaling by a real number. Same for addition. + * + */ + +template +concept MagnitudeScalable = detail::WeaklyRegular && requires(T a, T b, std::type_identity to_type) { + { mp_units::scale(mag<1>, a) }; + { mp_units::scale(to_type, mag<1>, a) } -> std::convertible_to; +}; + template concept Scalar = is_scalar; @@ -88,13 +111,9 @@ template using scaling_factor_type_t = conditional, long double, std::intmax_t>; template -concept ScalarRepresentation = Scalar && WeaklyRegular && requires(T a, T b, scaling_factor_type_t f) { - // scaling - { a* f } -> Scalar; - { f* a } -> Scalar; - { a / f } -> Scalar; - +concept ScalarRepresentation = Scalar && MagnitudeScalable && requires(T a, T b, scaling_factor_type_t f) { // scalar operations + { -a } -> Scalar; { a + b } -> Scalar; { a - b } -> Scalar; { a* b } -> Scalar; @@ -102,16 +121,9 @@ concept ScalarRepresentation = Scalar && WeaklyRegular && requires(T a, T }; template -concept ComplexRepresentation = Complex && WeaklyRegular && requires(T a, T b, scaling_factor_type_t f) { - // scaling - // TODO The below conversion to `T` is an exception compared to other representation types - // `std::complex` * `U` do not work, but `std::complex` is convertible from `U` - // Maybe expose this as a customization point? - { a* T(f) } -> Complex; - { T(f) * a } -> Complex; - { a / T(f) } -> Complex; - +concept ComplexRepresentation = Complex && MagnitudeScalable && requires(T a, T b, scaling_factor_type_t f) { // complex operations + { -a } -> Complex; { a + b } -> Complex; { a - b } -> Complex; { a* b } -> Complex; @@ -127,13 +139,9 @@ concept ComplexRepresentation = Complex && WeaklyRegular && requires(T a, // TODO how to check for a complex(Scalar, Scalar) -> Complex? template -concept VectorRepresentation = Vector && WeaklyRegular && requires(T a, T b, scaling_factor_type_t f) { - // scaling - { a* f } -> Vector; - { f* a } -> Vector; - { a / f } -> Vector; - +concept VectorRepresentation = Vector && MagnitudeScalable && requires(T a, T b, scaling_factor_type_t f) { // vector operations + { -a } -> Vector; { a + b } -> Vector; { a - b } -> Vector; // TBD @@ -148,12 +156,12 @@ concept VectorRepresentation = Vector && WeaklyRegular && requires(T a, T }; template -concept TensorRepresentation = Tensor && WeaklyRegular; // && requires(T a, T b) { - // TBD - // tensor operations - // { tensor_product(a, b) } -> Tensor4; - // { inner_product(a, b) } -> Tensor2; - // { scalar_product(a, b) } -> Scalar; +concept TensorRepresentation = Tensor && MagnitudeScalable; // && requires(T a, T b) { + // TBD + // tensor operations + // { tensor_product(a, b) } -> Tensor4; + // { inner_product(a, b) } -> Tensor2; + // { scalar_product(a, b) } -> Scalar; //}; } // namespace detail From 647ce6b8a7ddcd90616aac5da4246bdeb9efca4e Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Sun, 10 Nov 2024 23:10:38 +0100 Subject: [PATCH 19/34] fixed a few more details --- src/core/include/mp-units/bits/scaling.h | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/core/include/mp-units/bits/scaling.h b/src/core/include/mp-units/bits/scaling.h index de629a6cd..869dc4d2f 100644 --- a/src/core/include/mp-units/bits/scaling.h +++ b/src/core/include/mp-units/bits/scaling.h @@ -41,7 +41,7 @@ namespace detail { template using minimal_floating_point_type = - std::conditional_t<(std::numeric_limits::digits >= std::numeric_limits::digits), A, B>; + std::conditional_t<(std::numeric_limits::digits <= std::numeric_limits::digits), A, B>; template constexpr auto cast_integral(const T& value) @@ -76,7 +76,6 @@ template struct floating_point_scaling_impl { static constexpr Magnitude auto num = _numerator(M); static constexpr Magnitude auto den = _denominator(M); - static constexpr Magnitude auto irr = M * (den / num); template static constexpr T ratio = [] { using U = long double; @@ -200,9 +199,6 @@ inline constexpr auto select_scaling_traits = [] { template concept HasScalingTraits = !std::convertible_to), std::false_type>; -static_assert(HasScalingTraits); -static_assert(HasScalingTraits); - } // namespace detail template @@ -230,9 +226,4 @@ constexpr auto scale(M scaling_factor, const From& value) } } -static_assert(_is_integral(mag<299'792'458>)); -static_assert(_get_value(mag<299'792'458>) == 299'792'458); -static_assert(scale(mag<299'792'458>, 1) == 299'792'458); -static_assert(scale(std::type_identity{}, mag<299'792'458>, 1) == 299'792'458); - } // namespace mp_units From e933be7e2021ec5b2a47ee02dd048e1b485f7e78 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 11 Nov 2024 09:42:35 +0100 Subject: [PATCH 20/34] fix a few issues uncovered in CI --- src/core/include/mp-units/bits/fixed_point.h | 4 ++++ src/core/include/mp-units/bits/scaling.h | 5 +++-- test/runtime/fixed_point_test.cpp | 9 +++------ test/static/custom_rep_test_min_impl.cpp | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 3ef8b2ea7..1b06526c0 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -301,6 +301,10 @@ using min_width_uint_t = template using min_width_int_t = make_signed_t>; +// TODO: other standard floating point types (half-width floats?) +template +using min_digit_float_t = std::conditional_t<(N<=std::numeric_limits::digits),float, std::conditional_t<(N<=std::numeric_limits::digits),double,long double>>; + template using double_width_int_for_t = std::conditional_t, min_width_int_t * 2>, min_width_uint_t * 2>>; diff --git a/src/core/include/mp-units/bits/scaling.h b/src/core/include/mp-units/bits/scaling.h index 869dc4d2f..540fb4ac7 100644 --- a/src/core/include/mp-units/bits/scaling.h +++ b/src/core/include/mp-units/bits/scaling.h @@ -46,7 +46,7 @@ using minimal_floating_point_type = template constexpr auto cast_integral(const T& value) { - if constexpr (std::is_integral_v) { + if constexpr (std::is_integral_v>) { return static_cast(value); } else { return value; @@ -63,8 +63,9 @@ struct floating_point_scaling_factor_type { template struct floating_point_scaling_factor_type : std::type_identity {}; +// try to choose the smallest standard floating-point type which can represent the integer exactly (has at least as many mantiassa bits as the integer is wide) template -struct floating_point_scaling_factor_type : std::common_type {}; +struct floating_point_scaling_factor_type : std::type_identity::digits>> {}; template requires requires { typename scaling_traits::floating_point_scaling_factor_type; } diff --git a/test/runtime/fixed_point_test.cpp b/test/runtime/fixed_point_test.cpp index 43974dac5..781fb2d75 100644 --- a/test/runtime/fixed_point_test.cpp +++ b/test/runtime/fixed_point_test.cpp @@ -76,24 +76,21 @@ std::vector> cartesian_product(const std::vector&... src) template -using half_width_int_t = std::conditional_t, min_width_int_t / 2>, +using half_width_int_for_t = std::conditional_t, min_width_int_t / 2>, min_width_uint_t / 2>>; -template -using double_width_int_t = std::conditional_t, min_width_int_t * 2>, - min_width_uint_t * 2>>; template requires(integer_rep_width_v == integer_rep_width_v) auto combine_bits(Hi hi, Lo lo) { - using ret_t = double_width_int_t; + using ret_t = double_width_int_for_t; return (static_cast(hi) << integer_rep_width_v)+static_cast(lo); } template void check(double_width_int value, V&& visitor) { - using DT = double_width_int_t; + using DT = double_width_int_for_t; auto as_standard_int = static_cast
(value); auto expected = visitor(as_standard_int); auto actual = visitor(value); diff --git a/test/static/custom_rep_test_min_impl.cpp b/test/static/custom_rep_test_min_impl.cpp index e2810eaac..3e31334cd 100644 --- a/test/static/custom_rep_test_min_impl.cpp +++ b/test/static/custom_rep_test_min_impl.cpp @@ -70,6 +70,7 @@ struct std::common_type, U> : std::type_identity struct std::common_type> : std::type_identity>> {}; + namespace { using namespace mp_units; From 6873c8bfcfdebb65b6c41160224706113e27b2ea Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 11 Nov 2024 09:49:03 +0100 Subject: [PATCH 21/34] fix formatting --- src/core/include/mp-units/bits/fixed_point.h | 4 +++- src/core/include/mp-units/bits/scaling.h | 7 ++++--- test/runtime/fixed_point_test.cpp | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/include/mp-units/bits/fixed_point.h b/src/core/include/mp-units/bits/fixed_point.h index 1b06526c0..a3f72d7b7 100644 --- a/src/core/include/mp-units/bits/fixed_point.h +++ b/src/core/include/mp-units/bits/fixed_point.h @@ -303,7 +303,9 @@ using min_width_int_t = make_signed_t>; // TODO: other standard floating point types (half-width floats?) template -using min_digit_float_t = std::conditional_t<(N<=std::numeric_limits::digits),float, std::conditional_t<(N<=std::numeric_limits::digits),double,long double>>; +using min_digit_float_t = + std::conditional_t<(N <= std::numeric_limits::digits), float, + std::conditional_t<(N <= std::numeric_limits::digits), double, long double>>; template using double_width_int_for_t = std::conditional_t, min_width_int_t * 2>, diff --git a/src/core/include/mp-units/bits/scaling.h b/src/core/include/mp-units/bits/scaling.h index 540fb4ac7..b8bfa22f0 100644 --- a/src/core/include/mp-units/bits/scaling.h +++ b/src/core/include/mp-units/bits/scaling.h @@ -63,7 +63,8 @@ struct floating_point_scaling_factor_type { template struct floating_point_scaling_factor_type : std::type_identity {}; -// try to choose the smallest standard floating-point type which can represent the integer exactly (has at least as many mantiassa bits as the integer is wide) +// try to choose the smallest standard floating-point type which can represent the integer exactly (has at least as many +// mantiassa bits as the integer is wide) template struct floating_point_scaling_factor_type : std::type_identity::digits>> {}; @@ -119,7 +120,7 @@ struct floating_point_scaling_traits { static constexpr Rep scale_from(M, const From& value) { return floating_point_scaling_impl::template scale(value); - }; + } template static constexpr auto scale(M, const Rep& value) @@ -162,7 +163,7 @@ struct fixed_point_scaling_traits { static constexpr Rep scale_from(M, const From& value) { return fixed_point_scaling_impl::template scale(value); - }; + } template static constexpr auto scale(M, const Rep& value) diff --git a/test/runtime/fixed_point_test.cpp b/test/runtime/fixed_point_test.cpp index 781fb2d75..c56dc0315 100644 --- a/test/runtime/fixed_point_test.cpp +++ b/test/runtime/fixed_point_test.cpp @@ -77,7 +77,7 @@ std::vector> cartesian_product(const std::vector&... src) template using half_width_int_for_t = std::conditional_t, min_width_int_t / 2>, - min_width_uint_t / 2>>; + min_width_uint_t / 2>>; template requires(integer_rep_width_v == integer_rep_width_v) From 65a0ee4ce91f0d4216a34a675c9f8ad6537cfd13 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 11 Nov 2024 15:57:26 +0100 Subject: [PATCH 22/34] fix module exports - does not yet inlude other review input --- src/core/include/mp-units/bits/scaling.h | 17 +++++++++-------- .../mp-units/framework/customization_points.h | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/core/include/mp-units/bits/scaling.h b/src/core/include/mp-units/bits/scaling.h index b8bfa22f0..611d81576 100644 --- a/src/core/include/mp-units/bits/scaling.h +++ b/src/core/include/mp-units/bits/scaling.h @@ -76,8 +76,6 @@ struct floating_point_scaling_factor_type : template struct floating_point_scaling_impl { - static constexpr Magnitude auto num = _numerator(M); - static constexpr Magnitude auto den = _denominator(M); template static constexpr T ratio = [] { using U = long double; @@ -90,9 +88,9 @@ struct floating_point_scaling_impl { using U = minimal_floating_point_type::type, typename floating_point_scaling_factor_type::type>; if constexpr (_is_integral(M)) { - return static_cast(cast_integral(value) * _get_value(num)); + return static_cast(cast_integral(value) * _get_value(M)); } else if constexpr (_is_integral(_pow<-1>(M))) { - return static_cast(cast_integral(value) / _get_value(den)); + return static_cast(cast_integral(value) / _get_value(_pow<-1>(M))); } else { return static_cast(cast_integral(value) * ratio); } @@ -133,8 +131,6 @@ struct floating_point_scaling_traits { template struct fixed_point_scaling_impl { - static constexpr Magnitude auto num = _numerator(M); - static constexpr Magnitude auto den = _denominator(M); template static constexpr auto ratio = [] { using U = long double; @@ -147,9 +143,9 @@ struct fixed_point_scaling_impl { { using U = std::common_type_t, value_type_t>; if constexpr (_is_integral(M)) { - return static_cast(static_cast>(value) * _get_value(num)); + return static_cast(static_cast>(value) * _get_value(M)); } else if constexpr (_is_integral(_pow<-1>(M))) { - return static_cast(static_cast>(value) / _get_value(den)); + return static_cast(static_cast>(value) / _get_value(_pow<-1>(M))); } else { return static_cast(ratio.scale(static_cast>(value))); } @@ -203,6 +199,8 @@ concept HasScalingTraits = !std::convertible_to requires detail::HasScalingTraits || requires(const From& value) { value.scale(std::type_identity{}, M{}); } @@ -228,4 +226,7 @@ constexpr auto scale(M scaling_factor, const From& value) } } + +MP_UNITS_EXPORT_END + } // namespace mp_units diff --git a/src/core/include/mp-units/framework/customization_points.h b/src/core/include/mp-units/framework/customization_points.h index 004b0a423..0aaac015a 100644 --- a/src/core/include/mp-units/framework/customization_points.h +++ b/src/core/include/mp-units/framework/customization_points.h @@ -173,7 +173,7 @@ struct quantity_values { * return an instance of @c Rep that approximates `scaling_factor * value`, another element of $\mathcal{V}$. * This needs to be defined at least for `From = Rep`, as well as any other representation * types for which interoperability is desired. - * - `template static constexpr auto scale(M scaling_factor, const Rep &value)`: + * - `template static constexpr auto scale(M scaling_factor, const Rep &value)`: * Given an element of $\mathcal{V}$ represented by @c value and, a real number represented by @c scaling_factor, * return an approximation of `scaling_factor * value`, another element of $\mathcal{V}$. * Contrary to the `scale_from` case, here, the result representation is unspecified. From 0c1971eddbb2ea28a9de319ba0a03871328a1dc4 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Mon, 11 Nov 2024 17:48:13 +0100 Subject: [PATCH 23/34] addressed most review input --- example/measurement.cpp | 48 ++-- src/core/CMakeLists.txt | 2 +- src/core/include/mp-units/bits/scaling.h | 232 ------------------ .../mp-units/framework/customization_points.h | 37 +-- .../framework/representation_concepts.h | 4 +- src/core/include/mp-units/framework/scaling.h | 204 +++++++++++++++ 6 files changed, 254 insertions(+), 273 deletions(-) delete mode 100644 src/core/include/mp-units/bits/scaling.h create mode 100644 src/core/include/mp-units/framework/scaling.h diff --git a/example/measurement.cpp b/example/measurement.cpp index 6eaeaef4e..13e146b41 100644 --- a/example/measurement.cpp +++ b/example/measurement.cpp @@ -79,28 +79,6 @@ class measurement { return measurement{std::in_place, lhs.value() - rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; } - template - [[nodiscard]] constexpr measurement scale(std::type_identity>, M scaling_factor) const - { - constexpr std::type_identity to_value_type; - return measurement{ - std::in_place, - mp_units::scale(to_value_type, scaling_factor, value()), - mp_units::scale(to_value_type, scaling_factor, value()), - }; - } - - template - [[nodiscard]] constexpr auto scale(M scaling_factor) const - { - return measurement{ - std::in_place, - mp_units::scale(scaling_factor, value()), - mp_units::scale(scaling_factor, value()), - }; - } - - [[nodiscard]] friend constexpr measurement operator*(const measurement& lhs, const measurement& rhs) { const auto val = lhs.value() * rhs.value(); @@ -160,6 +138,32 @@ class measurement { } // namespace +template +struct mp_units::scaling_traits, mp_units::unspecified_rep> { + template + [[nodiscard]] static constexpr auto scale(const measurement& value) + { + return measurement{ + mp_units::scale(M, value.value()), + mp_units::scale(M, value.uncertainty()), + }; + } +}; + +template +struct mp_units::scaling_traits, measurement> { + template + [[nodiscard]] static constexpr measurement scale(const measurement& value) + { + constexpr std::type_identity to_type; + return measurement{ + mp_units::scale(to_type, M, value.value()), + mp_units::scale(to_type, M, value.uncertainty()), + }; + } +}; + + template constexpr bool mp_units::is_scalar> = true; template diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e5a78c7b7..e068d5cd2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,7 +40,6 @@ add_mp_units_module( include/mp-units/bits/module_macros.h include/mp-units/bits/quantity_spec_hierarchy.h include/mp-units/bits/ratio.h - include/mp-units/bits/scaling.h include/mp-units/bits/sudo_cast.h include/mp-units/bits/text_tools.h include/mp-units/bits/type_list.h @@ -69,6 +68,7 @@ add_mp_units_module( include/mp-units/framework/reference.h include/mp-units/framework/reference_concepts.h include/mp-units/framework/representation_concepts.h + include/mp-units/framework/scaling.h include/mp-units/framework/symbol_text.h include/mp-units/framework/system_reference.h include/mp-units/framework/unit.h diff --git a/src/core/include/mp-units/bits/scaling.h b/src/core/include/mp-units/bits/scaling.h deleted file mode 100644 index 611d81576..000000000 --- a/src/core/include/mp-units/bits/scaling.h +++ /dev/null @@ -1,232 +0,0 @@ -// 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 - -// IWYU pragma: private, include -#include -#include - -#ifndef MP_UNITS_IN_MODULE_INTERFACE -#ifdef MP_UNITS_IMPORT_STD -import std; -#else -#include -#endif -#endif - - -namespace mp_units { - -namespace detail { - -template -using minimal_floating_point_type = - std::conditional_t<(std::numeric_limits::digits <= std::numeric_limits::digits), A, B>; - -template -constexpr auto cast_integral(const T& value) -{ - if constexpr (std::is_integral_v>) { - return static_cast(value); - } else { - return value; - } -} - -template -struct floating_point_scaling_factor_type { - // fallback implementation for types with a `value_type` nested type - using type = - std::enable_if_t, T>, floating_point_scaling_factor_type>>::type; -}; - -template -struct floating_point_scaling_factor_type : std::type_identity {}; - -// try to choose the smallest standard floating-point type which can represent the integer exactly (has at least as many -// mantiassa bits as the integer is wide) -template -struct floating_point_scaling_factor_type : std::type_identity::digits>> {}; - -template - requires requires { typename scaling_traits::floating_point_scaling_factor_type; } -struct floating_point_scaling_factor_type : - std::type_identity::floating_point_scaling_factor_type> {}; - - -template -struct floating_point_scaling_impl { - template - static constexpr T ratio = [] { - using U = long double; - return static_cast(_get_value(M)); - }(); - - template - static constexpr To scale(const From& value) - { - using U = minimal_floating_point_type::type, - typename floating_point_scaling_factor_type::type>; - if constexpr (_is_integral(M)) { - return static_cast(cast_integral(value) * _get_value(M)); - } else if constexpr (_is_integral(_pow<-1>(M))) { - return static_cast(cast_integral(value) / _get_value(_pow<-1>(M))); - } else { - return static_cast(cast_integral(value) * ratio); - } - } -}; - -/** - * @brief Default implementation of `scaling_traits` for "floating-point like" types - * - * This class implements scaling by either multiplying or dividing the value with - * a floating-point representation of the scaling factor; the floating-point representation - * is chosen such that it is of comparable precision as the representation type, - * - * @note This is a low-level facility. Neither the `From`, nor the `To` types of the scaling - * operation are actually constrained to be floating-point or even "floating-point like" types. - * All it represents is the scaling operation as implemented by multiplication with a floating-point - * representation of the scaling factor. This is also used whenever simultaneously scaling and - * converting between integer and floating-point types. - * - * @tparam Rep Representation type - */ -template -struct floating_point_scaling_traits { - template - static constexpr Rep scale_from(M, const From& value) - { - return floating_point_scaling_impl::template scale(value); - } - - template - static constexpr auto scale(M, const Rep& value) - { - // for standard floating-point types, the result respresentation is always the same - return floating_point_scaling_impl::template scale(value); - } -}; - - -template -struct fixed_point_scaling_impl { - template - static constexpr auto ratio = [] { - using U = long double; - return detail::fixed_point(_get_value(M)); - }(); - - template - requires std::integral> && std::integral> - static constexpr To scale(const From& value) - { - using U = std::common_type_t, value_type_t>; - if constexpr (_is_integral(M)) { - return static_cast(static_cast>(value) * _get_value(M)); - } else if constexpr (_is_integral(_pow<-1>(M))) { - return static_cast(static_cast>(value) / _get_value(_pow<-1>(M))); - } else { - return static_cast(ratio.scale(static_cast>(value))); - } - } -}; - - -template -struct fixed_point_scaling_traits { - template - static constexpr Rep scale_from(M, const From& value) - { - return fixed_point_scaling_impl::template scale(value); - } - - template - static constexpr auto scale(M, const Rep& value) - { - // for standard integer types, the result respresentation is always the same - return fixed_point_scaling_impl::template scale(value); - } -}; - - -template -inline constexpr auto select_scaling_traits = [] { - if constexpr (requires { - // we only check if the traits class is complete, not it's members; we do not want to fall-back - // depending on the argument types - { sizeof(mp_units::scaling_traits) } -> std::integral; - }) { - // traits class is defined; use that - return mp_units::scaling_traits{}; - } else { - // undefined traits class; fall-back to default handling - if constexpr (mp_units::treat_as_floating_point || mp_units::treat_as_floating_point) { - return floating_point_scaling_traits{}; - } else if constexpr (std::is_integral_v || - (std::is_integral_v> && std::convertible_to, T> && - std::convertible_to>)) { - return fixed_point_scaling_traits>{}; - } else { - // placeholder to report failure - return std::false_type{}; - } - } -}(); - -template -concept HasScalingTraits = !std::convertible_to), std::false_type>; - -} // namespace detail - -MP_UNITS_EXPORT_BEGIN - -template - requires detail::HasScalingTraits || - requires(const From& value) { value.scale(std::type_identity{}, M{}); } -constexpr To scale(std::type_identity to_type, M scaling_factor, const From& value) -{ - if constexpr (requires { - { value.scale(to_type, scaling_factor) } -> std::convertible_to; - }) { - return value.scale(to_type, scaling_factor); - } else { - return detail::select_scaling_traits.scale_from(scaling_factor, value); - } -} - -template - requires detail::HasScalingTraits || requires(const From& value) { value.scale(M{}); } -constexpr auto scale(M scaling_factor, const From& value) -{ - if constexpr (requires { value.scale(scaling_factor); }) { - return value.scale(scaling_factor); - } else { - return detail::select_scaling_traits.scale(scaling_factor, value); - } -} - - -MP_UNITS_EXPORT_END - -} // namespace mp_units diff --git a/src/core/include/mp-units/framework/customization_points.h b/src/core/include/mp-units/framework/customization_points.h index 0aaac015a..0a2aeaa20 100644 --- a/src/core/include/mp-units/framework/customization_points.h +++ b/src/core/include/mp-units/framework/customization_points.h @@ -150,6 +150,11 @@ struct quantity_values { }; +/** + * @brief A type used in @c scaling_traits to indicate an unspecified @c To type. + */ +struct unspecified_rep {}; + /** * @brief A type trait that defines the behavior of scaling a value using a magnitude * @@ -166,23 +171,23 @@ struct quantity_values { * In the following, $\mathcal{V}$ shall denote the vector-space represented by all representation * types involved in the following discussion. * - * A specialisation @c scaling_traits for a type @c Rep shall provide the following - * template static method members: - * - `template static constexpr Rep scale_from(M scaling_factor, const From &value)`: - * Given an element of $\mathcal{V}$ represented by @c value and, a real number represented by @c scaling_factor, - * return an instance of @c Rep that approximates `scaling_factor * value`, another element of $\mathcal{V}$. - * This needs to be defined at least for `From = Rep`, as well as any other representation - * types for which interoperability is desired. - * - `template static constexpr auto scale(M scaling_factor, const Rep &value)`: - * Given an element of $\mathcal{V}$ represented by @c value and, a real number represented by @c scaling_factor, - * return an approximation of `scaling_factor * value`, another element of $\mathcal{V}$. - * Contrary to the `scale_from` case, here, the result representation is unspecified. - * Because the @c scaling_factor encodes the represented real value in the type, - * a representation may even depend on the actual scaling factor. - * - * @tparam Rep a representation type for which a type trait is defined + * A specialisation @c scaling_traits shall provide the following members: + * - `template static constexpr auto scale(const From &value)`: + * Given an element of $\mathcal{V}$ represented by @c value and, a real number represented by @c M, + * return a value representing `M * value`, another element of $\mathcal{V}$. + * Unless @c ToSpec is the type @c unspecified_rep, the result type is required to be convetrible to @c ToSpec. + * When @c ToSpec is the type @c unspecified_rep, the implemenation may choose the best + * representation availabe. + * Because the scaling factor @c M encodes the represented real value in its type, + * that representation may even depend on the actual scaling factor. + * - `template static constexpr bool implicitly_scalable = ...`: + * When true, the scaling is to be considered "safe", and may be used in implicit conversions. + * + * @tparam From a representation type whose value is being scaled + * @tparam To a representation type in which the result shall be represented, or @c unspecified_rep, indicating + * the implementation is free to chose a representation. */ -template +template struct scaling_traits; /** diff --git a/src/core/include/mp-units/framework/representation_concepts.h b/src/core/include/mp-units/framework/representation_concepts.h index 5c2c32428..25ae9fccf 100644 --- a/src/core/include/mp-units/framework/representation_concepts.h +++ b/src/core/include/mp-units/framework/representation_concepts.h @@ -24,10 +24,10 @@ // IWYU pragma: private, include #include -#include #include #include #include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE #ifdef MP_UNITS_IMPORT_STD @@ -86,7 +86,7 @@ concept WeaklyRegular = std::copyable && std::equality_comparable; template concept MagnitudeScalable = detail::WeaklyRegular && requires(T a, T b, std::type_identity to_type) { - { mp_units::scale(mag<1>, a) }; + { mp_units::scale(mag<1>, a) } -> WeaklyRegular; { mp_units::scale(to_type, mag<1>, a) } -> std::convertible_to; }; diff --git a/src/core/include/mp-units/framework/scaling.h b/src/core/include/mp-units/framework/scaling.h new file mode 100644 index 000000000..1a390989b --- /dev/null +++ b/src/core/include/mp-units/framework/scaling.h @@ -0,0 +1,204 @@ +// 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 + +// IWYU pragma: private, include +#include +#include + +#ifndef MP_UNITS_IN_MODULE_INTERFACE +#ifdef MP_UNITS_IMPORT_STD +import std; +#else +#include +#endif +#endif + + +namespace mp_units { + +namespace detail { + +template +constexpr auto cast_if_integral(const T& value) +{ + if constexpr (std::is_integral_v>) { + return static_cast(value); + } else { + return value; + } +} + +// @brief For a representation type that uses "floating-point scaling", select an appropriate floating-point type as +// scale factor. +template +struct floating_point_scaling_factor_type; + +template +struct floating_point_scaling_factor_type { + using type = T; +}; + +// try to choose the smallest standard floating-point type which can represent the integer exactly (has at least as many +// mantiassa bits as the integer is wide) +template +struct floating_point_scaling_factor_type { + using type = min_digit_float_t::digits>; +}; + +template + requires requires { + typename T::value_type; + typename floating_point_scaling_factor_type::type; + } +struct floating_point_scaling_factor_type { + using type = floating_point_scaling_factor_type::type; +}; + +template +concept UsesFloatingPointScaling = + treat_as_floating_point && requires(T value, floating_point_scaling_factor_type>::type f) { + // the result representation does not necessarily have to be the same. + { value* f } -> std::equality_comparable; + { value* f } -> std::copyable; + }; + +template +concept IsIntegerLike = std::is_integral_v> && std::is_convertible_v> && + std::is_convertible_v, T>; + +template +concept UsesFixedPointScaling = IsIntegerLike; + +template +concept UsesFloatingPointScalingOrIsIntegerLike = UsesFloatingPointScaling || IsIntegerLike; + + +template +concept HasScalingTraits = requires { + { sizeof(mp_units::scaling_traits) } -> std::convertible_to; +}; + +} // namespace detail + + +/** + * @brief `scaling_traits` for representations that scale by multiplication with a float + * + * This class implements scaling by either multiplying or dividing the value with + * a floating-point representation of the scaling factor; the floating-point representation + * is chosen such that it is of comparable precision as the representation type, + * + * It is used for all cases where at least one of the two is "floating-point like", + * and the other one is either "floating-point like" or "integer-like". + * Here, we call type "X-like" if it either is an "X"-standard type, or it has a + * a nested type `value_type` which is an "X"-standard type and those two are implicityl interconvertible. + * + * @tparam Rep Representation type + */ +template + requires((detail::UsesFloatingPointScaling || detail::UsesFloatingPointScaling)) +struct scaling_traits { + using _scaling_factor_type = std::common_type_t, value_type_t>; + static_assert(std::is_floating_point_v<_scaling_factor_type>); + + template + static constexpr bool implicitly_scalable = + std::is_convertible_v(std::declval()) * + std::declval<_scaling_factor_type>()), + To>; + + template + static constexpr To scale(const From& value) + { + using U = _scaling_factor_type; + if constexpr (_is_integral(_pow<-1>(M)) && !_is_integral(M)) { + constexpr U div = static_cast(_get_value(_pow<-1>(M))); + return static_cast(detail::cast_if_integral(value) / div); + } else { + constexpr U ratio = static_cast(_get_value(M)); + return static_cast(detail::cast_if_integral(value) * ratio); + } + } +}; + + +template +struct scaling_traits : scaling_traits {}; + + +template +struct scaling_traits { + using _common_type = std::common_type_t, value_type_t>; + static_assert(std::is_integral_v<_common_type>); + + // TODO: should we take possible overflow into account here? This would lead to this almost always resulting + // in explicit conversions, except for small integral factors combined with a widening conversion. + template + static constexpr bool implicitly_scalable = std::is_convertible_v && _is_integral(M); + + template + static constexpr To scale(const From& value) + { + if constexpr (_is_integral(M)) { + constexpr auto mul = _get_value<_common_type>(M); + return static_cast(static_cast>(value) * mul); + } else if constexpr (_is_integral(_pow<-1>(M))) { + constexpr auto div = _get_value<_common_type>(_pow<-1>(M)); + return static_cast(static_cast>(value) / div); + } else { + constexpr auto ratio = detail::fixed_point<_common_type>(_get_value(M)); + return static_cast(ratio.scale(static_cast>(value))); + } + } +}; + +template +struct scaling_traits : scaling_traits {}; + + +MP_UNITS_EXPORT_BEGIN + +// @brief approximate the result of the symbolic multiplication of @c from by @c scaling_factor, and represent it as an +// instance of @to +template + requires detail::HasScalingTraits +constexpr To scale(std::type_identity, M scaling_factor [[maybe_unused]], const From& value) +{ + static_assert(std::is_convertible_v::template scale(value)), To>, + "scaling_traits::scale must produce a value that is convertible to To"); + return scaling_traits::template scale(value); +} + +// @brief approximate the result of the symbolic multiplication of @c from by @c scaling_factor +template + requires detail::HasScalingTraits +constexpr auto scale(M scaling_factor [[maybe_unused]], const From& value) +{ + return scaling_traits::template scale(value); +} + + +MP_UNITS_EXPORT_END + +} // namespace mp_units From 4ef0210cc14a604a3221ec5d1157e702663abd6c Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 07:34:37 +0100 Subject: [PATCH 24/34] fix includes (and use curly braces for constructor calls in measurment.cpp as per c++ core guidelines) --- example/measurement.cpp | 22 ++++++++-------------- src/core/include/mp-units/framework.h | 1 + 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/example/measurement.cpp b/example/measurement.cpp index 13e146b41..14356ab01 100644 --- a/example/measurement.cpp +++ b/example/measurement.cpp @@ -70,51 +70,51 @@ class measurement { [[nodiscard]] friend constexpr measurement operator+(const measurement& lhs, const measurement& rhs) { using namespace std; - return measurement{std::in_place, lhs.value() + rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; + return measurement{lhs.value() + rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; } [[nodiscard]] friend constexpr measurement operator-(const measurement& lhs, const measurement& rhs) { using namespace std; - return measurement{std::in_place, lhs.value() - rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; + return measurement{lhs.value() - rhs.value(), hypot(lhs.uncertainty(), rhs.uncertainty())}; } [[nodiscard]] friend constexpr measurement operator*(const measurement& lhs, const measurement& rhs) { const auto val = lhs.value() * rhs.value(); using namespace std; - return measurement(val, val * hypot(lhs.relative_uncertainty(), rhs.relative_uncertainty())); + return measurement{val, val * hypot(lhs.relative_uncertainty(), rhs.relative_uncertainty())}; } [[nodiscard]] friend constexpr measurement operator*(const measurement& lhs, const value_type& value) { const auto val = lhs.value() * value; - return measurement(val, val * lhs.relative_uncertainty()); + return measurement{val, val * lhs.relative_uncertainty()}; } [[nodiscard]] friend constexpr measurement operator*(const value_type& value, const measurement& rhs) { const auto val = rhs.value() * value; - return measurement(val, val * rhs.relative_uncertainty()); + return measurement{val, val * rhs.relative_uncertainty()}; } [[nodiscard]] friend constexpr measurement operator/(const measurement& lhs, const measurement& rhs) { const auto val = lhs.value() / rhs.value(); using namespace std; - return measurement(val, val * hypot(lhs.relative_uncertainty(), rhs.relative_uncertainty())); + return measurement{val, val * hypot(lhs.relative_uncertainty(), rhs.relative_uncertainty())}; } [[nodiscard]] friend constexpr measurement operator/(const measurement& lhs, const value_type& value) { const auto val = lhs.value() / value; - return measurement(val, val * lhs.relative_uncertainty()); + return measurement{val, val * lhs.relative_uncertainty()}; } [[nodiscard]] friend constexpr measurement operator/(const value_type& value, const measurement& rhs) { const auto val = value / rhs.value(); - return measurement(val, val * rhs.relative_uncertainty()); + return measurement{val, val * rhs.relative_uncertainty()}; } [[nodiscard]] constexpr auto operator<=>(const measurement&) const = default; @@ -127,12 +127,6 @@ class measurement { private: value_type value_{}; value_type uncertainty_{}; - - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - constexpr measurement(std::in_place_t, value_type val, value_type err) : - value_(std::move(val)), uncertainty_(std::move(err)) - { - } }; } // namespace diff --git a/src/core/include/mp-units/framework.h b/src/core/include/mp-units/framework.h index 2de635797..49aef5672 100644 --- a/src/core/include/mp-units/framework.h +++ b/src/core/include/mp-units/framework.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include From 35ed4729757e6526b5066e4d11dd1dc9f45e3c90 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 18:07:52 +0100 Subject: [PATCH 25/34] first attempt at generating sparse CI run matrix in python; also, cancel previous runs on the same branch --- .github/generate-job-matrix.py | 168 ++++++++++++++++++++ .github/job_matrix.py | 113 +++++++++++++ .github/workflows/ci-clang-tidy.yml | 41 ++--- .github/workflows/ci-conan.yml | 158 +++--------------- .github/workflows/ci-freestanding.yml | 55 +++---- .github/workflows/ci-test-package-cmake.yml | 144 +++-------------- 6 files changed, 367 insertions(+), 312 deletions(-) create mode 100644 .github/generate-job-matrix.py create mode 100644 .github/job_matrix.py diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py new file mode 100644 index 000000000..b2465d056 --- /dev/null +++ b/.github/generate-job-matrix.py @@ -0,0 +1,168 @@ +import argparse +import json +import typing +import itertools +import random +import dataclasses +from types import SimpleNamespace +from dataclasses import dataclass + +from job_matrix import Configuration, Compiler, MatrixElement, CombinationCollector, dataclass_to_json + +def make_gcc_config(version: int) -> Configuration: + return Configuration( + name=f"GCC-{version}", + os="ubuntu-24.04", + compiler=Compiler( + type="GCC", + version=version, + cc=f"gcc-{version}", + cxx=f"g++-{version}", + ), + cxx_modules=False, + std_format_support=version >= 13, + ) + + +def make_clang_config(version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64") -> Configuration: + ret = SimpleNamespace( + name=f"Clang-{version} ({platform})", + os=None, # replaced below + compiler=SimpleNamespace( + type="CLANG", + version=version, + cc=f"clang-{version}", + cxx=f"clang++-{version}", + ), + lib="libc++", + cxx_modules=version >= 17, + std_format_support=version >= 17, + ) + match platform: + case "x86-64": + ret.os = "ubuntu-24.04" + case "arm64": + ret.os = "macos-14" + pfx = f"/opt/homebrew/opt/llvm@{version}/bin/" + ret.compiler.cc = pfx + ret.compiler.cc + ret.compiler.cxx = pfx + ret.compiler.cxx + case _: + raise KeyError(f"Unsupported platform {platform!r} for Clang") + ret.compiler = Compiler(**vars(ret.compiler)) + return Configuration(**vars(ret)) + + +def make_apple_clang_config(version: int) -> Configuration: + ret = Configuration( + name=f"Apple Clang {version}", + os="macos-13", + compiler=Compiler( + type="APPLE_CLANG", + version=f"{version}.0", + cc="clang", + cxx="clang++", + ), + cxx_modules=False, + std_format_support=False, + ) + return ret + + +def make_msvc_config(release: str, version: int) -> Configuration: + ret = Configuration( + name=f"MSVC {release}", + os="windows-2022", + compiler=Compiler( + type="MSVC", + version=version, + cc="", + cxx="", + ), + cxx_modules=False, + std_format_support=True, + ) + return ret + + +configs = {c.name: c for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + + [make_clang_config(ver, platform) for ver in [16, 17, 18, 19] for platform in ["x86-64", "arm64"]] + + [make_apple_clang_config(ver) for ver in [15]] + + [make_msvc_config(release="14.4", version=194)]} + +full_matrix = dict( + config=list(configs.values()), + std=[20, 23], + formatting=["std::format", "fmtlib"], + contracts=["none", "gsl-lite", "ms-gsl"], + build_type=["Release", "Debug"], +) + + +def main(): + parser = argparse.ArgumentParser() + # parser.add_argument("-I","--include",nargs="+",action="append") + # parser.add_argument("-X","--exclude",nargs="+",action="append") + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--preset", default=None) + parser.add_argument("--debug", nargs="+", default=["combinations"]) + parser.add_argument("--suppress-output", default=False, action="store_true") + + args = parser.parse_args() + + rgen = random.Random(args.seed) + + collector = CombinationCollector(full_matrix) + match args.preset: + case None: + pass + case "all": + collector.all_combinations() + case "conan" | "cmake": + collector.all_combinations(formatting="std::format", contracts="gsl-lite", build_type="Debug", std=20) + collector.all_combinations( + filter=lambda me: not me.config.std_format_support, + formatting="fmtlib", contracts="gsl-lite", build_type="Debug", std=20, + ) + collector.sample_combinations(rgen=rgen, min_samples_per_value=2) + case "clang-tidy": + collector.all_combinations(config=configs["Clang-18 (x86-64)"]) + case "freestanding": + collector.all_combinations( + config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]], + contracts="none", + std=23, + ) + case _: + raise KeyError(f"Unsupported preset {args.preset!r}") + + if not collector.combinations: + raise ValueError(f"No combination has been produced") + + data = sorted(collector.combinations) + + if not args.suppress_output: + print(f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}") + + for dbg in args.debug: + match dbg: + case "yaml": + import yaml + json_data = json.loads(json.dumps(data, default=dataclass_to_json)) + print(yaml.safe_dump(json_data)) + case "json": + print(json.dumps(data, default=dataclass_to_json, indent=4)) + case "combinations": + for e in data: + print(f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}") + case "counts": + print(f"Total combinations {len(data)}") + for (k, v), n in sorted(collector.per_value_counts.items()): + print(f" {k}={v}: {n}") + case "none": + pass + case _: + raise KeyError(f"Unknown debug mode {dbg!r}") + + +if __name__ == "__main__": + main() diff --git a/.github/job_matrix.py b/.github/job_matrix.py new file mode 100644 index 000000000..9065f3892 --- /dev/null +++ b/.github/job_matrix.py @@ -0,0 +1,113 @@ +import argparse +import json +import typing +import itertools +import random +import dataclasses +from types import SimpleNamespace +from dataclasses import dataclass + + +@dataclass(frozen=True, order=True) +class Compiler: + type: typing.Literal["GCC", "CLANG", "APPLE_CLANG", "MSVC"] + version: str | int + cc: str + cxx: str + + +@dataclass(frozen=True, order=True) +class Configuration: + name: str + os: str + compiler: Compiler + cxx_modules: bool + std_format_support: bool + conan_config: str = "" + lib: typing.Literal["libc++", "libstdc++"] | None = None + + def __str__(self): + return self.name + +@dataclass(frozen=True, order=True) +class MatrixElement: + config: Configuration + std: typing.Literal[20, 23] + formatting: typing.Literal["std::format", "fmtlib"] + contracts: typing.Literal["none", "gsl-lite", "ms-gsl"] + build_type: typing.Literal["Release", "Debug"] + + +def dataclass_to_json(obj): + """ Convert dataclasses to something json-serialisable """ + if dataclasses.is_dataclass(obj): + return dataclasses.asdict(obj) + raise TypeError(f"Unknown object of type {type(obj).__name__}") + + +class CombinationCollector: + """ Incremental builder of MatrixElements, allowing successive selection of entries. + """ + + def __init__(self, full_matrix: dict[str, list[typing.Any]]): + self.full_matrix = full_matrix + self.combinations: set[MatrixElement] = set() + self.per_value_counts: dict[tuple[str, typing.Any], int] = {(k, v): 0 for k, options in full_matrix.items() for + v in options} + + def _make_submatrix(self, **overrides): + new_matrix = dict(self.full_matrix) + for k, v in overrides.items(): + if not isinstance(v, list): + v = [v] + new_matrix[k] = v + return new_matrix + + def _add_combination(self, e: MatrixElement): + if e in self.combinations or (e.formatting == "std::format" and not e.config.std_format_support): + return + self.combinations.add(e) + # update per_value_counts + for k, v in vars(e).items(): + idx = (k, v) + self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1 + + def all_combinations(self, *, filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): + """ Adds all combinations in the submatrix defined by `overrides`. """ + matrix = self._make_submatrix(**overrides) + keys = tuple(matrix.keys()) + for combination in itertools.product(*matrix.values()): + cand = MatrixElement(**dict(zip(keys, combination))) + if filter and not filter(cand): + continue + self._add_combination(cand) + + def sample_combinations(self, *, rgen: random.Random, min_samples_per_value: int = 1, + filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): + """ Adds samples from the submatrix defined by `overrides`, ensuring each individual value appears at least n times. """ + matrix = self._make_submatrix(**overrides) + missing: dict[tuple[str, typing.Any], int] = {} + for key, options in matrix.items(): + for value in options: + idx = (key, value) + missing[idx] = min_samples_per_value - self.per_value_counts.get(idx, 0) + while missing: + (force_key, force_option), remaining = next(iter(missing.items())) + if remaining <= 0: + missing.pop((force_key, force_option)) + continue + choice = {} + for key, options in matrix.items(): + choice[key] = force_option if key == force_key else rgen.choice(options) + cand = MatrixElement(**choice) + if filter and not filter(cand): + continue + self._add_combination(cand) + for idx in choice.items(): + if missing.pop(idx, 0) <= 0: + continue + remaining = min_samples_per_value - self.per_value_counts.get(idx, 0) + if remaining > 0: + missing[idx] = remaining + + diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 015d42296..ddf7f23fc 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -35,32 +35,33 @@ on: - "docs/**" jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset clang-tidy --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - build_type: ["Release", "Debug"] + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 8d5fc75a4..f5377fc27 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -34,148 +34,36 @@ env: CHANNEL: ${{ fromJSON('["testing", "stable"]')[github.ref_type == 'tag' && startsWith(github.ref_name, 'v')] }} jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset conan --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "MSVC 14.4", - os: windows-2022, - compiler: { type: MSVC, version: 194, cc: "", cxx: "" }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "GCC-12", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 12, - cc: "gcc-12", - cxx: "g++-12", - }, - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - - { - name: "GCC-13", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 13, - cc: "gcc-13", - cxx: "g++-13", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-16", - os: ubuntu-22.04, - compiler: - { - type: CLANG, - version: 16, - cc: "clang-16", - cxx: "clang++-16", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - - { - name: "Clang-17", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 17, - cc: "clang-17", - cxx: "clang++-17", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18 on Apple M1 (arm64)", - os: macos-14, - compiler: - { - type: CLANG, - version: 18, - cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", - cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Apple Clang 15", - os: macos-13, - compiler: - { - type: APPLE_CLANG, - version: "15.0", - cc: "clang", - cxx: "clang++", - }, - cxx_modules: "False", - std_format_support: "False", - conan-config: "", - } - build_type: ["Release", "Debug"] - exclude: - - formatting: "std::format" - config: { std_format_support: "False" } - + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} CXX: ${{ matrix.config.compiler.cxx }} - steps: - uses: actions/checkout@v4 - name: Generate unique cache id @@ -265,14 +153,14 @@ jobs: run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=True \ - -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan-config }} + -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan_config }} - name: Create Conan package if: matrix.config.compiler.type == 'MSVC' shell: bash run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.mp-units.build:all=False \ - -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan-config }} + -o '&:cxx_modules=${{ matrix.config.cxx_modules }}' -o '&:import_std=${{ env.import_std }}' -o '&:std_format=${{ env.std_format }}' -o '&:contracts=${{ matrix.contracts }}' ${{ matrix.config.conan_config }} - name: Obtain package reference id: get-package-ref shell: bash diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index cde058db6..5ce2662cb 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -35,46 +35,33 @@ on: - "docs/**" jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset freestanding --seed 42 --debug combinations counts build: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none"] - std: [23] - config: - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True", - conan-config: "", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "True", - std_format_support: "True", - conan-config: "", - } - build_type: ["Release", "Debug"] + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler exclude: - build_type: "Debug" diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index d5bf5df0a..1644c495a 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -39,135 +39,33 @@ on: - "test/**" jobs: + cancel-previous: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" + generate-matrix: + name: "Generate build matrix for ${{ github.workflow }}" + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + runs-on: ubuntu-24.04 + needs: cancel-previous + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + - id: set-matrix + run: python .github/generate-job-matrix.py --preset conan --seed 42 --debug combinations counts test_package: name: "${{ matrix.formatting }} ${{ matrix.contracts }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} + needs: generate-matrix strategy: fail-fast: false matrix: - formatting: ["std::format", "fmtlib"] - contracts: ["none", "gsl-lite", "ms-gsl"] - std: [20, 23] - config: - - { - name: "MSVC 14.4", - os: windows-2022, - compiler: { type: MSVC, version: 194, cc: "", cxx: "" }, - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "GCC-12", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 12, - cc: "gcc-12", - cxx: "g++-12", - }, - cxx_modules: "False", - std_format_support: "False", - } - - { - name: "GCC-13", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 13, - cc: "gcc-13", - cxx: "g++-13", - }, - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "GCC-14", - os: ubuntu-24.04, - compiler: - { - type: GCC, - version: 14, - cc: "gcc-14", - cxx: "g++-14", - }, - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Clang-16", - os: ubuntu-22.04, - compiler: - { - type: CLANG, - version: 16, - cc: "clang-16", - cxx: "clang++-16", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "False", - } - - { - name: "Clang-17", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 17, - cc: "clang-17", - cxx: "clang++-17", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True", - } - - { - name: "Clang-18", - os: ubuntu-24.04, - compiler: - { - type: CLANG, - version: 18, - cc: "clang-18", - cxx: "clang++-18", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Clang-18 on Apple M1 (arm64)", - os: macos-14, - compiler: - { - type: CLANG, - version: 18, - cc: "/opt/homebrew/opt/llvm@18/bin/clang-18", - cxx: "/opt/homebrew/opt/llvm@18/bin/clang++", - }, - lib: "libc++", - cxx_modules: "False", - std_format_support: "True" - } - - { - name: "Apple Clang 15", - os: macos-14, - compiler: - { - type: APPLE_CLANG, - version: "15.0", - cc: "clang", - cxx: "clang++", - }, - cxx_modules: "False", - std_format_support: "False", - } - build_type: ["Release", "Debug"] - exclude: - - formatting: "std::format" - config: { std_format_support: "False" } + include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} env: CC: ${{ matrix.config.compiler.cc }} From 7fa15d224d272462d2228f989a25cf813cff4bc9 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 18:43:50 +0100 Subject: [PATCH 26/34] fix formatting --- .flake8 | 3 ++ .github/generate-job-matrix.py | 50 ++++++++++++++------- .github/job_matrix.py | 47 +++++++++++-------- .github/workflows/ci-clang-tidy.yml | 1 + .github/workflows/ci-conan.yml | 1 + .github/workflows/ci-freestanding.yml | 1 + .github/workflows/ci-test-package-cmake.yml | 1 + 7 files changed, 71 insertions(+), 33 deletions(-) diff --git a/.flake8 b/.flake8 index 4592939f2..140404565 100644 --- a/.flake8 +++ b/.flake8 @@ -7,3 +7,6 @@ ignore = E712, # line break before binary operator W503 +per-file-ignores = + # flake8 is just plain wrong here, contradicting black + .github/generate-job-matrix.py:E225,E231 diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index b2465d056..9ba3b8f3f 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -1,13 +1,11 @@ import argparse import json -import typing -import itertools import random -import dataclasses +import typing from types import SimpleNamespace -from dataclasses import dataclass -from job_matrix import Configuration, Compiler, MatrixElement, CombinationCollector, dataclass_to_json +from job_matrix import CombinationCollector, Compiler, Configuration, dataclass_to_json + def make_gcc_config(version: int) -> Configuration: return Configuration( @@ -24,7 +22,9 @@ def make_gcc_config(version: int) -> Configuration: ) -def make_clang_config(version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64") -> Configuration: +def make_clang_config( + version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64" +) -> Configuration: ret = SimpleNamespace( name=f"Clang-{version} ({platform})", os=None, # replaced below @@ -84,10 +84,17 @@ def make_msvc_config(release: str, version: int) -> Configuration: return ret -configs = {c.name: c for c in [make_gcc_config(ver) for ver in [12, 13, 14]] - + [make_clang_config(ver, platform) for ver in [16, 17, 18, 19] for platform in ["x86-64", "arm64"]] - + [make_apple_clang_config(ver) for ver in [15]] - + [make_msvc_config(release="14.4", version=194)]} +configs = { + c.name: c + for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + + [ + make_clang_config(ver, platform) + for ver in [16, 17, 18, 19] + for platform in ["x86-64", "arm64"] + ] + + [make_apple_clang_config(ver) for ver in [15]] + + [make_msvc_config(release="14.4", version=194)] +} full_matrix = dict( config=list(configs.values()), @@ -118,10 +125,18 @@ def main(): case "all": collector.all_combinations() case "conan" | "cmake": - collector.all_combinations(formatting="std::format", contracts="gsl-lite", build_type="Debug", std=20) + collector.all_combinations( + formatting="std::format", + contracts="gsl-lite", + build_type="Debug", + std=20, + ) collector.all_combinations( filter=lambda me: not me.config.std_format_support, - formatting="fmtlib", contracts="gsl-lite", build_type="Debug", std=20, + formatting="fmtlib", + contracts="gsl-lite", + build_type="Debug", + std=20, ) collector.sample_combinations(rgen=rgen, min_samples_per_value=2) case "clang-tidy": @@ -136,24 +151,29 @@ def main(): raise KeyError(f"Unsupported preset {args.preset!r}") if not collector.combinations: - raise ValueError(f"No combination has been produced") + raise ValueError("No combination has been produced") data = sorted(collector.combinations) if not args.suppress_output: - print(f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}") + print( + f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}" + ) for dbg in args.debug: match dbg: case "yaml": import yaml + json_data = json.loads(json.dumps(data, default=dataclass_to_json)) print(yaml.safe_dump(json_data)) case "json": print(json.dumps(data, default=dataclass_to_json, indent=4)) case "combinations": for e in data: - print(f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}") + print( + f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}" + ) case "counts": print(f"Total combinations {len(data)}") for (k, v), n in sorted(collector.per_value_counts.items()): diff --git a/.github/job_matrix.py b/.github/job_matrix.py index 9065f3892..eb9562687 100644 --- a/.github/job_matrix.py +++ b/.github/job_matrix.py @@ -1,10 +1,7 @@ -import argparse -import json -import typing +import dataclasses import itertools import random -import dataclasses -from types import SimpleNamespace +import typing from dataclasses import dataclass @@ -29,6 +26,7 @@ class Configuration: def __str__(self): return self.name + @dataclass(frozen=True, order=True) class MatrixElement: config: Configuration @@ -39,21 +37,21 @@ class MatrixElement: def dataclass_to_json(obj): - """ Convert dataclasses to something json-serialisable """ + """Convert dataclasses to something json-serialisable""" if dataclasses.is_dataclass(obj): return dataclasses.asdict(obj) raise TypeError(f"Unknown object of type {type(obj).__name__}") class CombinationCollector: - """ Incremental builder of MatrixElements, allowing successive selection of entries. - """ + """Incremental builder of MatrixElements, allowing successive selection of entries.""" def __init__(self, full_matrix: dict[str, list[typing.Any]]): self.full_matrix = full_matrix self.combinations: set[MatrixElement] = set() - self.per_value_counts: dict[tuple[str, typing.Any], int] = {(k, v): 0 for k, options in full_matrix.items() for - v in options} + self.per_value_counts: dict[tuple[str, typing.Any], int] = { + (k, v): 0 for k, options in full_matrix.items() for v in options + } def _make_submatrix(self, **overrides): new_matrix = dict(self.full_matrix) @@ -64,7 +62,9 @@ def _make_submatrix(self, **overrides): return new_matrix def _add_combination(self, e: MatrixElement): - if e in self.combinations or (e.formatting == "std::format" and not e.config.std_format_support): + if e in self.combinations or ( + e.formatting == "std::format" and not e.config.std_format_support + ): return self.combinations.add(e) # update per_value_counts @@ -72,8 +72,13 @@ def _add_combination(self, e: MatrixElement): idx = (k, v) self.per_value_counts[idx] = self.per_value_counts.get(idx, 0) + 1 - def all_combinations(self, *, filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): - """ Adds all combinations in the submatrix defined by `overrides`. """ + def all_combinations( + self, + *, + filter: typing.Callable[[MatrixElement], bool] | None = None, + **overrides, + ): + """Adds all combinations in the submatrix defined by `overrides`.""" matrix = self._make_submatrix(**overrides) keys = tuple(matrix.keys()) for combination in itertools.product(*matrix.values()): @@ -82,9 +87,17 @@ def all_combinations(self, *, filter: typing.Callable[[MatrixElement], bool] | N continue self._add_combination(cand) - def sample_combinations(self, *, rgen: random.Random, min_samples_per_value: int = 1, - filter: typing.Callable[[MatrixElement], bool] | None = None, **overrides): - """ Adds samples from the submatrix defined by `overrides`, ensuring each individual value appears at least n times. """ + def sample_combinations( + self, + *, + rgen: random.Random, + min_samples_per_value: int = 1, + filter: typing.Callable[[MatrixElement], bool] | None = None, + **overrides, + ): + """Adds samples from the submatrix defined by `overrides`, + ensuring each individual value appears at least n times. + """ matrix = self._make_submatrix(**overrides) missing: dict[tuple[str, typing.Any], int] = {} for key, options in matrix.items(): @@ -109,5 +122,3 @@ def sample_combinations(self, *, rgen: random.Random, min_samples_per_value: int remaining = min_samples_per_value - self.per_value_counts.get(idx, 0) if remaining > 0: missing[idx] = remaining - - diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index ddf7f23fc..402455a1b 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -39,6 +39,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index f5377fc27..f80f7776e 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -38,6 +38,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index 5ce2662cb..2b2f97da0 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -39,6 +39,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index 1644c495a..bb1e96091 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -43,6 +43,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + runs-on: ubuntu-24.04 steps: - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: From e464677200cf8df116d05335370096696c5f6c28 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 18:58:06 +0100 Subject: [PATCH 27/34] don't test Clang 19 just yet; fix cancel-in-progres --- .github/generate-job-matrix.py | 2 +- .github/workflows/ci-clang-tidy.yml | 13 +++++-------- .github/workflows/ci-conan.yml | 12 ++++-------- .github/workflows/ci-freestanding.yml | 12 ++++-------- .github/workflows/ci-test-package-cmake.yml | 12 ++++-------- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index 9ba3b8f3f..ae06b9ce9 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -89,7 +89,7 @@ def make_msvc_config(release: str, version: int) -> Configuration: for c in [make_gcc_config(ver) for ver in [12, 13, 14]] + [ make_clang_config(ver, platform) - for ver in [16, 17, 18, 19] + for ver in [16, 17, 18] for platform in ["x86-64", "arm64"] ] + [make_apple_clang_config(ver) for ver in [15]] diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 402455a1b..849442b00 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -34,20 +34,17 @@ on: paths-ignore: - "docs/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index f80f7776e..b439fa280 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -33,20 +33,16 @@ on: env: CHANNEL: ${{ fromJSON('["testing", "stable"]')[github.ref_type == 'tag' && startsWith(github.ref_name, 'v')] }} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index 2b2f97da0..f97d7cfb1 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -34,20 +34,16 @@ on: paths-ignore: - "docs/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index bb1e96091..396ca2a4e 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -38,20 +38,16 @@ on: - "example/**" - "test/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - cancel-previous: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-24.04 - steps: - - run: echo "Cancelling all previous runs of ${{ github.workflow }}-${{ github.ref }}" generate-matrix: name: "Generate build matrix for ${{ github.workflow }}" outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 - needs: cancel-previous steps: - name: Set up Python uses: actions/setup-python@v5 From cc9ea9dd1a1dbf44372cb2623d24332777d5a751 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 19:05:04 +0100 Subject: [PATCH 28/34] add cancel-in-progress to all workflows --- .github/workflows/ci-formatting.yml | 4 ++++ .github/workflows/citation.yml | 4 ++++ .github/workflows/codeql.yml | 4 ++++ .github/workflows/documentation.yml | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/.github/workflows/ci-formatting.yml b/.github/workflows/ci-formatting.yml index 086fe625a..e61d5b834 100644 --- a/.github/workflows/ci-formatting.yml +++ b/.github/workflows/ci-formatting.yml @@ -24,6 +24,10 @@ name: Formatting CI on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-24.04 diff --git a/.github/workflows/citation.yml b/.github/workflows/citation.yml index e5f22acf7..de8eb51c2 100644 --- a/.github/workflows/citation.yml +++ b/.github/workflows/citation.yml @@ -5,6 +5,10 @@ on: paths: - CITATION.cff +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Validate-CITATION-cff: runs-on: ubuntu-latest diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c7e0abb58..e742e8a17 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,6 +22,10 @@ on: paths-ignore: - "docs/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3072cd796..09750e9dd 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -36,6 +36,11 @@ on: - "mkdocs.yml" permissions: contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: deploy: runs-on: ubuntu-latest From a51462cc655a1be9ca960c36392aa18ebad97c45 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 19:09:12 +0100 Subject: [PATCH 29/34] missing checkout in generate-matrix step --- .github/workflows/ci-clang-tidy.yml | 1 + .github/workflows/ci-conan.yml | 1 + .github/workflows/ci-freestanding.yml | 1 + .github/workflows/ci-test-package-cmake.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/ci-clang-tidy.yml b/.github/workflows/ci-clang-tidy.yml index 849442b00..589a123a9 100644 --- a/.github/workflows/ci-clang-tidy.yml +++ b/.github/workflows/ci-clang-tidy.yml @@ -46,6 +46,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index b439fa280..c5e0ad7ff 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -44,6 +44,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index f97d7cfb1..4fb016dc9 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -45,6 +45,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index 396ca2a4e..10e4e2edc 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -49,6 +49,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: From f4c8e901ac418a6b5d7582d9b9149e6d19026946 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:18:36 +0100 Subject: [PATCH 30/34] fix boolean conan options in dynamic CI matrix --- .github/generate-job-matrix.py | 12 ++++++------ .github/job_matrix.py | 21 +++++++++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index ae06b9ce9..d711d3cce 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -4,7 +4,7 @@ import typing from types import SimpleNamespace -from job_matrix import CombinationCollector, Compiler, Configuration, dataclass_to_json +from job_matrix import CombinationCollector, Compiler, Configuration def make_gcc_config(version: int) -> Configuration: @@ -155,20 +155,20 @@ def main(): data = sorted(collector.combinations) + json_data = [e.as_json() for e in data] + if not args.suppress_output: - print( - f"::set-output name=matrix::{json.dumps(data, default=dataclass_to_json)}" - ) + print(f"::set-output name=matrix::{json.dumps(json_data)}") for dbg in args.debug: match dbg: case "yaml": import yaml - json_data = json.loads(json.dumps(data, default=dataclass_to_json)) + json_data = json.loads(json.dumps(json_data)) print(yaml.safe_dump(json_data)) case "json": - print(json.dumps(data, default=dataclass_to_json, indent=4)) + print(json.dumps(json_data, indent=4)) case "combinations": for e in data: print( diff --git a/.github/job_matrix.py b/.github/job_matrix.py index eb9562687..204be6799 100644 --- a/.github/job_matrix.py +++ b/.github/job_matrix.py @@ -35,12 +35,21 @@ class MatrixElement: contracts: typing.Literal["none", "gsl-lite", "ms-gsl"] build_type: typing.Literal["Release", "Debug"] - -def dataclass_to_json(obj): - """Convert dataclasses to something json-serialisable""" - if dataclasses.is_dataclass(obj): - return dataclasses.asdict(obj) - raise TypeError(f"Unknown object of type {type(obj).__name__}") + def as_json(self): + def dataclass_to_json(obj): + """Convert dataclasses to something json-serialisable""" + if dataclasses.is_dataclass(obj): + return { + k: dataclass_to_json(v) for k, v in dataclasses.asdict(obj).items() + } + return obj + + ret = dataclass_to_json(self) + # patch boolean conan configuration options + config = ret["config"] + for k in ["cxx_modules"]: + config[k] = "True" if config[k] else "False" + return ret class CombinationCollector: From 01f44c66fad892180fa9a706ab2506794afd7402 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:29:42 +0100 Subject: [PATCH 31/34] heed github warning, and use output file instead of set-output command; also, fix freestanding --- .github/generate-job-matrix.py | 9 ++++++++- .github/workflows/ci-freestanding.yml | 4 ---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index d711d3cce..ec819edeb 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -1,5 +1,6 @@ import argparse import json +import os import random import typing from types import SimpleNamespace @@ -157,8 +158,14 @@ def main(): json_data = [e.as_json() for e in data] + output_file = os.environ.get("GITHUB_OUTPUT") if not args.suppress_output: - print(f"::set-output name=matrix::{json.dumps(json_data)}") + if output_file: + print(f"Writing outputs to {output_file}") + with open(output_file, "wt") as fh: + fh.write(f"matrix={json.dumps(json_data)}") + else: + print("No output file received!") for dbg in args.debug: match dbg: diff --git a/.github/workflows/ci-freestanding.yml b/.github/workflows/ci-freestanding.yml index 4fb016dc9..7a234e023 100644 --- a/.github/workflows/ci-freestanding.yml +++ b/.github/workflows/ci-freestanding.yml @@ -60,10 +60,6 @@ jobs: fail-fast: false matrix: include: ${{fromJson(needs.generate-matrix.outputs.matrix)}} - # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler - exclude: - - build_type: "Debug" - config: { name: "Clang-18" } env: CC: ${{ matrix.config.compiler.cc }} From 5713243d075be0add1dec594c19caeaa48fac4f4 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:41:09 +0100 Subject: [PATCH 32/34] fix clang 16 --- .github/generate-job-matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index ec819edeb..b30ea573c 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -41,7 +41,7 @@ def make_clang_config( ) match platform: case "x86-64": - ret.os = "ubuntu-24.04" + ret.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" case "arm64": ret.os = "macos-14" pfx = f"/opt/homebrew/opt/llvm@{version}/bin/" From ff118784fa544057e3752e6c0e9291e68f8c800e Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:48:59 +0100 Subject: [PATCH 33/34] exclude clang18+debug from freestanding again --- .github/generate-job-matrix.py | 11 ++++++++++- .github/job_matrix.py | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index b30ea573c..e2033b997 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -119,7 +119,12 @@ def main(): rgen = random.Random(args.seed) - collector = CombinationCollector(full_matrix) + collector = CombinationCollector( + full_matrix, + hard_excludes=lambda e: ( + e.formatting == "std::format" and not e.config.std_format_support + ), + ) match args.preset: case None: pass @@ -143,7 +148,11 @@ def main(): case "clang-tidy": collector.all_combinations(config=configs["Clang-18 (x86-64)"]) case "freestanding": + # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler collector.all_combinations( + filter=lambda e: not ( + e.config.name.startswith("Clang-18") and e.build_type == "Debug" + ), config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]], contracts="none", std=23, diff --git a/.github/job_matrix.py b/.github/job_matrix.py index 204be6799..0458a1e9e 100644 --- a/.github/job_matrix.py +++ b/.github/job_matrix.py @@ -55,8 +55,14 @@ def dataclass_to_json(obj): class CombinationCollector: """Incremental builder of MatrixElements, allowing successive selection of entries.""" - def __init__(self, full_matrix: dict[str, list[typing.Any]]): + def __init__( + self, + full_matrix: dict[str, list[typing.Any]], + *, + hard_excludes: typing.Callable[[MatrixElement], bool] | None = None, + ): self.full_matrix = full_matrix + self.hard_excludes = hard_excludes self.combinations: set[MatrixElement] = set() self.per_value_counts: dict[tuple[str, typing.Any], int] = { (k, v): 0 for k, options in full_matrix.items() for v in options @@ -72,7 +78,7 @@ def _make_submatrix(self, **overrides): def _add_combination(self, e: MatrixElement): if e in self.combinations or ( - e.formatting == "std::format" and not e.config.std_format_support + self.hard_excludes is not None and self.hard_excludes(e) ): return self.combinations.add(e) From b35e241a98b1f3dc8451b7dbcd7980ea09df59f0 Mon Sep 17 00:00:00 2001 From: Yves Delley Date: Tue, 12 Nov 2024 21:58:38 +0100 Subject: [PATCH 34/34] fix clang on macos-14 (arm64) --- .github/generate-job-matrix.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/generate-job-matrix.py b/.github/generate-job-matrix.py index e2033b997..bd14ee000 100644 --- a/.github/generate-job-matrix.py +++ b/.github/generate-job-matrix.py @@ -26,14 +26,11 @@ def make_gcc_config(version: int) -> Configuration: def make_clang_config( version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64" ) -> Configuration: - ret = SimpleNamespace( + cfg = SimpleNamespace( name=f"Clang-{version} ({platform})", - os=None, # replaced below compiler=SimpleNamespace( type="CLANG", version=version, - cc=f"clang-{version}", - cxx=f"clang++-{version}", ), lib="libc++", cxx_modules=version >= 17, @@ -41,15 +38,18 @@ def make_clang_config( ) match platform: case "x86-64": - ret.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" + cfg.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04" + cfg.compiler.cc = f"clang-{version}" + cfg.compiler.cxx = f"clang++-{version}" case "arm64": - ret.os = "macos-14" - pfx = f"/opt/homebrew/opt/llvm@{version}/bin/" - ret.compiler.cc = pfx + ret.compiler.cc - ret.compiler.cxx = pfx + ret.compiler.cxx + cfg.os = "macos-14" + pfx = f"/opt/homebrew/opt/llvm@{version}/bin" + cfg.compiler.cc = f"{pfx}/clang" + cfg.compiler.cxx = f"{pfx}/clang++" case _: raise KeyError(f"Unsupported platform {platform!r} for Clang") - ret.compiler = Compiler(**vars(ret.compiler)) + ret = cfg + ret.compiler = Compiler(**vars(cfg.compiler)) return Configuration(**vars(ret)) @@ -92,6 +92,8 @@ def make_msvc_config(release: str, version: int) -> Configuration: make_clang_config(ver, platform) for ver in [16, 17, 18] for platform in ["x86-64", "arm64"] + # arm64 runners are expensive; only consider one version + if ver == 18 or platform != "arm64" ] + [make_apple_clang_config(ver) for ver in [15]] + [make_msvc_config(release="14.4", version=194)]