Fast and friendly Rust number formatting. # Format numbers. 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](#parsing) which can use a string to define a [`Formatter`] following a specific grammar. # Procedure Formatting is done through the [`Formatter::fmt`] which follows the procedure: 1. Scale the number with the defined [`Scales`], 2. Check if _scaled number_ is above or below the [scientific notation cutoffs](#scientific-notation), 3. Add defined thousands separator, 4. Stop at defined [`Precision`], 5. Applies valid prefix, suffix, and unit decorations. # Usage ## Default use [`Default::default`] provides a general use default formatter with the following properties: - [`Scales::short`] scaling, - `,` thousands separator, - 3 decimal places ```rust # 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"); ``` ## Custom use 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: ```rust # 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 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 Precision, either with number of decimals or significant figures can be specified with [`Precision`]. ```rust # 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"); ``` # Performance 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 | # Example - File size formatter Using a combination of a scale, suffix, and precision, a file size printer can be constructed: ```rust # 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"); ``` # Parsing 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_. ```text prefix[[.#|~#|.*][%|s|b|n][/]]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_. ## Prefix and Suffix 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. ### Example ```rust # 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 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`]. ### Example ```rust # 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(); // arbitary 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 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`]) ### Example ```rust # 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"); ``` ## Separator 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. ### Example ```rust # 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"); ``` ## Composing formats 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 arbitary. It is recommended to keep it consistent with _precison, scaling, separator_ as this assists with readability and lowers the risk of malformed formats (which will error on the parsing phase). ### Various composed examples ```rust # 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"); ```