From 17036cb2d08a292a59a889ab04eccbbaa6850494 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Sat, 22 Oct 2022 19:27:39 +0200 Subject: [PATCH] feat: unit symbol text output support added --- .../include/units/bits/derived_symbol_text.h | 89 ----- src/core/include/units/bits/unit_text.h | 166 --------- src/core/include/units/magnitude.h | 290 ++++++++------- src/core/include/units/unit.h | 338 +++++++++++++++--- test/unit_test/static/unit_test.cpp | 73 +++- 5 files changed, 499 insertions(+), 457 deletions(-) delete mode 100644 src/core/include/units/bits/derived_symbol_text.h delete mode 100644 src/core/include/units/bits/unit_text.h diff --git a/src/core/include/units/bits/derived_symbol_text.h b/src/core/include/units/bits/derived_symbol_text.h deleted file mode 100644 index 2d358725..00000000 --- a/src/core/include/units/bits/derived_symbol_text.h +++ /dev/null @@ -1,89 +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 - -#include -#include -#include -#include - -namespace units::detail { - -template -constexpr auto operator_text() -{ - if constexpr (Idx == 0) { - if constexpr (Divide && NegativeExpCount == 1) { - return basic_fixed_string("1/"); - } else { - return basic_fixed_string(""); - } - } else { - if constexpr (Divide && NegativeExpCount == 1) { - return basic_fixed_string("/"); - } else { - return basic_symbol_text("⋅", " "); - } - } -} - -template -constexpr auto exp_text() -{ - // get calculation operator + symbol - const auto txt = operator_text<(E::num < 0), NegativeExpCount, Idx>() + Symbol; - if constexpr (E::den != 1) { - // add root part - return txt + basic_fixed_string("^(") + regular() + basic_fixed_string("/") + regular() + - basic_fixed_string(")"); - } else if constexpr (E::num != 1) { - // add exponent part - if constexpr (NegativeExpCount > 1) { // no '/' sign here (only negative exponents) - return txt + superscript(); - } else if constexpr (E::num != -1) { // -1 is replaced with '/' sign here - return txt + superscript(); - } else { - return txt; - } - } else { - return txt; - } -} - -template -inline constexpr int negative_exp_count = ((Es::num < 0 ? 1 : 0) + ... + 0); - -template -constexpr auto derived_symbol_text(exponent_list, std::index_sequence) -{ - constexpr auto neg_exp = negative_exp_count; - return (exp_text() + ...); -} - -template -constexpr auto derived_symbol_text() -{ - return derived_symbol_text(typename Dim::recipe(), std::index_sequence_for()); -} - -} // namespace units::detail diff --git a/src/core/include/units/bits/unit_text.h b/src/core/include/units/bits/unit_text.h deleted file mode 100644 index 9421e5ef..00000000 --- a/src/core/include/units/bits/unit_text.h +++ /dev/null @@ -1,166 +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 - -#include -#include -#include -#include -#include - -namespace units::detail { - -inline constexpr basic_symbol_text base_multiplier("\u00D7 10", "x 10"); - -template -constexpr auto magnitude_text() -{ - constexpr auto exp10 = extract_power_of_10(M); - - constexpr Magnitude auto base = M / mag_power<10, exp10>; - constexpr Magnitude auto num = numerator(base); - constexpr Magnitude auto den = denominator(base); - static_assert(base == num / den, "Printing rational powers, or irrational bases, not yet supported"); - - 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 base_multiplier + superscript(); - } else if constexpr (num_value != 1 || den_value != 1 || exp10 != 0) { - auto txt = basic_fixed_string("[") + regular(); - if constexpr (den_value == 1) { - if constexpr (exp10 == 0) { - return txt + basic_fixed_string("]"); - } else { - return txt + " " + base_multiplier + superscript() + basic_fixed_string("]"); - } - } else { - if constexpr (exp10 == 0) { - return txt + basic_fixed_string("/") + regular() + basic_fixed_string("]"); - } else { - return txt + basic_fixed_string("/") + regular() + " " + base_multiplier + superscript() + - basic_fixed_string("]"); - } - } - } else { - return basic_fixed_string(""); - } -} - -template -constexpr auto prefix_or_magnitude_text() -{ - if constexpr (M == mag<1>()) { - // no ratio/prefix - return basic_fixed_string(""); - } else { - // try to form a prefix - using prefix = downcast>; - - if constexpr (can_be_prefixed && !is_same_v>) { - // print as a prefixed unit - return prefix::symbol; - } else { - // print as a ratio of the coherent unit - constexpr auto txt = magnitude_text(); - if constexpr (SymbolLen > 0 && txt.standard().size() > 0) - return txt + basic_fixed_string(" "); - else - return txt; - } - } -} - -template -constexpr auto derived_dimension_unit_text(exponent_list, std::index_sequence) -{ - return (exp_text::symbol, negative_exp_count, Idxs>() + ... + - basic_symbol_text(basic_fixed_string(""))); -} - -template -constexpr auto derived_dimension_unit_text(exponent_list list) -{ - return derived_dimension_unit_text(list, std::index_sequence_for()); -} - -template -constexpr auto exponent_list_with_named_units(exponent_list); - -template -constexpr auto exponent_list_with_named_units(Exp) -{ - using dim = TYPENAME Exp::dimension; - if constexpr (NamedUnit>) { - return exponent_list(); - } else { - using recipe = TYPENAME dim::recipe; - return exponent_list_with_named_units(recipe()); - } -} - -template -constexpr auto exponent_list_with_named_units(exponent_list) -{ - return type_list_join(); -} - -constexpr auto exponent_list_with_named_units(exponent_list<> empty) { return empty; } - -template -constexpr auto derived_dimension_unit_text() -{ - using recipe = TYPENAME Dim::recipe; - return derived_dimension_unit_text(exponent_list_with_named_units(recipe())); -} - -template -// TODO replace with `inline constexpr bool has_symbol` when MSVC cathes up -concept has_symbol = requires { T::symbol; }; - -template -constexpr auto unit_text() -{ - if constexpr (has_symbol) { - // already has a symbol so print it - return U::symbol; - } else { - // print as a prefix or ratio of a coherent unit - using coherent_unit = dimension_unit; - - constexpr auto symbol_text = []() { - if constexpr (has_symbol) - return coherent_unit::symbol; - else - return derived_dimension_unit_text(); - }(); - - constexpr auto prefix_txt = - prefix_or_magnitude_text(); - return prefix_txt + symbol_text; - } -} - -} // namespace units::detail diff --git a/src/core/include/units/magnitude.h b/src/core/include/units/magnitude.h index e4aff28b..234a16d5 100644 --- a/src/core/include/units/magnitude.h +++ b/src/core/include/units/magnitude.h @@ -146,15 +146,6 @@ struct power_v { namespace detail { -/** - * @brief Deduction guides for base_power: only permit deducing integral bases. - */ -// template U> -// base_power(T, U) -> base_power; -// template -// base_power(T) -> base_power; - -// Implementation for PowerV concept (below). template inline constexpr bool is_specialization_of_power_v = false; @@ -163,15 +154,39 @@ inline constexpr bool is_specialization_of_power_v> = true; } // namespace detail + +template +concept MagnitudeSpec = PowerVBase || detail::is_specialization_of_power_v; + +namespace detail { + +template +[[nodiscard]] consteval auto get_base(Element element) +{ + if constexpr (detail::is_specialization_of_power_v) + return Element::base; + else + return element; +} + +template +[[nodiscard]] consteval ratio get_exponent(Element) +{ + if constexpr (detail::is_specialization_of_power_v) + return Element::exponent; + else + return ratio{1}; +} + +} // namespace detail + + /** * @brief Concept to detect whether a _type_ is a valid base power. * * Note that this is somewhat incomplete. We must also detect whether a _value_ of that type is valid for use with * `magnitude<...>`. We will defer that second check to the constraints on the `magnitude` template. */ -template -concept PowerV = detail::is_specialization_of_power_v; - namespace detail { // We do not want magnitude type to have the `l` literal after a value for a small integral number. @@ -205,78 +220,82 @@ template } else { return power_v{}; } -}; +} -// consteval auto inverse(PowerV auto bp) { return power_v_or_T(); } +[[nodiscard]] consteval auto inverse(MagnitudeSpec auto el) +{ + return power_v_or_T(); +} // `widen_t` gives the widest arithmetic type in the same category, for intermediate computations. -// template -// using widen_t = conditional, -// conditional, long double, -// conditional, std::intmax_t, std::uintmax_t>>, -// T>; +template +using widen_t = conditional, + conditional, long double, + conditional, std::intmax_t, std::uintmax_t>>, + T>; // Raise an arbitrary arithmetic type to a positive integer power at compile time. -// template -// constexpr T int_power(T base, std::integral auto exp) -// { -// // As this function should only be called at compile time, the exceptions herein function as -// // "parameter-compatible static_asserts", and should not result in exceptions at runtime. -// if (exp < 0) { -// throw std::invalid_argument{"int_power only supports positive integer powers"}; -// } +template +[[nodiscard]] consteval T int_power(T base, std::integral auto exp) +{ + // As this function should only be called at compile time, the exceptions herein function as + // "parameter-compatible static_asserts", and should not result in exceptions at runtime. + if (exp < 0) { + throw std::invalid_argument{"int_power only supports positive integer powers"}; + } -// constexpr auto checked_multiply = [](auto a, auto b) { -// const auto result = a * b; -// UNITS_DIAGNOSTIC_PUSH -// UNITS_DIAGNOSTIC_IGNORE_FLOAT_EQUAL -// if (result / a != b) { -// throw std::overflow_error{"Wraparound detected"}; -// } -// UNITS_DIAGNOSTIC_POP -// return result; -// }; + constexpr auto checked_multiply = [](auto a, auto b) { + const auto result = a * b; + UNITS_DIAGNOSTIC_PUSH + UNITS_DIAGNOSTIC_IGNORE_FLOAT_EQUAL + if (result / a != b) { + throw std::overflow_error{"Wraparound detected"}; + } + UNITS_DIAGNOSTIC_POP + return result; + }; -// constexpr auto checked_square = [checked_multiply](auto a) { return checked_multiply(a, a); }; + constexpr auto checked_square = [checked_multiply](auto a) { return checked_multiply(a, a); }; -// // TODO(chogg): Unify this implementation with the one in pow.h. That one takes its exponent as a -// // template parameter, rather than a function parameter. + // TODO(chogg): Unify this implementation with the one in pow.h. That one takes its exponent as a + // template parameter, rather than a function parameter. -// if (exp == 0) { -// return T{1}; -// } + if (exp == 0) { + return T{1}; + } -// if (exp % 2 == 1) { -// return checked_multiply(base, int_power(base, exp - 1)); -// } + if (exp % 2 == 1) { + return checked_multiply(base, int_power(base, exp - 1)); + } -// return checked_square(int_power(base, exp / 2)); -// } + return checked_square(int_power(base, exp / 2)); +} -// template -// constexpr widen_t compute_base_power(PowerV auto bp) -// { -// // This utility can only handle integer powers. To compute rational powers at compile time, we'll -// // need to write a custom function. -// // -// // Note that since this function should only be called at compile time, the point of these -// // exceptions is to act as "static_assert substitutes", not to throw actual exceptions at runtime. -// if (bp.power.den != 1) { -// throw std::invalid_argument{"Rational powers not yet supported"}; -// } +template +[[nodiscard]] consteval widen_t compute_base_power(MagnitudeSpec auto el) +{ + // This utility can only handle integer powers. To compute rational powers at compile time, we'll + // need to write a custom function. + // + // Note that since this function should only be called at compile time, the point of these + // exceptions is to act as "static_assert substitutes", not to throw actual exceptions at runtime. + const auto exp = get_exponent(el); + if (exp.den != 1) { + throw std::invalid_argument{"Rational powers not yet supported"}; + } -// if (bp.power.num < 0) { -// if constexpr (std::is_integral_v) { -// throw std::invalid_argument{"Cannot represent reciprocal as integer"}; -// } else { -// return T{1} / compute_base_power(inverse(bp)); -// } -// } + if (exp.num < 0) { + if constexpr (std::is_integral_v) { + throw std::invalid_argument{"Cannot represent reciprocal as integer"}; + } else { + return T{1} / compute_base_power(inverse(el)); + } + } -// auto power = bp.power.num; -// return int_power(static_cast>(bp.get_base()), power); -// } + auto power = exp.num; + return int_power(static_cast>(get_base(el)), power); +} // A converter for the value member variable of magnitude (below). // @@ -285,7 +304,7 @@ template template // TODO(chogg): Migrate this to use `treat_as_floating_point`. requires(!std::is_integral_v || std::is_integral_v) -constexpr To checked_static_cast(From x) +[[nodiscard]] consteval To checked_static_cast(From x) { // This function should only ever be called at compile time. The purpose of these exceptions is // to produce compiler errors, because we cannot `static_assert` on function arguments. @@ -300,39 +319,10 @@ constexpr To checked_static_cast(From x) } // namespace detail -/** - * @brief Equality detection for two base powers. - */ -// template -// [[nodiscard]] consteval bool operator==(T, U) -// { -// return std::is_same_v; -// } - -template -concept MagnitudeSpec = PowerVBase || PowerV; // A variety of implementation detail helpers. namespace detail { -template -[[nodiscard]] consteval auto get_base(Element element) -{ - if constexpr (PowerV) - return Element::base; - else - return element; -} - -template -[[nodiscard]] consteval ratio get_exponent(Element) -{ - if constexpr (PowerV) - return Element::exponent; - else - return ratio{1}; -} - // The exponent of `factor` in the prime factorization of `n`. [[nodiscard]] consteval std::intmax_t multiplicity(std::intmax_t factor, std::intmax_t n) { @@ -493,16 +483,16 @@ inline constexpr bool is_specialization_of_magnitude> = true; /** * @brief The value of a Magnitude in a desired type T. */ -// template -// // TODO(chogg): Migrate this to use `treat_as_floating_point`. -// requires(!std::integral || is_integral(magnitude{})) -// constexpr T get_value(const magnitude&) -// { -// // Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow. -// constexpr auto result = detail::checked_static_cast((detail::compute_base_power(BPs) * ... * T{1})); +template +// TODO(chogg): Migrate this to use `treat_as_floating_point`. + requires(!std::integral || is_integral(magnitude{})) +constexpr T get_value(const magnitude&) +{ + // Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow. + constexpr auto result = detail::checked_static_cast((detail::compute_base_power(Ms) * ... * T{1})); -// return result; -// } + return result; +} /** @@ -525,7 +515,7 @@ template // Magnitude rational powers implementation. template -constexpr auto pow(magnitude) +[[nodiscard]] consteval auto pow(magnitude) { if constexpr (E.num == 0) { return magnitude<>{}; @@ -535,13 +525,13 @@ constexpr auto pow(magnitude) } template -constexpr auto sqrt(magnitude m) +[[nodiscard]] consteval auto sqrt(magnitude m) { return pow(m); } template -constexpr auto cbrt(magnitude m) +[[nodiscard]] consteval auto cbrt(magnitude m) { return pow(m); } @@ -571,7 +561,7 @@ constexpr Magnitude auto operator*(Magnitude auto m, magnitude<>) { return m; } // Recursive case for the product of any two non-identity Magnitudes. template -constexpr Magnitude auto operator*(magnitude, magnitude) +[[nodiscard]] consteval Magnitude auto operator*(magnitude, magnitude) { using namespace detail; @@ -611,7 +601,7 @@ constexpr Magnitude auto operator*(magnitude, magnitude) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude quotient implementation. -constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); } +[[nodiscard]] consteval auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1>(r); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Magnitude numerator and denominator implementation. @@ -619,19 +609,15 @@ constexpr auto operator/(Magnitude auto l, Magnitude auto r) { return l * pow<-1 namespace detail { // The largest integer which can be extracted from any magnitude with only a single basis vector. -template -constexpr auto integer_part(magnitude) +template +[[nodiscard]] consteval auto integer_part(magnitude) { - constexpr auto power_num = BP.power.num; - constexpr auto power_den = BP.power.den; + constexpr auto power_num = get_exponent(M).num; + constexpr auto power_den = get_exponent(M).den; - if constexpr (std::is_integral_v && (power_num >= power_den)) { - constexpr auto largest_integer_power = [=](PowerV auto bp) { - bp.power = (power_num / power_den); // Note: integer division intended. - return bp; - }(BP); // Note: lambda is immediately invoked. - - return magnitude{}; + if constexpr (std::is_integral_v && (power_num >= power_den)) { + // largest integer power + return magnitude()>{}; // Note: integer division intended } else { return magnitude<>{}; } @@ -639,13 +625,13 @@ constexpr auto integer_part(magnitude) } // namespace detail -template -constexpr auto numerator(magnitude) +template +[[nodiscard]] consteval auto numerator(magnitude) { - return (detail::integer_part(magnitude{}) * ... * magnitude<>{}); + return (detail::integer_part(magnitude{}) * ... * magnitude<>{}); } -constexpr auto denominator(Magnitude auto m) { return numerator(pow<-1>(m)); } +[[nodiscard]] consteval auto denominator(Magnitude auto m) { return numerator(pow<-1>(m)); } // Implementation of conversion to ratio goes here, because it needs `numerator()` and `denominator()`. // constexpr ratio as_ratio(Magnitude auto m) @@ -677,44 +663,51 @@ constexpr auto denominator(Magnitude auto m) { return numerator(pow<-1>(m)); } // minimum power for each base (where absent bases implicitly have a power of 0). namespace detail { -template -constexpr auto remove_positive_power(magnitude m) + +template +[[nodiscard]] consteval auto remove_positive_power(magnitude m) { - if constexpr (BP.power.num < 0) { + if constexpr (get_exponent(M).num < 0) { return m; } else { return magnitude<>{}; } } -template -constexpr auto remove_positive_powers(magnitude) +template +[[nodiscard]] consteval auto remove_positive_powers(magnitude) { - return (magnitude<>{} * ... * remove_positive_power(magnitude{})); + return (magnitude<>{} * ... * remove_positive_power(magnitude{})); } } // namespace detail // Base cases, for when either (or both) inputs are the identity. -constexpr auto common_magnitude(magnitude<>, magnitude<>) { return magnitude<>{}; } -constexpr auto common_magnitude(magnitude<>, Magnitude auto m) { return detail::remove_positive_powers(m); } -constexpr auto common_magnitude(Magnitude auto m, magnitude<>) { return detail::remove_positive_powers(m); } +[[nodiscard]] consteval auto common_magnitude(magnitude<>, magnitude<>) { return magnitude<>{}; } +[[nodiscard]] consteval auto common_magnitude(magnitude<>, Magnitude auto m) +{ + return detail::remove_positive_powers(m); +} +[[nodiscard]] consteval auto common_magnitude(Magnitude auto m, magnitude<>) +{ + return detail::remove_positive_powers(m); +} // Recursive case for the common Magnitude of any two non-identity Magnitudes. template -constexpr auto common_magnitude(magnitude, magnitude) +[[nodiscard]] consteval auto common_magnitude(magnitude, magnitude) { using detail::remove_positive_power; - if constexpr (H1.get_base() < H2.get_base()) { + if constexpr (get_base(H1) < get_base(H2)) { // When H1 has the smaller base, prepend to result from recursion. return remove_positive_power(magnitude

