feat: dimension text output added

Resolves #421
This commit is contained in:
Mateusz Pusz
2024-02-26 09:30:08 +01:00
parent 10ee4cc13c
commit e50d75a104
8 changed files with 349 additions and 102 deletions

View File

@ -23,9 +23,12 @@
#pragma once
#include <mp-units/bits/external/fixed_string.h>
#include <mp-units/bits/ratio.h>
#include <mp-units/bits/symbol_text.h>
namespace mp_units::detail {
namespace mp_units {
namespace detail {
template<std::intmax_t Value>
requires(0 <= Value) && (Value < 10)
@ -84,4 +87,73 @@ template<std::intmax_t Value>
return regular<Value / 10>() + regular<Value % 10>();
}
} // namespace mp_units::detail
} // namespace detail
enum class text_encoding : std::int8_t {
unicode, // m³; µs
ascii, // m^3; us
default_encoding = unicode
};
namespace detail {
template<typename CharT, std::size_t N, std::size_t M, std::output_iterator<CharT> Out>
constexpr Out copy(const basic_symbol_text<N, M>& txt, text_encoding encoding, Out out)
{
if (encoding == text_encoding::unicode) {
if constexpr (is_same_v<CharT, char8_t>)
return copy(txt.unicode(), out).out;
else if constexpr (is_same_v<CharT, char>) {
for (char8_t ch : txt.unicode()) *out++ = static_cast<char>(ch);
return out;
} else
throw std::invalid_argument("Unicode text can't be copied to CharT output");
} else {
if constexpr (is_same_v<CharT, char>)
return copy(txt.ascii(), out).out;
else
throw std::invalid_argument("ASCII text can't be copied to CharT output");
}
}
template<typename CharT, std::size_t N, std::size_t M, std::output_iterator<CharT> Out>
constexpr Out copy_symbol(const basic_symbol_text<N, M>& txt, text_encoding encoding, bool negative_power, Out out)
{
out = copy<CharT>(txt, encoding, out);
if (negative_power) {
constexpr auto exp = superscript<-1>();
out = copy<CharT>(exp, encoding, out);
}
return out;
}
template<typename CharT, int Num, int... Den, std::output_iterator<CharT> Out>
constexpr Out copy_symbol_exponent(text_encoding encoding, bool negative_power, Out out)
{
constexpr ratio r{Num, Den...};
if constexpr (r.den != 1) {
// add root part
if (negative_power) {
constexpr auto txt = basic_symbol_text("^-(") + regular<r.num>() + basic_symbol_text("/") + regular<r.den>() +
basic_symbol_text(")");
return copy<CharT>(txt, encoding, out);
} else {
constexpr auto txt =
basic_symbol_text("^(") + regular<r.num>() + basic_symbol_text("/") + regular<r.den>() + basic_symbol_text(")");
return copy<CharT>(txt, encoding, out);
}
} else if constexpr (r.num != 1) {
// add exponent part
if (negative_power) {
constexpr auto txt = superscript<-r.num>();
return copy<CharT>(txt, encoding, out);
} else {
constexpr auto txt = superscript<r.num>();
return copy<CharT>(txt, encoding, out);
}
}
}
} // namespace detail
} // namespace mp_units

View File

