From d783c3cda96b142cd457e992bff3f15cb40a012e Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 3 Oct 2024 18:39:41 +0200 Subject: [PATCH] feat: magnitude text now obeys formatting parameters and knows how to print constants --- .../include/mp-units/framework/magnitude.h | 183 +++++++++++++++--- src/core/include/mp-units/framework/unit.h | 18 +- test/static/unit_symbol_test.cpp | 53 +++++ 3 files changed, 210 insertions(+), 44 deletions(-) diff --git a/src/core/include/mp-units/framework/magnitude.h b/src/core/include/mp-units/framework/magnitude.h index 199e0b4d..a05354b8 100644 --- a/src/core/include/mp-units/framework/magnitude.h +++ b/src/core/include/mp-units/framework/magnitude.h @@ -33,6 +33,7 @@ #include #include #include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE #ifdef MP_UNITS_IMPORT_STD @@ -59,12 +60,15 @@ using factorizer = wheel_factorizer<4>; #if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18 MP_UNITS_EXPORT template -struct mag_constant {}; +struct mag_constant { + static constexpr auto symbol = Symbol; +}; #else MP_UNITS_EXPORT template struct mag_constant { + static constexpr auto symbol = Symbol; static constexpr auto value = Value; }; @@ -230,6 +234,12 @@ template template [[nodiscard]] consteval auto remove_positive_power(magnitude m); +template +[[nodiscard]] consteval auto remove_mag_constants(magnitude m); +template +[[nodiscard]] consteval auto only_positive_mag_constants(magnitude m); +template +[[nodiscard]] consteval auto only_negative_mag_constants(magnitude m); template requires detail::gt_zero @@ -311,29 +321,104 @@ struct magnitude_base> { } }; -template -[[nodiscard]] consteval auto ratio_txt() +template +[[nodiscard]] consteval std::size_t magnitude_list_size(magnitude) { - if constexpr (den_value == 1) - return detail::regular(); - else - return detail::regular() + symbol_text("/") + detail::regular(); -}; + return sizeof...(Ms); +} -template -[[nodiscard]] consteval auto magnitude_text_impl() +template Out> +constexpr Out print_separator(Out out, const unit_symbol_formatting& fmt) { - constexpr auto num_value = get_value(num); - constexpr auto den_value = get_value(den); - - if constexpr (num_value == 1 && den_value == 1 && exp10 != 0) { - return symbol_text("10") + detail::superscript(); + if (fmt.separator == unit_symbol_separator::half_high_dot) { + if (fmt.encoding != text_encoding::unicode) + MP_UNITS_THROW( + std::invalid_argument("'unit_symbol_separator::half_high_dot' can be only used with 'text_encoding::unicode'")); + const std::string_view dot = "⋅"; + out = detail::copy(dot.begin(), dot.end(), out); } else { - if constexpr (exp10 == 0) - return ratio_txt(); - else - return ratio_txt() + symbol_text(u8" × 10", " x 10") + detail::superscript(); + *out++ = ' '; } + return out; +} + +template Out, auto... Ms> + requires(sizeof...(Ms) == 0) +[[nodiscard]] consteval auto mag_constants_text(Out out, magnitude, const unit_symbol_formatting&, bool) +{ + return out; +} + +template Out, auto M, auto... Rest> +[[nodiscard]] consteval auto mag_constants_text(Out out, magnitude, const unit_symbol_formatting& fmt, + bool negative_power) +{ + return ((out = copy_symbol(get_base(M).symbol, fmt.encoding, negative_power, out)), ..., + (print_separator(out, fmt), + out = copy_symbol(get_base(Rest).symbol, fmt.encoding, negative_power, out))); +} + +template Out> +constexpr Out magnitude_symbol_impl(Out out, const unit_symbol_formatting& fmt) +{ + bool numerator = false; + constexpr auto num_value = get_value(Num); + if constexpr (num_value != 1) { + constexpr auto num = detail::regular(); + out = copy_symbol(num, fmt.encoding, false, out); + numerator = true; + } + + constexpr auto num_constants_size = magnitude_list_size(NumConstants); + if constexpr (num_constants_size) { + if (numerator) out = print_separator(out, fmt); + out = mag_constants_text(out, NumConstants, fmt, false); + numerator = true; + } + + using enum unit_symbol_solidus; + bool denominator = false; + constexpr auto den_value = get_value(Den); + constexpr auto den_constants_size = magnitude_list_size(DenConstants); + constexpr auto den_size = (den_value != 1) + den_constants_size; + auto start_denominator = [&]() { + if (fmt.solidus == always || (fmt.solidus == one_denominator && den_size == 1)) { + if (!numerator) *out++ = '1'; + *out++ = '/'; + if (den_size > 1) *out++ = '('; + } else if (numerator) { + out = print_separator(out, fmt); + } + }; + const bool negative_power = fmt.solidus == never || (fmt.solidus == one_denominator && den_size > 1); + if constexpr (den_value != 1) { + constexpr auto den = detail::regular(); + start_denominator(); + out = copy_symbol(den, fmt.encoding, negative_power, out); + denominator = true; + } + + if constexpr (den_constants_size) { + if (denominator) + out = print_separator(out, fmt); + else + start_denominator(); + out = mag_constants_text(out, DenConstants, fmt, negative_power); + if (fmt.solidus == always && den_size > 1) *out++ = ')'; + denominator = true; + } + + if constexpr (Exp10 != 0) { + if (numerator || denominator) { + constexpr auto mag_multiplier = symbol_text(u8" × ", " x "); + out = copy_symbol(mag_multiplier, fmt.encoding, negative_power, out); + } + constexpr auto exp = symbol_text("10") + detail::superscript(); + out = copy_symbol(exp, fmt.encoding, negative_power, out); + } + + return out; } } // namespace detail @@ -427,6 +512,18 @@ private: return (std::intmax_t{} * ... * decltype(detail::get_base_value(Ms)){}); } + [[nodiscard]] friend consteval auto _extract_components(magnitude) + { + constexpr auto ratio = (magnitude<>{} * ... * detail::remove_mag_constants(magnitude{})); + if constexpr (ratio == magnitude{}) + return std::tuple{ratio, magnitude<>{}, magnitude<>{}}; + else { + constexpr auto num_constants = (magnitude<>{} * ... * detail::only_positive_mag_constants(magnitude{})); + constexpr auto den_constants = (magnitude<>{} * ... * detail::only_negative_mag_constants(magnitude{})); + return std::tuple{ratio, num_constants, den_constants}; + } + } + template [[nodiscard]] friend consteval detail::ratio _get_power(T base, magnitude) { @@ -444,29 +541,34 @@ private: return detail::integer_part((detail::abs(power_of_2) < detail::abs(power_of_5)) ? power_of_2 : power_of_5); } - [[nodiscard]] friend consteval auto _magnitude_text(magnitude) + template Out> + friend constexpr Out _magnitude_symbol(Out out, magnitude, const unit_symbol_formatting& fmt) { if constexpr (magnitude{} == magnitude<1>{}) { - return symbol_text(""); + return out; } else { - constexpr auto exp10 = _extract_power_of_10(magnitude{}); + constexpr auto extract_res = _extract_components(magnitude{}); + constexpr Magnitude auto ratio = std::get<0>(extract_res); + constexpr Magnitude auto num_constants = std::get<1>(extract_res); + constexpr Magnitude auto den_constants = std::get<2>(extract_res); + constexpr std::intmax_t exp10 = _extract_power_of_10(ratio); if constexpr (detail::abs(exp10) < 3) { // print the value as a regular number (without exponent) constexpr Magnitude auto num = _numerator(magnitude{}); constexpr Magnitude auto den = _denominator(magnitude{}); // TODO address the below - static_assert(magnitude{} == num / den, "Printing rational powers, or irrational bases, not yet supported"); - return detail::magnitude_text_impl(); + static_assert(ratio == num / den, "Printing rational powers not yet supported"); + return detail::magnitude_symbol_impl(out, fmt); } else { // print the value as a number with exponent // if user wanted a regular number for this magnitude then probably a better scaled unit should be used - constexpr Magnitude auto base = magnitude{} / detail::mag_power_lazy<10, exp10>(); + constexpr Magnitude auto base = ratio / detail::mag_power_lazy<10, exp10>(); constexpr Magnitude auto num = _numerator(base); constexpr Magnitude auto den = _denominator(base); // TODO address the below - static_assert(base == num / den, "Printing rational powers, or irrational bases, not yet supported"); - return detail::magnitude_text_impl(); + static_assert(base == num / den, "Printing rational powers not yet supported"); + return detail::magnitude_symbol_impl(out, fmt); } } } @@ -504,6 +606,33 @@ template } } +template +[[nodiscard]] consteval auto remove_mag_constants(magnitude m) +{ + if constexpr (MagConstant) + return magnitude<>{}; + else + return m; +} + +template +[[nodiscard]] consteval auto only_positive_mag_constants(magnitude m) +{ + if constexpr (MagConstant && get_exponent(M) >= 0) + return m; + else + return magnitude<>{}; +} + +template +[[nodiscard]] consteval auto only_negative_mag_constants(magnitude m) +{ + if constexpr (MagConstant && get_exponent(M) < 0) + return m; + else + return magnitude<>{}; +} + // Returns the most precise type to express the magnitude factor template using common_magnitude_type = decltype(_common_magnitude_type_impl(M)); diff --git a/src/core/include/mp-units/framework/unit.h b/src/core/include/mp-units/framework/unit.h index 91658a34..4cf11aa0 100644 --- a/src/core/include/mp-units/framework/unit.h +++ b/src/core/include/mp-units/framework/unit.h @@ -762,21 +762,6 @@ MP_UNITS_EXPORT_END namespace detail { -template Out> -constexpr Out print_separator(Out out, const unit_symbol_formatting& fmt) -{ - if (fmt.separator == unit_symbol_separator::half_high_dot) { - if (fmt.encoding != text_encoding::unicode) - MP_UNITS_THROW( - std::invalid_argument("'unit_symbol_separator::half_high_dot' can be only used with 'text_encoding::unicode'")); - const std::string_view dot = "⋅"; - out = detail::copy(dot.begin(), dot.end(), out); - } else { - *out++ = ' '; - } - return out; -} - template Out, Unit U> requires requires { U::symbol; } constexpr Out unit_symbol_impl(Out out, U, const unit_symbol_formatting& fmt, bool negative_power) @@ -789,8 +774,7 @@ constexpr Out unit_symbol_impl(Out out, const scaled_unit_impl& u, const u bool negative_power) { *out++ = '['; - constexpr auto mag_txt = _magnitude_text(M); - out = copy(mag_txt, fmt.encoding, out); + _magnitude_symbol(out, M, fmt); if constexpr (space_before_unit_symbol::reference_unit>) *out++ = ' '; unit_symbol_impl(out, u.reference_unit, fmt, negative_power); *out++ = ']'; diff --git a/test/static/unit_symbol_test.cpp b/test/static/unit_symbol_test.cpp index 48b75510..64850327 100644 --- a/test/static/unit_symbol_test.cpp +++ b/test/static/unit_symbol_test.cpp @@ -130,6 +130,59 @@ static_assert(unit_symbol(mag_ratio<1, 18000> * (metre / second)) == "[1/18 × 1 static_assert(unit_symbol(mag_ratio<1, 18000> * metre / second) == "[1/18 x 10^-3 m]/s"); static_assert(unit_symbol(mag_ratio<1, 18000> * (metre / second)) == "[1/18 x 10^-3 m/s]"); +// magnitude constants +#if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18 +inline constexpr struct e final : mag_constant<"e"> { + static constexpr long double value = std::numbers::e_v; +#else +inline constexpr struct e final : mag_constant<"e", std::numbers::e_v > { +#endif +} e; + +static_assert(unit_symbol(mag * one) == "[𝜋]"); +static_assert(unit_symbol(mag * one) == "[pi]"); +static_assert(unit_symbol(mag * metre) == "[𝜋 m]"); +static_assert(unit_symbol(mag * metre) == "[pi m]"); +static_assert(unit_symbol(mag<2> * mag * metre) == "[2 𝜋 m]"); +static_assert(unit_symbol(mag<2> * mag * metre) == "[2 pi m]"); +static_assert(unit_symbol(mag<2> * mag * metre) == "[2⋅𝜋 m]"); + +static_assert(unit_symbol(mag<1> / mag * metre) == "[1/𝜋 m]"); +static_assert(unit_symbol(mag<1> / mag * metre) == "[1/pi m]"); +static_assert(unit_symbol(mag<1> / mag * metre) == "[𝜋⁻¹ m]"); +static_assert(unit_symbol(mag<1> / mag * metre) == "[pi^-1 m]"); + +static_assert(unit_symbol(mag<2> / mag * metre) == "[2/𝜋 m]"); +static_assert(unit_symbol(mag<2> / mag * metre) == "[2/pi m]"); +static_assert(unit_symbol(mag<2> / mag * metre) == "[2 𝜋⁻¹ m]"); +static_assert(unit_symbol(mag<2> / mag * metre) == "[2 pi^-1 m]"); +static_assert(unit_symbol(mag<2> / mag * metre) == "[2⋅𝜋⁻¹ m]"); + +static_assert(unit_symbol(mag<1> / (mag<2> * mag)*metre) == "[2⁻¹ 𝜋⁻¹ m]"); +static_assert(unit_symbol(mag<1> / (mag<2> * mag)*metre) == "[1/(2 𝜋) m]"); +static_assert(unit_symbol(mag<1> / (mag<2> * mag)*metre) == + "[1/(2 pi) m]"); +static_assert(unit_symbol(mag_ratio<1, 2> / mag * metre) == "[2⁻¹ 𝜋⁻¹ m]"); +static_assert(unit_symbol(mag_ratio<1, 2> / mag * metre) == "[1/(2 𝜋) m]"); +static_assert(unit_symbol(mag_ratio<1, 2> / mag * metre) == + "[1/(2 pi) m]"); +static_assert(unit_symbol(mag_ratio<1, 2> * mag * metre) == "[𝜋/2 m]"); + + +static_assert(unit_symbol(mag * mag * one) == "[e 𝜋]"); +static_assert(unit_symbol(mag * mag * one) == "[e 𝜋]"); +static_assert(unit_symbol(mag * mag * one) == "[e pi]"); +static_assert(unit_symbol(mag / mag * one) == "[𝜋/e]"); +static_assert(unit_symbol(mag<1> / mag * mag * one) == "[𝜋/e]"); +static_assert(unit_symbol(mag / mag * one) == "[𝜋 e⁻¹]"); +static_assert(unit_symbol(mag / mag * one) == "[e/𝜋]"); +static_assert(unit_symbol(mag<1> / mag * mag * one) == "[e/𝜋]"); +static_assert(unit_symbol(mag / mag * one) == "[e 𝜋⁻¹]"); +static_assert(unit_symbol(mag<1> / (mag * mag)*one) == "[e⁻¹ 𝜋⁻¹]"); +static_assert(unit_symbol(mag<1> / (mag * mag)*one) == "[1/(e 𝜋)]"); +static_assert(unit_symbol(mag<2> / (mag * mag)*one) == "[2 e⁻¹ 𝜋⁻¹]"); +static_assert(unit_symbol(mag<2> / (mag * mag)*one) == "[2/(e 𝜋)]"); + // common units static_assert(unit_symbol(get_common_unit(kilo, mile)) == "EQUIV{[1/25146 mi], [1/15625 km]}"); static_assert(unit_symbol(get_common_unit(kilo / hour, metre / second)) == "EQUIV{[1/5 km/h], [1/18 m/s]}");