{}) * common_magnitude(magnitude{}, magnitude{}); - } else if constexpr (H2.get_base() < H1.get_base()) { + } else if constexpr (get_base(H2) < get_base(H1)) { // When H2 has the smaller base, prepend to result from recursion. return remove_positive_power(magnitude

{}) * common_magnitude(magnitude{}, magnitude{}); } else { // When the bases are equal, pick whichever has the lower power. constexpr auto common_tail = common_magnitude(magnitude{}, magnitude{}); - if constexpr ((H1.power) < (H2.power)) { + if constexpr (get_exponent(H1) < get_exponent(H2)) { return magnitude

{} * common_tail; } else { return magnitude

{} * common_tail; @@ -740,7 +733,7 @@ namespace detail { template requires(N > 0) struct prime_factorization { - static constexpr std::intmax_t get_or_compute_first_factor() + [[nodiscard]] static consteval std::intmax_t get_or_compute_first_factor() { if constexpr (known_first_factor.has_value()) { return known_first_factor.value(); @@ -786,15 +779,16 @@ template inline constexpr Magnitude auto mag_power = pow(mag); namespace detail { -template -constexpr ratio get_power(T base, magnitude) + +template +[[nodiscard]] consteval ratio get_power(T base, magnitude) { - return ((BPs.get_base() == base ? BPs.power : ratio{0}) + ... + ratio{0}); + return ((get_base(Ms) == base ? get_exponent(Ms) : ratio{0}) + ... + ratio{0}); } -constexpr std::intmax_t integer_part(ratio r) { return r.num / r.den; } +[[nodiscard]] consteval std::intmax_t integer_part(ratio r) { return r.num / r.den; } -constexpr std::intmax_t extract_power_of_10(Magnitude auto m) +[[nodiscard]] consteval std::intmax_t extract_power_of_10(Magnitude auto m) { const auto power_of_2 = get_power(2, m); const auto power_of_5 = get_power(5, m); @@ -805,6 +799,6 @@ constexpr std::intmax_t extract_power_of_10(Magnitude auto m) return integer_part((detail::abs(power_of_2) < detail::abs(power_of_5)) ? power_of_2 : power_of_5); } -} // namespace detail +} // namespace detail } // namespace units diff --git a/src/core/include/units/unit.h b/src/core/include/units/unit.h index f537daac..a620c2cb 100644 --- a/src/core/include/units/unit.h +++ b/src/core/include/units/unit.h @@ -22,12 +22,16 @@ #pragma once +#include #include #include +#include #include #include #include #include +#include +#include // #include @@ -209,19 +213,16 @@ template inline constexpr bool is_power_of_unit = requires { requires is_specialization_of_power && Unit; }; -template -concept UnitLike = Unit || is_power_of_unit; - template inline constexpr bool is_per_of_units = false; template -inline constexpr bool is_per_of_units> = (... && UnitLike); +inline constexpr bool is_per_of_units> = (... && (Unit || is_power_of_unit)); } // namespace detail template -concept DerivedUnitSpec = detail::UnitLike || detail::is_per_of_units; +concept DerivedUnitSpec = Unit || detail::is_power_of_unit || detail::is_per_of_units; /** * @brief Measurement unit for a derived quantity @@ -312,63 +313,75 @@ inline constexpr bool is_unit = true; * @tparam U a unit to use as a `reference_unit` * @tparam M a Magnitude representing an absolute scaling factor of this unit */ -template +template struct canonical_unit { M mag; U reference_unit; }; -[[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u); +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&); -template -[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile scaled_unit&) +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit&); + +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const power&); + +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const derived_unit&); + +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const scaled_unit&) { - auto base = get_canonical_unit(U{}); + auto base = get_canonical_unit_impl(U{}, U{}); return canonical_unit{M * base.mag, base.reference_unit}; } -template -[[nodiscard]] constexpr auto get_canonical_unit_impl(T t, const volatile named_unit&) +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit&) { return canonical_unit{mag<1>, t}; } -template -[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile named_unit&) +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit&) { - return get_canonical_unit(U); + return get_canonical_unit_impl(U, U); } -template -[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile power&) +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const power&) { - auto base = get_canonical_unit(F{}); + auto base = get_canonical_unit_impl(F{}, F{}); return canonical_unit{ pow::exponent>(base.mag), derived_unit, power::exponent>>{}}; } -template -[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile derived_unit&) +template +[[nodiscard]] consteval auto get_canonical_unit_impl(T, const derived_unit&) { if constexpr (type_list_size::_den_> != 0) { - auto num = get_canonical_unit(type_list_map::_num_, derived_unit>{}); - auto den = get_canonical_unit(type_list_map::_den_, derived_unit>{}); + using num_type = type_list_map::_num_, derived_unit>; + using den_type = type_list_map::_den_, derived_unit>; + auto num = get_canonical_unit_impl(num_type{}, num_type{}); + auto den = get_canonical_unit_impl(den_type{}, den_type{}); return canonical_unit{num.mag / den.mag, num.reference_unit / den.reference_unit}; } else { - auto num = (one * ... * get_canonical_unit(Us{}).reference_unit); - auto mag = (units::mag<1> * ... * get_canonical_unit(Us{}).mag); + auto num = (one * ... * get_canonical_unit_impl(Us{}, Us{}).reference_unit); + auto mag = (units::mag<1> * ... * get_canonical_unit_impl(Us{}, Us{}).mag); return canonical_unit{mag, num}; } } -[[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u) { return get_canonical_unit_impl(u, u); } +[[nodiscard]] consteval auto get_canonical_unit(Unit auto u) { return get_canonical_unit_impl(u, u); } // TODO What if the same unit will have different types (i.e. user will inherit its own type from `metre`)? // Is there a better way to sort units here? Some of them may not have symbol at all (like all units of // dimensionless quantities). -template -struct unit_less : std::bool_constant() < type_name()> {}; +template +struct unit_less : std::bool_constant() < type_name()> {}; template using type_list_of_unit_less = expr_less; @@ -398,17 +411,17 @@ template * prevents passing it as an element to the `derived_unit`. In such case only the reference unit is passed * to the derived unit and the magnitude remains outside forming another scaled unit as a result of the operation. */ -template -[[nodiscard]] consteval Unit auto operator*(U1 u1, U2 u2) +template +[[nodiscard]] consteval Unit auto operator*(Lhs lhs, Rhs rhs) { - if constexpr (detail::is_specialization_of_scaled_unit && detail::is_specialization_of_scaled_unit) - return (U1::mag * U2::mag) * (U1::reference_unit * U2::reference_unit); - else if constexpr (detail::is_specialization_of_scaled_unit) - return U1::mag * (U1::reference_unit * u2); - else if constexpr (detail::is_specialization_of_scaled_unit) - return U2::mag * (u1 * U2::reference_unit); + if constexpr (detail::is_specialization_of_scaled_unit && detail::is_specialization_of_scaled_unit) + return (Lhs::mag * Rhs::mag) * (Lhs::reference_unit * Rhs::reference_unit); + else if constexpr (detail::is_specialization_of_scaled_unit) + return Lhs::mag * (Lhs::reference_unit * rhs); + else if constexpr (detail::is_specialization_of_scaled_unit) + return Rhs::mag * (lhs * Rhs::reference_unit); else - return detail::expr_multiply(); + return detail::expr_multiply(lhs, rhs); } /** @@ -416,31 +429,31 @@ template * prevents passing it as an element to the `derived_unit`. In such case only the reference unit is passed * to the derived unit and the magnitude remains outside forming another scaled unit as a result of the operation. */ -template -[[nodiscard]] consteval Unit auto operator/(U1 u1, U2 u2) +template +[[nodiscard]] consteval Unit auto operator/(Lhs lhs, Rhs rhs) { - if constexpr (detail::is_specialization_of_scaled_unit && detail::is_specialization_of_scaled_unit) - return (U1::mag / U2::mag) * (U1::reference_unit / U2::reference_unit); - else if constexpr (detail::is_specialization_of_scaled_unit) - return U1::mag * (U1::reference_unit / u2); - else if constexpr (detail::is_specialization_of_scaled_unit) - return U2::mag * (u1 / U2::reference_unit); + if constexpr (detail::is_specialization_of_scaled_unit && detail::is_specialization_of_scaled_unit) + return (Lhs::mag / Rhs::mag) * (Lhs::reference_unit / Rhs::reference_unit); + else if constexpr (detail::is_specialization_of_scaled_unit) + return Lhs::mag * (Lhs::reference_unit / rhs); + else if constexpr (detail::is_specialization_of_scaled_unit) + return Rhs::mag * (lhs / Rhs::reference_unit); else - return detail::expr_divide(); + return detail::expr_divide(lhs, rhs); } template -[[nodiscard]] consteval Unit auto operator/(int value, U) +[[nodiscard]] consteval Unit auto operator/(int value, U u) { gsl_Assert(value == 1); - return detail::expr_invert(); + return detail::expr_invert(u); } template [[nodiscard]] consteval Unit auto operator/(U, int) = delete; -template -[[nodiscard]] consteval bool operator==(U1 lhs, U2 rhs) +template +[[nodiscard]] consteval bool operator==(Lhs lhs, Rhs rhs) { auto canonical_lhs = detail::get_canonical_unit(lhs); auto canonical_rhs = detail::get_canonical_unit(rhs); @@ -450,8 +463,8 @@ template // Convertible -template -[[nodiscard]] consteval bool convertible(U1 lhs, U2 rhs) +template +[[nodiscard]] consteval bool convertible(Lhs lhs, Rhs rhs) { auto canonical_lhs = detail::get_canonical_unit(lhs); auto canonical_rhs = detail::get_canonical_unit(rhs); @@ -465,9 +478,232 @@ inline constexpr decltype(U * U) square; template inline constexpr decltype(U * U * U) cubic; + +// get_unit_symbol + +enum class text_encoding { + unicode, // m³; µs + ascii, // m^3; us + default_encoding = unicode +}; + +enum class unit_symbol_denominator { + solidus_one, // m/s; kg m-1 s-1 + always_solidus, // m/s; kg/(m s) + always_negative, // m s-1; kg m-1 s-1 + default_denominator = solidus_one +}; + +enum class unit_symbol_separator { + space, // kg m²/s² + dot, // kg⋅m²/s² (valid only for unicode encoding) + default_separator = space +}; + +struct unit_symbol_formatting { + text_encoding encoding = text_encoding::default_encoding; + unit_symbol_denominator denominator = unit_symbol_denominator::default_denominator; + unit_symbol_separator separator = unit_symbol_separator::default_separator; +}; + +namespace detail { + +// TODO Should `basic_symbol_text` be fixed to use `char` type for both encodings? +template Out> +constexpr Out copy(const basic_symbol_text& txt, text_encoding encoding, Out out) +{ + if (encoding == text_encoding::unicode) { + if (is_same_v) + return copy(txt.unicode(), out).out; + else + static_assert("Unicode text can't be copied to CharT output"); + } else { + if (is_same_v) + return copy(txt.ascii(), out).out; + else + static_assert("ASCII text can't be copied to CharT output"); + } +} + +inline constexpr basic_symbol_text base_multiplier("\u00D7 10", "x 10"); + +template +constexpr auto magnitude_text() +{ + constexpr auto exp10 = extract_power_of_10(M); + + constexpr Magnitude auto base = M / mag_power<10, exp10>; + constexpr Magnitude auto num = numerator(base); + constexpr Magnitude auto den = denominator(base); + static_assert(base == num / den, "Printing rational powers, or irrational bases, not yet supported"); + + 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 base_multiplier + superscript(); + } else if constexpr (num_value != 1 || den_value != 1 || exp10 != 0) { + auto txt = basic_fixed_string("[") + regular(); + if constexpr (den_value == 1) { + if constexpr (exp10 == 0) { + return txt + basic_fixed_string("]"); + } else { + return txt + " " + base_multiplier + superscript() + basic_fixed_string("]"); + } + } else { + if constexpr (exp10 == 0) { + return txt + basic_fixed_string("/") + regular() + basic_fixed_string("]"); + } else { + return txt + basic_fixed_string("/") + regular() + " " + base_multiplier + superscript() + + basic_fixed_string("]"); + } + } + } else { + return basic_fixed_string(""); + } +} + +template Out> +constexpr Out print_separator(Out out, unit_symbol_formatting fmt) +{ + if (fmt.separator == unit_symbol_separator::dot) { + if (fmt.encoding != text_encoding::unicode) + throw std::invalid_argument("'unit_symbol_separator::dot' can be only used with 'text_encoding::unicode'"); + copy(std::string_view("⋅"), out); + } else { + *out++ = ' '; + } + return out; +} + +template Out, Unit U> + requires requires { U::symbol; } +constexpr Out unit_symbol_impl(Out out, U, unit_symbol_formatting fmt, bool negative_power) +{ + out = copy(U::symbol, fmt.encoding, out); + if (negative_power) { + constexpr auto txt = superscript<-1>(); + out = copy(txt, fmt.encoding, out); + } + return out; +} + +template Out, auto M, typename U> +constexpr Out unit_symbol_impl(Out out, const scaled_unit& u, unit_symbol_formatting fmt, bool negative_power) +{ + if constexpr (M == mag<1>) { + // no ratio/prefix + return unit_symbol_impl(out, u.reference_unit, fmt, negative_power); + } else { + constexpr auto mag_txt = magnitude_text(); + out = copy(mag_txt, fmt.encoding, out); + + if constexpr (std::derived_from, derived_unit<>>) + return out; + else { + *out++ = ' '; + return unit_symbol_impl(out, u.reference_unit, fmt, negative_power); + } + } +} + +template Out, typename F, int Num, int... Den> +constexpr auto unit_symbol_impl(Out out, const power&, unit_symbol_formatting fmt, bool negative_power) +{ + out = unit_symbol_impl(out, F{}, fmt, false); // negative power component will be added below if needed + + constexpr ratio r = power::exponent; + if constexpr (r.den != 1) { + // add root part + constexpr auto txt = txt + basic_fixed_string("^(") + regular() + basic_fixed_string("/") + + regular() + basic_fixed_string(")"); + return copy(txt, fmt.encoding, out); + } else if constexpr (r.num != 1) { + // add exponent part + if (negative_power) { + constexpr auto txt = superscript<-r.num>(); + return copy(txt, fmt.encoding, out); + } else { + constexpr auto txt = superscript(); + return copy(txt, fmt.encoding, out); + } + } +} + +template Out, DerivedUnitSpec M> +constexpr Out unit_symbol_impl(Out out, M m, std::size_t Idx, unit_symbol_formatting fmt, bool negative_power) +{ + if (Idx > 0) out = print_separator(out, fmt); + return unit_symbol_impl(out, m, fmt, negative_power); +} + +template Out, DerivedUnitSpec... Ms, std::size_t... Idxs> +constexpr Out unit_symbol_impl(Out out, const type_list&, std::index_sequence, + unit_symbol_formatting fmt, bool negative_power) +{ + return (..., (out = unit_symbol_impl(out, Ms{}, Idxs, fmt, negative_power))); +} + +template Out, DerivedUnitSpec... Nums, DerivedUnitSpec... Dens> +constexpr Out unit_symbol_impl(Out out, const type_list& nums, const type_list& dens, + unit_symbol_formatting fmt) +{ + if constexpr (sizeof...(Nums) == 0 && sizeof...(Dens) == 0) { + // dimensionless quantity + return out; + } else if constexpr (sizeof...(Dens) == 0) { + // no denominator + return unit_symbol_impl(out, nums, std::index_sequence_for(), fmt, false); + } else { + using enum unit_symbol_denominator; + if constexpr (sizeof...(Nums) > 0) { + unit_symbol_impl(out, nums, std::index_sequence_for(), fmt, false); + } + + if (fmt.denominator == always_solidus || (fmt.denominator == solidus_one && sizeof...(Dens) == 1)) { + if constexpr (sizeof...(Nums) == 0) *out++ = '1'; + *out++ = '/'; + } else { + out = print_separator(out, fmt); + } + + if (fmt.denominator == always_solidus && sizeof...(Dens) > 1) *out++ = '('; + bool negative_power = fmt.denominator == always_negative || (fmt.denominator == solidus_one && sizeof...(Dens) > 1); + out = unit_symbol_impl(out, dens, std::index_sequence_for(), fmt, negative_power); + if (fmt.denominator == always_solidus && sizeof...(Dens) > 1) *out++ = ')'; + return out; + } +} + +template Out, typename... Us> +constexpr Out unit_symbol_impl(Out out, const derived_unit&, unit_symbol_formatting fmt, bool negative_power) +{ + gsl_Assert(negative_power == false); + return unit_symbol_impl(out, typename derived_unit::_num_{}, typename derived_unit::_den_{}, + fmt); +} + +} // namespace detail + + +template Out, Unit U> +constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{}) +{ + return detail::unit_symbol_impl(out, u, fmt, false); +} + +template +[[nodiscard]] constexpr std::basic_string unit_symbol(U u, unit_symbol_formatting fmt = unit_symbol_formatting{}) +{ + std::basic_string buffer; + unit_symbol_to(std::back_inserter(buffer), u, fmt); + return buffer; +} + } // namespace units namespace std { + // TODO implement this template requires(units::convertible(U1{}, U2{})) diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index 6d8b74d8..9d55eb61 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -235,6 +235,9 @@ static_assert(is_of_type, derived_unit>>); static_assert(is_of_type, derived_unit>>); static_assert(is_of_type * metre, derived_unit>>); static_assert(is_of_type, derived_unit>>); +static_assert(is_of_type / metre, metre_>); +static_assert(is_of_type / metre, derived_unit>>); +static_assert(is_of_type / square, metre_>); static_assert(is_of_type>>); static_assert(is_of_type, derived_unit>>>); @@ -350,8 +353,72 @@ static_assert(joule == newton * metre); static_assert(watt == joule / second); static_assert(watt == kilogram * square / cubic); -// static_assert(centimetre::symbol == "cm"); -// static_assert(kilometre::symbol == "km"); -// static_assert(kilometre_per_hour::symbol == "km/h"); +// unit symbols +#ifdef __cpp_lib_constexpr_string + +using enum text_encoding; +using enum unit_symbol_denominator; +using enum unit_symbol_separator; + +// named units +static_assert(unit_symbol(metre) == "m"); +static_assert(unit_symbol(second) == "s"); +static_assert(unit_symbol(joule) == "J"); +static_assert(unit_symbol(degree_Celsius) == "\u00B0C"); +static_assert(unit_symbol(degree_Celsius, {.encoding = ascii}) == "`C"); +static_assert(unit_symbol(kilometre) == "km"); +static_assert(unit_symbol(si::milli) == "mm"); +static_assert(unit_symbol(si::micro) == "µm"); +static_assert(unit_symbol(si::micro, {.encoding = ascii}) == "um"); +static_assert(unit_symbol(kilojoule) == "kJ"); +static_assert(unit_symbol(hour) == "h"); + +// scaled units +static_assert(unit_symbol(mag<100> * metre) == "× 10² m"); +static_assert(unit_symbol(mag<100> * metre, {.encoding = ascii}) == "x 10^2 m"); +static_assert(unit_symbol(mag<60> * second) == "[6 × 10¹] s"); +static_assert(unit_symbol(mag<60> * second, {.encoding = ascii}) == "[6 x 10^1] s"); + +static_assert(unit_symbol(one) == ""); +static_assert(unit_symbol(square) == "m²"); +static_assert(unit_symbol(square, {.encoding = ascii}) == "m^2"); +static_assert(unit_symbol(cubic) == "m³"); +static_assert(unit_symbol(cubic, {.encoding = ascii}) == "m^3"); +static_assert(unit_symbol(metre / second) == "m/s"); +static_assert(unit_symbol(metre / second, {.denominator = always_solidus}) == "m/s"); +static_assert(unit_symbol(metre / second, {.denominator = always_negative}) == "m s⁻¹"); +static_assert(unit_symbol(metre / second, {.encoding = ascii, .denominator = always_negative}) == "m s^-1"); +static_assert(unit_symbol(metre / second, {.denominator = always_negative, .separator = dot}) == "m⋅s⁻¹"); +static_assert(unit_symbol(metre / square) == "m/s²"); +static_assert(unit_symbol(metre / square, {.encoding = ascii}) == "m/s^2"); +static_assert(unit_symbol(metre / square, {.denominator = always_solidus}) == "m/s²"); +static_assert(unit_symbol(metre / square, {.encoding = ascii, .denominator = always_solidus}) == "m/s^2"); +static_assert(unit_symbol(metre / square, {.denominator = always_negative}) == "m s⁻²"); +static_assert(unit_symbol(metre / square, {.encoding = ascii, .denominator = always_negative}) == "m s^-2"); +static_assert(unit_symbol(metre / square, {.denominator = always_negative, .separator = dot}) == "m⋅s⁻²"); +static_assert(unit_symbol(kilogram * metre / square) == "kg m/s²"); +static_assert(unit_symbol(kilogram * metre / square, {.separator = dot}) == "kg⋅m/s²"); +static_assert(unit_symbol(kilogram * metre / square, {.encoding = ascii}) == "kg m/s^2"); +static_assert(unit_symbol(kilogram * metre / square, {.denominator = always_solidus}) == "kg m/s²"); +static_assert(unit_symbol(kilogram * metre / square, {.encoding = ascii, .denominator = always_solidus}) == + "kg m/s^2"); +static_assert(unit_symbol(kilogram * metre / square, {.denominator = always_negative}) == "kg m s⁻²"); +static_assert(unit_symbol(kilogram * metre / square, {.encoding = ascii, .denominator = always_negative}) == + "kg m s^-2"); +static_assert(unit_symbol(kilogram * metre / square, {.denominator = always_negative, .separator = dot}) == + "kg⋅m⋅s⁻²"); +static_assert(unit_symbol(kilogram / metre / square) == "kg m⁻¹ s⁻²"); +static_assert(unit_symbol(kilogram / metre / square, {.separator = dot}) == "kg⋅m⁻¹⋅s⁻²"); +static_assert(unit_symbol(kilogram / metre / square, {.encoding = ascii}) == "kg m^-1 s^-2"); +static_assert(unit_symbol(kilogram / metre / square, {.denominator = always_solidus}) == "kg/(m s²)"); +static_assert(unit_symbol(kilogram / metre / square, {.encoding = ascii, .denominator = always_solidus}) == + "kg/(m s^2)"); +static_assert(unit_symbol(kilogram / metre / square, {.denominator = always_negative}) == "kg m⁻¹ s⁻²"); +static_assert(unit_symbol(kilogram / metre / square, {.encoding = ascii, .denominator = always_negative}) == + "kg m^-1 s^-2"); +static_assert(unit_symbol(kilogram / metre / square, {.denominator = always_negative, .separator = dot}) == + "kg⋅m⁻¹⋅s⁻²"); + +#endif // __cpp_lib_constexpr_string } // namespace