@ -27,6 +27,7 @@
#include <mp-units/bits/external/type_traits.h>
#include <mp-units/bits/module_macros.h>
#include <mp-units/bits/symbol_text.h>
#include <mp-units/bits/text_tools.h>
namespace mp_units {
@ -164,13 +165,15 @@ template<auto Symbol>
} // namespace detail
MP_UNITS_EXPORT template<Dimension Lhs, Dimension Rhs>
MP_UNITS_EXPORT_BEGIN
template<Dimension Lhs, Dimension Rhs>
[[nodiscard]] consteval bool operator==(Lhs lhs, Rhs rhs)
{
return is_same_v<Lhs, Rhs> || detail::derived_from_the_same_base_dimension(lhs, rhs);
}
MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto inverse(Dimension auto d) { return dimension_one / d; }
[[nodiscard]] consteval Dimension auto inverse(Dimension auto d) { return dimension_one / d; }
/**
* @brief Computes the value of a dimension raised to the `Num/Den` power
@ -181,7 +184,7 @@ MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto inverse(Dimension auto d)
*
* @return Dimension The result of computation
*/
MP_UNITS_EXPORT template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
requires detail::non_zero<Den>
[[nodiscard]] consteval Dimension auto pow(D d)
{
@ -202,7 +205,7 @@ MP_UNITS_EXPORT template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
*
* @return Dimension The result of computation
*/
MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto sqrt(Dimension auto d) { return pow<1, 2>(d); }
[[nodiscard]] consteval Dimension auto sqrt(Dimension auto d) { return pow<1, 2>(d); }
/**
* @brief Computes the cubic root of a dimension
@ -211,9 +214,107 @@ MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto sqrt(Dimension auto d) {
*
* @return Dimension The result of computation
*/
MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto cbrt(Dimension auto d) { return pow<1, 3>(d); }
[[nodiscard]] consteval Dimension auto cbrt(Dimension auto d) { return pow<1, 3>(d); }
// TODO consider adding the support for text output of the dimensional equation
struct dimension_symbol_formatting {
text_encoding encoding = text_encoding::default_encoding;
};
MP_UNITS_EXPORT_END
namespace detail {
template<typename CharT, std::output_iterator<CharT> Out, Dimension D>
requires requires { D::symbol; }
constexpr Out dimension_symbol_impl(Out out, D, dimension_symbol_formatting fmt, bool negative_power)
{
return copy_symbol<CharT>(D::symbol, fmt.encoding, negative_power, out);
}
template<typename CharT, std::output_iterator<CharT> Out, typename F, int Num, int... Den>
constexpr auto dimension_symbol_impl(Out out, const power<F, Num, Den...>&, dimension_symbol_formatting fmt,
bool negative_power)
{
out = dimension_symbol_impl<CharT>(out, F{}, fmt, false); // negative power component will be added below if needed
return copy_symbol_exponent<CharT, Num, Den...>(fmt.encoding, negative_power, out);
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedDimensionExpr... Ms>
constexpr Out dimension_symbol_impl(Out out, const type_list<Ms...>&, dimension_symbol_formatting fmt,
bool negative_power)
{
return (..., (out = dimension_symbol_impl<CharT>(out, Ms{}, fmt, negative_power)));
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedDimensionExpr... Nums, DerivedDimensionExpr... Dens>
constexpr Out dimension_symbol_impl(Out out, const type_list<Nums...>& nums, const type_list<Dens...>& dens,
dimension_symbol_formatting fmt)
{
if constexpr (sizeof...(Nums) == 0 && sizeof...(Dens) == 0) {
// dimensionless quantity
*out++ = '1';
return out;
} else if constexpr (sizeof...(Dens) == 0) {
// no denominator
return dimension_symbol_impl<CharT>(out, nums, fmt, false);
} else {
if constexpr (sizeof...(Nums) > 0) out = dimension_symbol_impl<CharT>(out, nums, fmt, false);
return dimension_symbol_impl<CharT>(out, dens, fmt, true);
}
}
template<typename CharT, std::output_iterator<CharT> Out, typename... Expr>
constexpr Out dimension_symbol_impl(Out out, const derived_dimension<Expr...>&, dimension_symbol_formatting fmt,
bool negative_power)
{
gsl_Expects(negative_power == false);
return dimension_symbol_impl<CharT>(out, typename derived_dimension<Expr...>::_num_{},
typename derived_dimension<Expr...>::_den_{}, fmt);
}
} // namespace detail
MP_UNITS_EXPORT template<typename CharT = char, std::output_iterator<CharT> Out, Dimension D>
constexpr Out dimension_symbol_to(Out out, D d, dimension_symbol_formatting fmt = dimension_symbol_formatting{})
{
return detail::dimension_symbol_impl<CharT>(out, d, fmt, false);
}
namespace detail {
template<typename CharT, std::size_t N, dimension_symbol_formatting fmt, Dimension D>
[[nodiscard]] consteval std::array<CharT, N + 1> get_symbol_buffer(D)
{
std::array<CharT, N + 1> buffer{};
dimension_symbol_to<CharT>(buffer.begin(), D{}, fmt);
return buffer;
}
} // namespace detail
// TODO Refactor to `dimension_symbol(D, fmt)` when P1045: constexpr Function Parameters is available
MP_UNITS_EXPORT template<dimension_symbol_formatting fmt = dimension_symbol_formatting{}, typename CharT = char,
Dimension D>
[[nodiscard]] consteval auto dimension_symbol(D)
{
auto get_size = []() consteval {
std::basic_string<CharT> buffer;
dimension_symbol_to<CharT>(std::back_inserter(buffer), D{}, fmt);
return buffer.size();
};
#if __cpp_constexpr >= 202211L // Permitting static constexpr variables in constexpr functions
static constexpr std::size_t size = get_size();
static constexpr auto buffer = detail::get_symbol_buffer<CharT, size, fmt>(D{});
return std::string_view(buffer.data(), size);
#else
constexpr std::size_t size = get_size();
constexpr auto buffer = detail::get_symbol_buffer<CharT, size, fmt>(D{});
return basic_fixed_string(buffer.data(), std::integral_constant<std::size_t, size>{});
#endif
}
} // namespace mp_units

View File

@ -125,9 +125,80 @@ OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs<
// dimension-spec ::= [text-encoding]
// text-encoding ::= 'U' | 'A'
//
template<mp_units::Dimension D, typename Char>
class MP_UNITS_STD_FMT::formatter<D, Char> {
struct format_specs : mp_units::detail::fill_align_width_format_specs<Char>, mp_units::dimension_symbol_formatting {};
// template<typename Char>
// struct dimension_format_specs : fill_align_width_format_specs<Char>, dimension_symbol_formatting {};
std::basic_string_view<Char> fill_align_width_format_str_;
std::basic_string_view<Char> modifiers_format_str_;
format_specs specs_{};
struct format_checker {
using enum mp_units::text_encoding;
mp_units::text_encoding encoding = unicode;
constexpr void on_text_encoding(Char val) { encoding = (val == 'U') ? unicode : ascii; }
};
struct unit_formatter {
format_specs specs;
using enum mp_units::text_encoding;
constexpr void on_text_encoding(Char val) { specs.encoding = (val == 'U') ? unicode : ascii; }
};
template<typename Handler>
constexpr const Char* parse_dimension_specs(const Char* begin, const Char* end, Handler&& handler) const
{
auto it = begin;
if (it == end || *it == '}') return begin;
constexpr auto valid_modifiers = std::string_view{"UA"};
for (; it != end && *it != '}'; ++it) {
if (valid_modifiers.find(*it) == std::string_view::npos)
throw MP_UNITS_STD_FMT::format_error("invalid dimension modifier specified");
}
end = it;
if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it);
return end;
}
public:
constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin())
{
const auto begin = ctx.begin();
auto end = ctx.end();
auto it = parse_fill_align_width(ctx, begin, end, specs_);
fill_align_width_format_str_ = {begin, it};
if (it == end) return it;
format_checker checker;
end = parse_dimension_specs(it, end, checker);
modifiers_format_str_ = {it, end};
return end;
}
template<typename FormatContext>
auto format(const D& d, FormatContext& ctx) const -> decltype(ctx.out())
{
unit_formatter f{specs_};
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(f.specs.width, f.specs.width_ref, ctx);
parse_dimension_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f);
if (f.specs.width == 0) {
// Avoid extra copying if width is not specified
return mp_units::dimension_symbol_to<Char>(ctx.out(), d, f.specs);
} else {
std::basic_string<Char> unit_buffer;
mp_units::dimension_symbol_to<Char>(std::back_inserter(unit_buffer), d, f.specs);
std::basic_string<Char> global_format_buffer = "{:" + std::basic_string<Char>{fill_align_width_format_str_} + "}";
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
MP_UNITS_STD_FMT::make_format_args(unit_buffer));
}
}
};
//
@ -297,10 +368,9 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
return on_replacement_field<Rep>(begin);
else if (id == "U")
return on_replacement_field<unit_t>(begin);
else if (id == "D") {
return begin;
// on_replacement_field<dimension_t>(begin);
} else
else if (id == "D")
return on_replacement_field<dimension_t>(begin);
else
throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'");
}
@ -337,7 +407,10 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
{
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.unit));
}
void on_dimension(std::basic_string_view<Char>) {}
void on_dimension(std::basic_string_view<Char> format_str)
{
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.dimension));
}
void on_text(const Char* begin, const Char* end) const { std::copy(begin, end, out); }
constexpr const Char* on_replacement_field(std::basic_string_view<Char> id, const Char* begin)

