feat: magnitude text now obeys formatting parameters and knows how to print constants

This commit is contained in:
Mateusz Pusz
2024-10-03 18:39:41 +02:00
parent 5c1035a312
commit d783c3cda9
3 changed files with 210 additions and 44 deletions

View File

@ -33,6 +33,7 @@
#include <mp-units/framework/customization_points.h>
#include <mp-units/framework/expression_template.h>
#include <mp-units/framework/symbol_text.h>
#include <mp-units/framework/unit_symbol_formatting.h>
#ifndef MP_UNITS_IN_MODULE_INTERFACE
#ifdef MP_UNITS_IMPORT_STD
@ -59,12 +60,15 @@ using factorizer = wheel_factorizer<4>;
#if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18
MP_UNITS_EXPORT template<symbol_text Symbol>
struct mag_constant {};
struct mag_constant {
static constexpr auto symbol = Symbol;
};
#else
MP_UNITS_EXPORT template<symbol_text Symbol, auto Value>
struct mag_constant {
static constexpr auto symbol = Symbol;
static constexpr auto value = Value;
};
@ -230,6 +234,12 @@ template<auto M>
template<auto M>
[[nodiscard]] consteval auto remove_positive_power(magnitude<M> m);
template<auto M>
[[nodiscard]] consteval auto remove_mag_constants(magnitude<M> m);
template<auto M>
[[nodiscard]] consteval auto only_positive_mag_constants(magnitude<M> m);
template<auto M>
[[nodiscard]] consteval auto only_negative_mag_constants(magnitude<M> m);
template<std::intmax_t Base, int Num, int Den = 1>
requires detail::gt_zero<Base>
@ -311,29 +321,104 @@ struct magnitude_base<magnitude<H, T...>> {
}
};
template<auto num_value, auto den_value>
[[nodiscard]] consteval auto ratio_txt()
template<auto... Ms>
[[nodiscard]] consteval std::size_t magnitude_list_size(magnitude<Ms...>)
{
if constexpr (den_value == 1)
return detail::regular<num_value>();
else
return detail::regular<num_value>() + symbol_text("/") + detail::regular<den_value>();
};
return sizeof...(Ms);
}
template<auto num, auto den, auto exp10>
[[nodiscard]] consteval auto magnitude_text_impl()
template<typename CharT, std::output_iterator<CharT> Out>
constexpr Out print_separator(Out out, const unit_symbol_formatting& fmt)
{
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 symbol_text("10") + detail::superscript<exp10>();
if (fmt.separator == unit_symbol_separator::half_high_dot) {
if (fmt.encoding != text_encoding::unicode)
MP_UNITS_THROW(
std::invalid_argument("'unit_symbol_separator::half_high_dot' can be only used with 'text_encoding::unicode'"));
const std::string_view dot = "";
out = detail::copy(dot.begin(), dot.end(), out);
} else {
if constexpr (exp10 == 0)
return ratio_txt<num_value, den_value>();
else
return ratio_txt<num_value, den_value>() + symbol_text(u8" × 10", " x 10") + detail::superscript<exp10>();
*out++ = ' ';
}
return out;
}
template<typename CharT, std::output_iterator<CharT> Out, auto... Ms>
requires(sizeof...(Ms) == 0)
[[nodiscard]] consteval auto mag_constants_text(Out out, magnitude<Ms...>, const unit_symbol_formatting&, bool)
{
return out;
}
template<typename CharT, std::output_iterator<CharT> Out, auto M, auto... Rest>
[[nodiscard]] consteval auto mag_constants_text(Out out, magnitude<M, Rest...>, const unit_symbol_formatting& fmt,
bool negative_power)
{
return ((out = copy_symbol<CharT>(get_base(M).symbol, fmt.encoding, negative_power, out)), ...,
(print_separator<CharT>(out, fmt),
out = copy_symbol<CharT>(get_base(Rest).symbol, fmt.encoding, negative_power, out)));
}
template<typename CharT, Magnitude auto Num, Magnitude auto Den, Magnitude auto NumConstants,
Magnitude auto DenConstants, std::intmax_t Exp10, std::output_iterator<CharT> Out>
constexpr Out magnitude_symbol_impl(Out out, const unit_symbol_formatting& fmt)
{
bool numerator = false;
constexpr auto num_value = get_value<std::intmax_t>(Num);
if constexpr (num_value != 1) {
constexpr auto num = detail::regular<num_value>();
out = copy_symbol<CharT>(num, fmt.encoding, false, out);
numerator = true;
}
constexpr auto num_constants_size = magnitude_list_size(NumConstants);
if constexpr (num_constants_size) {
if (numerator) out = print_separator<CharT>(out, fmt);
out = mag_constants_text<CharT>(out, NumConstants, fmt, false);
numerator = true;
}
using enum unit_symbol_solidus;
bool denominator = false;
constexpr auto den_value = get_value<std::intmax_t>(Den);
constexpr auto den_constants_size = magnitude_list_size(DenConstants);
constexpr auto den_size = (den_value != 1) + den_constants_size;
auto start_denominator = [&]() {
if (fmt.solidus == always || (fmt.solidus == one_denominator && den_size == 1)) {
if (!numerator) *out++ = '1';
*out++ = '/';
if (den_size > 1) *out++ = '(';
} else if (numerator) {
out = print_separator<CharT>(out, fmt);
}
};
const bool negative_power = fmt.solidus == never || (fmt.solidus == one_denominator && den_size > 1);
if constexpr (den_value != 1) {
constexpr auto den = detail::regular<den_value>();
start_denominator();
out = copy_symbol<CharT>(den, fmt.encoding, negative_power, out);
denominator = true;
}
if constexpr (den_constants_size) {
if (denominator)
out = print_separator<CharT>(out, fmt);
else
start_denominator();
out = mag_constants_text<CharT>(out, DenConstants, fmt, negative_power);
if (fmt.solidus == always && den_size > 1) *out++ = ')';
denominator = true;
}
if constexpr (Exp10 != 0) {
if (numerator || denominator) {
constexpr auto mag_multiplier = symbol_text(u8" × ", " x ");
out = copy_symbol<CharT>(mag_multiplier, fmt.encoding, negative_power, out);
}
constexpr auto exp = symbol_text("10") + detail::superscript<Exp10>();
out = copy_symbol<CharT>(exp, fmt.encoding, negative_power, out);
}
return out;
}
} // namespace detail
@ -427,6 +512,18 @@ private:
return (std::intmax_t{} * ... * decltype(detail::get_base_value(Ms)){});
}
[[nodiscard]] friend consteval auto _extract_components(magnitude)
{
constexpr auto ratio = (magnitude<>{} * ... * detail::remove_mag_constants(magnitude<Ms>{}));
if constexpr (ratio == magnitude{})
return std::tuple{ratio, magnitude<>{}, magnitude<>{}};
else {
constexpr auto num_constants = (magnitude<>{} * ... * detail::only_positive_mag_constants(magnitude<Ms>{}));
constexpr auto den_constants = (magnitude<>{} * ... * detail::only_negative_mag_constants(magnitude<Ms>{}));
return std::tuple{ratio, num_constants, den_constants};
}
}
template<typename T>
[[nodiscard]] friend consteval detail::ratio _get_power(T base, magnitude)
{
@ -444,29 +541,34 @@ private:
return detail::integer_part((detail::abs(power_of_2) < detail::abs(power_of_5)) ? power_of_2 : power_of_5);
}
[[nodiscard]] friend consteval auto _magnitude_text(magnitude)
template<typename CharT, std::output_iterator<CharT> Out>
friend constexpr Out _magnitude_symbol(Out out, magnitude, const unit_symbol_formatting& fmt)
{
if constexpr (magnitude{} == magnitude<1>{}) {
return symbol_text("");
return out;
} else {
constexpr auto exp10 = _extract_power_of_10(magnitude{});
constexpr auto extract_res = _extract_components(magnitude{});
constexpr Magnitude auto ratio = std::get<0>(extract_res);
constexpr Magnitude auto num_constants = std::get<1>(extract_res);
constexpr Magnitude auto den_constants = std::get<2>(extract_res);
constexpr std::intmax_t exp10 = _extract_power_of_10(ratio);
if constexpr (detail::abs(exp10) < 3) {
// print the value as a regular number (without exponent)
constexpr Magnitude auto num = _numerator(magnitude{});
constexpr Magnitude auto den = _denominator(magnitude{});
// TODO address the below
static_assert(magnitude{} == num / den, "Printing rational powers, or irrational bases, not yet supported");
return detail::magnitude_text_impl<num, den, 0>();
static_assert(ratio == num / den, "Printing rational powers not yet supported");
return detail::magnitude_symbol_impl<CharT, num, den, num_constants, den_constants, 0>(out, fmt);
} else {
// print the value as a number with exponent
// if user wanted a regular number for this magnitude then probably a better scaled unit should be used
constexpr Magnitude auto base = magnitude{} / detail::mag_power_lazy<10, exp10>();
constexpr Magnitude auto base = ratio / detail::mag_power_lazy<10, exp10>();
constexpr Magnitude auto num = _numerator(base);
constexpr Magnitude auto den = _denominator(base);
// TODO address the below
static_assert(base == num / den, "Printing rational powers, or irrational bases, not yet supported");
return detail::magnitude_text_impl<num, den, exp10>();
static_assert(base == num / den, "Printing rational powers not yet supported");
return detail::magnitude_symbol_impl<CharT, num, den, num_constants, den_constants, exp10>(out, fmt);
}
}
}
@ -504,6 +606,33 @@ template<auto M>
}
}
template<auto M>
[[nodiscard]] consteval auto remove_mag_constants(magnitude<M> m)
{
if constexpr (MagConstant<decltype(get_base(M))>)
return magnitude<>{};
else
return m;
}
template<auto M>
[[nodiscard]] consteval auto only_positive_mag_constants(magnitude<M> m)
{
if constexpr (MagConstant<decltype(get_base(M))> && get_exponent(M) >= 0)
return m;
else
return magnitude<>{};
}
template<auto M>
[[nodiscard]] consteval auto only_negative_mag_constants(magnitude<M> m)
{
if constexpr (MagConstant<decltype(get_base(M))> && get_exponent(M) < 0)
return m;
else
return magnitude<>{};
}
// Returns the most precise type to express the magnitude factor
template<Magnitude auto M>
using common_magnitude_type = decltype(_common_magnitude_type_impl(M));

