num-runtime-fmt

Format numbers according to runtime specifications.

Build and Test Status

Note: This crate is currently under development. This document currently serves more as a development guideline than as an accurate description of capabilities.

Why another numeric formatting crate?

This crate implements numeric formatting with a combination of properties not found elsewhere:

Alternatives

Format String Reference

The formatters in this crate implement a near superset of features available in the format macros provided by std::fmt. However, they are exclusively concerned with formatting a single numeric value. Therefore, the specification language is somewhat truncated: it omits both braces and the colon which precedes the format specification. Therefore, where in the standard formatting machinery you might write:

rust let hex_digit = format!("{:02x}", 0xf0);

With this library, the equivalent would be:

rust let hex_digit = NumFmt::from_str("02x")?.fmt(0xf0);

Note: though these formatters support a superset of features of the standard ones, in that anything possible with the standard formatters is possible with this library, they do not have a superset of the syntax: while the intersection is large, there are a few syntax elements legal in the standard formatter which are not legal for this formatter. In particular, the standard formatter supports named width parameters. Dynamic width parameters are legal in this crate, but they cannot be named.

Grammar

The gramar for the format string derives substantially from the standard library's:

text format_spec := [[fill]align][sign]['#'][['0']width]['.' precision][format][separator[spacing]] fill := character align := '<' | '^' | '>' | 'v' sign := '+' | '-' width := integer not beginning with '0' precision := integer format := 'b' | 'o' | 'd' | 'x' | 'X' separator := '_', | ',' | ' ' spacing := integer

Note: there is no special syntax for dynamic insertion of with, precision and spacing. Simply use NumFmt::format_with; the dynamic values there always override any values for those fields, whether set or unset in the format string.

fill

Any single char which precedes an align specifier is construed as the fill character: when width is greater than the actual rendered width of the number, the excess is padded with this character.

Note: Wide characters are counted according to their bit width, not their quantity.

rust let heart = '🖤'; assert_eq!(heart.len_utf8(), 4); let fmt = NumFmt::builder().fill(heart).width(6).build(); // Note that this renders as two characters: we requested a width of 6. // The number renders as a single character. The heart fills up the next 4 for a total of 5. // Adding an extra heart would exceed the requested width, so it only renders one. assert_eq!(fmt.fmt(1), "🖤1");

alignment

sign

#

If a # character is present, print a base specification before the number according to its format (see format below).

This base specification counts toward the width of the number:

rust assert_eq!(NumFmt::from_str("#04b").unwrap().fmt(2), "0b10");

0

Engage the zero handler.

The zero handler overrides the padding specification to 0, and treats pad characters as part of the number, in contrast to the default behavior which treats them as arbitrary spacing.

Examples

rust // sign handling assert_eq!(NumFmt::from_str("-03").unwrap().fmt(-1).unwrap(), "-01"); assert_eq!(NumFmt::from_str("0>-3").unwrap().fmt(-1).unwrap(), "-001");

rust // separator handling assert_eq!(NumFmt::from_str("0>7,").fmt(1).unwrap(), "0000001"); assert_eq!(NumFmt::from_str("07,").fmt(1).unwrap(), "000,001");

width

This is a parameter for the "minimum width" that the format should take up. If the value's string does not fill up this many characters, then the padding specified by fill/alignment will be used to take up the required space (see fill above).

When using the $ sigil instead of an explicit width, the width can be set dynamically:

rust assert_eq!(NumFmt::from_str("-^$").unwrap().fmt_with(1, Dynamic::width(5)), "--1--");

If an explicit width is not provided, defaults to 0.

precision

How many digits after the decimal point are printed. Note that integers can be forced to emit decimal places with this modifier.

If an explicit precision is not provided, defaults to emitting all post-decimal digits emitted by the underlying type.

rust assert_eq!(NumFmt::from_str(".2").unwrap().fmt(3.14159).unwrap(), "3.14"); assert_eq!(NumFmt::from_str(".7").unwrap().fmt(3.14159).unwrap(), "3.1415900");

If the requested precision exceeds the native precision available to this number, the remainder is always filled with '0', even if fill is specified:

rust assert_eq!(NumFmt::from_str("-<6.2").unwrap().fmt(1.0_f32).unwrap(), "1.00--");

format

Note: This is one of a few areas where the standard library has capabilities this library does not: it supports some other numeric formats. Pull requests welcomed to bring this up to parity.

separator

A separator is a (typically non-numeric) character inserted between groups of digits to make it easier for humans to parse the number when reading. Different separators may be desirable in different contexts.

By default, numeric groups are not separated. It is not possible to explicitly specify that numeric groups are not separated when using a format string. However, this can be specified when building the formatter via builder.

Wyhen using the builder to explicitly set formatter options, it is also possible to separate numeric groups with an arbitrary char. This can be desirable to i.e. support German number formats, which use a . to separate numeric groups and a , as a decimal separator.

spacing

Spacing determines the number of characters in each character group. It is only of interest when the separator is set. The default spacing is 3.

Apparently some cultures separate numeric digits with a non-constant group size. Please file an issue if this feature is important to you.

Decimal separator

When using the builder to explicitly set formatter options, it is possible to set the decimal separator to any char. This can be desirable to i.e. support German number formats, which use a . to separate numeric groups and a , as a decimal separator.