feat: unit symbol text output support added

This commit is contained in:
Mateusz Pusz
2022-10-22 19:27:39 +02:00
parent 17fd0900a4
commit 17036cb2d0
5 changed files with 499 additions and 457 deletions

View File

@@ -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 <units/bits/external/fixed_string.h>
#include <units/bits/external/text_tools.h>
#include <units/derived_dimension.h>
#include <units/symbol_text.h>
namespace units::detail {
template<bool Divide, std::size_t NegativeExpCount, std::size_t Idx>
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<typename E, basic_symbol_text Symbol, std::size_t NegativeExpCount, std::size_t Idx>
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<abs(E::num)>() + basic_fixed_string("/") + regular<E::den>() +
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<E::num>();
} else if constexpr (E::num != -1) { // -1 is replaced with '/' sign here
return txt + superscript<abs(E::num)>();
} else {
return txt;
}
} else {
return txt;
}
}
template<typename... Es>
inline constexpr int negative_exp_count = ((Es::num < 0 ? 1 : 0) + ... + 0);
template<typename... Us, typename... Es, std::size_t... Idxs>
constexpr auto derived_symbol_text(exponent_list<Es...>, std::index_sequence<Idxs...>)
{
constexpr auto neg_exp = negative_exp_count<Es...>;
return (exp_text<Es, Us::symbol, neg_exp, Idxs>() + ...);
}
template<DerivedDimension Dim, Unit... Us>
constexpr auto derived_symbol_text()
{
return derived_symbol_text<Us...>(typename Dim::recipe(), std::index_sequence_for<Us...>());
}
} // namespace units::detail

View File

@@ -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 <units/bits/derived_symbol_text.h>
#include <units/bits/external/text_tools.h>
#include <units/derived_dimension.h>
#include <units/prefix.h>
#include <units/unit.h>
namespace units::detail {
inline constexpr basic_symbol_text base_multiplier("\u00D7 10", "x 10");
template<Magnitude auto M>
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<std::intmax_t>(num);
constexpr auto den_value = get_value<std::intmax_t>(den);
if constexpr (num_value == 1 && den_value == 1 && exp10 != 0) {
return base_multiplier + superscript<exp10>();
} else if constexpr (num_value != 1 || den_value != 1 || exp10 != 0) {
auto txt = basic_fixed_string("[") + regular<num_value>();
if constexpr (den_value == 1) {
if constexpr (exp10 == 0) {
return txt + basic_fixed_string("]");
} else {
return txt + " " + base_multiplier + superscript<exp10>() + basic_fixed_string("]");
}
} else {
if constexpr (exp10 == 0) {
return txt + basic_fixed_string("/") + regular<den_value>() + basic_fixed_string("]");
} else {
return txt + basic_fixed_string("/") + regular<den_value>() + " " + base_multiplier + superscript<exp10>() +
basic_fixed_string("]");
}
}
} else {
return basic_fixed_string("");
}
}
template<Unit U, Magnitude auto M, std::size_t SymbolLen>
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<detail::prefix_base<M>>;
if constexpr (can_be_prefixed<U> && !is_same_v<prefix, prefix_base<M>>) {
// print as a prefixed unit
return prefix::symbol;
} else {
// print as a ratio of the coherent unit
constexpr auto txt = magnitude_text<M>();
if constexpr (SymbolLen > 0 && txt.standard().size() > 0)
return txt + basic_fixed_string(" ");
else
return txt;
}
}
}
template<typename... Es, std::size_t... Idxs>
constexpr auto derived_dimension_unit_text(exponent_list<Es...>, std::index_sequence<Idxs...>)
{
return (exp_text<Es, dimension_unit<typename Es::dimension>::symbol, negative_exp_count<Es...>, Idxs>() + ... +
basic_symbol_text(basic_fixed_string("")));
}
template<typename... Es>
constexpr auto derived_dimension_unit_text(exponent_list<Es...> list)
{
return derived_dimension_unit_text(list, std::index_sequence_for<Es...>());
}
template<Exponent... Es>
constexpr auto exponent_list_with_named_units(exponent_list<Es...>);
template<Exponent Exp>
constexpr auto exponent_list_with_named_units(Exp)
{
using dim = TYPENAME Exp::dimension;
if constexpr (NamedUnit<dimension_unit<dim>>) {
return exponent_list<Exp>();
} else {
using recipe = TYPENAME dim::recipe;
return exponent_list_with_named_units(recipe());
}
}
template<Exponent... Es>
constexpr auto exponent_list_with_named_units(exponent_list<Es...>)
{
return type_list_join<decltype(exponent_list_with_named_units(Es()))...>();
}
constexpr auto exponent_list_with_named_units(exponent_list<> empty) { return empty; }
template<Dimension Dim>
constexpr auto derived_dimension_unit_text()
{
using recipe = TYPENAME Dim::recipe;
return derived_dimension_unit_text(exponent_list_with_named_units(recipe()));
}
template<typename T>
// TODO replace with `inline constexpr bool has_symbol` when MSVC cathes up
concept has_symbol = requires { T::symbol; };
template<Dimension Dim, Unit U>
constexpr auto unit_text()
{
if constexpr (has_symbol<U>) {
// 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<Dim>;
constexpr auto symbol_text = []() {
if constexpr (has_symbol<coherent_unit>)
return coherent_unit::symbol;
else
return derived_dimension_unit_text<Dim>();
}();
constexpr auto prefix_txt =
prefix_or_magnitude_text<U, U::mag / coherent_unit::mag, symbol_text.standard().size()>();
return prefix_txt + symbol_text;
}
}
} // namespace units::detail

