Skip to content

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

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.

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.

Derived unit symbols generation

Based on the provided definitions for base units, the library creates symbols for derived ones.

unit_symbol_formatting

unit_symbol_formatting is a data type describing the configuration of the symbol generation algorithm. It contains three orthogonal fields, and each of them has a default value.

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:

template<unit_symbol_formatting fmt = unit_symbol_formatting{}, typename CharT = char, Unit U>
[[nodiscard]] consteval auto unit_symbol(U);

For example:

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.

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:

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:

kg⋅m⋅s⁻²

Quantity text output

Customization point

The SI Brochure says:

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:

template<>
inline constexpr bool space_before_unit_symbol<non_si::degree> = false;

Note

The above works only for the 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

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:

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

The text output will always print the value of a quantity typically followed by a space and then the symbol of a unit associated with this quantity.

Important: Don't assume a unit

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 before passing it to the text output:

std::cout << v1.in(km / h) << '\n';       // 110 km/h
std::cout << v1.force_in(m / s) << '\n';  // 30.5556 m/s

Output stream formatting

Only basic formatting can be applied for 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:

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*****|

std::format

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

units-format-spec   ::=  [fill-and-align] [width] [units-specs]
units-specs         ::=  conversion-spec
                         units-specs conversion-spec
                         units-specs literal-char
literal-char        ::=  any character other than '{' or '}'
conversion-spec     ::=  '%' units-type
units-type          ::=  [units-rep-modifier] 'Q'
                         [units-unit-modifier] 'q'
units-rep-modifier  ::=  [sign] [#] [precision] [L] [units-rep-type]
units-rep-type      ::=  one of "aAbBdeEfFgGoxX"
units-unit-modifier ::=  [units-text-encoding units-unit-symbol-denominator units-unit-symbol-separator]
units-text-encoding ::=  one of "UA"
units-unit-symbol-solidus   ::=  one of "oan"
units-unit-symbol-separator ::=  one of "sd"

In the above grammar:

  • fill-and-align, width, sign, #, precision, and L tokens, as well as the individual tokens of units-rep-type are defined in the format.string.std chapter of the C++ standard specification,
  • tokens Q and q of units-type are described in the time.format chapter of the C++ standard specification,
  • units-text-encoding tokens specify the unit text encoding:
    • U (default) uses the Unicode symbols defined by the SI specification (e.g. , µs)
    • A token forces non-standard ASCII-only output (e.g. m^3, us)
  • units-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⁻¹)
  • units-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 units-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:

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 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:

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. In case we do not have access to C++23, we can obtain the same output with:

std::cout << std::format("<format-string>\n", <format-args>);

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:

std::println("{:%Q}", 123 * km);    // 123
std::println("{:%q}", 123 * km);    // km
std::println("{:%Q%q}", 123 * km);  // 123km

Quantity value formatting

sign token allows us to specify how the value's sign is being printed:

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:

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

units-rep-type specifies how a value of the representation type is being printed. For integral types:

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:

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

For floating-point values, the units-rep-type token works as follows:

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 (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 units-text-encoding token:

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 and SI leave some freedom on how to print unit symbols. This is why two additional tokens were introduced.

units-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.

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:

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 provides the support for 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 to just use the · symbol as a separator.

Note

Please let us know in case you require more formatting options here.

The units-unit-symbol-separator token allows us to obtain the following outputs:

std::println("{:%Q %q}", 1 * kg * m2 / s2);   // 1 kg m²/s²
std::println("{:%Q %dq}", 1 * kg * m2 / s2);  // 1 kg⋅m²/s²