View File

@ -35,14 +35,21 @@ namespace mp_units {
namespace detail {
template<typename CharT, class Traits, Dimension D>
void to_stream_impl(std::basic_ostream<CharT, Traits>& os, D d)
{
dimension_symbol_to<CharT>(std::ostream_iterator<CharT>(os), d);
}
template<typename CharT, class Traits, Unit U>
void to_stream(std::basic_ostream<CharT, Traits>& os, U u)
void to_stream_impl(std::basic_ostream<CharT, Traits>& os, U u)
{
unit_symbol_to<CharT>(std::ostream_iterator<CharT>(os), u);
}
template<typename CharT, class Traits, auto R, typename Rep>
void to_stream(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
void to_stream_impl(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
requires requires { os << q.numerical_value_ref_in(q.unit); }
{
if constexpr (is_same_v<Rep, std::uint8_t> || is_same_v<Rep, std::int8_t>)
// promote the value to int
@ -50,46 +57,36 @@ void to_stream(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
else
os << q.numerical_value_ref_in(q.unit);
if constexpr (space_before_unit_symbol<get_unit(R)>) os << " ";
to_stream(os, get_unit(R));
to_stream_impl(os, get_unit(R));
}
template<typename CharT, class Traits, typename T>
std::basic_ostream<CharT, Traits>& to_stream(std::basic_ostream<CharT, Traits>& os, const T& v)
requires requires { detail::to_stream_impl(os, v); }
{
if (os.width()) {
// std::setw() applies to the whole output so it has to be first put into std::string
std::basic_ostringstream<CharT, Traits> oss;
oss.flags(os.flags());
oss.imbue(os.getloc());
oss.precision(os.precision());
detail::to_stream_impl(oss, v);
return os << std::move(oss).str();
}
detail::to_stream_impl(os, v);
return os;
}
} // namespace detail
MP_UNITS_EXPORT_BEGIN
template<typename CharT, typename Traits, Unit U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, U u)
template<typename CharT, typename Traits, typename T>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const T& v)
requires requires { detail::to_stream_impl(os, v); }
{
if (os.width()) {
// std::setw() applies to the whole uni output so it has to be first put into std::string
std::basic_ostringstream<CharT, Traits> oss;
oss.flags(os.flags());
oss.imbue(os.getloc());
oss.precision(os.precision());
detail::to_stream(oss, u);
return os << std::move(oss).str();
}
detail::to_stream(os, u);
return os;
}
template<typename CharT, typename Traits, auto R, typename Rep>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const quantity<R, Rep>& q)
requires requires { os << q.numerical_value_ref_in(q.unit); }
{
if (os.width()) {
// std::setw() applies to the whole quantity output so it has to be first put into std::string
std::basic_ostringstream<CharT, Traits> oss;
oss.flags(os.flags());
oss.imbue(os.getloc());
oss.precision(os.precision());
detail::to_stream(oss, q);
return os << std::move(oss).str();
}
detail::to_stream(os, q);
return os;
return detail::to_stream(os, v);
}
MP_UNITS_EXPORT_END

View File

@ -682,12 +682,6 @@ inline constexpr bool space_before_unit_symbol<per_mille> = false;
// get_unit_symbol
enum class text_encoding : std::int8_t {
unicode, // m³; µs
ascii, // m^3; us
default_encoding = unicode
};
enum class unit_symbol_solidus : std::int8_t {
one_denominator, // m/s; kg m⁻¹ s⁻¹
always, // m/s; kg/(m s)
@ -711,25 +705,6 @@ MP_UNITS_EXPORT_END
namespace detail {
template<typename CharT, std::size_t N, std::size_t M, std::output_iterator<CharT> Out>
constexpr Out copy(const basic_symbol_text<N, M>& txt, text_encoding encoding, Out out)
{
if (encoding == text_encoding::unicode) {
if constexpr (is_same_v<CharT, char8_t>)
return copy(txt.unicode(), out).out;
else if constexpr (is_same_v<CharT, char>) {
for (char8_t ch : txt.unicode()) *out++ = static_cast<char>(ch);
return out;
} else
throw std::invalid_argument("Unicode text can't be copied to CharT output");
} else {
if constexpr (is_same_v<CharT, char>)
return copy(txt.ascii(), out).out;
else
throw std::invalid_argument("ASCII text can't be copied to CharT output");
}
}
template<typename CharT, std::output_iterator<CharT> Out>
constexpr Out print_separator(Out out, unit_symbol_formatting fmt)
{
@ -748,12 +723,7 @@ 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;
return copy_symbol<CharT>(U::symbol, fmt.encoding, negative_power, out);
}
template<typename CharT, std::output_iterator<CharT> Out, auto M, typename U>
@ -775,23 +745,7 @@ template<typename CharT, std::output_iterator<CharT> Out, typename F, int Num, i
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 =
basic_symbol_text("^(") + regular<r.num>() + basic_symbol_text("/") + regular<r.den>() + basic_symbol_text(")");
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);
}
}
return copy_symbol_exponent<CharT, Num, Den...>(fmt.encoding, negative_power, out);
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitExpr M>