View File

@ -762,21 +762,6 @@ MP_UNITS_EXPORT_END
namespace detail {
template<typename CharT, std::output_iterator<CharT> Out>
constexpr Out print_separator(Out out, const unit_symbol_formatting& fmt)
{
if (fmt.separator == unit_symbol_separator::half_high_dot) {
if (fmt.encoding != text_encoding::unicode)
MP_UNITS_THROW(
std::invalid_argument("'unit_symbol_separator::half_high_dot' can be only used with 'text_encoding::unicode'"));
const std::string_view dot = "";
out = detail::copy(dot.begin(), dot.end(), out);
} else {
*out++ = ' ';
}
return out;
}
template<typename CharT, std::output_iterator<CharT> Out, Unit U>
requires requires { U::symbol; }
constexpr Out unit_symbol_impl(Out out, U, const unit_symbol_formatting& fmt, bool negative_power)
@ -789,8 +774,7 @@ constexpr Out unit_symbol_impl(Out out, const scaled_unit_impl<M, U>& u, const u
bool negative_power)
{
*out++ = '[';
constexpr auto mag_txt = _magnitude_text(M);
out = copy<CharT>(mag_txt, fmt.encoding, out);
_magnitude_symbol<CharT>(out, M, fmt);
if constexpr (space_before_unit_symbol<scaled_unit<M, U>::reference_unit>) *out++ = ' ';
unit_symbol_impl<CharT>(out, u.reference_unit, fmt, negative_power);
*out++ = ']';

View File

@ -130,6 +130,59 @@ static_assert(unit_symbol(mag_ratio<1, 18000> * (metre / second)) == "[1/18 × 1
static_assert(unit_symbol<usf{.encoding = ascii}>(mag_ratio<1, 18000> * metre / second) == "[1/18 x 10^-3 m]/s");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag_ratio<1, 18000> * (metre / second)) == "[1/18 x 10^-3 m/s]");
// magnitude constants
#if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18
inline constexpr struct e final : mag_constant<"e"> {
static constexpr long double value = std::numbers::e_v<long double>;
#else
inline constexpr struct e final : mag_constant<"e", std::numbers::e_v<long double> > {
#endif
} e;
static_assert(unit_symbol(mag<pi> * one) == "[𝜋]");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag<pi> * one) == "[pi]");
static_assert(unit_symbol(mag<pi> * metre) == "[𝜋 m]");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag<pi> * metre) == "[pi m]");
static_assert(unit_symbol(mag<2> * mag<pi> * metre) == "[2 𝜋 m]");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag<2> * mag<pi> * metre) == "[2 pi m]");
static_assert(unit_symbol<usf{.separator = half_high_dot}>(mag<2> * mag<pi> * metre) == "[2⋅𝜋 m]");
static_assert(unit_symbol(mag<1> / mag<pi> * metre) == "[1/𝜋 m]");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag<1> / mag<pi> * metre) == "[1/pi m]");
static_assert(unit_symbol<usf{.solidus = never}>(mag<1> / mag<pi> * metre) == "[𝜋⁻¹ m]");
static_assert(unit_symbol<usf{.encoding = ascii, .solidus = never}>(mag<1> / mag<pi> * metre) == "[pi^-1 m]");
static_assert(unit_symbol(mag<2> / mag<pi> * metre) == "[2/𝜋 m]");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag<2> / mag<pi> * metre) == "[2/pi m]");
static_assert(unit_symbol<usf{.solidus = never}>(mag<2> / mag<pi> * metre) == "[2 𝜋⁻¹ m]");
static_assert(unit_symbol<usf{.encoding = ascii, .solidus = never}>(mag<2> / mag<pi> * metre) == "[2 pi^-1 m]");
static_assert(unit_symbol<usf{.solidus = never, .separator = half_high_dot}>(mag<2> / mag<pi> * metre) == "[2⋅𝜋⁻¹ m]");
static_assert(unit_symbol(mag<1> / (mag<2> * mag<pi>)*metre) == "[2⁻¹ 𝜋⁻¹ m]");
static_assert(unit_symbol<usf{.solidus = always}>(mag<1> / (mag<2> * mag<pi>)*metre) == "[1/(2 𝜋) m]");
static_assert(unit_symbol<usf{.encoding = ascii, .solidus = always}>(mag<1> / (mag<2> * mag<pi>)*metre) ==
"[1/(2 pi) m]");
static_assert(unit_symbol(mag_ratio<1, 2> / mag<pi> * metre) == "[2⁻¹ 𝜋⁻¹ m]");
static_assert(unit_symbol<usf{.solidus = always}>(mag_ratio<1, 2> / mag<pi> * metre) == "[1/(2 𝜋) m]");
static_assert(unit_symbol<usf{.encoding = ascii, .solidus = always}>(mag_ratio<1, 2> / mag<pi> * metre) ==
"[1/(2 pi) m]");
static_assert(unit_symbol(mag_ratio<1, 2> * mag<pi> * metre) == "[𝜋/2 m]");
static_assert(unit_symbol(mag<pi> * mag<e> * one) == "[e 𝜋]");
static_assert(unit_symbol(mag<e> * mag<pi> * one) == "[e 𝜋]");
static_assert(unit_symbol<usf{.encoding = ascii}>(mag<pi> * mag<e> * one) == "[e pi]");
static_assert(unit_symbol(mag<pi> / mag<e> * one) == "[𝜋/e]");
static_assert(unit_symbol(mag<1> / mag<e> * mag<pi> * one) == "[𝜋/e]");
static_assert(unit_symbol<usf{.solidus = never}>(mag<pi> / mag<e> * one) == "[𝜋 e⁻¹]");
static_assert(unit_symbol(mag<e> / mag<pi> * one) == "[e/𝜋]");
static_assert(unit_symbol(mag<1> / mag<pi> * mag<e> * one) == "[e/𝜋]");
static_assert(unit_symbol<usf{.solidus = never}>(mag<e> / mag<pi> * one) == "[e 𝜋⁻¹]");
static_assert(unit_symbol(mag<1> / (mag<pi> * mag<e>)*one) == "[e⁻¹ 𝜋⁻¹]");
static_assert(unit_symbol<usf{.solidus = always}>(mag<1> / (mag<pi> * mag<e>)*one) == "[1/(e 𝜋)]");
static_assert(unit_symbol(mag<2> / (mag<pi> * mag<e>)*one) == "[2 e⁻¹ 𝜋⁻¹]");
static_assert(unit_symbol<usf{.solidus = always}>(mag<2> / (mag<pi> * mag<e>)*one) == "[2/(e 𝜋)]");
// common units
static_assert(unit_symbol(get_common_unit(kilo<metre>, mile)) == "EQUIV{[1/25146 mi], [1/15625 km]}");
static_assert(unit_symbol(get_common_unit(kilo<metre> / hour, metre / second)) == "EQUIV{[1/5 km/h], [1/18 m/s]}");