diff --git a/docs/users_guide/framework_basics/text_output.md b/docs/users_guide/framework_basics/text_output.md index d2996eb4..40f1a630 100644 --- a/docs/users_guide/framework_basics/text_output.md +++ b/docs/users_guide/framework_basics/text_output.md @@ -1,36 +1,199 @@ # Text Output -Besides providing dimensional analysis and units conversions, the library also tries hard to print -any quantity in the most user-friendly way. +Besides providing dimensional analysis and unit conversions, the library also tries hard to print +any quantity in the most user-friendly way. We can print the entire quantity or its +selected parts (numerical value, unit, or dimension). !!! note - The library does not provide a text output for quantity points, as printing just a number and - a unit is not enough to adequately describe a quantity point. Often, an additional postfix is - required. + The library does not provide a text output for quantity points. The quantity stored inside + is just an implementation detail of this type. It is a vector from a specific origin. + Without the knowledge of the origin, the vector by itself is useless as we can't determine + which point it describes. - For example, the text output of `42 m` may mean many things and can also be confused with an - output of a regular quantity. On the other hand, printing `42 m AMSL` for altitudes above mean - sea level is a much better solution, but the library does not have enough information to print - it that way by itself. + In the current library design, point origin does not provide any text in its definition. + Even if we could add such information to the point's definition, we would not + know how to output it in the text. There may be many ways to do it. For example, should we + prepend or append the origin part to the quantity text? + + For example, the text output of `42 m` for a quantity point may mean many things. It may be + an offset from the mountain top, sea level, or maybe the center of Mars. + Printing `42 m AMSL` for altitudes above mean sea level is a much better solution, but the + library does not have enough information to print it that way by itself. + + Please let us know if you have a good idea of how to solve this issue. -## Derived unit symbols generation +## Predefined symbols -The library creates symbols for derived ones based on the provided definitions for base units. +The definitions of dimensions, units, prefixes, and constants require assigning text symbols +for each entity. Those symbols will be composed by the library's framework to express dimensions +and units of derived quantities. -### `unit_symbol_formatting` +=== "Dimensions" + + ```cpp + inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; + inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; + inline constexpr struct dim_time : base_dimension<"T"> {} dim_time; + inline constexpr struct dim_electric_current : base_dimension<"I"> {} dim_electric_current; + inline constexpr struct dim_thermodynamic_temperature : base_dimension<{u8"Θ", "O"}> {} dim_thermodynamic_temperature; + inline constexpr struct dim_amount_of_substance : base_dimension<"N"> {} dim_amount_of_substance; + inline constexpr struct dim_luminous_intensity : base_dimension<"J"> {} dim_luminous_intensity; + ``` + +=== "Units" + + ```cpp + inline constexpr struct second : named_unit<"s", kind_of> {} second; + inline constexpr struct metre : named_unit<"m", kind_of> {} metre; + inline constexpr struct gram : named_unit<"g", kind_of> {} gram; + inline constexpr struct kilogram : decltype(kilo) {} kilogram; + + inline constexpr struct newton : named_unit<"N", kilogram * metre / square(second)> {} newton; + inline constexpr struct joule : named_unit<"J", newton * metre> {} joule; + inline constexpr struct watt : named_unit<"W", joule / second> {} watt; + inline constexpr struct coulomb : named_unit<"C", ampere * second> {} coulomb; + inline constexpr struct volt : named_unit<"V", watt / ampere> {} volt; + inline constexpr struct farad : named_unit<"F", coulomb / volt> {} farad; + inline constexpr struct ohm : named_unit<{u8"Ω", "ohm"}, volt / ampere> {} ohm; + ``` + +=== "Prefixes" + + ```cpp + template struct micro_ : prefixed_unit<{u8"µ", "u"}, mag_power<10, -6>, U> {}; + template struct milli_ : prefixed_unit<"m", mag_power<10, -3>, U> {}; + template struct centi_ : prefixed_unit<"c", mag_power<10, -2>, U> {}; + template struct deci_ : prefixed_unit<"d", mag_power<10, -1>, U> {}; + template struct deca_ : prefixed_unit<"da", mag_power<10, 1>, U> {}; + template struct hecto_ : prefixed_unit<"h", mag_power<10, 2>, U> {}; + template struct kilo_ : prefixed_unit<"k", mag_power<10, 3>, U> {}; + template struct mega_ : prefixed_unit<"M", mag_power<10, 6>, U> {}; + ``` + +=== "Constants" + + ```cpp + inline constexpr struct hyperfine_structure_transition_frequency_of_cs : named_unit<{u8"Δν_Cs", "dv_Cs"}, mag<9'192'631'770> * hertz> {} hyperfine_structure_transition_frequency_of_cs; + inline constexpr struct speed_of_light_in_vacuum : named_unit<"c", mag<299'792'458> * metre / second> {} speed_of_light_in_vacuum; + inline constexpr struct planck_constant : named_unit<"h", mag * mag_power<10, -34> * joule * second> {} planck_constant; + inline constexpr struct elementary_charge : named_unit<"e", mag * mag_power<10, -19> * coulomb> {} elementary_charge; + inline constexpr struct boltzmann_constant : named_unit<"k", mag * mag_power<10, -23> * joule / kelvin> {} boltzmann_constant; + inline constexpr struct avogadro_constant : named_unit<"N_A", mag * mag_power<10, 23> / mole> {} avogadro_constant; + inline constexpr struct luminous_efficacy : named_unit<"K_cd", mag<683> * lumen / watt> {} luminous_efficacy; + ``` + +!!! important + + Two symbols always have to be provided if the primary symbol contains characters outside of + the [basic literal character set](https://en.cppreference.com/w/cpp/language/charset). + The first must be provided as a UTF-8 literal and may contain any Unicode characters. + The second one must provide an alternative spelling and only use characters from within of + [basic literal character set](https://en.cppreference.com/w/cpp/language/charset). + +!!! note + + Unicode provides only a minimal set of characters available as subscripts, which are often used + to differentiate various constants and quantities of the same kind. To workaround this issue, + **mp-units** uses the '_' character to specify that the following characters should be considered + a subscript of the symbol. + +!!! tip + + For older compilers, it might be required to specify a `basic_symbol_text` class explicitly + template name to initialize it with two symbols: + + ```cpp + inline constexpr struct ohm : named_unit {} ohm; + ``` + + +## Symbols for derived entities + +### `text_encoding` + +[ISQ](../../appendix/glossary.md#isq) and [SI](../../appendix/glossary.md#si) standards always +specify symbols using Unicode encoding. This is why it is a default and primary target for +text output. However, in some applications or environments, a standard ASCII-like text output +using only the characters from the [basic literal character set](https://en.cppreference.com/w/cpp/language/charset) +can be preferred by users. + +This is why the library provides an option to change the default encoding to the ASCII one with: + +```cpp +enum class text_encoding : std::int8_t { + unicode, // µs; m³; L²MT⁻³ + ascii, // us; m^3; L^2MT^-3 + default_encoding = unicode +}; +``` + +### Symbols of derived dimensions + +#### `dimension_symbol_formatting` + +`dimension_symbol_formatting` is a data type describing the configuration of the symbol generation +algorithm. + +```cpp +struct dimension_symbol_formatting { + text_encoding encoding = text_encoding::default_encoding; +}; +``` + +#### `dimension_symbol()` + +Returns a `std::string_view` with the symbol of a dimension for the provided configuration: + +```cpp +template +[[nodiscard]] consteval std::string_view dimension_symbol(D); +``` + +For example: + +```cpp +static_assert(dimension_symbol<{.encoding = text_encoding::ascii}>(isq::power.dimension) == "L^2MT^-3"); +``` + +!!! note + + `std::string_view` is returned only when C++23 is available. Otherwise, an instance of a + `basic_fixed_string` is being returned. + +#### `dimension_symbol_to()` + +Inserts the generated dimension symbol into the output text iterator at runtime. + +```cpp +template Out, Dimension D> +constexpr Out dimension_symbol_to(Out out, D d, dimension_symbol_formatting fmt = dimension_symbol_formatting{}); +``` + +For example: + +```cpp +std::string txt; +dimension_symbol_to(std::back_inserter(txt), isq::power.dimension, {.encoding = text_encoding::ascii}); +std::cout << txt << "\n"; +``` + +The above prints: + +```text +L^2MT^-3 +``` + + +### Symbols of derived units + +#### `unit_symbol_formatting` `unit_symbol_formatting` is a data type describing the configuration of the symbol generation algorithm. It contains three orthogonal fields, each with a default value. ```cpp -enum class text_encoding : std::int8_t { - unicode, // m³; µs - ascii, // m^3; us - default_encoding = unicode -}; - enum class unit_symbol_solidus : std::int8_t { one_denominator, // m/s; kg m⁻¹ s⁻¹ always, // m/s; kg/(m s) @@ -51,13 +214,20 @@ struct unit_symbol_formatting { }; ``` -### `unit_symbol()` +`unit_symbol_solidus` impacts how the division of unit symbols is being presented in the text +output. By default, the '/' will be printed if only one unit component is in the +denominator. Otherwise, the exponent syntax will be used. + +`unit_symbol_separator` specifies how multiple multiplied units should be separated from each +other. By default, the space (' ') will be used as a separator. + +#### `unit_symbol()` Returns a `std::string_view` with the symbol of a unit for the provided configuration: ```cpp template -[[nodiscard]] consteval auto unit_symbol(U); +[[nodiscard]] consteval std::string_view unit_symbol(U); ``` For example: @@ -70,12 +240,13 @@ static_assert(unit_symbol<{.solidus = unit_symbol_solidus::never, !!! note `std::string_view` is returned only when C++23 is available. Otherwise, an instance of a - `basic_fixed_string` is being returned. + `basic_fixed_string` is being returned. See more in the + [C++ compiler support](../../getting_started/installation_and_usage.md#static-constexpr-variables-in-constexpr-functions) + chapter. -### `unit_symbol_to()` +#### `unit_symbol_to()` -Inserts the generated unit symbol to the output text iterator at runtime based on the provided -configuration. +Inserts the generated unit symbol into the output text iterator at runtime. ```cpp template Out, Unit U> @@ -97,9 +268,7 @@ The above prints: kg⋅m⋅s⁻² ``` -## Quantity text output - -### Customization point +## `space_before_unit_symbol` customization point The [SI Brochure](../../appendix/references.md#SIBrochure) says: @@ -110,9 +279,12 @@ The [SI Brochure](../../appendix/references.md#SIBrochure) says: 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: +There are more units with such properties. For example, percent (`%`) and per mille(`‰`). + +To support the above and other similar cases, 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 inserted in the output text. To change this behavior, we have to provide a partial +specialization for a specific unit: ```cpp template<> @@ -121,36 +293,35 @@ 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 (e.g., `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. + The above works only for [the default formatting](#default-formatting) or for the format + strings that use `%?` placement field (`std::format("{}", q)` is equivalent to + `std::format("{:%N%?%U}", q)`). + + In case a user provides custom format specification (e.g., `std::format("{:%N %U}", q)`), + the library will always obey this specification for all the units (no matter what the actual + value of the `space_before_unit_symbol` customization point is) and the separating space will always + be used in this case. -### Output streams +## Output streams !!! tip The output streaming support is opt-in and can be enabled by including the `` header file. -The easiest way to print a quantity is to provide its object to the output stream: +The easiest way to print a dimension, unit, or quantity is to provide its object to the output stream: ```cpp -using namespace mp_units; -using namespace mp_units::si::unit_symbols; -using namespace mp_units::international::unit_symbols; - const QuantityOf auto v1 = avg_speed(220. * km, 2 * h); const QuantityOf auto v2 = avg_speed(140. * mi, 2 * h); -std::cout << v1 << '\n'; // 110 km/h -std::cout << v2 << '\n'; // 70 mi/h +std::cout << v1 << '\n'; // 110 km/h +std::cout << v2 << '\n'; // 70 mi/h +std::cout << v2.unit << '\n'; // mi/h +std::cout << v2.dimension << '\n'; // LT⁻¹ ``` -The text output will always print the [value of a quantity](../../appendix/glossary.md#quantity-value) -typically followed by a space and then the symbol of a [unit](../../appendix/glossary.md#unit) -associated with this quantity. +The text output will always print the value using the default formatting for this entity. !!! important "Important: Don't assume a unit" @@ -164,11 +335,14 @@ associated with this quantity. ``` -#### Output stream formatting +### Output stream formatting Only basic formatting can be applied to output streams. It includes control over width, fill, -and alignment of the entire quantity and formatting of a quantity numerical value according -to the general C++ rules: +and alignment. + +The numerical value of the quantity will be printed according to the current stream state and standard +manipulators may be used to customize that (assuming that the underlying representation type +respects them). ```cpp std::cout << "|" << std::setw(10) << 123 * m << "|\n"; // | 123 m| @@ -182,94 +356,22 @@ std::cout << "|" << std::setw(10) << std::setfill('*') << 123 * m << "|\n"; // stream just use `std::cout << std::format(...)`. -### `std::format` +## Text formatting + +The library provides custom formatters for `std::format` facility, which allows fine-grained control +over what and how it is being printed in the text output. !!! tip The text formatting facility support is opt-in and can be enabled by including the `` header file. -The **mp-units** library provides custom formatters for `std::format` facility which allows -fine-grained control over what and how it is being printed in the text output. +### Controlling width, fill, and alignment - -#### Grammar - -```ebnf -quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] -quantity-specs ::= conversion-spec - quantity-specs conversion-spec - quantity-specs literal-char -literal-char ::= any character other than '{' or '}' -conversion-spec ::= '%' type -type ::= [rep-modifier] 'Q' - [unit-modifier] 'q' -rep-modifier ::= [sign] [#] [precision] [L] [rep-type] -rep-type ::= one of - a A b B d e E f F g G o x X -unit-modifier ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator] - [text-encoding] [unit-symbol-separator] [unit-symbol-solidus] - [unit-symbol-solidus] [text-encoding] [unit-symbol-separator] - [unit-symbol-solidus] [unit-symbol-separator] [text-encoding] - [unit-symbol-separator] [text-encoding] [unit-symbol-solidus] - [unit-symbol-separator] [unit-symbol-solidus] [text-encoding] -text-encoding ::= one of - U A -unit-symbol-solidus ::= one of - o a n -unit-symbol-separator ::= one of - s d -``` - - -In the above grammar: - -- `fill-and-align`, `width`, `sign`, `#`, `precision`, and `L` tokens, as well as the individual - tokens of `rep-type` are defined in the [format.string.std](https://wg21.link/format.string.std) - chapter of the C++ standard specification, -- tokens `Q` and `q` of `type` are described in the [time.format](https://wg21.link/time.format) - chapter of the C++ standard specification, -- `text-encoding` tokens specify the unit text encoding: - - `U` (default) uses the **Unicode** symbols defined by the [SI](../../appendix/glossary.md#si) - specification (e.g., `m³`, `µs`) - - `A` token forces non-standard **ASCII**-only output (e.g., `m^3`, `us`) -- `unit-symbol-solidus` tokens specify how the division of units should look like: - - `o` (default) outputs `/` only when there is only **one** unit in the denominator, otherwise - negative exponents are printed (e.g., `m/s`, `kg m⁻¹ s⁻¹`) - - `a` **always** uses solidus (e.g., `m/s`, `kg/(m s)`) - - `n` **never** prints solidus, which means that negative exponents are always used - (e.g., `m s⁻¹`, `kg m⁻¹ s⁻¹`) -- `unit-symbol-separator` tokens specify how multiplied unit symbols should be separated: - - `s` (default) uses **space** as a separator (e.g., `kg m²/s²`) - - `d` uses half-high **dot** (`⋅`) as a separator (e.g., `kg⋅m²/s²`) - - -#### Default formatting - -To format `quantity` values, the formatting facility uses `quantity-format-spec`. If left empty, -the default formatting is applied. The same default formatting is also applied to the output streams. -This is why the following code lines produce the same output: - -```cpp -std::cout << "Distance: " << 123 * km << "\n"; -std::cout << std::format("Distance: {}\n", 123 * km); -std::cout << std::format("Distance: {:%Q %q}\n", 123 * km); -``` - -!!! note - - For some quantities, the `{:%Q %q}` format may provide a different output than the default one. - It will happen, for example for: - - - units for which [`space_before_unit_symbol`](#customization-point) customization point is set - to `false`, - - quantities of dimension one with a unit one. - - -#### Controlling width, fill, and alignment - -To control width, fill, and alignment, the C++ standard grammar tokens `fill-and-align` and `width` -are being used, and they treat a quantity value and symbol as a contiguous text: +Formatting grammar for all the entities provides control over width, fill, and alignment. The C++ +standard grammar tokens `fill-and-align` and `width` are being used. They treat the entity as +a contiguous text to be aligned. For example, here are a few examples of the quantity numerical +value and symbol formatting: ```cpp std::println("|{:0}|", 123 * m); // |123 m| @@ -282,6 +384,10 @@ std::println("|{:*>10}|", 123 * m); // |*****123 m| std::println("|{:*^10}|", 123 * m); // |**123 m***| ``` +It is important to note that in the second line above, the quantity text is aligned to +the right by default, which is consistent with the formatting of numeric types. Units and dimensions behave +as text and, thus, are aligned to the left by default. + !!! note [`std::println` is a C++23 facility](https://en.cppreference.com/w/cpp/io/print). In case we @@ -291,26 +397,268 @@ std::println("|{:*^10}|", 123 * m); // |**123 m***| std::cout << std::format("\n", ); ``` +### Dimension formatting -#### Quantity value, symbol, or both? - -The user can easily decide to either print a whole quantity (value and symbol) or only its parts. -Also, a custom style of quantity formatting might be applied: - -```cpp -std::println("{:%Q}", 123 * km); // 123 -std::println("{:%q}", 123 * km); // km -std::println("{:%Q%q}", 123 * km); // 123km +```bnf +dimension-format-spec ::= [fill-and-align] [width] [dimension-spec] +dimension-spec ::= [text-encoding] +text-encoding ::= 'U' | 'A' ``` +In the above grammar: -#### Quantity value formatting +- `fill-and-align` and `width` tokens are defined in the [format.string.std](https://wg21.link/format.string.std) + chapter of the C++ standard specification, +- `text-encoding` token specifies the symbol text encoding: + - `U` (default) uses the **Unicode** symbols defined by [@ISO80000] (e.g., `LT⁻²`), + - `A` forces non-standard **ASCII**-only output (e.g., `LT^-2`). + +Dimension symbols of some quantities are specified to use Unicode signs by the +[ISQ](../../appendix/glossary.md#isq) (e.g., `Θ` symbol for the _thermodynamic temperature_ +dimension). The library follows this by default. From the engineering point of view, sometimes +Unicode text might not be the best solution, as terminals of many (especially embedded) devices +can output only letters from the basic literal character set. In such a case, the dimension +symbol can be forced to be printed using such characters thanks to `text-encoding` token: + +```cpp +std::println("{}", isq::dim_thermodynamic_temperature); // Θ +std::println("{:A}", isq::dim_thermodynamic_temperature); // O +std::println("{}", isq::power.dimension); // L²MT⁻³ +std::println("{:A}", isq::power.dimension); // L^2MT^-3 +``` + +### Unit formatting + +```bnf +unit-format-spec ::= [fill-and-align] [width] [unit-spec] +unit-spec ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator] [L] + [text-encoding] [unit-symbol-separator] [unit-symbol-solidus] [L] + [unit-symbol-solidus] [text-encoding] [unit-symbol-separator] [L] + [unit-symbol-solidus] [unit-symbol-separator] [text-encoding] [L] + [unit-symbol-separator] [text-encoding] [unit-symbol-solidus] [L] + [unit-symbol-separator] [unit-symbol-solidus] [text-encoding] [L] +unit-symbol-solidus ::= '1' | 'a' | 'n' +unit-symbol-separator ::= 's' | 'd' +``` + +In the above grammar: + +- `fill-and-align` and `width` tokens are defined in the [format.string.std](https://wg21.link/format.string.std) + chapter of the C++ standard specification, +- `unit-symbol-solidus` token specifies how the division of units should look like: + - '1' (default) outputs `/` only when there is only **one** unit in the denominator, otherwise + negative exponents are printed (e.g., `m/s`, `kg m⁻¹ s⁻¹`) + - 'a' **always** uses solidus (e.g., `m/s`, `kg/(m s)`) + - 'n' **never** prints solidus, which means that negative exponents are always used + (e.g., `m s⁻¹`, `kg m⁻¹ s⁻¹`) +- `unit-symbol-separator` token specifies how multiplied unit symbols should be separated: + - 's' (default) uses **space** as a separator (e.g., `kg m²/s²`) + - 'd' uses half-high **dot** (`⋅`) as a separator (e.g., `kg⋅m²/s²`) (requires the Unicode encoding) +- 'L' is reserved for possible future localization use in case the C++ standard library gets access to + the ICU-like database. + +!!! note + + The above grammar intended that the elements of `unit-spec` can appear in + any order as they have unique characters. Users shouldn't have to remember the order of those tokens + to control the formatting of a unit symbol. + +Unit symbols of some quantities are specified to use Unicode signs by the [SI](../../appendix/glossary.md#si) +(e.g., `Ω` symbol for the _resistance_ quantity). The library follows this by default. From the +engineering point of view, Unicode text might not be the best solution sometimes, as terminals +of many (especially embedded) devices can output only letters from the basic literal character set. +In such a case, the unit symbol can be forced to be printed using such characters thanks to +`text-encoding` token: + +```cpp +std::println("{}", si::ohm); // Ω +std::println("{:A}", si::ohm); // ohm +std::println("{}", us); // µs +std::println("{:A}", us); // us +std::println("{}", m / s2); // m/s² +std::println("{:A}", m / s2); // m/s^2 +``` + +Additionally, both ISO 80000 and [SI](../../appendix/glossary.md#si) leave some freedom on how to +print unit symbols. This is why two additional tokens were introduced. + +`unit-symbol-solidus` specifies how the division of units should look like. By default, +`/` will be used only when the denominator contains only one unit. However, with the 'a' or 'n' +options, we can force the facility to print the `/` character always (even when there are more units +in the denominator), or never, in which case a parenthesis will be added to enclose all denominator +units. + +```cpp +std::println("{}", m / s); // m/s +std::println("{}", kg / m / s2); // kg m⁻¹ s⁻² +std::println("{:a}", m / s); // m/s +std::println("{:a}", kg / m / s2); // kg/(m s²) +std::println("{:n}", m / s); // m s⁻¹ +std::println("{:n}", kg / m / s2); // kg m⁻¹ s⁻² +``` + +Also, there are a few options to separate the units being multiplied. ISO 80000 (part 1) says: + +!!! quote "ISO 80000-1" + + When symbols for quantities are combined in a product of two or more quantities, this combination + is indicated in one of the following ways: `ab`, `a b`, `a · b`, `a × b` + + _NOTE 1_ In some fields, e.g., vector algebra, distinction is made between `a ∙ b` and `a × b`. + +The library supports `a b` and `a · b` only. Additionally, we decided that the extraneous space +in the latter case makes the result too verbose, so we decided just to use the `·` symbol as +a separator. + +!!! note + + Please let us know if you require more formatting options here. + +The `unit-symbol-separator` token allows us to obtain the following outputs: + +```cpp +std::println("{}", kg * m2 / s2); // kg m²/s² +std::println("{:d}", kg * m2 / s2); // kg⋅m²/s² +``` + +!!! note + + 'd' requires the Unicode encoding to be set. + + +### Quantity formatting + +```bnf +quantity-format-spec ::= [fill-and-align] [width] [quantity-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 ::= +``` + +In the above grammar: + +- `fill-and-align` and `width` tokens are defined in the [format.string.std](https://wg21.link/format.string.std) + chapter of the C++ standard specification, +- `placement-type` token specifies which entity should be put and where: + - 'N' inserts a default-formatted numerical value of the quantity, + - 'U' inserts a default-formatted unit of the quantity, + - 'D' inserts a default-formatted dimension of the quantity, + - '?' 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. + +#### Default formatting + +To format `quantity` values, the formatting facility uses `quantity-format-spec`. If left empty, +the default formatting is applied. The same default formatting is also applied to the output streams. +This is why the following code lines produce the same output: + +```cpp +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 + + For some quantities, the `{:%N %U}` format may provide a different output than the default one, + as some units have `space_before_unit_symbol` customization point explicitly set to `false` + (e.g., `%` and `°`). + + +#### Quantity numerical value, unit symbol, or both? + +Thanks to the grammar provided above, the user can easily decide to either: + +- print a whole quantity: + + ```cpp + std::println("Speed: {}", 120 * km / h); + ``` + + ```text + Speed: 120 km/h + ``` + +- provide custom quantity formatting: + + ```cpp + std::println("Speed: {:%N in %U}", 120 * km / h); + ``` + + ```text + Speed: 120 in km/h + ``` + +- provide custom formatting for components: + + ```cpp + std::println("Speed: {:{%N:.2f} {%U:n}}", 100. * km / (3 * h)); + ``` + + ```text + Speed: 33.33 km h⁻¹ + ``` + +- print only specific components (numerical value, unit, or dimension): + + ```cpp + std::println("Speed:\n- number: {0:%N}\n- unit: {0:%U}\n- dimension: {0:%D}", 120 * km / h); + ``` + + ```text + Speed: + - number: 120 + - unit: km/h + - 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. + +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 +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:%Q %q},{0:%+Q %q},{0:%-Q %q},{0:% Q %q}", 1 * m); // 1 m,+1 m,1 m, 1 m -std::println("{0:%Q %q},{0:%+Q %q},{0:%-Q %q},{0:% Q %q}", -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:%N %U},{0:{%N:+} %U},{0:{%N:-} %U},{0:{%N: } %U}", -1 * m); // -1 m,-1 m,-1 m,-1 m ``` where: @@ -324,111 +672,54 @@ where: `precision` token is allowed only for floating-point representation types: ```cpp -std::println("{:%.0Q %q}", 1.2345 * m); // 1 m -std::println("{:%.1Q %q}", 1.2345 * m); // 1.2 m -std::println("{:%.2Q %q}", 1.2345 * m); // 1.23 m +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 ``` -`rep-type` specifies how a value of the representation type is being printed. +`type` specifies how a value of the representation type is being printed. For integral types: ```cpp -std::println("{:%bQ %q}", 42 * m); // 101010 m -std::println("{:%BQ %q}", 42 * m); // 101010 m -std::println("{:%dQ %q}", 42 * m); // 42 m -std::println("{:%oQ %q}", 42 * m); // 52 m -std::println("{:%xQ %q}", 42 * m); // 2a m -std::println("{:%XQ %q}", 42 * m); // 2A m +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 ``` The above can be printed in an alternate version thanks to the `#` token: ```cpp -std::println("{:%#bQ %q}", 42 * m); // 0b101010 m -std::println("{:%#BQ %q}", 42 * m); // 0B101010 m -std::println("{:%#oQ %q}", 42 * m); // 052 m -std::println("{:%#xQ %q}", 42 * m); // 0x2a m -std::println("{:%#XQ %q}", 42 * m); // 0X2A m +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 ``` -For floating-point values, the `rep-type` token works as follows: +For floating-point values, the `type` token works as follows: ```cpp -std::println("{:%aQ %q}", 1.2345678 * m); // 0x1.3c0ca2a5b1d5dp+0 m -std::println("{:%.3aQ %q}", 1.2345678 * m); // 0x1.3c1p+0 m -std::println("{:%AQ %q}", 1.2345678 * m); // 0X1.3C0CA2A5B1D5DP+0 m -std::println("{:%.3AQ %q}", 1.2345678 * m); // 0X1.3C1P+0 m -std::println("{:%eQ %q}", 1.2345678 * m); // 1.234568e+00 m -std::println("{:%.3eQ %q}", 1.2345678 * m); // 1.235e+00 m -std::println("{:%EQ %q}", 1.2345678 * m); // 1.234568E+00 m -std::println("{:%.3EQ %q}", 1.2345678 * m); // 1.235E+00 m -std::println("{:%gQ %q}", 1.2345678 * m); // 1.23457 m -std::println("{:%gQ %q}", 1.2345678e8 * m); // 1.23457e+08 m -std::println("{:%.3gQ %q}", 1.2345678 * m); // 1.23 m -std::println("{:%.3gQ %q}", 1.2345678e8 * m); // 1.23e+08 m -std::println("{:%GQ %q}", 1.2345678 * m); // 1.23457 m -std::println("{:%GQ %q}", 1.2345678e8 * m); // 1.23457E+08 m -std::println("{:%.3GQ %q}", 1.2345678 * m); // 1.23 m -std::println("{:%.3GQ %q}", 1.2345678e8 * m); // 1.23E+08 m -``` - - -#### Unit symbol formatting - -Unit symbols of some quantities are specified to use Unicode signs by the -[SI](../../appendix/glossary.md#si) (e.g., `Ω` symbol for the resistance quantity). The **mp-units** -library follows this by default. From the engineering point of view, sometimes Unicode text might -not be the best solution as terminals of many (especially embedded) devices are ASCII-only. -In such a case, the unit symbol can be forced to be printed using ASCII-only characters thanks to -the `text-encoding` token: - -```cpp -std::println("{}", 10 * si::ohm); // 10 Ω -std::println("{:%Q %Aq}", 10 * si::ohm); // 10 ohm -std::println("{}", 125 * us); // 125 µs -std::println("{:%Q %Aq}", 125 * us); // 125 us -std::println("{}", 9.8 * (m / s2)); // 9.8 m/s² -std::println("{:%Q %Aq}", 9.8 * (m / s2)); // 9.8 m/s^2 -``` - -Additionally, both [ISQ](../../appendix/glossary.md#isq) and [SI](../../appendix/glossary.md#si) -leave some freedom on how to print unit symbols. This is why two additional tokens were introduced. - -`unit-symbol-solidus` specifies how the division of units should look like. By default, -`/` will be used only when the denominator contains only one unit. However, with the `a` or `n` -options, we can force the facility to print the `/` character always (even when there are more units -in the denominator), or never in which case a parenthesis will be added to enclose all denominator -units. - -```cpp -std::println("{:%Q %q}", 1 * m / s); // 1 m/s -std::println("{:%Q %q}", 1 * kg / m / s2); // 1 kg m⁻¹ s⁻² -std::println("{:%Q %aq}", 1 * m / s); // 1 m/s -std::println("{:%Q %aq}", 1 * kg / m / s2); // 1 kg/(m s²) -std::println("{:%Q %nq}", 1 * m / s); // 1 m s⁻¹ -std::println("{:%Q %nq}", 1 * kg / m / s2); // 1 kg m⁻¹ s⁻² -``` - -Also, there are a few options to separate the units being multiplied: - -!!! quote "ISO 80000-1" - - When symbols for quantities are combined in a product of two or more quantities, this combination - is indicated in one of the following ways: `ab`, `a b`, `a · b`, `a × b` - - _NOTE 1_ In some fields, e.g., vector algebra, distinction is made between `a ∙ b` and `a × b`. - -As of today, the **mp-units** library only supports `a b` and `a · b`. Additionally, -we decided that the extraneous space in the latter case makes the result too verbose, so we decided -to just use the `·` symbol as a separator. - -!!! note - - Please let us know if you require more formatting options here. - -The `unit-symbol-separator` token allows us to obtain the following outputs: - -```cpp -std::println("{:%Q %q}", 1 * kg * m2 / s2); // 1 kg m²/s² -std::println("{:%Q %dq}", 1 * kg * m2 / s2); // 1 kg⋅m²/s² +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 ```