View File

@ -38,6 +38,7 @@ add_library(
# custom_rep_test_min_expl.cpp
custom_rep_test_min_impl.cpp
dimension_test.cpp
dimension_symbol_test.cpp
fixed_string_test.cpp
fractional_exponent_quantity.cpp
hep_test.cpp

View File

@ -0,0 +1,52 @@
// 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.
#include <mp-units/systems/isq/isq.h>
namespace {
using namespace mp_units;
using enum text_encoding;
static_assert(dimension_symbol(dimension_one) == "1");
// base dimensions
static_assert(dimension_symbol(isq::dim_length) == "L");
static_assert(dimension_symbol(isq::dim_thermodynamic_temperature) == "Θ");
static_assert(dimension_symbol<dimension_symbol_formatting{.encoding = ascii}>(isq::dim_thermodynamic_temperature) ==
"O");
// derived dimensions
static_assert(dimension_symbol(isq::speed.dimension) == "LT⁻¹");
static_assert(dimension_symbol<dimension_symbol_formatting{.encoding = ascii}>(isq::speed.dimension) == "LT^-1");
static_assert(dimension_symbol(isq::power.dimension) == "L²MT⁻³");
static_assert(dimension_symbol<dimension_symbol_formatting{.encoding = ascii}>(isq::power.dimension) == "L^2MT^-3");
static_assert(dimension_symbol(pow<123>(isq::dim_length)) == "L¹²³");
static_assert(dimension_symbol(pow<1, 2>(isq::dim_length)) == "L^(1/2)");
static_assert(dimension_symbol(pow<3, 5>(isq::dim_length)) == "L^(3/5)");
static_assert(dimension_symbol(pow<123>(isq::speed.dimension)) == "L¹²³T⁻¹²³");
static_assert(dimension_symbol(pow<1, 2>(isq::speed.dimension)) == "L^(1/2)T^-(1/2)");
static_assert(dimension_symbol(pow<3, 5>(isq::speed.dimension)) == "L^(3/5)T^-(3/5)");
} // namespace

View File

@ -30,8 +30,6 @@ using namespace mp_units;
using namespace mp_units::si;
using namespace mp_units::iec80000;
#if __cpp_lib_constexpr_string && (!defined MP_UNITS_COMP_GCC || MP_UNITS_COMP_GCC > 11)
using enum text_encoding;
using enum unit_symbol_solidus;
using enum unit_symbol_separator;
@ -178,6 +176,7 @@ static_assert(unit_symbol(pow<123>(metre)) == "m¹²³");
static_assert(unit_symbol(pow<1, 2>(metre)) == "m^(1/2)");
static_assert(unit_symbol(pow<3, 5>(metre)) == "m^(3/5)");
static_assert(unit_symbol(pow<1, 2>(metre / second)) == "m^(1/2)/s^(1/2)");
static_assert(unit_symbol<unit_symbol_formatting{.solidus = never}>(pow<1, 2>(metre / second)) == "m^(1/2) s^-(1/2)");
// dimensionless unit
static_assert(unit_symbol(radian) == "rad");
@ -188,6 +187,4 @@ static_assert(unit_symbol(gram * standard_gravity * si2019::speed_of_light_in_va
static_assert(unit_symbol(gram / standard_gravity) == "g/g₀");
static_assert(unit_symbol(kilo<metre> / second / mega<iau::parsec>) == "km Mpc⁻¹ s⁻¹");
#endif // __cpp_lib_constexpr_string
} // namespace