numfmt/lib.rs
1//! Fast and friendly number formatting.
2//!
3//! Provides a [`Formatter`] to format decimal numbers with various methods. Formatting is
4//! performance focused, it is generally faster than `std` with more features. There is also a
5//! [string parser](#parsing) which can use a string to define a [`Formatter`] following a specific
6//! grammar.
7//!
8//! # Procedure
9//! Formatting is done through the [`Formatter::fmt`] which follows the procedure:
10//! 1. Scale the number with the defined [`Scales`],
11//! 2. Check if _scaled number_ is above or below the [scientific notation
12//! cutoffs](#scientific-notation),
13//! 3. Add defined thousands separator,
14//! 4. Stop at defined [`Precision`],
15//! 5. Applies valid prefix, suffix, and unit decorations.
16//!
17//! # Usage
18//! ## Default use
19//! [`Default::default`] provides a general use default formatter with the following properties:
20//! - [`Scales::short`] scaling,
21//! - `,` thousands separator,
22//! - 3 decimal places
23//!
24//! ```rust
25//! # use numfmt::*;
26//! let mut f = Formatter::default();
27//! assert_eq!(f.fmt(0.0), "0");
28//! assert_eq!(f.fmt(12345.6789), "12.345 K");
29//! assert_eq!(f.fmt(0.00012345), "1.234e-4");
30//! assert_eq!(f.fmt(123456e22), "1,234.559 Y");
31//! ```
32//!
33//! ## Custom use
34//! The [`Formatter`] has many different options to customise how the number should be formatted.
35//! The example below shows how a currency format would be developed:
36//! ```rust
37//! # use numfmt::*;
38//! let mut f = Formatter::new() // start with blank representation
39//! .separator(',').unwrap()
40//! .prefix("AU$").unwrap()
41//! .precision(Precision::Decimals(2));
42//!
43//! assert_eq!(f.fmt(0.52), "AU$0.52");
44//! assert_eq!(f.fmt(1234.567), "AU$1,234.56");
45//! assert_eq!(f.fmt(12345678900.0), "AU$12,345,678,900.0");
46//! ```
47//!
48//! # Scientific Notation
49//! Scientific notation kicks in when the scaled number is greater than 12 integer digits
50//! (123,456,789,000) or less than 3 leading zeros (0.0001234). The number _always_ has a leading
51//! integer digit and has a default of **7 significant figures**.
52//!
53//! # Precision
54//! Precision, either with number of decimals or significant figures can be specified with
55//! [`Precision`].
56//! ```rust
57//! # use numfmt::*;
58//! let mut f = Formatter::new();
59//! assert_eq!(f.fmt(1234.56789), "1234.56789");
60//!
61//! f = f.precision(Precision::Decimals(2));
62//! assert_eq!(f.fmt(1234.56789), "1234.56");
63//!
64//! f = f.precision(Precision::Significance(5));
65//! assert_eq!(f.fmt(1234.56789), "1234.5");
66//! ```
67//!
68//! # Performance
69//! Formatting is generally faster than `std`'s `f64::to_string` implementation. When constructing
70//! a [`Formatter`] there is an allocation for the buffer, and an allocation for any scales.
71//! Reusing a [`Formatter`] is recommended to avoid unnecessary allocations. The `cached` row shows
72//! the better performance reusing a formatter.
73//!
74//! | Time (ns) | 0.0 | 0.1234 | 2.718281828459045 | 1.797693148623157e307 |
75//! | ---------------- | --- | ------ | ----------------- | --------------------- |
76//! | numfmt - default | 35 | 115 | 153 | 195 |
77//! | numfmt - cached | 2 | 75 | 89 | 126 |
78//! | std | 35 | 96 | 105 | 214 |
79//!
80//! # Example - File size formatter
81//! Using a combination of a scale, suffix, and precision, a file size printer can be constructed:
82//! ```rust
83//! # use numfmt::*;
84//! let mut f = Formatter::new()
85//! .scales(Scales::binary())
86//! .precision(Precision::Significance(3))
87//! .suffix("B").unwrap();
88//!
89//! assert_eq!(f.fmt(123_f64), "123 B");
90//! assert_eq!(f.fmt(1234_f64), "1.20 kiB");
91//! assert_eq!(f.fmt(1_048_576_f64), "1.0 MiB");
92//! assert_eq!(f.fmt(123456789876543_f64), "112 TiB");
93//! ```
94//!
95//! # Parsing
96//! A grammar is defined that can parse into a [`Formatter`]. This string representation can be
97//! used as a user input for formatting numbers. The grammar is defined by a _prefix_, the number
98//! format enclosed in brackets, and then the _suffix_.
99//! ```text
100//! prefix[[.#|,#|~#|.*|,*][%|s|b|n][/<char>]]suffix
101//! ^----^ ^--------------^^-------^^-------^ ^----^
102//! prefix precision scale separator suffix
103//! ```
104//! > Each component is optional, including the number format. All formats are applied to the
105//! > _default_ [`Formatter`] so an empty format results in the default _formatter_.
106//!
107//! ## Prefix and Suffix
108//! The prefix and suffix are bound to the supported lengths, and can have any character in them.
109//! To use `[]` characters, a double bracket must be used.
110//!
111//! ### Example
112//! ```rust
113//! # use numfmt::*;
114//! let mut f: Formatter;
115//! f = "".parse().unwrap();
116//! assert_eq!(f.fmt(1.234), "1.234");
117//!
118//! f = "prefix ".parse().unwrap();
119//! assert_eq!(f.fmt(1.234), "prefix 1.234");
120//!
121//! f = "[] suffix".parse().unwrap();
122//! assert_eq!(f.fmt(1.234), "1.234 suffix");
123//!
124//! f = "[[prefix [] suffix]]".parse().unwrap();
125//! assert_eq!(f.fmt(1.234), "[prefix 1.234 suffix]");
126//! ```
127//!
128//! ## Precision
129//! Precision is defined using a `.`/`,` for decimals, or a `~` for significant figures, followed by
130//! a number. A maximum of 255 is supported. There is a special case: `.*`/`,*` which removes any
131//! default precision and uses [`Precision::Unspecified`].
132//! Note that usage of `,` signals to use periods as the separator and comma as the
133//! decimal marker. To use a comma with signficant figures, use a period separator.
134//!
135//! ### Example
136//! ```rust
137//! # use numfmt::*;
138//! let mut f: Formatter;
139//! f = "[.2]".parse().unwrap(); // use two decimal places
140//! assert_eq!(f.fmt(1.2345), "1.23");
141//!
142//! f = "[,2]".parse().unwrap(); // use two decimal places with comma
143//! assert_eq!(f.fmt(1.2345), "1,23");
144//!
145//! f = "[.0]".parse().unwrap(); // use zero decimal places
146//! assert_eq!(f.fmt(10.234), "10");
147//!
148//! f = "[.*]".parse().unwrap(); // arbitrary precision
149//! assert_eq!(f.fmt(1.234), "1.234");
150//! assert_eq!(f.fmt(12.2), "12.2");
151//!
152//! f = "[,*]".parse().unwrap(); // arbitrary precision with comma
153//! assert_eq!(f.fmt(1.234), "1,234");
154//!
155//! f = "[~3]".parse().unwrap(); // 3 significant figures
156//! assert_eq!(f.fmt(1.234), "1.23");
157//! assert_eq!(f.fmt(10.234), "10.2");
158
159//! f = "[~3/.]".parse().unwrap(); // 3 significant figures with comma
160//! assert_eq!(f.fmt(1.234), "1,23");
161//! ```
162//!
163//! ## Scale
164//! Scale uses a character to denote what scaling should be used. By default the SI scaling is
165//! used. The following characters are supported:
166//! - `s` for SI scaling ([`Scales::short`]),
167//! - `%` for percentage scaling ([`Formatter::percentage`]),
168//! - `m` for metric scaling ([`Scales::metric`]),
169//! - `b` for binary scaling ([`Scales::binary`]),
170//! - `n` for no scaling ([`Scales::none`])
171//!
172//! ### Example
173//! ```rust
174//! # use numfmt::*;
175//! let mut f: Formatter;
176//! f = "".parse().unwrap(); // default si scaling used
177//! assert_eq!(f.fmt(12345.0), "12.345 K");
178//!
179//! f = "[n]".parse().unwrap(); // turn off scaling
180//! assert_eq!(f.fmt(12345.0), "12,345.0");
181//!
182//! f = "[%.2]".parse().unwrap(); // format as percentages with 2 decimal places
183//! assert_eq!(f.fmt(0.234), "23.40%");
184//!
185//! f = "[b]".parse().unwrap(); // use a binary scaler
186//! assert_eq!(f.fmt(3.14 * 1024.0 * 1024.0), "3.14 Mi");
187//! ```
188//!
189//! ## Separator
190//! A separator character can be specified by using a forward slash `/` followed by a character.
191//! The parser uses the _next character_, unless that character is `]` in which case the
192//! separator is set to `None`. The default separator is a comma.
193//! If a period separator `.` is specified, we take this as a signal to use a comma `,` as
194//! the decimal signifier.
195//!
196//! ### Example
197//! ```rust
198//! # use numfmt::*;
199//! let mut f: Formatter;
200//! f = "[n]".parse().unwrap(); // turn off scaling to see separator
201//! assert_eq!(f.fmt(12345.0), "12,345.0");
202//!
203//! f = "[n/]".parse().unwrap(); // use no separator
204//! assert_eq!(f.fmt(12345.0), "12345.0");
205//!
206//! f = "[n/_]".parse().unwrap(); // use a underscroll
207//! assert_eq!(f.fmt(12345.0), "12_345.0");
208//!
209//! f = "[n/ ]".parse().unwrap(); // use a space
210//! assert_eq!(f.fmt(12345.0), "12 345.0");
211//!
212//! f = "[n/.]".parse().unwrap(); // use period and commas
213//! assert_eq!(f.fmt(12345.0), "12.345,0");
214//! ```
215//!
216//! ## Composing formats
217//! There have been examples of composing formats already. The `prefix[num]suffix` order must be
218//! adhered to, but the ordering within the number format is arbitrary. It is recommended to keep it
219//! consistent with _precision, scaling, separator_ as this assists with readability and lowers the
220//! risk of malformed formats (which will error on the parsing phase).
221//!
222//! ### Various composed examples
223//! ```rust
224//! # use numfmt::*;
225//! let mut f: Formatter;
226//!
227//! // Percentages to two decimal places
228//! f = "[.2%]".parse().unwrap();
229//! assert_eq!(f.fmt(0.012345), "1.23%");
230//!
231//! // Currency to zero decimal places
232//! // notice the `n` for no scaling
233//! f = "$[.0n] USD".parse().unwrap();
234//! assert_eq!(f.fmt(123_456_789.12345), "$123,456,789 USD");
235//!
236//! // Formatting file sizes
237//! f = "[~3b]B".parse().unwrap();
238//! assert_eq!(f.fmt(123_456_789.0), "117 MiB");
239//!
240//! // Units to 1 decimal place
241//! f = "[.1n] m/s".parse().unwrap();
242//! assert_eq!(f.fmt(12345.68), "12,345.6 m/s");
243//!
244//! // Using custom separator and period for decimals
245//! f = "[,1n/_]".parse().unwrap();
246//! assert_eq!(f.fmt(12345.68), "12_345,6");
247//! ```
248#![warn(missing_docs)]
249use std::{cmp::*, error, fmt, hash::*};
250use Precision::*;
251
252mod numeric;
253mod parse;
254#[cfg(test)]
255mod tests;
256
257pub use numeric::Numeric;
258pub use parse::ParseError;
259
260/// Result type for [`Formatter`] methods.
261pub type Result = std::result::Result<Formatter, Error>;
262
263const SN_BIG_CUTOFF: f64 = 1_000_000_000_000f64;
264const SN_SML_CUTOFF: f64 = 0.001;
265const SN_PREC: Precision = Significance(7);
266const PREFIX_LIM: usize = 12;
267const UNITS_LIM: usize = 12;
268const SUFFIX_LIM: usize = 12;
269const FLOATBUF_LEN: usize = 22;
270const BUF_LEN: usize = PREFIX_LIM + FLOATBUF_LEN + 3 + UNITS_LIM + SUFFIX_LIM;
271
272// ########### FORMATTER #################################################################
273/// The number formatter configurations. See the [module documentation for use][link].
274///
275/// [`Formatter`] has a `FromStr` implementation that can parse a string into a formatter using a
276/// specific grammar. Please [consult the parsing section in the module
277/// documentation](./index.html#parsing).
278///
279/// [link]: crate
280#[derive(Debug, Clone)]
281pub struct Formatter {
282 /// The formatter uses a buffer to avoid allocating when constructing the formatted string.
283 /// The formatting algorithm assumes the buffer size is large enough to accommodate writes into
284 /// it, care must be taken when altering what gets written to buffer. Ensure buffer is of
285 /// adequate size.
286 ///
287 /// The buffer is sized for:
288 /// - 12 bytes: prefix
289 /// - 22 bytes: float repr <https://github.com/dtolnay/dtoa/issues/22>
290 /// - 3 bytes: 3x thou separator
291 /// - 12 bytes: units
292 /// - 12 bytes: suffix
293 strbuf: Vec<u8>,
294 /// Optional thousands separator character (restricted to a single byte)
295 thou_sep: Option<u8>,
296 /// comma separation
297 comma: bool,
298 /// If prefixed with something, this is the start of the _number_ portion.
299 start: usize,
300 /// Precision limits to formatting.
301 precision: Precision,
302 /// The auto scales.
303 scales: Scales,
304 /// Optional suffix.
305 suffix: [u8; SUFFIX_LIM],
306 suffix_len: usize,
307 /// Direct conversion.
308 convert: fn(f64) -> f64,
309}
310
311impl Formatter {
312 /// Construct a new formatter.
313 ///
314 /// No scaling is set, so this is only does a single allocation for the buffer.
315 ///
316 /// # Example
317 /// ```rust
318 /// # use numfmt::*;
319 /// let mut f = Formatter::new();
320 /// assert_eq!(f.fmt(12345.6789), "12345.6789");
321 /// ```
322 pub fn new() -> Self {
323 Self {
324 strbuf: vec![0; BUF_LEN],
325 thou_sep: None,
326 start: 0,
327 precision: Precision::Unspecified,
328 scales: Scales::none(),
329 suffix: [0; SUFFIX_LIM],
330 suffix_len: 0,
331 convert: |x| x,
332 comma: false,
333 }
334 }
335
336 /// Create a formatter that formats numbers as a currency.
337 ///
338 /// # Example
339 /// ```rust
340 /// # use numfmt::*;
341 /// let mut f = Formatter::currency("$").unwrap();
342 /// assert_eq!(f.fmt(12345.6789), "$12,345.67");
343 /// assert_eq!(f.fmt(1234_f64), "$1,234.0");
344 /// ```
345 pub fn currency(prefix: &str) -> Result {
346 Self::new()
347 .separator(',')
348 .unwrap()
349 .precision(Decimals(2))
350 .prefix(prefix)
351 }
352
353 /// Create a formatter that formats numbers as a percentage.
354 ///
355 /// # Example
356 /// ```rust
357 /// # use numfmt::*;
358 /// let mut f = Formatter::percentage();
359 /// assert_eq!(f.fmt(0.678912), "67.8912%");
360 /// assert_eq!(f.fmt(1.23), "123.0%");
361 /// assert_eq!(f.fmt(1.2), "120.0%");
362 ///
363 /// f = f.precision(Precision::Decimals(2));
364 /// assert_eq!(f.fmt(0.01234), "1.23%");
365 /// ```
366 pub fn percentage() -> Self {
367 Self::new().convert(|x| x * 100.0).suffix("%").unwrap()
368 }
369
370 /// Set the value converter.
371 ///
372 /// Use a converter to transform the input number into another number. This is done before all
373 /// steps and the number follows the same procedure as normal. A good example of a use of a
374 /// converter is to make a percentage number by _always_ multiplying by 100.
375 pub fn convert(mut self, f: fn(f64) -> f64) -> Self {
376 self.convert = f;
377 self
378 }
379
380 /// Set the precision.
381 pub fn precision(mut self, precision: Precision) -> Self {
382 self.precision = precision;
383 self
384 }
385
386 /// Set the scaling.
387 pub fn scales(mut self, scales: Scales) -> Self {
388 self.scales = scales;
389 self
390 }
391
392 /// Set the scaling via [`Scales::new`].
393 pub fn build_scales(mut self, base: u16, units: Vec<&'static str>) -> Result {
394 let scales = Scales::new(base, units)?;
395 self.scales = scales;
396 Ok(self)
397 }
398
399 /// Set the thousands separator.
400 ///
401 /// If separator is not a single byte, an error is returned.
402 /// If the separator is a period `.`, this signals to use a comma for the decimal marker.
403 ///
404 /// # Example
405 /// ```rust
406 /// # use numfmt::*;
407 /// let mut f = Formatter::new().separator(',').unwrap(); // use a comma
408 /// assert_eq!(f.fmt(12345.67), "12,345.67");
409 ///
410 /// f = f.separator(' ').unwrap(); // use a space
411 /// assert_eq!(f.fmt(12345.67), "12 345.67");
412 ///
413 /// f = f.separator(None).unwrap(); // no separator
414 /// assert_eq!(f.fmt(12345.67), "12345.67");
415 ///
416 /// f = f.separator('.').unwrap(); // use a period separator and comma for decimal
417 /// assert_eq!(f.fmt(12345.67), "12.345,67");
418 /// ```
419 pub fn separator<S: Into<Option<char>>>(mut self, sep: S) -> Result {
420 if let Some(sep) = sep.into() {
421 if sep.len_utf8() != 1 {
422 Err(Error::InvalidSeparator(sep))
423 } else {
424 if sep == '.' {
425 self.comma = true;
426 }
427 let mut buf = [0];
428 sep.encode_utf8(&mut buf);
429 self.thou_sep = Some(buf[0]);
430 Ok(self)
431 }
432 } else {
433 self.thou_sep = None;
434 Ok(self)
435 }
436 }
437
438 /// Set the comma option.
439 ///
440 /// If set to true it will use a comma instead of a period.
441 /// If a comma is the separator, a period will be used instead.
442 ///
443 /// # Example
444 ///
445 /// ```rust
446 /// # use numfmt::*;
447 /// let mut f = Formatter::new();
448 /// assert_eq!(f.fmt(12345.67), "12345.67");
449 /// f = f.comma(true);
450 /// assert_eq!(f.fmt(12345.67), "12345,67");
451 ///
452 /// f = f.separator('.').unwrap();
453 /// assert_eq!(f.fmt(12345.67), "12.345,67");
454 /// ```
455 pub fn comma(mut self, comma: bool) -> Self {
456 self.comma = comma;
457 if comma && self.thou_sep == Some(b',') {
458 self.thou_sep = Some(b'.');
459 }
460 self
461 }
462
463 /// Sets the prefix.
464 ///
465 /// If the prefix is longer than the supported length, an error is returned.
466 pub fn prefix(mut self, prefix: &str) -> Result {
467 if prefix.len() > PREFIX_LIM {
468 Err(Error::InvalidPrefix(prefix.to_string()))
469 } else {
470 let n = prefix.len();
471 self.strbuf[..n].copy_from_slice(prefix.as_bytes());
472 self.start = n;
473 Ok(self)
474 }
475 }
476
477 /// Set the suffix.
478 ///
479 /// If the suffix is longer than the supported length, an error is returned.
480 pub fn suffix(mut self, suffix: &str) -> Result {
481 if suffix.len() > SUFFIX_LIM {
482 Err(Error::InvalidSuffix(suffix.to_string()))
483 } else {
484 let n = suffix.len();
485 self.suffix[..n].copy_from_slice(suffix.as_bytes());
486 self.suffix_len = n;
487 Ok(self)
488 }
489 }
490
491 /// Format the number!
492 #[deprecated = "consider using Formatter::fmt2 instead"]
493 pub fn fmt(&mut self, num: f64) -> &str {
494 self.fmt2(num)
495 }
496
497 /// Format any number implementing [`Numeric`].
498 pub fn fmt2<N: Numeric>(&mut self, num: N) -> &str {
499 let mut buf = std::mem::take(&mut self.strbuf);
500 let bytes = self.fmt_into_buf(&mut buf, num);
501 self.strbuf = buf;
502 std::str::from_utf8(&self.strbuf[..bytes]).expect("will be valid string")
503 }
504
505 /// Format any number implementing [`Numeric`], appending to the supplied `buf`.
506 ///
507 /// This is functionally the same as [`Self::fmt2`], however it does not use the backing
508 /// buffer, instead extending the supplied string.
509 /// Useful when the receiver is shared.
510 pub fn fmt_into<N: Numeric>(&self, buf: &mut String, num: N) {
511 let start = buf.len();
512
513 // pad string buffer to write into
514 buf.extend(std::iter::repeat_n('\0', self.strbuf.len()));
515
516 // SAFETY: Only UTF-8 characters will be written.
517 let bytes = unsafe { buf.as_bytes_mut() };
518
519 // write the prefix into the buffer
520 bytes[start..start + self.start].copy_from_slice(&self.strbuf[..self.start]);
521
522 let written = self.fmt_into_buf(&mut bytes[start..], num);
523 let end = start + written;
524
525 buf.truncate(end); // drop the unused padding
526
527 debug_assert!(std::str::from_utf8(buf.as_bytes()).is_ok());
528 }
529
530 /// Format any number implementing [`Numeric`], returning an owned [`String`].
531 ///
532 /// This is functionally the same as [`Self::fmt2`], however it does not use the backing
533 /// buffer, instead allocating a new string to write into.
534 /// Useful when the receiver is shared.
535 pub fn fmt_string<N: Numeric>(&self, num: N) -> String {
536 let mut buf = String::new();
537 self.fmt_into(&mut buf, num);
538 buf
539 }
540
541 /// Format the number into `strbuf`. Returns the number of bytes written.
542 fn fmt_into_buf<N: Numeric>(&self, strbuf: &mut [u8], num: N) -> usize {
543 debug_assert_eq!(
544 strbuf.len(),
545 BUF_LEN,
546 "the buffer is expected to be {BUF_LEN} wide"
547 );
548
549 if num.is_nan() {
550 strbuf[..3].copy_from_slice(b"NaN");
551 3
552 } else if num.is_infinite() && num.is_negative() {
553 strbuf[..4].copy_from_slice(b"-\xE2\x88\x9E"); // -∞
554 4
555 } else if num.is_infinite() {
556 strbuf[..3].copy_from_slice(b"\xE2\x88\x9E");
557 3
558 } else if num.is_zero() {
559 strbuf[..1].copy_from_slice(b"0");
560 1
561 } else {
562 let num = (self.convert)(num.to_f64());
563
564 // scale num to supplied scales
565 let (scaled, unit) = self.scales.scale(num);
566
567 // check if the scaled version hits sn cutoffs
568 // use original number if it does
569 let abs = scaled.abs();
570 // This adjusts the sn cutoff if decimals is low
571 let sn_sml_cutoff = match self.precision {
572 Decimals(d) | Significance(d) if d <= 3 => 10f64.powi(d as i32).recip(),
573 _ => SN_SML_CUTOFF,
574 };
575 if abs >= SN_BIG_CUTOFF || abs < sn_sml_cutoff {
576 // fmt with scientific notation
577 let (num, exponent) = reduce_to_sn(num);
578 let precision = match self.precision {
579 Unspecified => SN_PREC,
580 x => x,
581 };
582 let cursor = self.start + self.write_num(strbuf, num, precision);
583 strbuf[cursor] = b'e'; // exponent
584 let cursor = 1 + cursor;
585 let written = {
586 let mut buf = itoa::Buffer::new();
587 let s = buf.format(exponent);
588 let end = cursor + s.len();
589 strbuf[cursor..end].copy_from_slice(s.as_bytes());
590 s.len()
591 };
592 let cursor = cursor + written;
593 self.apply_suffix(strbuf, cursor)
594 } else {
595 // write out the scaled number
596 let mut cursor = self.start + self.write_num(strbuf, scaled, self.precision);
597 if !unit.is_empty() {
598 let s = cursor;
599 cursor += unit.len();
600 strbuf[s..cursor].copy_from_slice(unit.as_bytes());
601 }
602 self.apply_suffix(strbuf, cursor)
603 }
604 }
605 }
606
607 /// Writes `num` into the string buffer with the specified `precision`.
608 /// Returns the number of bytes written.
609 /// Injects the thousands separator into the integer portion if it exists.
610 fn write_num(&self, strbuf: &mut [u8], num: f64, precision: Precision) -> usize {
611 let mut tmp = dtoa::Buffer::new();
612 let s = tmp.format(num);
613 let tmp = s.as_bytes();
614 let n = tmp.len();
615 let mut digits = 0;
616 let mut written = 0;
617 let mut in_frac = false;
618 let mut thou = 2 - (num.abs().log10().trunc() as u8) % 3;
619 let mut idx = self.start;
620
621 for i in 0..n {
622 let byte = tmp[i]; // obtain byte
623 strbuf[idx] = byte; // write byte
624 idx += 1;
625 written += 1; // increment counter
626
627 if byte.is_ascii_digit() {
628 digits += 1;
629 thou += 1;
630 }
631
632 // look ahead otherwise it would include the decimal always even for 0 precision
633 if i + 1 < n && tmp[i + 1] == b'.' {
634 in_frac = true;
635 if let Decimals(_) = precision {
636 digits = 0
637 }
638 } else if in_frac && byte == b'.' && self.comma {
639 strbuf[idx - 1] = b',';
640 } else if !in_frac && thou == 3 {
641 if let Some(sep) = self.thou_sep {
642 thou = 0;
643 strbuf[idx] = sep;
644 idx += 1;
645 written += 1;
646 }
647 }
648
649 match precision {
650 Significance(d) | Decimals(d) if in_frac => {
651 if digits >= d {
652 break;
653 }
654 }
655 _ => (),
656 }
657 }
658
659 written
660 }
661
662 fn apply_suffix(&self, strbuf: &mut [u8], mut pos: usize) -> usize {
663 if !self.suffix.is_empty() {
664 let s = pos;
665 pos = s + self.suffix_len;
666 strbuf[s..pos].copy_from_slice(&self.suffix[..self.suffix_len]);
667 }
668 pos
669 }
670}
671
672impl Default for Formatter {
673 fn default() -> Self {
674 Self::new()
675 .separator(',')
676 .unwrap()
677 .scales(Scales::short())
678 .precision(Decimals(3))
679 }
680}
681
682impl std::str::FromStr for Formatter {
683 type Err = parse::ParseError;
684 fn from_str(s: &str) -> std::result::Result<Self, ParseError> {
685 parse::parse_formatter(s)
686 }
687}
688
689// Eq and Hash have specialised impls as the _state_ of the buffer should not impact equality
690// checking
691impl PartialEq for Formatter {
692 #[allow(clippy::suspicious_operation_groupings)]
693 fn eq(&self, other: &Self) -> bool {
694 std::ptr::fn_addr_eq(self.convert, other.convert)
695 && self.precision == other.precision
696 && self.thou_sep == other.thou_sep
697 // need to use the other suffix len.
698 && self.suffix[..self.suffix_len] == other.suffix[..other.suffix_len]
699 && self.strbuf[..self.start] == other.strbuf[..other.start]
700 && self.scales == other.scales
701 }
702}
703
704impl Eq for Formatter {}
705
706impl Hash for Formatter {
707 fn hash<H: Hasher>(&self, hasher: &mut H) {
708 self.strbuf[..self.start].hash(hasher);
709 self.thou_sep.hash(hasher);
710 self.precision.hash(hasher);
711 self.scales.hash(hasher);
712 self.suffix[..self.suffix_len].hash(hasher);
713 self.convert.hash(hasher);
714 }
715}
716
717/// Returns `(reduced, exponent)`.
718fn reduce_to_sn(n: f64) -> (f64, i32) {
719 if n == 0.0 || n == -0.0 {
720 (0.0, 0)
721 } else {
722 let abs = n.abs();
723 let mut e = abs.log10().trunc() as i32;
724 if abs < 1.0 {
725 e -= 1;
726 }
727 let n = n * 10_f64.powi(-e);
728 (n, e)
729 }
730}
731
732// ########### ERROR #####################################################################
733/// Errors when configuring a [`Formatter`].
734#[derive(Debug, PartialEq)]
735pub enum Error {
736 /// Prefix is longer than supported length.
737 InvalidPrefix(String),
738 /// Separator is not a byte long.
739 InvalidSeparator(char),
740 /// Suffix is longer than supported length.
741 InvalidSuffix(String),
742 /// Unit is longer than supported length.
743 InvalidUnit(&'static str),
744 /// Scaling base is 0.
745 ZeroBase,
746}
747
748impl error::Error for Error {}
749
750impl fmt::Display for Error {
751 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
752 use Error::*;
753 match self {
754 InvalidPrefix(prefix) => write!(
755 f,
756 "Invalid prefix `{prefix}`. Prefix is longer than the supported {PREFIX_LIM} bytes"
757 ),
758 InvalidSeparator(sep) => write!(
759 f,
760 "Invalid separator `{sep}`. Separator can only be one byte long"
761 ),
762 InvalidSuffix(suffix) => write!(
763 f,
764 "Invalid suffix `{suffix}`. Suffix is longer than the supported {SUFFIX_LIM} bytes"
765 ),
766 InvalidUnit(unit) => write!(
767 f,
768 "Invalid unit `{unit}`. Unit is longer than the supported {UNITS_LIM} bytes"
769 ),
770 ZeroBase => write!(f, "Invalid scale base, base must be greater than zero"),
771 }
772 }
773}
774
775// ########### PRECISION #################################################################
776/// Number precision.
777#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
778#[allow(missing_docs)]
779pub enum Precision {
780 Significance(u8),
781 Decimals(u8),
782 Unspecified,
783}
784
785// ########### SCALES ####################################################################
786/// Scale numbers.
787#[derive(Debug, PartialEq, Eq, Clone, Hash)]
788pub struct Scales {
789 base: u16,
790 units: Vec<&'static str>,
791}
792
793impl Scales {
794 /// Create a new scale.
795 ///
796 /// If a unit is longer than the supported length, an error will be returned.
797 pub fn new(base: u16, units: Vec<&'static str>) -> std::result::Result<Self, Error> {
798 if base == 0 {
799 return Err(Error::ZeroBase);
800 }
801
802 for unit in &units {
803 if unit.len() > UNITS_LIM {
804 return Err(Error::InvalidUnit(unit));
805 }
806 }
807 Ok(Self { base, units })
808 }
809
810 /// Create a scale which is dummy and does not scale.
811 pub fn none() -> Self {
812 Self {
813 base: u16::MAX,
814 units: Vec::new(),
815 }
816 }
817
818 /// The default scaling method.
819 ///
820 /// Based on a [short scale](https://en.wikipedia.org/wiki/Long_and_short_scales)
821 /// the scaling uses base `1000`. The units are meant to be used to denote _magnitude_ of the
822 /// number, so the empty base is empty.
823 ///
824 /// # Example
825 /// ```rust
826 /// # use numfmt::*;
827 /// let mut f = Formatter::default()
828 /// .scales(Scales::short())
829 /// .precision(Precision::Decimals(1));
830 /// assert_eq!(f.fmt(12.34e0), "12.3");
831 /// assert_eq!(f.fmt(12.34e3), "12.3 K");
832 /// assert_eq!(f.fmt(12.34e6), "12.3 M");
833 /// assert_eq!(f.fmt(12.34e9), "12.3 B");
834 /// assert_eq!(f.fmt(12.34e12), "12.3 T");
835 /// assert_eq!(f.fmt(12.34e15), "12.3 P");
836 /// assert_eq!(f.fmt(12.34e18), "12.3 E");
837 /// assert_eq!(f.fmt(12.34e21), "12.3 Z");
838 /// assert_eq!(f.fmt(12.34e24), "12.3 Y");
839 /// assert_eq!(f.fmt(12.34e27), "12,339.9 Y");
840 /// ```
841 pub fn short() -> Self {
842 Scales {
843 base: 1000,
844 units: vec!["", " K", " M", " B", " T", " P", " E", " Z", " Y"],
845 }
846 }
847
848 /// Create a metric SI scale.
849 ///
850 /// The [SI scale](https://en.wikipedia.org/wiki/International_System_of_Units#Prefixes)
851 /// steps with base `1000`. It is intended for use as a units prefix, so the empty base
852 /// contains a space.
853 ///
854 /// # Example
855 /// ```rust
856 /// # use numfmt::*;
857 /// let mut f = Formatter::new().scales(Scales::metric());
858 /// assert_eq!(f.fmt(123456.0), "123.456 k");
859 /// assert_eq!(f.fmt(123456789.0), "123.456789 M");
860 /// ```
861 pub fn metric() -> Self {
862 Scales {
863 base: 1000,
864 units: vec![" ", " k", " M", " G", " T", " P", " E", " Z", " Y"],
865 }
866 }
867
868 /// Create a binary scale.
869 ///
870 /// The [binary scale](https://en.wikipedia.org/wiki/Binary_prefix)
871 /// steps with base `1024`. It is intended for use as a units prefix, so the empty base
872 /// contains a space.
873 ///
874 /// # Example
875 /// ```rust
876 /// # use numfmt::*;
877 /// let mut f = Formatter::new().scales(Scales::binary());
878 /// assert_eq!(f.fmt(1024.0 * 1024.0), "1.0 Mi");
879 /// assert_eq!(f.fmt(3.14 * 1024.0 * 1024.0), "3.14 Mi");
880 /// ```
881 pub fn binary() -> Self {
882 Scales {
883 base: 1024,
884 units: vec![" ", " ki", " Mi", " Gi", " Ti", " Pi", " Ei", " Zi", " Yi"],
885 }
886 }
887
888 /// The set base.
889 pub fn base(&self) -> u16 {
890 self.base
891 }
892
893 /// The set units.
894 pub fn units(&self) -> &[&'static str] {
895 self.units.as_slice()
896 }
897
898 /// Extract the `(base, units)`.
899 pub fn into_inner(self) -> (u16, Vec<&'static str>) {
900 (self.base, self.units)
901 }
902
903 /// Scale a number and return the scaled number with the unit.
904 pub fn scale(&self, mut num: f64) -> (f64, &'static str) {
905 let base = self.base as f64;
906 let mut u = "";
907 let mut n2 = num;
908 // use n2 as a delayed write to not downsize num on last entry numbers
909 for unit in &self.units {
910 num = n2;
911 u = unit;
912 if num.abs() >= base {
913 n2 = num / base;
914 } else {
915 break;
916 }
917 }
918 (num, u)
919 }
920}