From 1e3a93d10cb02d48165d1ea52fe53cfa9fc1935b Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 17 Apr 2024 14:20:15 +0100 Subject: [PATCH 1/8] docs: new formatting syntax ideas --- README.md | 4 +- docs/getting_started/look_and_feel.md | 8 +-- docs/index.md | 4 +- .../faster_than_lightspeed_constants.md | 2 +- .../simple_and_typed_quantities.md | 8 +-- .../framework_basics/the_affine_space.md | 2 +- src/core/include/mp-units/format.h | 54 ++++++++++++++----- 7 files changed, 54 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index aabb9b4dc..97634d969 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,8 @@ int main() std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h std::cout << std::format("{:*^10}\n", v3); // *110 km/h* std::println("{:%N in %U of %D}", v4); // 70 in mi/h of LT⁻¹ - std::println("{:{%N:.2f}%?%U}", v5); // 30.56 m/s - std::println("{:{%N:.2f}%?{%U:dn}}", v6); // 31.29 m⋅s⁻¹ + std::println("{::N[.2f]}", v5); // 30.56 m/s + std::println("{::N[.2f]U[dn]}", v6); // 31.29 m⋅s⁻¹ std::println("{:%N}", v7); // 31 } ``` diff --git a/docs/getting_started/look_and_feel.md b/docs/getting_started/look_and_feel.md index 545bb3684..ed6052b18 100644 --- a/docs/getting_started/look_and_feel.md +++ b/docs/getting_started/look_and_feel.md @@ -100,8 +100,8 @@ performed without sacrificing accuracy. Please see the below example for a quick std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h std::cout << std::format("{:*^10}\n", v3); // *110 km/h* std::println("{:%N in %U of %D}", v4); // 70 in mi/h of LT⁻¹ - std::println("{:{%N:.2f}%?%U}", v5); // 30.56 m/s - std::println("{:{%N:.2f}%?{%U:dn}}", v6); // 31.29 m⋅s⁻¹ + std::println("{::N[.2f]}", v5); // 30.56 m/s + std::println("{::N[.2f]U[dn]}", v6); // 31.29 m⋅s⁻¹ std::println("{:%N}", v7); // 31 } ``` @@ -144,8 +144,8 @@ performed without sacrificing accuracy. Please see the below example for a quick std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h std::cout << std::format("{:*^10}\n", v3); // *110 km/h* std::println("{:%N in %U of %D}", v4); // 70 in mi/h of LT⁻¹ - std::println("{:{%N:.2f}%?%U}", v5); // 30.56 m/s - std::println("{:{%N:.2f}%?{%U:dn}}", v6); // 31.29 m⋅s⁻¹ + std::println("{::N[.2f]}", v5); // 30.56 m/s + std::println("{::N[.2f]U[dn]}", v6); // 31.29 m⋅s⁻¹ std::println("{:%N}", v7); // 31 } ``` diff --git a/docs/index.md b/docs/index.md index dcf78bf89..54b36c629 100644 --- a/docs/index.md +++ b/docs/index.md @@ -38,7 +38,7 @@ The library source code is hosted on [GitHub](https://github.com/mpusz/mp-units) int main() { constexpr quantity dist = 364.4 * smoot; - std::println("Harvard Bridge length = {:{%N:.5} %U} ({:{%N:.5} %U}, {:{%N:.5} %U}) ± 1 εar", + std::println("Harvard Bridge length = {::N[.5]} ({::N[.5]}, {::N[.5]}) ± 1 εar", dist, dist.in(usc::foot), dist.in(si::metre)); } ``` @@ -58,7 +58,7 @@ The library source code is hosted on [GitHub](https://github.com/mpusz/mp-units) int main() { constexpr quantity dist = 364.4 * smoot; - std::println("Harvard Bridge length = {:{%N:.5} %U} ({:{%N:.5} %U}, {:{%N:.5} %U}) ± 1 εar", + std::println("Harvard Bridge length = {::N[.5]} ({::N[.5]}, {::N[.5]}) ± 1 εar", dist, dist.in(usc::foot), dist.in(si::metre)); } ``` diff --git a/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md b/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md index 13ce4e344..d69ef39c1 100644 --- a/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md +++ b/docs/users_guide/framework_basics/faster_than_lightspeed_constants.md @@ -61,7 +61,7 @@ constexpr auto speed_of_light_in_vacuum = 1 * si::si2019::speed_of_light_in_vacu QuantityOf auto q = 1 / (permeability_of_vacuum * pow<2>(speed_of_light_in_vacuum)); -std::println("permittivity of vacuum = {} = {:{%N:.3e} %U}", q, q.in(F / m)); +std::println("permittivity of vacuum = {} = {::N[.3e]}", q, q.in(F / m)); ``` The above first prints the following: diff --git a/docs/users_guide/framework_basics/simple_and_typed_quantities.md b/docs/users_guide/framework_basics/simple_and_typed_quantities.md index 35e4f83e3..1204aef77 100644 --- a/docs/users_guide/framework_basics/simple_and_typed_quantities.md +++ b/docs/users_guide/framework_basics/simple_and_typed_quantities.md @@ -75,7 +75,7 @@ Here is a simple example showing how to deal with such quantities: const quantity duration = 2 * h; const quantity speed = avg_speed(distance, duration); - std::println("A car driving {} in {} has an average speed of {:{%N:.4} %U} ({:{%N:.4} %U})", + std::println("A car driving {} in {} has an average speed of {::N[.4]} ({::N[.4]})", distance, duration, speed, speed.in(km / h)); } ``` @@ -103,7 +103,7 @@ Here is a simple example showing how to deal with such quantities: const quantity duration = 2 * h; const quantity speed = avg_speed(distance, duration); - std::println("A car driving {} in {} has an average speed of {:{%N:.4} %U} ({:{%N:.4} %U})", + std::println("A car driving {} in {} has an average speed of {::N[.4]} ({::N[.4]})", distance, duration, speed, speed.in(km / h)); } ``` @@ -194,7 +194,7 @@ The previous example can be re-typed using typed quantities in the following way const quantity duration = isq::time(2 * h); const quantity speed = avg_speed(distance, duration); - std::println("A car driving {} in {} has an average speed of {:{%N:.4} %U} ({:{%N:.4} %U})", + std::println("A car driving {} in {} has an average speed of {::N[.4]} ({::N[.4]})", distance, duration, speed, speed.in(km / h)); } ``` @@ -223,7 +223,7 @@ The previous example can be re-typed using typed quantities in the following way const quantity duration = isq::time(2 * h); const quantity speed = avg_speed(distance, duration); - std::println("A car driving {} in {} has an average speed of {:{%N:.4} %U} ({:{%N:.4} %U})", + std::println("A car driving {} in {} has an average speed of {::N[.4]} ({::N[.4]})", distance, duration, speed, speed.in(km / h)); } ``` diff --git a/docs/users_guide/framework_basics/the_affine_space.md b/docs/users_guide/framework_basics/the_affine_space.md index ee0def4cd..e04f3eb30 100644 --- a/docs/users_guide/framework_basics/the_affine_space.md +++ b/docs/users_guide/framework_basics/the_affine_space.md @@ -501,7 +501,7 @@ std::println("| {:<18} | {:^18} | {:^18} | {:^18} |", std::println("|{0:=^20}|{0:=^20}|{0:=^20}|{0:=^20}|", ""); auto print_temp = [&](std::string_view label, auto v) { - std::println("| {:<18} | {:^18} | {:^18} | {:^18{%N:.2f}%?%U} |", label, + std::println("| {:<14} | {:^18} | {:^18} | {:^18:N[.2f]} |", label, v - room_reference_temp, (v - si::ice_point).in(deg_C), (v - si::absolute_zero).in(deg_C)); }; diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 2d81027e7..510e66afd 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -349,6 +349,9 @@ class MP_UNITS_STD_FMT::formatter, Char> { using format_specs = mp_units::detail::fill_align_width_format_specs; std::basic_string_view modifiers_format_str_; + std::basic_string_view default_number_format_str_ = {}; + std::basic_string_view default_unit_format_str_ = {}; + std::basic_string_view default_dimension_format_str_ = {}; std::vector format_str_lengths_; format_specs specs_{}; @@ -431,9 +434,9 @@ class MP_UNITS_STD_FMT::formatter, Char> { quantity_formatter(OutputIt, Args...) -> quantity_formatter; template - constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const + constexpr const Char* parse_format_spec(const Char* begin, const Char* end, Handler&& handler) const { - if (begin == end || *begin == '}') return begin; + if (begin == end || *begin == ':' || *begin == '}') return begin; if (*begin != '%' && *begin != '{') throw MP_UNITS_STD_FMT::format_error( "`quantity-specs` should start with a `conversion-spec` ('%' or '{' characters expected)})"); @@ -441,6 +444,15 @@ class MP_UNITS_STD_FMT::formatter, Char> { while (ptr != end) { auto c = *ptr; if (c == '}') break; + if (c == ":") { + if (ptr + 1 != end && *(ptr + 1) == ":") { + handler.on_text(begin, ++ptr); // account for ':' + ++ptr; // consume the second ':' + continue; + } else + // default specs started + break; + } if (c == '{') { if (begin != ptr) handler.on_text(begin, ptr); begin = ptr = mp_units::detail::parse_subentity_replacement_field(ptr, end, handler); @@ -457,13 +469,13 @@ class MP_UNITS_STD_FMT::formatter, Char> { c = *ptr++; switch (c) { case 'N': - handler.on_number("{}"); + handler.on_number(default_number_format_str_); break; case 'U': - handler.on_unit("{}"); + handler.on_unit(default_unit_format_str_); break; case 'D': - handler.on_dimension("{}"); + handler.on_dimension(default_dimension_format_str_); break; case '?': handler.on_maybe_space(); @@ -477,19 +489,33 @@ class MP_UNITS_STD_FMT::formatter, Char> { begin = ptr; } if (begin != ptr) handler.on_text(begin, ptr); + if (ptr != end&&* ptr = ':') { + } return ptr; } + template + constexpr const Char* parse_default_specs(const Char* begin, const Char* end, Handler&& handler) const + { + } + + template + constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const + { + auto it = parse_format_spec(begin, end, handler); + } + template OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const { std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); if (modifiers_format_str_.empty()) { // default format should print value followed by the unit separated with 1 space - out = MP_UNITS_STD_FMT::vformat_to(out, locale, "{}", + out = MP_UNITS_STD_FMT::vformat_to(out, locale, default_number_format_str_, MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; - return MP_UNITS_STD_FMT::vformat_to(out, locale, "{}", MP_UNITS_STD_FMT::make_format_args(q.unit)); + return MP_UNITS_STD_FMT::vformat_to(out, locale, default_unit_format_str_, + MP_UNITS_STD_FMT::make_format_args(q.unit)); } else { // user provided format quantity_formatter f{out, q, format_str_lengths_.cbegin(), locale}; @@ -501,16 +527,16 @@ class MP_UNITS_STD_FMT::formatter, Char> { public: constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - const auto begin = ctx.begin(); - auto end = ctx.end(); + auto begin = ctx.begin(), end = ctx.end(); - auto it = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); - if (it == end) return it; + auto begin = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); + if (begin == end) return begin; format_checker checker{ctx, format_str_lengths_}; - end = parse_quantity_specs(it, end, checker); - modifiers_format_str_ = {it, end}; - return end; + auto it = parse_quantity_specs(begin, end, checker); + modifiers_format_str_ = {begin, it}; + + return parse_default_specs(it, end, handler); } template From fa04e93687d58dab3c54187049a08eb0a89427b1 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 18 Apr 2024 22:29:09 +0100 Subject: [PATCH 2/8] feat: New formatting specification implemented --- example/clcpp_response.cpp | 8 +- example/glide_computer.cpp | 18 +- .../glide_computer_lib/glide_computer_lib.cpp | 4 +- example/hello_units.cpp | 14 +- example/si_constants.cpp | 12 +- example/unmanned_aerial_vehicle.cpp | 2 +- src/core/include/mp-units/format.h | 337 +++++++----------- test/runtime/fmt_test.cpp | 308 +++++++++++----- 8 files changed, 375 insertions(+), 328 deletions(-) diff --git a/example/clcpp_response.cpp b/example/clcpp_response.cpp index db2be2cae..d385e1c00 100644 --- a/example/clcpp_response.cpp +++ b/example/clcpp_response.cpp @@ -118,7 +118,7 @@ void calcs_comparison() const auto L1A = 2.f * fm; const auto L2A = 3.f * fm; const auto LrA = L1A + L2A; - std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30} %U}\n + {:{%N:.30} %U}\n = {:{%N:.30} %U}\n\n", L1A, L2A, LrA); + std::cout << MP_UNITS_STD_FMT::format("{::N[.30]}\n + {::N[.30]}\n = {::N[.30]}\n\n", L1A, L2A, LrA); std::cout << "The single unit method must convert large\n" "or small values in other units to the base unit.\n" @@ -127,17 +127,17 @@ void calcs_comparison() const auto L1B = L1A.in(m); const auto L2B = L2A.in(m); const auto LrB = L1B + L2B; - std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30e} %U}\n + {:{%N:.30e} %U}\n = {:{%N:.30e} %U}\n\n", L1B, L2B, LrB); + std::cout << MP_UNITS_STD_FMT::format("{::N[.30e]}\n + {::N[.30e]}\n = {::N[.30e]}\n\n", L1B, L2B, LrB); std::cout << "In multiplication and division:\n\n"; const quantity ArA = L1A * L2A; - std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30} %U}\n * {:{%N:.30} %U}\n = {:{%N:.30} %U}\n\n", L1A, L2A, ArA); + std::cout << MP_UNITS_STD_FMT::format("{::N[.30]}\n * {::N[.30]}\n = {::N[.30]}\n\n", L1A, L2A, ArA); std::cout << "similar problems arise\n\n"; const quantity ArB = L1B * L2B; - std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30e} %U}\n * {:{%N:.30e} %U}\n = {:{%N:.30e} %U}\n\n", L1B, L2B, ArB); + std::cout << MP_UNITS_STD_FMT::format("{::N[.30e]}\n * {::N[.30e]}\n = {::N[.30e]}\n\n", L1B, L2B, ArB); } } // namespace diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index 5408d39fd..aaec10ab8 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -87,7 +87,7 @@ void print(const R& gliders) std::cout << "- Polar:\n"; for (const auto& p : g.polar) { const auto ratio = glide_ratio(g.polar[0]).force_in(one); - std::cout << MP_UNITS_STD_FMT::format(" * {:{%N:.4} %U} @ {:{%N:.1} %U} -> {:{%N:.1} %U} ({:{%N:.1} %U})\n", + std::cout << MP_UNITS_STD_FMT::format(" * {::N[.4]} @ {::N[.1]} -> {::N[.1]} ({::N[.1]})\n", p.climb, p.v, ratio, // TODO is it possible to make ADL work below (we need another set of trig // functions for strong angle in a different namespace) @@ -106,8 +106,8 @@ void print(const R& conditions) for (const auto& c : conditions) { std::cout << "- " << c.first << "\n"; const auto& w = c.second; - std::cout << " * Cloud base: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", w.cloud_base) << " AGL\n"; - std::cout << " * Thermals strength: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", w.thermal_strength) << "\n"; + std::cout << " * Cloud base: " << MP_UNITS_STD_FMT::format("{::N[.0]}", w.cloud_base) << " AGL\n"; + std::cout << " * Thermals strength: " << MP_UNITS_STD_FMT::format("{::N[.1]}", w.thermal_strength) << "\n"; std::cout << "\n"; } } @@ -119,7 +119,7 @@ void print(const R& waypoints) std::cout << "Waypoints:\n"; std::cout << "==========\n"; for (const auto& w : waypoints) - std::cout << MP_UNITS_STD_FMT::format("- {}: {} {}, {:{%N:.1} %U}\n", w.name, w.pos.lat, w.pos.lon, w.alt); + std::cout << MP_UNITS_STD_FMT::format("- {}: {} {}, {::N[.1]}\n", w.name, w.pos.lat, w.pos.lon, w.alt); std::cout << "\n"; } @@ -130,12 +130,12 @@ void print(const task& t) std::cout << "- Start: " << t.get_start().name << "\n"; std::cout << "- Finish: " << t.get_finish().name << "\n"; - std::cout << "- Length: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", t.get_distance()) << "\n"; + std::cout << "- Length: " << MP_UNITS_STD_FMT::format("{::N[.1]}", t.get_distance()) << "\n"; std::cout << "- Legs: " << "\n"; for (const auto& l : t.get_legs()) - std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({:{%N:.1} %U})\n", l.begin().name, l.end().name, + std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({::N[.1]})\n", l.begin().name, l.end().name, l.get_distance()); std::cout << "\n"; } @@ -144,7 +144,7 @@ void print(const safety& s) { std::cout << "Safety:\n"; std::cout << "=======\n"; - std::cout << "- Min AGL separation: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", s.min_agl_height) << "\n"; + std::cout << "- Min AGL separation: " << MP_UNITS_STD_FMT::format("{::N[.0]}", s.min_agl_height) << "\n"; std::cout << "\n"; } @@ -153,8 +153,8 @@ void print(const aircraft_tow& tow) std::cout << "Tow:\n"; std::cout << "====\n"; std::cout << "- Type: aircraft\n"; - std::cout << "- Height: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", tow.height_agl) << "\n"; - std::cout << "- Performance: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", tow.performance) << "\n"; + std::cout << "- Height: " << MP_UNITS_STD_FMT::format("{::N[.0]}", tow.height_agl) << "\n"; + std::cout << "- Performance: " << MP_UNITS_STD_FMT::format("{::N[.1]}", tow.performance) << "\n"; std::cout << "\n"; } diff --git a/example/glide_computer_lib/glide_computer_lib.cpp b/example/glide_computer_lib/glide_computer_lib.cpp index 85cf2a0e6..b286dde6a 100644 --- a/example/glide_computer_lib/glide_computer_lib.cpp +++ b/example/glide_computer_lib/glide_computer_lib.cpp @@ -82,9 +82,7 @@ void print(std::string_view phase_name, timestamp start_ts, const glide_computer const glide_computer::flight_point& new_point) { std::cout << MP_UNITS_STD_FMT::format( - "| {:<12} | {:>9{%N:.1} %U} (Total: {:>9{%N:.1} %U}) | {:>8{%N:.1} %U} (Total: {:>8{%N:.1} %U}) | {:>7{%N:.0} %U} " - "({:>6{%N:.0} %U}) " - "|\n", + "| {:<12} | {:>9:N[.1]} (Total: {:>9:N[.1]}) | {:>8:N[.1]} (Total: {:>8:N[.1]}) | {:>7:N[.0]} ({:>6:N[.0]}) |\n", phase_name, value_cast(new_point.ts - point.ts), value_cast(new_point.ts - start_ts), new_point.dist - point.dist, new_point.dist, new_point.alt - point.alt, new_point.alt); } diff --git a/example/hello_units.cpp b/example/hello_units.cpp index 93523e368..bbea5f2e3 100644 --- a/example/hello_units.cpp +++ b/example/hello_units.cpp @@ -58,11 +58,11 @@ int main() constexpr quantity v6 = value_cast(v4); constexpr quantity v7 = value_cast(v6); - std::cout << v1 << '\n'; // 110 km/h - std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h - std::cout << MP_UNITS_STD_FMT::format("{:*^10}\n", v3); // *110 km/h* - std::cout << MP_UNITS_STD_FMT::format("{:%N in %U of %D}\n", v4); // 70 in mi/h of LT⁻¹ - std::cout << MP_UNITS_STD_FMT::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s - std::cout << MP_UNITS_STD_FMT::format("{:{%N:.2f}%?{%U:dn}}\n", v6); // 31.29 in m⋅s⁻¹ - std::cout << MP_UNITS_STD_FMT::format("{:%N}\n", v7); // 31 + std::cout << v1 << '\n'; // 110 km/h + std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h + std::cout << MP_UNITS_STD_FMT::format("{:*^10}\n", v3); // *110 km/h* + std::cout << MP_UNITS_STD_FMT::format("{:%N in %U of %D}\n", v4); // 70 in mi/h of LT⁻¹ + std::cout << MP_UNITS_STD_FMT::format("{::N[.2f]}\n", v5); // 30.56 m/s + std::cout << MP_UNITS_STD_FMT::format("{::N[.2f]U[dn]}\n", v6); // 31.29 m⋅s⁻¹ + std::cout << MP_UNITS_STD_FMT::format("{:%N}\n", v7); // 31 } diff --git a/example/si_constants.cpp b/example/si_constants.cpp index a2f38295d..dfdfd4bd6 100644 --- a/example/si_constants.cpp +++ b/example/si_constants.cpp @@ -45,19 +45,19 @@ int main() using namespace mp_units::si::unit_symbols; std::cout << "The seven defining constants of the SI and the seven corresponding units they define:\n"; - std::cout << MP_UNITS_STD_FMT::format("- hyperfine transition frequency of Cs: {} = {:{%N:.0} %U}\n", + std::cout << MP_UNITS_STD_FMT::format("- hyperfine transition frequency of Cs: {} = {::N[.0]}\n", 1. * si2019::hyperfine_structure_transition_frequency_of_cs, (1. * si2019::hyperfine_structure_transition_frequency_of_cs).in(Hz)); - std::cout << MP_UNITS_STD_FMT::format("- speed of light in vacuum: {} = {:{%N:.0} %U}\n", + std::cout << MP_UNITS_STD_FMT::format("- speed of light in vacuum: {} = {::N[.0]}\n", 1. * si2019::speed_of_light_in_vacuum, (1. * si2019::speed_of_light_in_vacuum).in(m / s)); - std::cout << MP_UNITS_STD_FMT::format("- Planck constant: {} = {:{%N:.8e} %U}\n", + std::cout << MP_UNITS_STD_FMT::format("- Planck constant: {} = {::N[.8e]}\n", 1. * si2019::planck_constant, (1. * si2019::planck_constant).in(J * s)); - std::cout << MP_UNITS_STD_FMT::format("- elementary charge: {} = {:{%N:.9e} %U}\n", + std::cout << MP_UNITS_STD_FMT::format("- elementary charge: {} = {::N[.9e]}\n", 1. * si2019::elementary_charge, (1. * si2019::elementary_charge).in(C)); - std::cout << MP_UNITS_STD_FMT::format("- Boltzmann constant: {} = {:{%N:.6e} %U}\n", + std::cout << MP_UNITS_STD_FMT::format("- Boltzmann constant: {} = {::N[.6e]}\n", 1. * si2019::boltzmann_constant, (1. * si2019::boltzmann_constant).in(J / K)); - std::cout << MP_UNITS_STD_FMT::format("- Avogadro constant: {} = {:{%N:.8e} %U}\n", + std::cout << MP_UNITS_STD_FMT::format("- Avogadro constant: {} = {::N[.8e]}\n", 1. * si2019::avogadro_constant, (1. * si2019::avogadro_constant).in(one / mol)); std::cout << MP_UNITS_STD_FMT::format("- luminous efficacy: {} = {}\n", 1. * si2019::luminous_efficacy, (1. * si2019::luminous_efficacy).in(lm / W)); diff --git a/example/unmanned_aerial_vehicle.cpp b/example/unmanned_aerial_vehicle.cpp index 13f248401..e2d257107 100644 --- a/example/unmanned_aerial_vehicle.cpp +++ b/example/unmanned_aerial_vehicle.cpp @@ -170,6 +170,6 @@ int main() }; waypoint wpt = {"EPPR", {54.24772_N, 18.6745_E}, mean_sea_level + 16. * ft}; - std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {:{%N:.2} %U}, {:{%N:.2} %U}\n", wpt.name, wpt.pos.lat, wpt.pos.lon, + std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {::N[.2]}, {::N[.2]}\n", wpt.name, wpt.pos.lat, wpt.pos.lon, wpt.msl_alt, to_hae(wpt.msl_alt, wpt.pos)); } diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 510e66afd..74f575807 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -67,31 +67,6 @@ template return mp_units::detail::parse_dynamic_spec(it, end, specs.width, specs.width_ref, ctx); } -template -[[nodiscard]] constexpr const Char* parse_subentity_replacement_field(const Char* begin, const Char* end, - Handler&& handler) -{ - if (end - begin++ < 4) - return MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` too short")), end; - if (*begin++ != '%') - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should start with '%'")); - if (*begin == '}') - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should have an identifier")); - auto it = begin; - for (; it != end; ++it) { - if (*it == '{' || *it == '%') - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid `subentity-replacement-field` format")); - if (*it == '}' || *it == ':') break; - } - if (it == end) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` too short")); - std::string_view id{begin, it}; - if (*it == ':') ++it; - it = handler.on_replacement_field(id, it); - if (it == end || *it != '}') - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should end with '}'")); - return ++it; -} - template OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs& specs) { @@ -128,25 +103,10 @@ OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs< template class MP_UNITS_STD_FMT::formatter { struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::dimension_symbol_formatting {}; - - std::basic_string_view fill_align_width_format_str_; - std::basic_string_view modifiers_format_str_; format_specs specs_{}; + std::basic_string_view fill_align_width_format_str_; - 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 - constexpr const Char* parse_dimension_specs(const Char* begin, const Char* end, Handler&& handler) const + constexpr const Char* parse_dimension_specs(const Char* begin, const Char* end) { auto it = begin; if (it == end || *it == '}') return begin; @@ -158,7 +118,9 @@ class MP_UNITS_STD_FMT::formatter { } end = it; - if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it); + if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) + specs_.encoding = (*it == 'U') ? mp_units::text_encoding::unicode : mp_units::text_encoding::ascii; + return end; } @@ -172,26 +134,21 @@ class MP_UNITS_STD_FMT::formatter { 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; + return parse_dimension_specs(it, end); } template auto format(const D& d, FormatContext& ctx) const -> decltype(ctx.out()) { - unit_formatter f{specs_}; - mp_units::detail::handle_dynamic_spec(f.specs.width, f.specs.width_ref, ctx); - - parse_dimension_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f); + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - if (f.specs.width == 0) { + if (specs.width == 0) { // Avoid extra copying if width is not specified - return mp_units::dimension_symbol_to(ctx.out(), d, f.specs); + return mp_units::dimension_symbol_to(ctx.out(), d, specs); } else { std::basic_string unit_buffer; - mp_units::dimension_symbol_to(std::back_inserter(unit_buffer), d, f.specs); + mp_units::dimension_symbol_to(std::back_inserter(unit_buffer), d, specs); std::basic_string global_format_buffer = "{:" + std::basic_string{fill_align_width_format_str_} + "}"; return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, @@ -217,66 +174,43 @@ class MP_UNITS_STD_FMT::formatter { template class MP_UNITS_STD_FMT::formatter { struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::unit_symbol_formatting {}; - - std::basic_string_view fill_align_width_format_str_; - std::basic_string_view modifiers_format_str_; format_specs specs_{}; - struct format_checker { - using enum mp_units::text_encoding; + std::basic_string_view fill_align_width_format_str_; - mp_units::text_encoding encoding = unicode; + constexpr const Char* parse_unit_specs(const Char* begin, const Char* end) + { + auto it = begin; + if (it == end || *it == '}') return begin; - constexpr void on_text_encoding(Char val) { encoding = (val == 'U') ? unicode : ascii; } - constexpr void on_unit_symbol_solidus(Char) const {} - constexpr void on_unit_symbol_separator(Char val) const - { - if (val == 'd' && encoding == ascii) - throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding"); + constexpr auto valid_modifiers = std::string_view{"UA1ansd"}; + for (; it != end && *it != '}'; ++it) { + if (valid_modifiers.find(*it) == std::string_view::npos) + throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified"); } - }; - - struct unit_formatter { - format_specs specs; - - using enum mp_units::text_encoding; - using enum mp_units::unit_symbol_solidus; - using enum mp_units::unit_symbol_separator; + end = it; - constexpr void on_text_encoding(Char val) { specs.encoding = (val == 'U') ? unicode : ascii; } - constexpr void on_unit_symbol_solidus(Char val) - { - switch (val) { + if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) + specs_.encoding = (*it == 'U') ? mp_units::text_encoding::unicode : mp_units::text_encoding::ascii; + if (it = mp_units::detail::at_most_one_of(begin, end, "1an"); it != end) { + switch (*it) { case '1': - specs.solidus = one_denominator; + specs_.solidus = mp_units::unit_symbol_solidus::one_denominator; break; case 'a': - specs.solidus = always; + specs_.solidus = mp_units::unit_symbol_solidus::always; break; case 'n': - specs.solidus = never; + specs_.solidus = mp_units::unit_symbol_solidus::never; break; } } - constexpr void on_unit_symbol_separator(Char val) { specs.separator = (val == 's') ? space : half_high_dot; } - }; - - template - constexpr const Char* parse_unit_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{"UA1ansd"}; - for (; it != end && *it != '}'; ++it) { - if (valid_modifiers.find(*it) == std::string_view::npos) - throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified"); + if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) { + if (*it == 'd' && specs_.encoding == mp_units::text_encoding::ascii) + throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding"); + specs_.separator = + (*it == 's') ? mp_units::unit_symbol_separator::space : mp_units::unit_symbol_separator::half_high_dot; } - end = it; - - if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it); - if (it = mp_units::detail::at_most_one_of(begin, end, "1an"); it != end) handler.on_unit_symbol_solidus(*it); - if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) handler.on_unit_symbol_separator(*it); return end; } @@ -290,26 +224,21 @@ class MP_UNITS_STD_FMT::formatter { fill_align_width_format_str_ = {begin, it}; if (it == end) return it; - format_checker checker; - end = parse_unit_specs(it, end, checker); - modifiers_format_str_ = {it, end}; - return end; + return parse_unit_specs(it, end); } template auto format(const U& u, FormatContext& ctx) const -> decltype(ctx.out()) { - unit_formatter f{specs_}; - mp_units::detail::handle_dynamic_spec(f.specs.width, f.specs.width_ref, ctx); - - parse_unit_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f); + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - if (f.specs.width == 0) { + if (specs.width == 0) { // Avoid extra copying if width is not specified - return mp_units::unit_symbol_to(ctx.out(), u, f.specs); + return mp_units::unit_symbol_to(ctx.out(), u, specs); } else { std::basic_string unit_buffer; - mp_units::unit_symbol_to(std::back_inserter(unit_buffer), u, f.specs); + mp_units::unit_symbol_to(std::back_inserter(unit_buffer), u, specs); std::basic_string global_format_buffer = "{:" + std::basic_string{fill_align_width_format_str_} + "}"; return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, @@ -322,22 +251,21 @@ class MP_UNITS_STD_FMT::formatter { // // Grammar // -// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] +// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] [defaults-specs] // quantity-specs ::= conversion-spec // quantity-specs conversion-spec // quantity-specs literal-char // literal-char ::= -// conversion-spec ::= placement-spec -// subentity-replacement-field -// placement-spec ::= '%' placement-type -// placement-type ::= 'N' | 'U' | 'D' | '?' | '%' -// subentity-replacement-field ::= '{' '%' subentity-id [format-specifier] '}' -// subentity-id ::= literal-char -// subentity-id literal-char -// format-specifier ::= ':' format-spec -// format-spec ::= +// conversion-spec ::= '%' placement-type +// placement-type ::= subentity-id | '?' | '%' +// defaults-specs ::= ':' default-spec-list +// default-spec-list ::= default-spec +// default-spec-list default-spec +// default-spec ::= subentity-id '[' format-spec ']' +// subentity-id ::= 'N' | 'U' | 'D' +// format-spec ::= // -template +template Rep> class MP_UNITS_STD_FMT::formatter, Char> { static constexpr auto unit = get_unit(Reference); static constexpr auto dimension = get_quantity_spec(Reference).dimension; @@ -345,107 +273,68 @@ class MP_UNITS_STD_FMT::formatter, Char> { using quantity_t = mp_units::quantity; using unit_t = std::remove_const_t; using dimension_t = std::remove_const_t; - using format_specs = mp_units::detail::fill_align_width_format_specs; - std::basic_string_view modifiers_format_str_; - std::basic_string_view default_number_format_str_ = {}; - std::basic_string_view default_unit_format_str_ = {}; - std::basic_string_view default_dimension_format_str_ = {}; - std::vector format_str_lengths_; format_specs specs_{}; - struct format_checker { - MP_UNITS_STD_FMT::basic_format_parse_context& ctx; - std::vector& format_str_lengths; + std::basic_string_view modifiers_format_str_; + std::basic_string rep_format_str_ = "{}"; + std::basic_string unit_format_str_ = "{}"; + std::basic_string dimension_format_str_ = "{}"; + + MP_UNITS_STD_FMT::formatter rep_formatter_; + MP_UNITS_STD_FMT::formatter unit_formatter_; + MP_UNITS_STD_FMT::formatter dimension_formatter_; - constexpr void on_number(std::basic_string_view) const {} + struct format_checker { + constexpr void on_number() const {} constexpr void on_maybe_space() const {} - constexpr void on_unit(std::basic_string_view) const {} - constexpr void on_dimension(std::basic_string_view) const {} + constexpr void on_unit() const {} + constexpr void on_dimension() const {} constexpr void on_text(const Char*, const Char*) const {} - - constexpr const Char* on_replacement_field(std::basic_string_view id, const Char* begin) - { - if (id == "N") - return on_replacement_field(begin); - else if (id == "U") - return on_replacement_field(begin); - else if (id == "D") - return on_replacement_field(begin); - else - throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'"); - } - - private: - template - constexpr const Char* on_replacement_field(const Char* begin) const - { - MP_UNITS_STD_FMT::formatter sf; - ctx.advance_to(begin); - auto ptr = sf.parse(ctx); - if (*ptr != '}') throw MP_UNITS_STD_FMT::format_error("unmatched '}' in format string"); - format_str_lengths.push_back(mp_units::detail::to_unsigned(ptr - begin)); - return ptr; - } }; template struct quantity_formatter { + const formatter& f; OutputIt out; const quantity_t& q; - std::vector::const_iterator format_str_lengths_it; std::locale locale; - void on_number(std::basic_string_view format_str) + void on_number() { - out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, + out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.rep_format_str_, MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); } void on_maybe_space() { if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; } - void on_unit(std::basic_string_view format_str) + void on_unit() { - out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.unit)); + out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit)); } - void on_dimension(std::basic_string_view format_str) + void on_dimension() { - out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.dimension)); + out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.dimension_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 id, const Char* begin) - { - auto format_str = [&] { return "{:" + std::string(begin, *format_str_lengths_it + 1); }; - if (id == "N") - on_number(format_str()); - else if (id == "U") - on_unit(format_str()); - else if (id == "D") - on_dimension(format_str()); - else - throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'"); - return begin + *format_str_lengths_it++; - } }; - template - quantity_formatter(OutputIt, Args...) -> quantity_formatter; template - constexpr const Char* parse_format_spec(const Char* begin, const Char* end, Handler&& handler) const + constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const { if (begin == end || *begin == ':' || *begin == '}') return begin; - if (*begin != '%' && *begin != '{') + if (*begin != '%') throw MP_UNITS_STD_FMT::format_error( - "`quantity-specs` should start with a `conversion-spec` ('%' or '{' characters expected)})"); + "`quantity-specs` should start with a `conversion-spec` ('%' characters expected)"); auto ptr = begin; while (ptr != end) { auto c = *ptr; if (c == '}') break; - if (c == ":") { - if (ptr + 1 != end && *(ptr + 1) == ":") { + if (c == ':') { + if (ptr + 1 != end && *(ptr + 1) == ':') { handler.on_text(begin, ++ptr); // account for ':' ++ptr; // consume the second ':' continue; @@ -453,29 +342,24 @@ class MP_UNITS_STD_FMT::formatter, Char> { // default specs started break; } - if (c == '{') { - if (begin != ptr) handler.on_text(begin, ptr); - begin = ptr = mp_units::detail::parse_subentity_replacement_field(ptr, end, handler); - continue; - } if (c != '%') { ++ptr; continue; } if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' - if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid `placement-spec` format"); + if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid `conversion-spec` format"); c = *ptr++; switch (c) { case 'N': - handler.on_number(default_number_format_str_); + handler.on_number(); break; case 'U': - handler.on_unit(default_unit_format_str_); + handler.on_unit(); break; case 'D': - handler.on_dimension(default_dimension_format_str_); + handler.on_dimension(); break; case '?': handler.on_maybe_space(); @@ -484,25 +368,56 @@ class MP_UNITS_STD_FMT::formatter, Char> { handler.on_text(ptr - 1, ptr); break; default: - throw MP_UNITS_STD_FMT::format_error(std::string("unknown `placement-spec` token '") + c + "'"); + throw MP_UNITS_STD_FMT::format_error(std::string("unknown `placement-type` token '") + c + "'"); } begin = ptr; } if (begin != ptr) handler.on_text(begin, ptr); - if (ptr != end&&* ptr = ':') { - } return ptr; } - template - constexpr const Char* parse_default_specs(const Char* begin, const Char* end, Handler&& handler) const + template + constexpr const Char* parse_default_spec(const Char* begin, const Char* end, Formatter& f, std::string& format_str) { + if (begin == end || *begin++ != '[') + throw MP_UNITS_STD_FMT::format_error("`default-spec` should contain a `[` character"); + auto it = begin; + for (int nested_brackets = 0; it != end && !(*it == ']' && nested_brackets == 0); it++) { + if (*it == '[') ++nested_brackets; + if (*it == ']') { + if (nested_brackets == 0) throw MP_UNITS_STD_FMT::format_error("unmatched ']' in format string"); + --nested_brackets; + } + } + format_str = "{:" + std::string(begin, it) + '}'; + if (it == end) throw MP_UNITS_STD_FMT::format_error("unmatched '[' in format string"); + MP_UNITS_STD_FMT::basic_format_parse_context ctx(std::string_view(begin, it)); + auto ptr = f.parse(ctx); + if (ptr != it) throw MP_UNITS_STD_FMT::format_error("invalid subentity format '" + std::string(begin, it) + "'"); + return ++it; // skip `]` } - template - constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const + [[nodiscard]] constexpr const Char* parse_defaults_specs(const Char* begin, const Char* end) { - auto it = parse_format_spec(begin, end, handler); + if (begin == end || *begin == '}') return begin; + if (*begin++ != ':') throw MP_UNITS_STD_FMT::format_error("`defaults-specs` should start with a `:`"); + do { + auto c = *begin++; + switch (c) { + case 'N': + begin = parse_default_spec(begin, end, rep_formatter_, rep_format_str_); + break; + case 'U': + begin = parse_default_spec(begin, end, unit_formatter_, unit_format_str_); + break; + case 'D': + begin = parse_default_spec(begin, end, dimension_formatter_, dimension_format_str_); + break; + default: + throw MP_UNITS_STD_FMT::format_error(std::string("unknown `subentity-id` token '") + c + "'"); + } + } while (begin != end && *begin != '}'); + return begin; } template @@ -510,15 +425,14 @@ class MP_UNITS_STD_FMT::formatter, Char> { { std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); if (modifiers_format_str_.empty()) { - // default format should print value followed by the unit separated with 1 space - out = MP_UNITS_STD_FMT::vformat_to(out, locale, default_number_format_str_, + // default + out = MP_UNITS_STD_FMT::vformat_to(out, locale, rep_format_str_, MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; - return MP_UNITS_STD_FMT::vformat_to(out, locale, default_unit_format_str_, - MP_UNITS_STD_FMT::make_format_args(q.unit)); + return MP_UNITS_STD_FMT::vformat_to(out, locale, unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit)); } else { // user provided format - quantity_formatter f{out, q, format_str_lengths_.cbegin(), locale}; + quantity_formatter f{*this, out, q, locale}; parse_quantity_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f); return f.out; } @@ -529,14 +443,14 @@ class MP_UNITS_STD_FMT::formatter, Char> { { auto begin = ctx.begin(), end = ctx.end(); - auto begin = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); + begin = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); if (begin == end) return begin; - format_checker checker{ctx, format_str_lengths_}; + format_checker checker{}; auto it = parse_quantity_specs(begin, end, checker); modifiers_format_str_ = {begin, it}; - return parse_default_specs(it, end, handler); + return parse_defaults_specs(it, end); } template @@ -547,7 +461,8 @@ class MP_UNITS_STD_FMT::formatter, Char> { if (specs.width == 0) { // Avoid extra copying if width is not specified - return format_quantity(ctx.out(), q, ctx); + format_quantity(ctx.out(), q, ctx); + return ctx.out(); } else { std::basic_string quantity_buffer; format_quantity(std::back_inserter(quantity_buffer), q, ctx); diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index 840cdda92..5d91e6b6a 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -738,45 +738,72 @@ TEST_CASE("sign specification", "[text][fmt]") SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", 1 * isq::length[m]) == + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", 1 * isq::length[m]) == "1m,+1m,1m, 1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", -1 * isq::length[m]) == + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", -1 * isq::length[m]) == "-1m,-1m,-1m,-1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", inf) == "infm,+infm,infm, infm"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", nan) == "nanm,+nanm,nanm, nanm"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", inf) == + "infm,+infm,infm, infm"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:%N%U:N[+]},{0:%N%U:N[-]},{0:%N%U:N[ ]}", nan) == + "nanm,+nanm,nanm, nanm"); } SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", 1 * isq::length[m]) == "1,+1,1, 1"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", -1 * isq::length[m]) == "-1,-1,-1,-1"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", inf) == "inf,+inf,inf, inf"); - CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", nan) == "nan,+nan,nan, nan"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", 1 * isq::length[m]) == "1,+1,1, 1"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", -1 * isq::length[m]) == "-1,-1,-1,-1"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", inf) == "inf,+inf,inf, inf"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:%N:N[+]},{0:%N:N[-]},{0:%N:N[ ]}", nan) == "nan,+nan,nan, nan"); } } TEST_CASE("precision specification", "[text][fmt]") { - SECTION("full format {:%N%?%U} on a quantity") + SECTION("full format on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.0f}%?%U}", 1.2345 * isq::length[m]) == "1 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.1f}%?%U}", 1.2345 * isq::length[m]) == "1.2 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.2f}%?%U}", 1.2345 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3f}%?%U}", 1.2345 * isq::length[m]) == "1.234 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.4f}%?%U}", 1.2345 * isq::length[m]) == "1.2345 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.5f}%?%U}", 1.2345 * isq::length[m]) == "1.23450 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.10f}%?%U}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.0f]}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.0f]}", 1.2345 * isq::length[m]) == "1m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.1f]}", 1.2345 * isq::length[m]) == "1.2m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.2f]}", 1.2345 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3f]}", 1.2345 * isq::length[m]) == "1.234m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000m"); + } } SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.0f}}", 1.2345 * isq::length[m]) == "1"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.1f}}", 1.2345 * isq::length[m]) == "1.2"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.2f}}", 1.2345 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3f}}", 1.2345 * isq::length[m]) == "1.234"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.4f}}", 1.2345 * isq::length[m]) == "1.2345"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.5f}}", 1.2345 * isq::length[m]) == "1.23450"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.10f}}", 1.2345 * isq::length[m]) == "1.2345000000"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.0f]}", 1.2345 * isq::length[m]) == "1"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.1f]}", 1.2345 * isq::length[m]) == "1.2"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.2f]}", 1.2345 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3f]}", 1.2345 * isq::length[m]) == "1.234"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.4f]}", 1.2345 * isq::length[m]) == "1.2345"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.5f]}", 1.2345 * isq::length[m]) == "1.23450"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.10f]}", 1.2345 * isq::length[m]) == "1.2345000000"); } } @@ -784,70 +811,141 @@ TEST_CASE("type specification", "[text][fmt]") { SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:{%N:b}%?%U}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:B}%?%U}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:d}%?%U}", 42 * isq::length[m]) == "42 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:o}%?%U}", 42 * isq::length[m]) == "52 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:x}%?%U}", 42 * isq::length[m]) == "2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:X}%?%U}", 42 * isq::length[m]) == "2A m"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[b]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[B]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[d]}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[o]}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[x]}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[X]}", 42 * isq::length[m]) == "2A m"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}%?%U}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}%?%U}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}%?%U}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}%?%U}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); #else - CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}%?%U}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}%?%U}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}%?%U}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}%?%U}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:{%N:e}%?%U}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3e}%?%U}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:E}%?%U}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3E}%?%U}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}%?%U}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}%?%U}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}%?%U}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}%?%U}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[b]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[B]}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[d]}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[o]}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[x]}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[X]}", 42 * isq::length[m]) == "2A m"); + +#if MP_UNITS_USE_FMTLIB + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); +#else + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); +#endif + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[b]}", 42 * isq::length[m]) == "101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[B]}", 42 * isq::length[m]) == "101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[d]}", 42 * isq::length[m]) == "42m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[o]}", 42 * isq::length[m]) == "52m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[x]}", 42 * isq::length[m]) == "2am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[X]}", 42 * isq::length[m]) == "2Am"); + +#if MP_UNITS_USE_FMTLIB + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0m"); +#else + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0m"); +#endif + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678 * isq::length[m]) == "1.23457m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678 * isq::length[m]) == "1.23457m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08m"); + } } SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:{%N:b}}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:B}}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:d}}", 42 * isq::length[m]) == "42"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:o}}", 42 * isq::length[m]) == "52"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:x}}", 42 * isq::length[m]) == "2a"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:X}}", 42 * isq::length[m]) == "2A"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[b]}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[B]}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[d]}", 42 * isq::length[m]) == "42"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[o]}", 42 * isq::length[m]) == "52"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[x]}", 42 * isq::length[m]) == "2a"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[X]}", 42 * isq::length[m]) == "2A"); #if MP_UNITS_USE_FMTLIB - CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); #else - CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[a]}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3a]}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[A]}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3A]}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); #endif - CHECK(MP_UNITS_STD_FMT::format("{:{%N:e}}", 1.2345678 * isq::length[m]) == "1.234568e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3e}}", 1.2345678 * isq::length[m]) == "1.235e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:E}}", 1.2345678 * isq::length[m]) == "1.234568E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3E}}", 1.2345678 * isq::length[m]) == "1.235E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}}", 1.2345678 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}}", 1.2345678 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[e]}", 1.2345678 * isq::length[m]) == "1.234568e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3e]}", 1.2345678 * isq::length[m]) == "1.235e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[E]}", 1.2345678 * isq::length[m]) == "1.234568E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3E]}", 1.2345678 * isq::length[m]) == "1.235E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[g]}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3g]}", 1.2345678 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3g]}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[G]}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[.3G]}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); } } @@ -855,20 +953,41 @@ TEST_CASE("different base types with the # specifier", "[text][fmt]") { SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#b}%?%U}", 42 * isq::length[m]) == "0b101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#B}%?%U}", 42 * isq::length[m]) == "0B101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#o}%?%U}", 42 * isq::length[m]) == "052 m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#x}%?%U}", 42 * isq::length[m]) == "0x2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#X}%?%U}", 42 * isq::length[m]) == "0X2A m"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format("{::N[#b]}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#B]}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#o]}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#x]}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{::N[#X]}", 42 * isq::length[m]) == "0X2A m"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#b]}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#B]}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#o]}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#x]}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U:N[#X]}", 42 * isq::length[m]) == "0X2A m"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#b]}", 42 * isq::length[m]) == "0b101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#B]}", 42 * isq::length[m]) == "0B101010m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#o]}", 42 * isq::length[m]) == "052m"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#x]}", 42 * isq::length[m]) == "0x2am"); + CHECK(MP_UNITS_STD_FMT::format("{:%N%U:N[#X]}", 42 * isq::length[m]) == "0X2Am"); + } } SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#b}}", 42 * isq::length[m]) == "0b101010"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#B}}", 42 * isq::length[m]) == "0B101010"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#o}}", 42 * isq::length[m]) == "052"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#x}}", 42 * isq::length[m]) == "0x2a"); - CHECK(MP_UNITS_STD_FMT::format("{:{%N:#X}}", 42 * isq::length[m]) == "0X2A"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#b]}", 42 * isq::length[m]) == "0b101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#B]}", 42 * isq::length[m]) == "0B101010"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#o]}", 42 * isq::length[m]) == "052"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#x]}", 42 * isq::length[m]) == "0x2a"); + CHECK(MP_UNITS_STD_FMT::format("{:%N:N[#X]}", 42 * isq::length[m]) == "0X2A"); } } @@ -889,8 +1008,23 @@ TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]") SECTION("full format {:{%N:L}%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{:{%N:L}%?%U}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{:{%N:L}%?%U}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + SECTION("default spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{::N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + } + + SECTION("explicit spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%?%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + } + + SECTION("modified spec") + { + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%N%U:N[L]}", 299'792'458 * isq::speed[m / s]) == "299'792'458m/s"); + } } } From c5afd722e7cda542589128faebe3f96b526d5b89 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 18 Apr 2024 22:37:24 +0100 Subject: [PATCH 3/8] docs: Some docs updated to reflect the latest formatting changes --- docs/blog/posts/2.2.0-released.md | 4 ++-- docs/users_guide/framework_basics/the_affine_space.md | 2 +- test/runtime/fmt_test.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/blog/posts/2.2.0-released.md b/docs/blog/posts/2.2.0-released.md index 02e236900..f2736952b 100644 --- a/docs/blog/posts/2.2.0-released.md +++ b/docs/blog/posts/2.2.0-released.md @@ -242,9 +242,9 @@ quantity q = (90. * km / h).in(mph); std::cout << "Number: " << q.numerical_value_in(mph) << "\n"; std::cout << "Unit: " << q.unit << "\n"; std::cout << "Dimension: " << q.dimension << "\n"; -std::println("{:{%N:.2f}%?%U}", q); +std::println("{::N[.2f]}", q); std::println("{:.4f} in {} of {}", q.numerical_value_in(mph), q.unit, q.dimension); -std::println("{:{%N:.4f} in %U of %D}", q); +std::println("{:%N in %U of %D:N[.4f]}", q); ``` ```text diff --git a/docs/users_guide/framework_basics/the_affine_space.md b/docs/users_guide/framework_basics/the_affine_space.md index e04f3eb30..768a22a75 100644 --- a/docs/users_guide/framework_basics/the_affine_space.md +++ b/docs/users_guide/framework_basics/the_affine_space.md @@ -491,7 +491,7 @@ room_temp room_ref{}; room_temp room_low = room_ref - number_of_steps * step_delta; room_temp room_high = room_ref + number_of_steps * step_delta; -std::println("Room reference temperature: {} ({}, {:{%N:.2f}%?%U})\n", +std::println("Room reference temperature: {} ({}, {::N[.2f]})\n", room_ref.quantity_from_zero(), room_ref.in(usc::degree_Fahrenheit).quantity_from_zero(), room_ref.in(si::kelvin).quantity_from_zero()); diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index 5d91e6b6a..c76032389 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -1006,7 +1006,7 @@ TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]") std::locale grp2{std::locale::classic(), new group2}; std::locale grp3{std::locale::classic(), new group3}; - SECTION("full format {:{%N:L}%?%U} on a quantity") + SECTION("full format on a quantity") { SECTION("default spec") { From 86743d6d247127c04279c2038f4ad8f5595759a3 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 18 Apr 2024 22:41:13 +0100 Subject: [PATCH 4/8] fix: `std::formattable` usage is now conditional --- src/core/include/mp-units/format.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 74f575807..18b1e5c85 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -265,7 +265,11 @@ class MP_UNITS_STD_FMT::formatter { // subentity-id ::= 'N' | 'U' | 'D' // format-spec ::= // +#if __cpp_lib_format_ranges template Rep> +#else +template +#endif class MP_UNITS_STD_FMT::formatter, Char> { static constexpr auto unit = get_unit(Reference); static constexpr auto dimension = get_quantity_spec(Reference).dimension; From 4ee7fb3375c2dc64e75d50f832d94819857c0c52 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 18 Apr 2024 22:41:57 +0100 Subject: [PATCH 5/8] style: formatting fixed to make pre-commit happy --- example/glide_computer.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index aaec10ab8..1971d1bd0 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -87,8 +87,7 @@ void print(const R& gliders) std::cout << "- Polar:\n"; for (const auto& p : g.polar) { const auto ratio = glide_ratio(g.polar[0]).force_in(one); - std::cout << MP_UNITS_STD_FMT::format(" * {::N[.4]} @ {::N[.1]} -> {::N[.1]} ({::N[.1]})\n", - p.climb, p.v, ratio, + std::cout << MP_UNITS_STD_FMT::format(" * {::N[.4]} @ {::N[.1]} -> {::N[.1]} ({::N[.1]})\n", p.climb, p.v, ratio, // TODO is it possible to make ADL work below (we need another set of trig // functions for strong angle in a different namespace) si::asin(1 / ratio).force_in(si::degree)); @@ -135,8 +134,7 @@ void print(const task& t) std::cout << "- Legs: " << "\n"; for (const auto& l : t.get_legs()) - std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({::N[.1]})\n", l.begin().name, l.end().name, - l.get_distance()); + std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({::N[.1]})\n", l.begin().name, l.end().name, l.get_distance()); std::cout << "\n"; } From 00372cc5d5c89cb87362cfa28b7fe6e884f64c41 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 18 Apr 2024 22:55:19 +0100 Subject: [PATCH 6/8] docs: "Quantity formatting" chapter updated --- .../framework_basics/text_output.md | 119 ++++++++---------- 1 file changed, 50 insertions(+), 69 deletions(-) diff --git a/docs/users_guide/framework_basics/text_output.md b/docs/users_guide/framework_basics/text_output.md index 365b8ece3..0a7e4c6da 100644 --- a/docs/users_guide/framework_basics/text_output.md +++ b/docs/users_guide/framework_basics/text_output.md @@ -529,20 +529,19 @@ std::println("{:d}", kg * m2 / s2); // kg⋅m²/s² ### Quantity formatting ```bnf -quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] +quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] [defaults-specs] quantity-specs ::= conversion-spec quantity-specs conversion-spec quantity-specs literal-char literal-char ::= -conversion-spec ::= placement-spec - subentity-replacement-field -placement-spec ::= '%' placement-type -placement-type ::= 'N' | 'U' | 'D' | '?' | '%' -subentity-replacement-field ::= '{' '%' subentity-id [format-specifier] '}' -subentity-id ::= literal-char - subentity-id literal-char -format-specifier ::= ':' format-spec -format-spec ::= +conversion-spec ::= '%' placement-type +placement-type ::= subentity-id | '?' | '%' +defaults-specs ::= ':' default-spec-list +default-spec-list ::= default-spec + default-spec-list default-spec +default-spec ::= subentity-id '[' format-spec ']' +subentity-id ::= 'N' | 'U' | 'D' +format-spec ::= ``` In the above grammar: @@ -556,12 +555,9 @@ In the above grammar: - '?' inserts an optional separator between the number and a unit based on the value of `space_before_unit_symbol` for this unit, - '%' just inserts '%' character. -- `subentity-replacement-field` token allows the composition of formatters. The following identifiers - are recognized by the quantity formatter: - - 'N' passes `format-spec` to the `formatter` specialization for the quantity representation - type, - - 'U' passes `format-spec` to the `formatter` specialization for the unit type, - - 'D' passes `format-spec` to the `formatter` specialization for the dimension type. +- `defaults-specs` token allows overwriting defaults for the underlying formatters with the custom + format string. Each override starts with a subentity identifier ('N', 'U', or 'D') followed by + the format string enclosed in square brackets. #### Default formatting @@ -573,7 +569,6 @@ This is why the following code lines produce the same output: std::cout << "Distance: " << 123 * km << "\n"; std::cout << std::format("Distance: {}\n", 123 * km); std::cout << std::format("Distance: {:%N%?%U}\n", 123 * km); -std::cout << std::format("Distance: {:{%N}%?{%U}}\n", 123 * km); ``` !!! note @@ -610,7 +605,7 @@ Thanks to the grammar provided above, the user can easily decide to either: - provide custom formatting for components: ```cpp - std::println("Speed: {:{%N:.2f} {%U:n}}", 100. * km / (3 * h)); + std::println("Speed: {::N[.2f]U[n]}", 100. * km / (3 * h)); ``` ```text @@ -630,25 +625,11 @@ Thanks to the grammar provided above, the user can easily decide to either: - dimension: LT⁻¹ ``` -!!! note - - The above grammar allows repeating the same field many times, possibly with a different - format spec. For example: - - ```cpp - std::println("Speed: {:%N {%N:.4f} {%N:.2f} {%U:n}}", 100. * km / (3 * h)); - ``` - - ```text - Speed: 33.333333333333336 33.3333 33.33 km h⁻¹ - ``` - - #### Formatting of the quantity numerical value The representation type used as a numerical value of a quantity must provide its own formatter specialization. It will be called by the quantity formatter with the format-spec provided -by the user in the `%N` replacement field. +by the user in the `N` defaults specification. In case we use C++ fundamental arithmetic types with our quantities the standard formatter specified in [format.string.std](https://wg21.link/format.string.std) will be used. The rest @@ -657,8 +638,8 @@ of this chapter assumes that it is the case and provides some usage examples. `sign` token allows us to specify how the value's sign is being printed: ```cpp -std::println("{0:%N %U},{0:{%N:+} %U},{0:{%N:-} %U},{0:{%N: } %U}", 1 * m); // 1 m,+1 m,1 m, 1 m -std::println("{0:%N %U},{0:{%N:+} %U},{0:{%N:-} %U},{0:{%N: } %U}", -1 * m); // -1 m,-1 m,-1 m,-1 m +std::println("{0},{0::N[+]},{0::N[-]},{0::N[ ]}", 1 * m); // 1 m,+1 m,1 m, 1 m +std::println("{0},{0::N[+]},{0::N[-]},{0::N[ ]}", -1 * m); // -1 m,-1 m,-1 m,-1 m ``` where: @@ -672,54 +653,54 @@ where: `precision` token is allowed only for floating-point representation types: ```cpp -std::println("{:{%N:.0} %U}", 1.2345 * m); // 1 m -std::println("{:{%N:.1} %U}", 1.2345 * m); // 1 m -std::println("{:{%N:.2} %U}", 1.2345 * m); // 1.2 m -std::println("{:{%N:.3} %U}", 1.2345 * m); // 1.23 m -std::println("{:{%N:.0f} %U}", 1.2345 * m); // 1 m -std::println("{:{%N:.1f} %U}", 1.2345 * m); // 1.2 m -std::println("{:{%N:.2f} %U}", 1.2345 * m); // 1.23 m +std::println("{::N[.0]}", 1.2345 * m); // 1 m +std::println("{::N[.1]}", 1.2345 * m); // 1 m +std::println("{::N[.2]}", 1.2345 * m); // 1.2 m +std::println("{::N[.3]}", 1.2345 * m); // 1.23 m +std::println("{::N[.0f]}", 1.2345 * m); // 1 m +std::println("{::N[.1f]}", 1.2345 * m); // 1.2 m +std::println("{::N[.2f]}", 1.2345 * m); // 1.23 m ``` `type` specifies how a value of the representation type is being printed. For integral types: ```cpp -std::println("{:{%N:b} %U}", 42 * m); // 101010 m -std::println("{:{%N:B} %U}", 42 * m); // 101010 m -std::println("{:{%N:d} %U}", 42 * m); // 42 m -std::println("{:{%N:o} %U}", 42 * m); // 52 m -std::println("{:{%N:x} %U}", 42 * m); // 2a m -std::println("{:{%N:X} %U}", 42 * m); // 2A m +std::println("{::N[b]}", 42 * m); // 101010 m +std::println("{::N[B]}", 42 * m); // 101010 m +std::println("{::N[d]}", 42 * m); // 42 m +std::println("{::N[o]}", 42 * m); // 52 m +std::println("{::N[x]}", 42 * m); // 2a m +std::println("{::N[X]}", 42 * m); // 2A m ``` The above can be printed in an alternate version thanks to the `#` token: ```cpp -std::println("{:{%N:#b} %U}", 42 * m); // 0b101010 m -std::println("{:{%N:#B} %U}", 42 * m); // 0B101010 m -std::println("{:{%N:#o} %U}", 42 * m); // 052 m -std::println("{:{%N:#x} %U}", 42 * m); // 0x2a m -std::println("{:{%N:#X} %U}", 42 * m); // 0X2A m +std::println("{::N[#b]}", 42 * m); // 0b101010 m +std::println("{::N[#B]}", 42 * m); // 0B101010 m +std::println("{::N[#o]}", 42 * m); // 052 m +std::println("{::N[#x]}", 42 * m); // 0x2a m +std::println("{::N[#X]}", 42 * m); // 0X2A m ``` For floating-point values, the `type` token works as follows: ```cpp -std::println("{:{%N:a} %U}", 1.2345678 * m); // 1.3c0ca2a5b1d5dp+0 m -std::println("{:{%N:.3a} %U}", 1.2345678 * m); // 1.3c1p+0 m -std::println("{:{%N:A} %U}", 1.2345678 * m); // 1.3C0CA2A5B1D5DP+0 m -std::println("{:{%N:.3A} %U}", 1.2345678 * m); // 1.3C1P+0 m -std::println("{:{%N:e} %U}", 1.2345678 * m); // 1.234568e+00 m -std::println("{:{%N:.3e} %U}", 1.2345678 * m); // 1.235e+00 m -std::println("{:{%N:E} %U}", 1.2345678 * m); // 1.234568E+00 m -std::println("{:{%N:.3E} %U}", 1.2345678 * m); // 1.235E+00 m -std::println("{:{%N:g} %U}", 1.2345678 * m); // 1.23457 m -std::println("{:{%N:g} %U}", 1.2345678e8 * m); // 1.23457e+08 m -std::println("{:{%N:.3g} %U}", 1.2345678 * m); // 1.23 m -std::println("{:{%N:.3g} %U}", 1.2345678e8 * m); // 1.23e+08 m -std::println("{:{%N:G} %U}", 1.2345678 * m); // 1.23457 m -std::println("{:{%N:G} %U}", 1.2345678e8 * m); // 1.23457E+08 m -std::println("{:{%N:.3G} %U}", 1.2345678 * m); // 1.23 m -std::println("{:{%N:.3G} %U}", 1.2345678e8 * m); // 1.23E+08 m +std::println("{::N[a]}", 1.2345678 * m); // 1.3c0ca2a5b1d5dp+0 m +std::println("{::N[.3a]}", 1.2345678 * m); // 1.3c1p+0 m +std::println("{::N[A]}", 1.2345678 * m); // 1.3C0CA2A5B1D5DP+0 m +std::println("{::N[.3A]}", 1.2345678 * m); // 1.3C1P+0 m +std::println("{::N[e]}", 1.2345678 * m); // 1.234568e+00 m +std::println("{::N[.3e]}", 1.2345678 * m); // 1.235e+00 m +std::println("{::N[E]}", 1.2345678 * m); // 1.234568E+00 m +std::println("{::N[.3E]}", 1.2345678 * m); // 1.235E+00 m +std::println("{::N[g]}", 1.2345678 * m); // 1.23457 m +std::println("{::N[g]}", 1.2345678e8 * m); // 1.23457e+08 m +std::println("{::N[.3g]}", 1.2345678 * m); // 1.23 m +std::println("{::N[.3g]}", 1.2345678e8 * m); // 1.23e+08 m +std::println("{::N[G]}", 1.2345678 * m); // 1.23457 m +std::println("{::N[G]}", 1.2345678e8 * m); // 1.23457E+08 m +std::println("{::N[.3G]}", 1.2345678 * m); // 1.23 m +std::println("{::N[.3G]}", 1.2345678e8 * m); // 1.23E+08 m ``` From 17de6bd8726818705928f1627dfb9140f98a72e8 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 19 Apr 2024 14:38:11 +0100 Subject: [PATCH 7/8] fix: clang compilation fixed workarounded with the CTAD for `quantity_formatter` --- src/core/include/mp-units/format.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 18b1e5c85..14e2c7281 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -325,6 +325,8 @@ class MP_UNITS_STD_FMT::formatter, Char> { } void on_text(const Char* begin, const Char* end) const { std::copy(begin, end, out); } }; + template + quantity_formatter(const formatter&, OutputIt, Args...) -> quantity_formatter; template constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const From 72491443fceb4651de48aaeb2d6073cb4b5a9678 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 19 Apr 2024 15:30:42 +0100 Subject: [PATCH 8/8] fix: use `std::formattable` only when fmtlib is not used --- src/core/include/mp-units/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index 14e2c7281..061f676ce 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -265,7 +265,7 @@ class MP_UNITS_STD_FMT::formatter { // subentity-id ::= 'N' | 'U' | 'D' // format-spec ::= // -#if __cpp_lib_format_ranges +#if __cpp_lib_format_ranges && !MP_UNITS_USE_FMTLIB template Rep> #else template