Fast and friendly Rust number formatting.
Provides a [Formatter
] to format decimal numbers with various methods. Formatting is
performance focused, it is generally faster than std
with more features. There is also a
string parser which can use a string to define a [Formatter
] following a specific
grammar.
Formatting is done through the [Formatter::fmt
] which follows the procedure:
- Scale the number with the defined [
Scales
], - Check if scaled number is above or below the scientific notation cutoffs,
- Add defined thousands separator,
- Stop at defined [
Precision
], - Applies valid prefix, suffix, and unit decorations.
[Default::default
] provides a general use default formatter with the following properties:
- [
Scales::short
] scaling, ,
thousands separator,- 3 decimal places
# use numfmt::*;
let mut f = Formatter::default();
assert_eq!(f.fmt(0.0), "0");
assert_eq!(f.fmt(12345.6789), "12.345 K");
assert_eq!(f.fmt(0.00012345), "1.234e-4");
assert_eq!(f.fmt(123456e22), "1,234.559 Y");
The [Formatter
] has many different options to customise how the number should be formatted.
The example below shows how a currency format would be developed:
# use numfmt::*;
let mut f = Formatter::new() // start with blank representation
.separator(',').unwrap()
.prefix("AU$").unwrap()
.precision(Precision::Decimals(2));
assert_eq!(f.fmt(0.52), "AU$0.52");
assert_eq!(f.fmt(1234.567), "AU$1,234.56");
assert_eq!(f.fmt(12345678900.0), "AU$12,345,678,900.0");
Scientific notation kicks in when the scaled number is greater than 12 integer digits (123,456,789,000) or less than 3 leading zeros (0.0001234). The number always has a leading integer digit and has a default of 7 significant figures.
Precision, either with number of decimals or significant figures can be specified with
[Precision
].
# use numfmt::*;
let mut f = Formatter::new();
assert_eq!(f.fmt(1234.56789), "1234.56789");
f = f.precision(Precision::Decimals(2));
assert_eq!(f.fmt(1234.56789), "1234.56");
f = f.precision(Precision::Significance(5));
assert_eq!(f.fmt(1234.56789), "1234.5");
Formatting is generally faster than std
's f64::to_string
implementation. When constructing
a [Formatter
] there is an allocation for the buffer, and an allocation for any scales.
Reusing a [Formatter
] is recommended to avoid unnecessary allocations. The cached
row shows
the better performance reusing a formatter.
Time (ns) | 0.0 | 0.1234 | 2.718281828459045 | 1.797693148623157e307 |
---|---|---|---|---|
numfmt - default | 35 | 115 | 153 | 195 |
numfmt - cached | 2 | 75 | 89 | 126 |
std | 35 | 96 | 105 | 214 |
Using a combination of a scale, suffix, and precision, a file size printer can be constructed:
# use numfmt::*;
let mut f = Formatter::new()
.scales(Scales::binary())
.precision(Precision::Significance(3))
.suffix("B").unwrap();
assert_eq!(f.fmt(123_f64), "123 B");
assert_eq!(f.fmt(1234_f64), "1.20 kiB");
assert_eq!(f.fmt(1_048_576_f64), "1.0 MiB");
assert_eq!(f.fmt(123456789876543_f64), "112 TiB");
A grammar is defined that can parse into a [Formatter
]. This string representation can be
used as a user input for formatting numbers. The grammar is defined by a prefix, the number
format enclosed in brackets, and then the suffix.
prefix[[.#|~#|.*][%|s|b|n][/<char>]]suffix
^----^ ^--------^^-------^^-------^ ^----^
prefix precision scale separator suffix
Each component is optional, including the number format. All formats are applied to the default [
Formatter
] so an empty format results in the default formatter.
The prefix and suffix are bound to the supported lengths, and can have any character in them.
To use []
characters, a double bracket must be used.
# use numfmt::*;
let mut f: Formatter;
f = "".parse().unwrap();
assert_eq!(f.fmt(1.234), "1.234");
f = "prefix ".parse().unwrap();
assert_eq!(f.fmt(1.234), "prefix 1.234");
f = "[] suffix".parse().unwrap();
assert_eq!(f.fmt(1.234), "1.234 suffix");
f = "[[prefix [] suffix]]".parse().unwrap();
assert_eq!(f.fmt(1.234), "[prefix 1.234 suffix]");
Precision is defined using a .
for decimals, or a ~
for significant figures, followed by
a number. A maximum of 255 is supported. There is a special case: .*
which removes any
default precision and uses [Precision::Unspecified
].
# use numfmt::*;
let mut f: Formatter;
f = "[.2]".parse().unwrap(); // use two decimal places
assert_eq!(f.fmt(1.2345), "1.23");
f = "[.0]".parse().unwrap(); // use zero decimal places
assert_eq!(f.fmt(10.234), "10");
f = "[.*]".parse().unwrap(); // arbitrary precision
assert_eq!(f.fmt(1.234), "1.234");
assert_eq!(f.fmt(12.2), "12.2");
f = "[~3]".parse().unwrap(); // 3 significant figures
assert_eq!(f.fmt(1.234), "1.23");
assert_eq!(f.fmt(10.234), "10.2");
Scale uses a character to denote what scaling should be used. By default the SI scaling is used. The following characters are supported:
s
for SI scaling ([Scales::short
]),%
for percentage scaling ([Formatter::percentage
]),m
for metric scaling ([Scales::metric
]),b
for binary scaling ([Scales::binary
]),n
for no scaling ([Scales::none
])
# use numfmt::*;
let mut f: Formatter;
f = "".parse().unwrap(); // default si scaling used
assert_eq!(f.fmt(12345.0), "12.345 K");
f = "[n]".parse().unwrap(); // turn off scaling
assert_eq!(f.fmt(12345.0), "12,345.0");
f = "[%.2]".parse().unwrap(); // format as percentages with 2 decimal places
assert_eq!(f.fmt(0.234), "23.40%");
f = "[b]".parse().unwrap(); // use a binary scaler
assert_eq!(f.fmt(3.14 * 1024.0 * 1024.0), "3.14 Mi");
A separator character can be specified by using a forward slash /
followed by a character.
The parser uses the next character verbatim, unless that character is ]
in which case the
separator is set to None
. The default separator is a comma.
# use numfmt::*;
let mut f: Formatter;
f = "[n]".parse().unwrap(); // turn off scaling to see separator
assert_eq!(f.fmt(12345.0), "12,345.0");
f = "[n/]".parse().unwrap(); // use no separator
assert_eq!(f.fmt(12345.0), "12345.0");
f = "[n/.]".parse().unwrap(); // use a period
assert_eq!(f.fmt(12345.0), "12.345.0");
f = "[n/_]".parse().unwrap(); // use a underscroll
assert_eq!(f.fmt(12345.0), "12_345.0");
f = "[n/ ]".parse().unwrap(); // use a space
assert_eq!(f.fmt(12345.0), "12 345.0");
There have been examples of composing formats already. The prefix[num]suffix
order must be
adhered to, but the ordering within the number format is arbitrary. It is recommended to keep it
consistent with precision, scaling, separator as this assists with readability and lowers the
risk of malformed formats (which will error on the parsing phase).
# use numfmt::*;
let mut f: Formatter;
// Percentages to two decimal places
f = "[.2%]".parse().unwrap();
assert_eq!(f.fmt(0.012345), "1.23%");
// Currency to zero decimal places
// notice the `n` for no scaling
f = "$[.0n] USD".parse().unwrap();
assert_eq!(f.fmt(123_456_789.12345), "$123,456,789 USD");
// Formatting file sizes
f = "[~3b]B".parse().unwrap();
assert_eq!(f.fmt(123_456_789.0), "117 MiB");
// Units to 1 decimal place
f = "[.1n] m/s".parse().unwrap();
assert_eq!(f.fmt(12345.68), "12,345.6 m/s");