diff --git a/src/include/units/bits/format_utils.h b/src/include/units/bits/format_utils.h deleted file mode 100644 index eb407530..00000000 --- a/src/include/units/bits/format_utils.h +++ /dev/null @@ -1,155 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018 Mateusz Pusz -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#pragma once - -#include -#include -#include -#include - -namespace units { - - namespace detail { - - template - void print_ratio(std::basic_ostream& os) - { - if constexpr(Ratio::num != 1 || Ratio::den != 1) { - if constexpr(Ratio::den == 1) { - os << "[" << Ratio::num << "]"; - } - else { - os << "[" << Ratio::num << "/" << Ratio::den << "]"; - } - } - } - - template - void print_prefix_or_ratio(std::basic_ostream& os) - { - if constexpr(Ratio::num != 1 || Ratio::den != 1) { - if(!std::same_as) { - using prefix = downcast>; - - if constexpr(!std::same_as>) { - // print as a prefixed unit - os << prefix::symbol; - return; - } - } - // print as a ratio of the coherent unit - print_ratio(os); - } - } - - - template - requires (0 <= Value) && (Value < 10) - inline constexpr basic_fixed_string superscript_number = "\u2070"; - -// template<> inline constexpr basic_fixed_string superscript_number<0> = "\u2070"; - template<> inline constexpr basic_fixed_string superscript_number<1> = "\u00b9"; - template<> inline constexpr basic_fixed_string superscript_number<2> = "\u00b2"; - template<> inline constexpr basic_fixed_string superscript_number<3> = "\u00b3"; - template<> inline constexpr basic_fixed_string superscript_number<4> = "\u2074"; - template<> inline constexpr basic_fixed_string superscript_number<5> = "\u2075"; - template<> inline constexpr basic_fixed_string superscript_number<6> = "\u2076"; - template<> inline constexpr basic_fixed_string superscript_number<7> = "\u2077"; - template<> inline constexpr basic_fixed_string superscript_number<8> = "\u2078"; - template<> inline constexpr basic_fixed_string superscript_number<9> = "\u2079"; - - template - requires (Value >= 0) - constexpr auto superscript() - { - if constexpr(Value < 10) - return superscript_number; - else - return superscript() + superscript(); - } - - template - requires (Value >= 0) - constexpr auto regular() - { - if constexpr(Value < 10) - return basic_fixed_string(static_cast('0' + Value)); - else - return regular() + regular(); - } - - - template - constexpr auto operator_txt() - { - if constexpr(Idx == 0) { - if constexpr(Divide) { - return basic_fixed_string("1/"); - } - else { - return basic_fixed_string(""); - } - } - else { - if constexpr(Divide) { - return basic_fixed_string("/"); - } - else { - return basic_fixed_string("⋅"); - } - } - } - - template - constexpr auto exp_txt() - { - // get calculation operator + symbol - const auto txt = operator_txt() + E::dimension::symbol; - if constexpr(E::den != 1) { - // add root part - return txt + basic_fixed_string("^(") + regular() + basic_fixed_string("/") + regular() + basic_fixed_string(")"); - } - else if constexpr(abs(E::num) != 1) { - // add exponent part - return txt + superscript(); - } - else { - return txt; - } - } - - template - constexpr auto symbol_text_impl(dimension, std::index_sequence) - { - return (exp_txt() + ...); - } - - template - constexpr auto symbol_text(dimension d) - { - return symbol_text_impl<>(d, std::index_sequence_for()); - } - - } - -} // namespace units diff --git a/src/include/units/format.h b/src/include/units/format.h index c38f2fed..d12cdb1a 100644 --- a/src/include/units/format.h +++ b/src/include/units/format.h @@ -25,15 +25,60 @@ #include #include +// units-format-spec: +// fill-and-align[opt] width[opt] precision[opt] units-specs[opt] +// units-specs: +// conversion-spec +// units-specs conversion-spec +// units-specs literal-char +// literal-char: +// any character other than { or } +// conversion-spec: +// % modifier[opt] type +// modifier: one of +// E O +// type: one of +// q Q % + template struct fmt::formatter, CharT> { - template - constexpr auto parse(ParseContext& ctx) { return ctx.begin(); } +private: + fmt::basic_format_specs specs; + int precision = -1; + using arg_ref_type = fmt::internal::arg_ref; + arg_ref_type width_ref; + arg_ref_type precision_ref; + mutable basic_string_view format_str; + using quantity = units::quantity; + + // auto parse_unit_format() { + // if (s != ctx.end() && *s == 'q') { + // quantity = true; + // return ++s; + // } + // } + +public: + constexpr auto parse(fmt::basic_parse_context& ctx) + { + // [ctx.begin(), ctx.end()) is a range of CharTs containing format-specs, + // e.g. in format("{:%Q %q}", ...) it is "%Q %q}" (format string after ':') + // auto begin = ctx.begin(), end = ctx.end(); + // Look at do_parse in fmt/chrono.h and provide replacement for parse_chrono_format. + // fill-and-align_opt ... + // begin = fmt::internal::parse_align(begin, end, handler); + // parse_unit_format(); + return ctx.end(); + } + // format("{:{}}", 'x', 10) template auto format(const units::quantity& q, FormatContext& ctx) { - return format_to(ctx.out(), "{:.1f}", q.count()); + // ctx.out() - output iterator you write to. + // auto s = format("{0:.{1}} {2}", q.count(), precision, unit(q)); + // return format_to(ctx.out(), "{:{}}", s, width); + return format_to(ctx.out(), "{} {}", q.count(), units::detail::unit_text().c_str()); } }; diff --git a/src/include/units/quantity.h b/src/include/units/quantity.h index 855a3694..f8ab68dc 100644 --- a/src/include/units/quantity.h +++ b/src/include/units/quantity.h @@ -23,7 +23,6 @@ #pragma once #include -#include #include #include #include @@ -283,29 +282,7 @@ namespace units { template friend std::basic_ostream& operator<<(std::basic_ostream& os, const quantity& q) { - os << q.count() << " "; - if constexpr(!detail::is_unit) { - // print user-defined unit - os << unit::symbol; - } - else { - using ratio = quantity::unit::ratio; - using dim = quantity::unit::dimension; - if constexpr(!detail::is_dimension) { - // print as a prefix or ratio of a coherent unit symbol defined by the user - using coherent_unit = downcast>>; - detail::print_prefix_or_ratio(os); - os << coherent_unit::symbol; - } - else { - // print as a ratio of a coherent unit - detail::print_ratio(os); - - // print coherent unit dimensions and their exponents - os << detail::symbol_text(dim{}); - } - } - return os; + return os << q.count() << " " << detail::unit_text(); } }; diff --git a/src/include/units/unit.h b/src/include/units/unit.h index 98740204..e6c6e29b 100644 --- a/src/include/units/unit.h +++ b/src/include/units/unit.h @@ -22,7 +22,6 @@ #pragma once -#include #include #include #include @@ -115,9 +114,164 @@ namespace units { } - // derived_unit + namespace detail { - struct no_prefix; + template + requires (0 <= Value) && (Value < 10) + inline constexpr basic_fixed_string superscript_number = "\u2070"; + +// template<> inline constexpr basic_fixed_string superscript_number<0> = "\u2070"; + template<> inline constexpr basic_fixed_string superscript_number<1> = "\u00b9"; + template<> inline constexpr basic_fixed_string superscript_number<2> = "\u00b2"; + template<> inline constexpr basic_fixed_string superscript_number<3> = "\u00b3"; + template<> inline constexpr basic_fixed_string superscript_number<4> = "\u2074"; + template<> inline constexpr basic_fixed_string superscript_number<5> = "\u2075"; + template<> inline constexpr basic_fixed_string superscript_number<6> = "\u2076"; + template<> inline constexpr basic_fixed_string superscript_number<7> = "\u2077"; + template<> inline constexpr basic_fixed_string superscript_number<8> = "\u2078"; + template<> inline constexpr basic_fixed_string superscript_number<9> = "\u2079"; + + template + requires (Value >= 0) + constexpr auto superscript() + { + if constexpr(Value < 10) + return superscript_number; + else + return superscript() + superscript(); + } + + template + requires (Value >= 0) + constexpr auto regular() + { + if constexpr(Value < 10) + return basic_fixed_string(static_cast('0' + Value)); + else + return regular() + regular(); + } + + + template + constexpr auto ratio_text() + { + if constexpr(Ratio::num != 1 || Ratio::den != 1) { + auto txt = basic_fixed_string("[") + regular(); + if constexpr(Ratio::den == 1) { + return txt + basic_fixed_string("]"); + } + else { + return txt + basic_fixed_string("/") + regular() + basic_fixed_string("]"); + } + } + else { + return basic_fixed_string(""); + } + } + + template + constexpr auto prefix_or_ratio_text() + { + if constexpr(Ratio::num != 1 || Ratio::den != 1) { + if constexpr (!std::same_as) { + using prefix = downcast>; + + if constexpr(!std::same_as>) { + // print as a prefixed unit + return prefix::symbol; + } + else { + // print as a ratio of the coherent unit + return ratio_text(); + } + } + else { + // print as a ratio of the coherent unit + return ratio_text(); + } + } + } + + + template + constexpr auto operator_text() + { + if constexpr(Idx == 0) { + if constexpr(Divide) { + return basic_fixed_string("1/"); + } + else { + return basic_fixed_string(""); + } + } + else { + if constexpr(Divide) { + return basic_fixed_string("/"); + } + else { + return basic_fixed_string("⋅"); + } + } + } + + template + constexpr auto exp_text() + { + // get calculation operator + symbol + const auto txt = operator_text() + E::dimension::symbol; + if constexpr(E::den != 1) { + // add root part + return txt + basic_fixed_string("^(") + regular() + basic_fixed_string("/") + regular() + basic_fixed_string(")"); + } + else if constexpr(abs(E::num) != 1) { + // add exponent part + return txt + superscript(); + } + else { + return txt; + } + } + + template + constexpr auto symbol_text_impl(dimension, std::index_sequence) + { + return (exp_text() + ...); + } + + template + constexpr auto symbol_text(dimension d) + { + return symbol_text_impl<>(d, std::index_sequence_for()); + } + + template + constexpr auto unit_text() + { + if constexpr(!is_unit) { + // Unit is a downcasted derived unit child class already so just print defined symbol immediately + return Unit::symbol; + } + else { + // we are dealing with a non-user-defined unit here + using ratio = Unit::ratio; + using dim = Unit::dimension; + if constexpr(!is_dimension) { + // downcasted user-defined dimension + // print as a prefix or ratio of a coherent unit symbol defined by the user + using coherent_unit = downcast>>; + return prefix_or_ratio_text() + coherent_unit::symbol; + } + else { + // print as a ratio of a coherent unit + coherent unit dimensions and their exponents + return ratio_text() + symbol_text(dim{}); + } + } + } + + } // namespace detail + + + // derived_unit template struct named_coherent_derived_unit : downcast_child>> { @@ -138,7 +292,7 @@ namespace units { template struct derived_unit : downcast_child> { - static constexpr auto symbol = "aaa"; + static constexpr auto symbol = basic_fixed_string("aaa"); }; template @@ -155,7 +309,7 @@ namespace units { template struct deduced_derived_unit : downcast_child> { - static constexpr auto symbol = "bbb"; + static constexpr auto symbol = basic_fixed_string("bbb"); }; } // namespace units diff --git a/test/unit_test/runtime/text_test.cpp b/test/unit_test/runtime/text_test.cpp index 51f7501e..af522a8b 100644 --- a/test/unit_test/runtime/text_test.cpp +++ b/test/unit_test/runtime/text_test.cpp @@ -32,7 +32,7 @@ using namespace units; -TEST_CASE("operator<< on a quantity", "[text][ostream]") +TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") { std::stringstream stream; @@ -40,14 +40,44 @@ TEST_CASE("operator<< on a quantity", "[text][ostream]") { SECTION("integral representation") { - stream << 60W; - REQUIRE(stream.str() == "60 W"); + const auto q = 60W; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "60 W"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("floating-point representation") { - stream << 1023.5Pa; - REQUIRE(stream.str() == "1023.5 Pa"); + const auto q = 1023.5Pa; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "1023.5 Pa"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } } @@ -55,14 +85,44 @@ TEST_CASE("operator<< on a quantity", "[text][ostream]") { SECTION("in terms of base units") { - stream << 125us; - REQUIRE(stream.str() == "125 µs"); + const auto q = 125us; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "125 µs"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("in terms of derived units") { - stream << 60kJ; - REQUIRE(stream.str() == "60 kJ"); + const auto q = 60kJ; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "60 kJ"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } } @@ -72,14 +132,44 @@ TEST_CASE("operator<< on a quantity", "[text][ostream]") { SECTION("acceleration") { - stream << 20.m / 2s / 1s; - REQUIRE(stream.str() == "10 m/s²"); + const auto q = 20.m / 2s / 1s; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "10 m/s²"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("volume") { - stream << 2m * 1m * 1m; - REQUIRE(stream.str() == "2 m³"); + const auto q = 2m * 1m * 1m; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "2 m³"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } } @@ -87,14 +177,44 @@ TEST_CASE("operator<< on a quantity", "[text][ostream]") { SECTION("velocity") { - stream << 20.km / 2h; - REQUIRE(stream.str() == "10 km/h"); + const auto q = 20.km / 2h; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "10 km/h"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("surface tension") { - stream << 20.N / 2m; - REQUIRE(stream.str() == "10 N/m"); + const auto q = 20.N / 2m; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "10 N/m"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } } } @@ -103,32 +223,107 @@ TEST_CASE("operator<< on a quantity", "[text][ostream]") { SECTION("unit::ratio as an SI prefix for a dimension with a special symbol") { - stream << 4.N * 2cm; - REQUIRE(stream.str() == "8 cJ"); + const auto q = 4.N * 2cm; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "8 cJ"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio for a dimension without a special symbol") { - stream << 2.cm * 2m * 2m; - REQUIRE(stream.str() == "8 [1/100]m³"); + const auto q = 2.cm * 2m * 2m; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "8 [1/100]m³"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio::num != 1 && unit::ratio::den == 1") { - stream << 4 * 2min / (2s * 2s); - REQUIRE(stream.str() == "2 [60]Hz"); + const auto q = 4 * 2min / (2s * 2s); + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "2 [60]Hz"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio::num == 1 && unit::ratio::den != 1") { - stream << 20._J / 2min; - REQUIRE(stream.str() == "10 [1/60]W"); + const auto q = 20._J / 2min; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "10 [1/60]W"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio::num != 1 && unit::ratio::den != 1") { - stream << 60.kJ / 2min; - REQUIRE(stream.str() == "30 [50/3]W"); + const auto q = 60.kJ / 2min; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "30 [50/3]W"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } } @@ -136,215 +331,233 @@ TEST_CASE("operator<< on a quantity", "[text][ostream]") { SECTION("unit::ratio::num == 1 && unit::ratio::den == 1") { - stream << 2s * 2m * 2kg; - REQUIRE(stream.str() == "8 m⋅kg⋅s"); + const auto q = 2s * 2m * 2kg; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "8 m⋅kg⋅s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio as an SI prefix") { - stream << 4km * 2s; - REQUIRE(stream.str() == "8 [1000]m⋅s"); + const auto q = 4km * 2s; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "8 [1000]m⋅s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio::num != 1 && unit::ratio::den == 1") { - stream << 4kg * 2min / (2s * 2s); - REQUIRE(stream.str() == "2 [60]kg/s"); + const auto q = 4kg * 2min / (2s * 2s); + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "2 [60]kg/s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio::num == 1 && unit::ratio::den != 1") { - stream << 20.kg / 2min; - REQUIRE(stream.str() == "10 [1/60]kg/s"); + const auto q = 20.kg / 2min; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "10 [1/60]kg/s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("unit::ratio::num != 1 && unit::ratio::den != 1") { - stream << 60.min / 2km; - REQUIRE(stream.str() == "30 [3/50]1/m⋅s"); + const auto q = 60.min / 2km; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "30 [3/50]1/m⋅s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("exp::num == 1 && exp::den == 1") { - stream << 4m * 2s; - REQUIRE(stream.str() == "8 m⋅s"); + const auto q = 4m * 2s; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "8 m⋅s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("exp::num == 2 && exp::den == 1 for positive exponent") { - stream << 4m * 2s * 2s; - REQUIRE(stream.str() == "16 m⋅s²"); + const auto q = 4m * 2s * 2s; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "16 m⋅s²"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("exp::num == 2 && exp::den == 1 for negative exponent (first dimension)") { - stream << 8.s / 2m / 2m; - REQUIRE(stream.str() == "2 1/m²⋅s"); + const auto q = 8.s / 2m / 2m; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "2 1/m²⋅s"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("exp::num == 2 && exp::den == 1 for negative exponent (not first dimension)") { - stream << 8.m / 2kg / 2kg; - REQUIRE(stream.str() == "2 m/kg²"); + const auto q = 8.m / 2kg / 2kg; + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "2 m/kg²"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("fractional positive exponent") { - stream << sqrt(9.m); - REQUIRE(stream.str() == "3 m^(1/2)"); + const auto q = sqrt(9.m); + stream << q; + + SECTION("iostream") + { + REQUIRE(stream.str() == "3 m^(1/2)"); + } + + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } + + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } SECTION("fractional negative exponent") { - stream << sqrt(9 / 1.m); - REQUIRE(stream.str() == "3 1/m^(1/2)"); - } - } -} - -TEST_CASE("fmt with default format {} on a quantity", "[text][fmt]") -{ - std::stringstream stream; - - SECTION("quantity with a predefined unit") - { - SECTION("integral representation") - { - constexpr auto q = 60W; + const auto q = sqrt(9 / 1.m); stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - SECTION("floating-point representation") - { - constexpr auto q = 72.5kJ; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } + SECTION("iostream") + { + REQUIRE(stream.str() == "3 1/m^(1/2)"); + } - SECTION("unit with a prefix") - { - constexpr auto q = 125us; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - } + SECTION("fmt with default format {} on a quantity") + { + REQUIRE(fmt::format("{}", q) == stream.str()); + } - SECTION("quantity with a predefined dimension but unknown unit") - { - SECTION("unit::ratio as an SI prefix for a dimension with a special symbol") - { - constexpr auto q = 4.N * 2cm; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio for a dimension without a special symbol") - { - constexpr auto q = 2.cm * 2m * 2m; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio::num != 1 && unit::ratio::den == 1") - { - constexpr auto q = 4 * 2min / (2s * 2s); - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio::num == 1 && unit::ratio::den != 1") - { - constexpr auto q = 20._J / 2min; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio::num != 1 && unit::ratio::den != 1") - { - constexpr auto q = 60.kJ / 2min; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - } - - SECTION("quantity with an unkown dimension") - { - SECTION("unit::ratio::num == 1 && unit::ratio::den == 1") - { - constexpr auto q = 2s * 2m * 2kg; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio as an SI prefix") - { - constexpr auto q = 4km * 2s; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio::num != 1 && unit::ratio::den == 1") - { - constexpr auto q = 4kg * 2min / (2s * 2s); - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio::num == 1 && unit::ratio::den != 1") - { - constexpr auto q = 20.kg / 2min; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("unit::ratio::num != 1 && unit::ratio::den != 1") - { - constexpr auto q = 60.min / 2km; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("exp::num == 1 && exp::den == 1") - { - constexpr auto q = 4m * 2s; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("exp::num == 2 && exp::den == 1 for positive exponent") - { - constexpr auto q = 4m * 2s * 2s; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("exp::num == 2 && exp::den == 1 for negative exponent (first dimension)") - { - constexpr auto q = 8.s / 2m / 2m; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("exp::num == 2 && exp::den == 1 for negative exponent (not first dimension)") - { - constexpr auto q = 8.m / 2kg / 2kg; - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("fractional positive exponent") - { - auto q = sqrt(9.m); - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); - } - - SECTION("fractional negative exponent") - { - auto q = sqrt(9 / 1.m); - stream << q; - REQUIRE(fmt::format("{}", q) == stream.str()); + SECTION("fmt with format {:%Q %q} on a quantity") + { + REQUIRE(fmt::format("{:%Q %q}", q) == stream.str()); + } } } } @@ -359,4 +572,3 @@ TEST_CASE("fmt with default format {} on a quantity", "[text][fmt]") // contains a precision specification. // string s = format("{:=>8}", 42ms); // value of s is "====42ms" -