View File

@@ -146,15 +146,6 @@ struct power_v {
namespace detail {
/**
* @brief Deduction guides for base_power: only permit deducing integral bases.
*/
// template<std::integral T, std::convertible_to<ratio> U>
// base_power(T, U) -> base_power<std::intmax_t>;
// template<std::integral T>
// base_power(T) -> base_power<std::intmax_t>;
// Implementation for PowerV concept (below).
template<typename T>
inline constexpr bool is_specialization_of_power_v = false;
@@ -163,15 +154,39 @@ inline constexpr bool is_specialization_of_power_v<power_v<V, Ints...>> = true;
} // namespace detail
template<typename T>
concept MagnitudeSpec = PowerVBase<T> || detail::is_specialization_of_power_v<T>;
namespace detail {
template<MagnitudeSpec Element>
[[nodiscard]] consteval auto get_base(Element element)
{
if constexpr (detail::is_specialization_of_power_v<Element>)
return Element::base;
else
return element;
}
template<MagnitudeSpec Element>
[[nodiscard]] consteval ratio get_exponent(Element)
{
if constexpr (detail::is_specialization_of_power_v<Element>)
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<typename T>
concept PowerV = detail::is_specialization_of_power_v<T>;
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<PowerVBase auto V, ratio R>
} else {
return power_v<shortT, R.num, R.den>{};
}
};
}
// consteval auto inverse(PowerV auto bp) { return power_v_or_T<bp.base, bp.exponent * (-1)>(); }
[[nodiscard]] consteval auto inverse(MagnitudeSpec auto el)
{
return power_v_or_T<get_base(el), get_exponent(el) * (-1)>();
}
// `widen_t` gives the widest arithmetic type in the same category, for intermediate computations.
// template<typename T>
// using widen_t = conditional<std::is_arithmetic_v<T>,
// conditional<std::is_floating_point_v<T>, long double,
// conditional<std::is_signed_v<T>, std::intmax_t, std::uintmax_t>>,
// T>;
template<typename T>
using widen_t = conditional<std::is_arithmetic_v<T>,
conditional<std::is_floating_point_v<T>, long double,
conditional<std::is_signed_v<T>, std::intmax_t, std::uintmax_t>>,
T>;
// Raise an arbitrary arithmetic type to a positive integer power at compile time.
// template<typename T>
// 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<typename T>
[[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<typename T>
// constexpr widen_t<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<typename T>
[[nodiscard]] consteval widen_t<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<T>) {
// throw std::invalid_argument{"Cannot represent reciprocal as integer"};
// } else {
// return T{1} / compute_base_power<T>(inverse(bp));
// }
// }
if (exp.num < 0) {
if constexpr (std::is_integral_v<T>) {
throw std::invalid_argument{"Cannot represent reciprocal as integer"};
} else {
return T{1} / compute_base_power<T>(inverse(el));
}
}
// auto power = bp.power.num;
// return int_power(static_cast<widen_t<T>>(bp.get_base()), power);
// }
auto power = exp.num;
return int_power(static_cast<widen_t<T>>(get_base(el)), power);
}
// A converter for the value member variable of magnitude (below).
//
@@ -285,7 +304,7 @@ template<PowerVBase auto V, ratio R>
template<typename To, typename From>
// TODO(chogg): Migrate this to use `treat_as_floating_point`.
requires(!std::is_integral_v<To> || std::is_integral_v<From>)
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<PowerV T, PowerV U>
// [[nodiscard]] consteval bool operator==(T, U)
// {
// return std::is_same_v<T, U>;
// }
template<typename T>
concept MagnitudeSpec = PowerVBase<T> || PowerV<T>;
// A variety of implementation detail helpers.
namespace detail {
template<MagnitudeSpec Element>
[[nodiscard]] consteval auto get_base(Element element)
{
if constexpr (PowerV<Element>)
return Element::base;
else
return element;
}
template<MagnitudeSpec Element>
[[nodiscard]] consteval ratio get_exponent(Element)
{
if constexpr (PowerV<Element>)
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<magnitude<Ms...>> = true;
/**
* @brief The value of a Magnitude in a desired type T.
*/
// template<typename T, auto... BPs>
// // TODO(chogg): Migrate this to use `treat_as_floating_point`.
// requires(!std::integral<T> || is_integral(magnitude<BPs...>{}))
// constexpr T get_value(const magnitude<BPs...>&)
// {
// // Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow.
// constexpr auto result = detail::checked_static_cast<T>((detail::compute_base_power<T>(BPs) * ... * T{1}));
template<typename T, auto... Ms>
// TODO(chogg): Migrate this to use `treat_as_floating_point`.
requires(!std::integral<T> || is_integral(magnitude<Ms...>{}))
constexpr T get_value(const magnitude<Ms...>&)
{
// Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow.
constexpr auto result = detail::checked_static_cast<T>((detail::compute_base_power<T>(Ms) * ... * T{1}));
// return result;
// }
return result;
}
/**
@@ -525,7 +515,7 @@ template<Magnitude M1, Magnitude M2>
// Magnitude rational powers implementation.
template<ratio E, auto... Ms>
constexpr auto pow(magnitude<Ms...>)
[[nodiscard]] consteval auto pow(magnitude<Ms...>)
{
if constexpr (E.num == 0) {
return magnitude<>{};
@@ -535,13 +525,13 @@ constexpr auto pow(magnitude<Ms...>)
}
template<auto... Ms>
constexpr auto sqrt(magnitude<Ms...> m)
[[nodiscard]] consteval auto sqrt(magnitude<Ms...> m)
{
return pow<ratio{1, 2}>(m);
}
template<auto... Ms>
constexpr auto cbrt(magnitude<Ms...> m)
[[nodiscard]] consteval auto cbrt(magnitude<Ms...> m)
{
return pow<ratio{1, 3}>(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<auto H1, auto... T1, auto H2, auto... T2>
constexpr Magnitude auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>)
[[nodiscard]] consteval Magnitude auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>)
{
using namespace detail;
@@ -611,7 +601,7 @@ constexpr Magnitude auto operator*(magnitude<H1, T1...>, magnitude<H2, T2...>)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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<auto BP>
constexpr auto integer_part(magnitude<BP>)
template<auto M>
[[nodiscard]] consteval auto integer_part(magnitude<M>)
{
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<decltype(BP.get_base())> && (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<largest_integer_power>{};
if constexpr (std::is_integral_v<decltype(get_base(M))> && (power_num >= power_den)) {
// largest integer power
return magnitude<power_v_or_T<get_base(M), power_num / power_den>()>{}; // Note: integer division intended
} else {
return magnitude<>{};
}
@@ -639,13 +625,13 @@ constexpr auto integer_part(magnitude<BP>)
} // namespace detail
template<auto... BPs>
constexpr auto numerator(magnitude<BPs...>)
template<auto... Ms>
[[nodiscard]] consteval auto numerator(magnitude<Ms...>)
{
return (detail::integer_part(magnitude<BPs>{}) * ... * magnitude<>{});
return (detail::integer_part(magnitude<Ms>{}) * ... * 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<auto BP>
constexpr auto remove_positive_power(magnitude<BP> m)
template<auto M>
[[nodiscard]] consteval auto remove_positive_power(magnitude<M> m)
{
if constexpr (BP.power.num < 0) {
if constexpr (get_exponent(M).num < 0) {
return m;
} else {
return magnitude<>{};
}
}
template<auto... BPs>
constexpr auto remove_positive_powers(magnitude<BPs...>)
template<auto... Ms>
[[nodiscard]] consteval auto remove_positive_powers(magnitude<Ms...>)
{
return (magnitude<>{} * ... * remove_positive_power(magnitude<BPs>{}));
return (magnitude<>{} * ... * remove_positive_power(magnitude<Ms>{}));
}
} // 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<auto H1, auto... T1, auto H2, auto... T2>
constexpr auto common_magnitude(magnitude<H1, T1...>, magnitude<H2, T2...>)
[[nodiscard]] consteval auto common_magnitude(magnitude<H1, T1...>, magnitude<H2, T2...>)
{
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<H1>{}) * common_magnitude(magnitude<T1...>{}, magnitude<H2, T2...>{});
} 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<H2>{}) * common_magnitude(magnitude<H1, T1...>{}, magnitude<T2...>{});
} else {
// When the bases are equal, pick whichever has the lower power.
constexpr auto common_tail = common_magnitude(magnitude<T1...>{}, magnitude<T2...>{});
if constexpr ((H1.power) < (H2.power)) {
if constexpr (get_exponent(H1) < get_exponent(H2)) {
return magnitude<H1>{} * common_tail;
} else {
return magnitude<H2>{} * common_tail;
@@ -740,7 +733,7 @@ namespace detail {
template<std::intmax_t N>
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<N>.has_value()) {
return known_first_factor<N>.value();
@@ -786,15 +779,16 @@ template<ratio Base, ratio Pow>
inline constexpr Magnitude auto mag_power = pow<Pow>(mag<Base>);
namespace detail {
template<typename T, PowerV auto... BPs>
constexpr ratio get_power(T base, magnitude<BPs...>)
template<typename T, auto... Ms>
[[nodiscard]] consteval ratio get_power(T base, magnitude<Ms...>)
{
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

View File

@@ -22,12 +22,16 @@
#pragma once
#include <units/bits/algorithm.h>
#include <units/bits/expression_template.h>
#include <units/bits/external/fixed_string.h>
#include <units/bits/external/text_tools.h>
#include <units/bits/external/type_name.h>
#include <units/bits/external/type_traits.h>
#include <units/magnitude.h>
#include <units/symbol_text.h>
#include <iterator>
#include <string>
// #include <units/bits/derived_symbol_text.h>
@@ -209,19 +213,16 @@ template<typename T>
inline constexpr bool is_power_of_unit =
requires { requires is_specialization_of_power<T> && Unit<typename T::factor>; };
template<typename T>
concept UnitLike = Unit<T> || is_power_of_unit<T>;
template<typename T>
inline constexpr bool is_per_of_units = false;
template<typename... Ts>
inline constexpr bool is_per_of_units<per<Ts...>> = (... && UnitLike<Ts>);
inline constexpr bool is_per_of_units<per<Ts...>> = (... && (Unit<Ts> || is_power_of_unit<Ts>));
} // namespace detail
template<typename T>
concept DerivedUnitSpec = detail::UnitLike<T> || detail::is_per_of_units<T>;
concept DerivedUnitSpec = Unit<T> || detail::is_power_of_unit<T> || detail::is_per_of_units<T>;
/**
* @brief Measurement unit for a derived quantity
@@ -312,63 +313,75 @@ inline constexpr bool is_unit<T> = true;
* @tparam U a unit to use as a `reference_unit`
* @tparam M a Magnitude representing an absolute scaling factor of this unit
*/
template<Magnitude M, UnitLike U>
template<Magnitude M, Unit U>
struct canonical_unit {
M mag;
U reference_unit;
};
[[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u);
template<Unit T, basic_symbol_text Symbol>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol>&);
template<UnitLike T, auto M, typename U>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile scaled_unit<M, U>&)
template<Unit T, basic_symbol_text Symbol, Unit auto U>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit<Symbol, U>&);
template<typename T, typename F, int Num, int... Den>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const power<F, Num, Den...>&);
template<Unit T, typename... Us>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const derived_unit<Us...>&);
template<Unit T, auto M, typename U>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const scaled_unit<M, U>&)
{
auto base = get_canonical_unit(U{});
auto base = get_canonical_unit_impl(U{}, U{});
return canonical_unit{M * base.mag, base.reference_unit};
}
template<UnitLike T, basic_symbol_text Symbol>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T t, const volatile named_unit<Symbol>&)
template<Unit T, basic_symbol_text Symbol>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol>&)
{
return canonical_unit{mag<1>, t};
}
template<UnitLike T, basic_symbol_text Symbol, Unit auto U>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile named_unit<Symbol, U>&)
template<Unit T, basic_symbol_text Symbol, Unit auto U>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit<Symbol, U>&)
{
return get_canonical_unit(U);
return get_canonical_unit_impl(U, U);
}
template<UnitLike T, typename F, int Num, int... Den>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile power<F, Num, Den...>&)
template<typename T, typename F, int Num, int... Den>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const power<F, Num, Den...>&)
{
auto base = get_canonical_unit(F{});
auto base = get_canonical_unit_impl(F{}, F{});
return canonical_unit{
pow<power<F, Num, Den...>::exponent>(base.mag),
derived_unit<power_or_T<std::remove_const_t<decltype(base.reference_unit)>, power<F, Num, Den...>::exponent>>{}};
}
template<UnitLike T, DerivedUnitSpec... Us>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile derived_unit<Us...>&)
template<Unit T, typename... Us>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const derived_unit<Us...>&)
{
if constexpr (type_list_size<typename derived_unit<Us...>::_den_> != 0) {
auto num = get_canonical_unit(type_list_map<typename derived_unit<Us...>::_num_, derived_unit>{});
auto den = get_canonical_unit(type_list_map<typename derived_unit<Us...>::_den_, derived_unit>{});
using num_type = type_list_map<typename derived_unit<Us...>::_num_, derived_unit>;
using den_type = type_list_map<typename derived_unit<Us...>::_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<Unit U1, Unit U2>
struct unit_less : std::bool_constant<type_name<U1>() < type_name<U2>()> {};
template<Unit Lhs, Unit Rhs>
struct unit_less : std::bool_constant<type_name<Lhs>() < type_name<Rhs>()> {};
template<typename T1, typename T2>
using type_list_of_unit_less = expr_less<T1, T2, unit_less>;
@@ -398,17 +411,17 @@ template<Magnitude M, Unit U>
* 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<Unit U1, Unit U2>
[[nodiscard]] consteval Unit auto operator*(U1 u1, U2 u2)
template<Unit Lhs, Unit Rhs>
[[nodiscard]] consteval Unit auto operator*(Lhs lhs, Rhs rhs)
{
if constexpr (detail::is_specialization_of_scaled_unit<U1> && detail::is_specialization_of_scaled_unit<U2>)
return (U1::mag * U2::mag) * (U1::reference_unit * U2::reference_unit);
else if constexpr (detail::is_specialization_of_scaled_unit<U1>)
return U1::mag * (U1::reference_unit * u2);
else if constexpr (detail::is_specialization_of_scaled_unit<U2>)
return U2::mag * (u1 * U2::reference_unit);
if constexpr (detail::is_specialization_of_scaled_unit<Lhs> && detail::is_specialization_of_scaled_unit<Rhs>)
return (Lhs::mag * Rhs::mag) * (Lhs::reference_unit * Rhs::reference_unit);
else if constexpr (detail::is_specialization_of_scaled_unit<Lhs>)
return Lhs::mag * (Lhs::reference_unit * rhs);
else if constexpr (detail::is_specialization_of_scaled_unit<Rhs>)
return Rhs::mag * (lhs * Rhs::reference_unit);
else
return detail::expr_multiply<U1, U2, struct one, detail::type_list_of_unit_less, derived_unit>();
return detail::expr_multiply<derived_unit, struct one, detail::type_list_of_unit_less>(lhs, rhs);
}
/**
@@ -416,31 +429,31 @@ template<Unit U1, Unit U2>
* 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<Unit U1, Unit U2>
[[nodiscard]] consteval Unit auto operator/(U1 u1, U2 u2)
template<Unit Lhs, Unit Rhs>
[[nodiscard]] consteval Unit auto operator/(Lhs lhs, Rhs rhs)
{
if constexpr (detail::is_specialization_of_scaled_unit<U1> && detail::is_specialization_of_scaled_unit<U2>)
return (U1::mag / U2::mag) * (U1::reference_unit / U2::reference_unit);
else if constexpr (detail::is_specialization_of_scaled_unit<U1>)
return U1::mag * (U1::reference_unit / u2);
else if constexpr (detail::is_specialization_of_scaled_unit<U2>)
return U2::mag * (u1 / U2::reference_unit);
if constexpr (detail::is_specialization_of_scaled_unit<Lhs> && detail::is_specialization_of_scaled_unit<Rhs>)
return (Lhs::mag / Rhs::mag) * (Lhs::reference_unit / Rhs::reference_unit);
else if constexpr (detail::is_specialization_of_scaled_unit<Lhs>)
return Lhs::mag * (Lhs::reference_unit / rhs);
else if constexpr (detail::is_specialization_of_scaled_unit<Rhs>)
return Rhs::mag * (lhs / Rhs::reference_unit);
else
return detail::expr_divide<U1, U2, struct one, detail::type_list_of_unit_less, derived_unit>();
return detail::expr_divide<derived_unit, struct one, detail::type_list_of_unit_less>(lhs, rhs);
}
template<Unit U>
[[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<U, struct one, derived_unit>();
return detail::expr_invert<derived_unit, struct one>(u);
}
template<Unit U>
[[nodiscard]] consteval Unit auto operator/(U, int) = delete;
template<Unit U1, Unit U2>
[[nodiscard]] consteval bool operator==(U1 lhs, U2 rhs)
template<Unit Lhs, Unit Rhs>
[[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<Unit U1, Unit U2>
// Convertible
template<Unit U1, Unit U2>
[[nodiscard]] consteval bool convertible(U1 lhs, U2 rhs)
template<Unit Lhs, Unit Rhs>
[[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<Unit auto U>
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<typename CharT, typename UnicodeCharT, std::size_t N, std::size_t M, std::output_iterator<CharT> Out>
constexpr Out copy(const basic_symbol_text<UnicodeCharT, N, M>& txt, text_encoding encoding, Out out)
{
if (encoding == text_encoding::unicode) {
if (is_same_v<CharT, UnicodeCharT>)
return copy(txt.unicode(), out).out;
else
static_assert("Unicode text can't be copied to CharT output");
} else {
if (is_same_v<CharT, char>)
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<Magnitude auto M>
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<std::intmax_t>(num);
constexpr auto den_value = get_value<std::intmax_t>(den);
if constexpr (num_value == 1 && den_value == 1 && exp10 != 0) {
return base_multiplier + superscript<exp10>();
} else if constexpr (num_value != 1 || den_value != 1 || exp10 != 0) {
auto txt = basic_fixed_string("[") + regular<num_value>();
if constexpr (den_value == 1) {
if constexpr (exp10 == 0) {
return txt + basic_fixed_string("]");
} else {
return txt + " " + base_multiplier + superscript<exp10>() + basic_fixed_string("]");
}
} else {
if constexpr (exp10 == 0) {
return txt + basic_fixed_string("/") + regular<den_value>() + basic_fixed_string("]");
} else {
return txt + basic_fixed_string("/") + regular<den_value>() + " " + base_multiplier + superscript<exp10>() +
basic_fixed_string("]");
}
}
} else {
return basic_fixed_string("");
}
}
template<typename CharT, std::output_iterator<CharT> 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<typename CharT, std::output_iterator<CharT> Out, Unit U>
requires requires { U::symbol; }
constexpr Out unit_symbol_impl(Out out, U, unit_symbol_formatting fmt, bool negative_power)
{
out = copy<CharT>(U::symbol, fmt.encoding, out);
if (negative_power) {
constexpr auto txt = superscript<-1>();
out = copy<CharT>(txt, fmt.encoding, out);
}
return out;
}
template<typename CharT, std::output_iterator<CharT> Out, auto M, typename U>
constexpr Out unit_symbol_impl(Out out, const scaled_unit<M, U>& u, unit_symbol_formatting fmt, bool negative_power)
{
if constexpr (M == mag<1>) {
// no ratio/prefix
return unit_symbol_impl<CharT>(out, u.reference_unit, fmt, negative_power);
} else {
constexpr auto mag_txt = magnitude_text<M>();
out = copy<CharT>(mag_txt, fmt.encoding, out);
if constexpr (std::derived_from<std::remove_const_t<decltype(u.reference_unit)>, derived_unit<>>)
return out;
else {
*out++ = ' ';
return unit_symbol_impl<CharT>(out, u.reference_unit, fmt, negative_power);
}
}
}
template<typename CharT, std::output_iterator<CharT> Out, typename F, int Num, int... Den>
constexpr auto unit_symbol_impl(Out out, const power<F, Num, Den...>&, unit_symbol_formatting fmt, bool negative_power)
{
out = unit_symbol_impl<CharT>(out, F{}, fmt, false); // negative power component will be added below if needed
constexpr ratio r = power<F, Num, Den...>::exponent;
if constexpr (r.den != 1) {
// add root part
constexpr auto txt = txt + basic_fixed_string("^(") + regular<r.num>() + basic_fixed_string("/") +
regular<r.den>() + basic_fixed_string(")");
return copy<CharT>(txt, fmt.encoding, out);
} else if constexpr (r.num != 1) {
// add exponent part
if (negative_power) {
constexpr auto txt = superscript<-r.num>();
return copy<CharT>(txt, fmt.encoding, out);
} else {
constexpr auto txt = superscript<r.num>();
return copy<CharT>(txt, fmt.encoding, out);
}
}
}
template<typename CharT, std::output_iterator<CharT> 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<CharT>(out, fmt);
return unit_symbol_impl<CharT>(out, m, fmt, negative_power);
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitSpec... Ms, std::size_t... Idxs>
constexpr Out unit_symbol_impl(Out out, const type_list<Ms...>&, std::index_sequence<Idxs...>,
unit_symbol_formatting fmt, bool negative_power)
{
return (..., (out = unit_symbol_impl<CharT>(out, Ms{}, Idxs, fmt, negative_power)));
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitSpec... Nums, DerivedUnitSpec... Dens>
constexpr Out unit_symbol_impl(Out out, const type_list<Nums...>& nums, const type_list<Dens...>& 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<CharT>(out, nums, std::index_sequence_for<Nums...>(), fmt, false);
} else {
using enum unit_symbol_denominator;
if constexpr (sizeof...(Nums) > 0) {
unit_symbol_impl<CharT>(out, nums, std::index_sequence_for<Nums...>(), 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<CharT>(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<CharT>(out, dens, std::index_sequence_for<Dens...>(), fmt, negative_power);
if (fmt.denominator == always_solidus && sizeof...(Dens) > 1) *out++ = ')';
return out;
}
}
template<typename CharT, std::output_iterator<CharT> Out, typename... Us>
constexpr Out unit_symbol_impl(Out out, const derived_unit<Us...>&, unit_symbol_formatting fmt, bool negative_power)
{
gsl_Assert(negative_power == false);
return unit_symbol_impl<CharT>(out, typename derived_unit<Us...>::_num_{}, typename derived_unit<Us...>::_den_{},
fmt);
}
} // namespace detail
template<typename CharT = char, std::output_iterator<CharT> Out, Unit U>
constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{})
{
return detail::unit_symbol_impl<CharT>(out, u, fmt, false);
}
template<typename CharT = char, Unit U>
[[nodiscard]] constexpr std::basic_string<CharT> unit_symbol(U u, unit_symbol_formatting fmt = unit_symbol_formatting{})
{
std::basic_string<CharT> buffer;
unit_symbol_to<CharT>(std::back_inserter(buffer), u, fmt);
return buffer;
}
} // namespace units
namespace std {
// TODO implement this
template<units::Unit U1, units::Unit U2>
requires(units::convertible(U1{}, U2{}))

View File

@@ -235,6 +235,9 @@ static_assert(is_of_type<square<metre>, derived_unit<power<metre_, 2>>>);
static_assert(is_of_type<cubic<metre>, derived_unit<power<metre_, 3>>>);
static_assert(is_of_type<square<metre> * metre, derived_unit<power<metre_, 3>>>);
static_assert(is_of_type<metre * square<metre>, derived_unit<power<metre_, 3>>>);
static_assert(is_of_type<square<metre> / metre, metre_>);
static_assert(is_of_type<cubic<metre> / metre, derived_unit<power<metre_, 2>>>);
static_assert(is_of_type<cubic<metre> / square<metre>, metre_>);
static_assert(is_of_type<metre / second, derived_unit<metre_, per<second_>>>);
static_assert(is_of_type<metre / square<second>, derived_unit<metre_, per<power<second_, 2>>>>);
@@ -350,8 +353,72 @@ static_assert(joule == newton * metre);
static_assert(watt == joule / second);
static_assert(watt == kilogram * square<metre> / cubic<second>);
// 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<metre>) == "mm");
static_assert(unit_symbol(si::micro<metre>) == "µm");
static_assert(unit_symbol(si::micro<metre>, {.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<metre>) == "");
static_assert(unit_symbol(square<metre>, {.encoding = ascii}) == "m^2");
static_assert(unit_symbol(cubic<metre>) == "");
static_assert(unit_symbol(cubic<metre>, {.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<second>) == "m/s²");
static_assert(unit_symbol(metre / square<second>, {.encoding = ascii}) == "m/s^2");
static_assert(unit_symbol(metre / square<second>, {.denominator = always_solidus}) == "m/s²");
static_assert(unit_symbol(metre / square<second>, {.encoding = ascii, .denominator = always_solidus}) == "m/s^2");
static_assert(unit_symbol(metre / square<second>, {.denominator = always_negative}) == "m s⁻²");
static_assert(unit_symbol(metre / square<second>, {.encoding = ascii, .denominator = always_negative}) == "m s^-2");
static_assert(unit_symbol(metre / square<second>, {.denominator = always_negative, .separator = dot}) == "m⋅s⁻²");
static_assert(unit_symbol(kilogram * metre / square<second>) == "kg m/s²");
static_assert(unit_symbol(kilogram * metre / square<second>, {.separator = dot}) == "kg⋅m/s²");
static_assert(unit_symbol(kilogram * metre / square<second>, {.encoding = ascii}) == "kg m/s^2");
static_assert(unit_symbol(kilogram * metre / square<second>, {.denominator = always_solidus}) == "kg m/s²");
static_assert(unit_symbol(kilogram * metre / square<second>, {.encoding = ascii, .denominator = always_solidus}) ==
"kg m/s^2");
static_assert(unit_symbol(kilogram * metre / square<second>, {.denominator = always_negative}) == "kg m s⁻²");
static_assert(unit_symbol(kilogram * metre / square<second>, {.encoding = ascii, .denominator = always_negative}) ==
"kg m s^-2");
static_assert(unit_symbol(kilogram * metre / square<second>, {.denominator = always_negative, .separator = dot}) ==
"kg⋅m⋅s⁻²");
static_assert(unit_symbol(kilogram / metre / square<second>) == "kg m⁻¹ s⁻²");
static_assert(unit_symbol(kilogram / metre / square<second>, {.separator = dot}) == "kg⋅m⁻¹⋅s⁻²");
static_assert(unit_symbol(kilogram / metre / square<second>, {.encoding = ascii}) == "kg m^-1 s^-2");
static_assert(unit_symbol(kilogram / metre / square<second>, {.denominator = always_solidus}) == "kg/(m s²)");
static_assert(unit_symbol(kilogram / metre / square<second>, {.encoding = ascii, .denominator = always_solidus}) ==
"kg/(m s^2)");
static_assert(unit_symbol(kilogram / metre / square<second>, {.denominator = always_negative}) == "kg m⁻¹ s⁻²");
static_assert(unit_symbol(kilogram / metre / square<second>, {.encoding = ascii, .denominator = always_negative}) ==
"kg m^-1 s^-2");
static_assert(unit_symbol(kilogram / metre / square<second>, {.denominator = always_negative, .separator = dot}) ==
"kg⋅m⁻¹⋅s⁻²");
#endif // __cpp_lib_constexpr_string
} // namespace