mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-06 13:44:27 +02:00
feat: unit symbol text output support added
This commit is contained in:
@@ -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
|
@@ -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
|
@@ -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
|
||||
|
@@ -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{}))
|
||||
|
@@ -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>) == "m²");
|
||||
static_assert(unit_symbol(square<metre>, {.encoding = ascii}) == "m^2");
|
||||
static_assert(unit_symbol(cubic<metre>) == "m³");
|
||||
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
|
||||
|
Reference in New Issue
Block a user