diff --git a/docs/users_guide/framework_basics/text_output.md b/docs/users_guide/framework_basics/text_output.md index 1439f17e..d7899668 100644 --- a/docs/users_guide/framework_basics/text_output.md +++ b/docs/users_guide/framework_basics/text_output.md @@ -13,7 +13,36 @@ any quantity in the most user-friendly way. a much better solution, but the library does not have enough information to print it that way by itself. -## Output Streams +## Customization point + +The [SI Brochure](../appendix/references.md#SIBrochure) says: + +!!! quote "SI Brochure" + + The numerical value always precedes the unit and a space is always used to separate the unit from + the number. ... The only exceptions to this rule are for the unit symbols for degree, minute and + second for plane angle, `°`, `′` and `″`, respectively, for which no space is left between the + numerical value and the unit symbol. + +To support the above, the library exposes `space_before_unit_symbol` customization point. By default, +its value is `true` for all the units, so the space between a number and a unit will be present in the +output text. To change this behavior, we have to provide a partial specialization for a specific unit: + +```cpp +template<> +inline constexpr bool space_before_unit_symbol = false; +``` + +!!! note + + The above works only for [the default formatting](#default-formatting). In case we provide our own + format specification (i.e. `std::format("{:%Q %q}", q)`), the library will always obey this + specification for all the units (no matter of what is the actual value of the + `space_before_unit_symbol` customization point) and the separating space will always be present + in this case. + + +## Output streams !!! tip diff --git a/src/core-fmt/include/mp-units/format.h b/src/core-fmt/include/mp-units/format.h index 81257f4a..4b0a3ec4 100644 --- a/src/core-fmt/include/mp-units/format.h +++ b/src/core-fmt/include/mp-units/format.h @@ -398,7 +398,7 @@ private: // default format should print value followed by the unit separated with 1 space out = mp_units::detail::format_units_quantity_value(out, q.numerical_value(), specs.rep, ctx.locale()); if constexpr (mp_units::detail::has_unit_symbol(get_unit(Reference))) { - *out++ = CharT(' '); + if constexpr (mp_units::space_before_unit_symbol) *out++ = CharT(' '); out = unit_symbol_to(out, get_unit(Reference)); } } else { diff --git a/src/core-io/include/mp-units/ostream.h b/src/core-io/include/mp-units/ostream.h index ae09f23a..5fc8489c 100644 --- a/src/core-io/include/mp-units/ostream.h +++ b/src/core-io/include/mp-units/ostream.h @@ -40,7 +40,7 @@ void to_stream(std::basic_ostream& os, const quantity& q) else os << q.numerical_value(); if constexpr (has_unit_symbol(get_unit(R))) { - os << " "; + if constexpr (space_before_unit_symbol) os << " "; unit_symbol_to(std::ostream_iterator(os), get_unit(R)); } } diff --git a/src/core/include/mp-units/unit.h b/src/core/include/mp-units/unit.h index 7b54c261..98adfa06 100644 --- a/src/core/include/mp-units/unit.h +++ b/src/core/include/mp-units/unit.h @@ -807,6 +807,15 @@ constexpr Out unit_symbol_impl(Out out, const derived_unit&, unit_symbo } // namespace detail +/** + * @brief Puts a space ' ' sign before a unit symbol + * + * Quantities of some units (e.g. degree, arcminute, arcsecond) should not be printed with the + * space between a number and a unit. For those a partial specialization with the value `false` should + * be provided. + */ +template +inline constexpr bool space_before_unit_symbol = true; template Out, Unit U> constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{}) diff --git a/src/systems/si/include/mp-units/systems/si/units.h b/src/systems/si/include/mp-units/systems/si/units.h index 1303c796..0800907e 100644 --- a/src/systems/si/include/mp-units/systems/si/units.h +++ b/src/systems/si/include/mp-units/systems/si/units.h @@ -123,4 +123,11 @@ inline constexpr bool unit_can_be_prefixed = false; template<> inline constexpr bool unit_can_be_prefixed = false; +template<> +inline constexpr bool space_before_unit_symbol = false; +template<> +inline constexpr bool space_before_unit_symbol = false; +template<> +inline constexpr bool space_before_unit_symbol = false; + } // namespace mp_units diff --git a/test/unit_test/runtime/fmt_test.cpp b/test/unit_test/runtime/fmt_test.cpp index 9bdce188..600d491b 100644 --- a/test/unit_test/runtime/fmt_test.cpp +++ b/test/unit_test/runtime/fmt_test.cpp @@ -236,6 +236,45 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") } } + SECTION("no space before unit symbol") + { + SECTION("degree") + { + const auto q = 42 * deg; + os << q; + + SECTION("iostream") { CHECK(os.str() == "42°"); } + + SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } + + SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 °"); } + } + + SECTION("arcminute") + { + const auto q = 42 * arcmin; + os << q; + + SECTION("iostream") { CHECK(os.str() == "42′"); } + + SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } + + SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 ′"); } + } + + SECTION("arcsecond") + { + const auto q = 42 * arcsec; + os << q; + + SECTION("iostream") { CHECK(os.str() == "42″"); } + + SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } + + SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 ″"); } + } + } + SECTION("8-bit integers") { SECTION("signed positive")