Files
mp-units/docs/users_guide/framework_basics/text_output.md

435 lines
16 KiB
Markdown
Raw Normal View History

2023-07-24 17:36:49 +02:00
# Text Output
Besides providing dimensional analysis and units conversions, the library also tries hard to print
any quantity in the most user-friendly way.
!!! note
2023-12-27 11:34:06 +01:00
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.
2023-12-27 11:34:06 +01:00
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.
2023-07-24 17:36:49 +02:00
## Derived unit symbols generation
2023-12-27 11:34:06 +01:00
The library creates symbols for derived ones based on the provided definitions for base units.
### `unit_symbol_formatting`
`unit_symbol_formatting` is a data type describing the configuration of the symbol generation
2023-12-27 11:34:06 +01:00
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)
never, // m s⁻¹; kg m⁻¹ s⁻¹
default_denominator = one_denominator
};
enum class unit_symbol_separator : std::int8_t {
space, // kg m²/s²
half_high_dot, // kg⋅m²/s² (valid only for unicode encoding)
default_separator = space
};
struct unit_symbol_formatting {
text_encoding encoding = text_encoding::default_encoding;
unit_symbol_solidus solidus = unit_symbol_solidus::default_denominator;
unit_symbol_separator separator = unit_symbol_separator::default_separator;
};
```
### `unit_symbol()`
Returns a `std::string_view` with the symbol of a unit for the provided configuration:
```cpp
template<unit_symbol_formatting fmt = unit_symbol_formatting{}, typename CharT = char, Unit U>
[[nodiscard]] consteval auto unit_symbol(U);
```
For example:
```cpp
static_assert(unit_symbol<{.solidus = unit_symbol_solidus::never,
.separator = unit_symbol_separator::half_high_dot}>(kg * m / s2) == "kg⋅m⋅s⁻²");
```
!!! note
`std::string_view` is returned only when C++23 is available. Otherwise, an instance of a
`basic_fixed_string` is being returned.
### `unit_symbol_to()`
Inserts the generated unit symbol to the output text iterator at runtime based on the provided
configuration.
```cpp
template<typename CharT = char, std::output_iterator<CharT> Out, Unit U>
constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{});
```
For example:
```cpp
std::string txt;
unit_symbol_to(std::back_inserter(txt), kg * m / s2,
{.solidus = unit_symbol_solidus::never, .separator = unit_symbol_separator::half_high_dot});
std::cout << txt << "\n";
```
The above prints:
```text
kg⋅m⋅s⁻²
```
## Quantity text output
### Customization point
2023-09-29 11:21:27 +02:00
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<non_si::degree> = false;
```
!!! note
2023-12-27 11:34:06 +01:00
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.
### Output streams
2023-07-24 17:36:49 +02:00
!!! tip
The output streaming support is opt-in and can be enabled by including the `<mp-units/ostream.h>`
header file.
The easiest way to print a 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<isq::speed> auto v1 = avg_speed(220. * km, 2 * h);
const QuantityOf<isq::speed> auto v2 = avg_speed(140. * mi, 2 * h);
std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h
```
2023-08-03 21:23:34 +02:00
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)
2023-07-24 17:36:49 +02:00
associated with this quantity.
!!! important "Important: Don't assume a unit"
2023-07-24 17:36:49 +02:00
2023-12-27 11:26:56 +01:00
Remember that when we deal with a quantity of an "unknown" (e.g., `auto`) type, it is a good
practice to always [convert the unit to the expected one](value_conversions.md#value-conversions)
2023-07-24 17:36:49 +02:00
before passing it to the text output:
```cpp
std::cout << v1.in(km / h) << '\n'; // 110 km/h
std::cout << v1.force_in(m / s) << '\n'; // 30.5556 m/s
2023-07-24 17:36:49 +02:00
```
#### Output stream formatting
2023-07-24 17:36:49 +02:00
2023-12-27 11:34:06 +01:00
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:
2023-07-24 17:36:49 +02:00
```cpp
std::cout << "|" << std::setw(10) << 123 * m << "|\n"; // | 123 m|
std::cout << "|" << std::setw(10) << std::left << 123 * m << "|\n"; // |123 m |
std::cout << "|" << std::setw(10) << std::setfill('*') << 123 * m << "|\n"; // |123 m*****|
```
2023-12-27 11:26:56 +01:00
!!! note
To have more control over the formatting of the quantity that is printed with the output
stream just use `std::cout << std::format(...)`.
2023-07-24 17:36:49 +02:00
### `std::format`
2023-07-24 17:36:49 +02:00
!!! tip
The text formatting facility support is opt-in and can be enabled by including the
`<mp-units/format.h>` 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.
#### Grammar
2023-07-24 17:36:49 +02:00
2023-12-27 11:26:56 +01:00
```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]
2023-12-27 11:26:56 +01:00
text-encoding ::= one of
U A
unit-symbol-solidus ::= one of
o a n
unit-symbol-separator ::= one of
s d
2023-07-24 17:36:49 +02:00
```
In the above grammar:
- `fill-and-align`, `width`, `sign`, `#`, `precision`, and `L` tokens, as well as the individual
2023-12-27 11:26:56 +01:00
tokens of `rep-type` are defined in the [format.string.std](https://wg21.link/format.string.std)
2023-07-24 17:36:49 +02:00
chapter of the C++ standard specification,
2023-12-27 11:26:56 +01:00
- tokens `Q` and `q` of `type` are described in the [time.format](https://wg21.link/time.format)
2023-07-24 17:36:49 +02:00
chapter of the C++ standard specification,
2023-12-27 11:26:56 +01:00
- `text-encoding` tokens specify the unit text encoding:
2023-08-03 21:23:34 +02:00
- `U` (default) uses the **Unicode** symbols defined by the [SI](../../appendix/glossary.md#si)
2023-12-27 11:26:56 +01:00
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:
2023-12-27 11:34:06 +01:00
- `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⁻¹`)
2023-12-27 11:26:56 +01:00
- `a` **always** uses solidus (e.g., `m/s`, `kg/(m s)`)
2023-12-27 11:34:06 +01:00
- `n` **never** prints solidus, which means that negative exponents are always used
(e.g., `m s⁻¹`, `kg m⁻¹ s⁻¹`)
2023-12-27 11:26:56 +01:00
- `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²`)
2023-07-24 17:36:49 +02:00
#### Default formatting
2023-07-24 17:36:49 +02:00
2023-12-27 11:26:56 +01:00
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:
2023-07-24 17:36:49 +02:00
```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
2023-12-27 11:34:06 +01:00
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.
2023-07-24 17:36:49 +02:00
#### Controlling width, fill, and alignment
2023-07-24 17:36:49 +02:00
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:
```cpp
std::println("|{:0}|", 123 * m); // |123 m|
std::println("|{:10}|", 123 * m); // | 123 m|
std::println("|{:<10}|", 123 * m); // |123 m |
std::println("|{:>10}|", 123 * m); // | 123 m|
std::println("|{:^10}|", 123 * m); // | 123 m |
std::println("|{:*<10}|", 123 * m); // |123 m*****|
std::println("|{:*>10}|", 123 * m); // |*****123 m|
std::println("|{:*^10}|", 123 * m); // |**123 m***|
```
!!! note
[`std::println` is a C++23 facility](https://en.cppreference.com/w/cpp/io/print). In case we
do not have access to C++23, we can obtain the same output with:
2023-08-03 21:23:34 +02:00
2023-07-24 17:36:49 +02:00
```cpp
std::cout << std::format("<format-string>\n", <format-args>);
```
#### Quantity value, symbol, or both?
2023-07-24 17:36:49 +02:00
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
```
#### Quantity value formatting
2023-07-24 17:36:49 +02:00
`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
```
where:
- `+` indicates that a sign should be used for both non-negative and negative numbers,
- `-` indicates that a sign should be used for negative numbers and negative zero only
(this is the default behavior),
- `<space>` indicates that a leading space should be used for non-negative numbers other
than negative zero, and a minus sign for negative numbers and negative zero.
`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
```
2023-12-27 11:26:56 +01:00
`rep-type` specifies how a value of the representation type is being printed.
2023-07-24 17:36:49 +02:00
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
```
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
```
2023-12-27 11:26:56 +01:00
For floating-point values, the `rep-type` token works as follows:
2023-07-24 17:36:49 +02:00
```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
2023-07-24 17:36:49 +02:00
Unit symbols of some quantities are specified to use Unicode signs by the
2023-12-27 11:26:56 +01:00
[SI](../../appendix/glossary.md#si) (e.g., `Ω` symbol for the resistance quantity). The **mp-units**
2023-07-24 17:36:49 +02:00
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
2023-12-27 11:34:06 +01:00
the `text-encoding` token:
2023-07-24 17:36:49 +02:00
```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
```
2023-08-03 21:23:34 +02:00
Additionally, both [ISQ](../../appendix/glossary.md#isq) and [SI](../../appendix/glossary.md#si)
2023-07-24 17:36:49 +02:00
leave some freedom on how to print unit symbols. This is why two additional tokens were introduced.
2023-12-27 11:26:56 +01:00
`unit-symbol-solidus` specifies how the division of units should look like. By default,
2023-07-24 17:36:49 +02:00
`/` 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⁻²
2023-07-24 17:36:49 +02:00
```
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`.
2023-12-27 11:34:06 +01:00
As of today, the **mp-units** library only supports `a b` and `a · b`. Additionally,
2023-07-24 17:36:49 +02:00
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
2023-12-27 11:34:06 +01:00
Please let us know if you require more formatting options here.
2023-07-24 17:36:49 +02:00
2023-12-27 11:26:56 +01:00
The `unit-symbol-separator` token allows us to obtain the following outputs:
2023-07-24 17:36:49 +02:00
```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²
2023-07-24 17:36:49 +02:00
```