si_scale/
lib.rs

1#![warn(missing_docs)]
2#![allow(clippy::needless_doctest_main)]
3
4//! Format value with units according to SI ([système international d'unités](https://en.wikipedia.org/wiki/International_System_of_Units)).
5//!
6//! Version requirement: _rustc 1.74+_
7//!
8//! ```toml
9//! [dependencies]
10//! si-scale = "0.2"
11//! ```
12//!
13//! ## Overview
14//!
15//! This crate formats numbers using the
16//! [SI Scales](https://en.wikipedia.org/wiki/International_System_of_Units):
17//! from 1 y (yocto, i.e. 1e-24) to 1 Y (Yotta, i.e. 1e24).
18//!
19//! It has the same purpose as the great
20//! [human-repr](https://docs.rs/human-repr), but strikes a different balance:
21//!
22//! - this crate yields more terse code at the call sites
23//! - it gives you more control over the output. As shown later in this page,
24//!   you can extend it pretty easily to handle throughput, etc. (seriously, see
25//!   below)
26//! - but it only operates on numbers, so it does not prevent you from using a
27//!   function to print meters on a duration value (which human-repr does
28//!   brilliantly).
29//!
30//! ## Getting started
31//!
32//! To use this crate, either use one of the few pre-defined helper functions,
33//! or build your own.
34//!
35//! Basic example:
36//!
37//! ```rust
38//! use si_scale::helpers::{seconds, seconds3};
39//!
40//! let actual = format!("{}", seconds(1.3e-5));
41//! let expected = "13 µs";
42//! assert_eq!(actual, expected);
43//!
44//! let actual = format!("{}", seconds3(1.3e-5));
45//! let expected = "13.000 µs";
46//! assert_eq!(actual, expected);
47//! ```
48//!
49//! ## Pre-defined helper functions
50//!
51//! The helper functions use the following naming convention:
52//!
53//! - the name indicates the units to use
54//! - a number suffix indicates the decimal digits for floating points
55//! - a `_` suffix indicates the digits use "thousands grouping"
56//!
57//! But that's up to you to depart from that when writing your own functions.
58//!
59//! Currently the helper functions are:
60//!
61//! | helper fn    | input                  | output                 |
62//! | ---          | ---                    | ---                    |
63//! | `number_()`  | `1.234567`, `1515`     | `1.234_567`, `1_515`   |
64//! | ---          | ---                    | ---                    |
65//! | `seconds()`  | `1.234567e-6`, `16e-3` | `1.234567 µs`, `16 ms` |
66//! | `seconds3()` | `1.234567e-6`, `16e-3` | `1.235 µs`, `16.000 ms`|
67//! | ---          | ---                    | ---                    |
68//! | `bytes()`    | `1234567`              | `1.234567 MB`          |
69//! | `bytes_()`   | `1234567`              | `1_234_567 B`          |
70//! | `bytes1()`   | `2.3 * 1e12`           | `2.3 TB`               |
71//! | `bytes2()`   | `2.3 * 1e12`           | `2.30 TB`              |
72//! | ---          | ---                    | ---                    |
73//! | `bibytes()`  | `1024 * 1024 * 1.25`   | `1.25 MiB`             |
74//! | `bibytes1()` | `1024 * 1024 * 1.25`   | `1.3 MiB`              |
75//! | `bibytes2()` | `1024 * 1024 * 1.25`   | `1.25 MiB`             |
76//!
77//! ## Custom helper functions - BYOU (bring your own unit)
78//!
79//! To define your own format function, use the
80//! [`scale_fn!()`](`crate::scale_fn!()`) macro. All pre-defined helper
81//! functions from this crate are defined using this macro.
82//!
83//! | helper fn    | mantissa  | prefix constraint | base  | groupings | input                  | output                 |
84//! | ---          | --        | ---               | ---   | ---       | ---                    | ---                    |
85//! | `number_()`  | `"{}"`    | `UnitOnly`        | B1000 | `_`       | `1.234567`, `1515`     | `1.234_567`, `1_515`   |
86//! | ---          | --        | ---               | ---   | ---       | ---                    | ---                    |
87//! | `seconds()`  | `"{}"`    | `UnitAndBelow`    | B1000 | none      | `1.234567e-6`, `16e-3` | `1.234567 µs`, `16 ms` |
88//! | `seconds3()` | `"{:.3}"` | `UnitAndBelow`    | B1000 | none      | `1.234567e-6`, `16e-3` | `1.235 µs`, `16.000 ms`|
89//! | ---          | --        | ---               | ---   | ---       | ---                    | ---                    |
90//! | `bytes()`    | `"{}"`    | `UnitAndAbove`    | B1000 | none      | `1234567`              | `1.234567 MB`          |
91//! | `bytes_()`   | `"{}"`    | `UnitOnly`        | B1000 | `_`       | `1234567`              | `1_234_567 B`          |
92//! | `bytes1()`   | `"{:.1}"` | `UnitAndAbove`    | B1000 | none      | `2.3 * 1e12`           | `2.3 TB`               |
93//! | `bytes2()`   | `"{:.2}"` | `UnitAndAbove`    | B1000 | none      | `2.3 * 1e12`           | `2.30 TB`              |
94//! | ---          | --        | ---               | ---   | ---       | ---                    | ---                    |
95//! | `bibytes()`  | `"{}"`    | `UnitAndAbove`    | B1024 | none      | `1024 * 1024 * 1.25`   | `1.25 MiB`             |
96//! | `bibytes1()` | `"{:.1}"` | `UnitAndAbove`    | B1024 | none      | `1024 * 1024 * 1.25`   | `1.3 MiB`              |
97//! | `bibytes2()` | `"{:.2}"` | `UnitAndAbove`    | B1024 | none      | `1024 * 1024 * 1.25`   | `1.25 MiB`             |
98//!
99//! The additional table columns show the underlying controls.
100//!
101//! ### The "mantissa" column
102//!
103//! It is a format string which only acts on the mantissa after scaling. For
104//! instance, `"{}"` will display the value with all its digits or no digits if
105//! it is round, and `"{:.1}"` for instance will always display one decimal.
106//!
107//! ### The "prefix constraint" column
108//!
109//! In a nutshell, this allows values to be represented in unsurprising scales:
110//! for instance, you would never write `1.2 ksec`, but always `1200 sec` or
111//! `1.2e3 sec`. In the same vein, you would never write `2 mB`, but always
112//! `0.002 B` or `2e-3 B`.
113//!
114//! So, here the term "unit" refers to the unit scale (`1`), and has nothing to
115//! do with units of measurements. It constrains the possible scales for a
116//! value:
117//!
118//! - `UnitOnly` means the provided value won't be scaled: if you provide a
119//!   value larger than 1000, say 1234, it will be printed as 1234.
120//! - `UnitAndAbove` means the provided value can only use higher scales, for
121//!   instance `16 GB` but never `4.3 µB`.
122//! - `UnitAndBelow` means the provided value can only use lower scales, for
123//!   instance `1.3 µsec` but not `16 Gsec`.
124//!
125//! ### The "base" column
126//!
127//! Base B1000 means 1k = 1000, the base B1024 means 1k = 1024. This is defined
128//! in an [IEC document](https://www.iec.ch/prefixes-binary-multiples). If you
129//! set the base to `B1024`, the mantissa will be scaled appropriately, but in
130//! most cases, you will be using `B1000`.
131//!
132//! ### The "groupings" column
133//!
134//! Groupings refer to "thousands groupings"; the provided char will be
135//! used (for instance 1234 is displayed as 1\_234), if none, the value is
136//! displayed 1234.
137//!
138//! ### Example - how to define a helper for kibits/s
139//!
140//! For instance, let's define a formatting function for bits per sec which
141//! prints the mantissa with 2 decimals, and also uses base 1024 (where 1 ki =
142//! 1024). Note that although we define the function in a separate module,
143//! this is not a requirement.
144//!
145//! ```rust
146//! mod unit_fmt {
147//!     use si_scale::scale_fn;
148//!     use si_scale::prelude::Value;
149//!
150//!     // defines the `bits_per_sec()` function
151//!     scale_fn!(bits_per_sec,
152//!               base: B1024,
153//!               constraint: UnitAndAbove,
154//!               mantissa_fmt: "{:.2}",
155//!               groupings: '_',
156//!               unit: "bit/s",
157//!               doc: "Return a string with the value and its si-scaled unit of bit/s.");
158//! }
159//!
160//! use unit_fmt::bits_per_sec;
161//!
162//! fn main() {
163//!     let x = 2.1 * 1024 as f32;
164//!     let actual = format!("throughput: {:>15}", bits_per_sec(x));
165//!     let expected = "throughput:    2.10 kibit/s";
166//!     assert_eq!(actual, expected);
167//!
168//!     let x = 2;
169//!     let actual = format!("throughput: {}", bits_per_sec(x));
170//!     let expected = "throughput: 2.00 bit/s";
171//!     assert_eq!(actual, expected);
172//! }
173//!
174//! ```
175//!
176//! You can omit the `groupings` argument of the macro to not separate
177//! thousands.
178//!
179//! ## SI Scales - Developer doc
180//!
181//! With base = 1000, 1k = 1000, 1M = 1\_000\_000, 1m = 0.001, 1µ = 0.000\_001,
182//! etc.
183//!
184//! | min (incl.) | max (excl.)      | magnitude | prefix          |
185//! | ---         | ---              | ---       | ----            |
186//! | ..          | ..               | -24       | `Prefix::Yocto` |
187//! | ..          | ..               | -21       | `Prefix::Zepto` |
188//! | ..          | ..               | -18       | `Prefix::Atto`  |
189//! | ..          | ..               | -15       | `Prefix::Femto` |
190//! | ..          | ..               | -12       | `Prefix::Pico`  |
191//! | ..          | ..               | -9        | `Prefix::Nano`  |
192//! | 0.000\_001  | 0.001            | -6        | `Prefix::Micro` |
193//! | 0.001       | 1                | -3        | `Prefix::Milli` |
194//! | 1           | 1_000            | 0         | `Prefix::Unit`  |
195//! | 1000        | 1\_000\_000      | 3         | `Prefix::Kilo`  |
196//! | 1\_000\_000 | 1\_000\_000\_000 | 6         | `Prefix::Mega`  |
197//! | ..          | ..               | 9         | `Prefix::Giga`  |
198//! | ..          | ..               | 12        | `Prefix::Tera`  |
199//! | ..          | ..               | 15        | `Prefix::Peta`  |
200//! | ..          | ..               | 18        | `Prefix::Exa`   |
201//! | ..          | ..               | 21        | `Prefix::Zetta` |
202//! | ..          | ..               | 24        | `Prefix::Yotta` |
203//!
204//! The base is usually 1000, but can also be 1024 (bibytes).
205//!
206//! With base = 1024, 1ki = 1024, 1Mi = 1024 * 1024, etc.
207//!
208//! ### API overview
209//!
210//! The central representation is the [`Value`](`crate::value::Value`) type,
211//! which holds
212//!
213//! - the mantissa,
214//! - the SI unit prefix (such as "kilo", "Mega", etc),
215//! - and the base which represents the cases where "1 k" means 1000 (most
216//!   common) and the cases where "1 k" means 1024 (for kiB, MiB, etc).
217//!
218//! This crate provides 2 APIs: a low-level API, and a high-level API for
219//! convenience.
220//!
221//! For the low-level API, the typical use case is
222//!
223//! - first parse a number into a [`Value`](`crate::value::Value`). For doing
224//!   this, you have to specify the base, and maybe some constraint on the SI
225//!   scales. See [`Value::new()`](`crate::value::Value::new()`) and
226//!   [`Value::new_with()`](`crate::value::Value::new_with()`)
227//!
228//! - then display the `Value` either by yourself formatting the mantissa
229//!   and prefix (implements the `fmt::Display` trait), or using the provided
230//!   Formatter.
231//!
232//! For the high-level API, the typical use cases are
233//!
234//! 1. parse and display a number using the provided functions such as
235//!    `bibytes()`, `bytes()` or `seconds()`, they will choose for each number
236//!    the most appropriate SI scale.
237//!
238//! 2. In case you want the same control granularity as the low-level API
239//!    (e.g. constraining the scale in some way, using some base, specific
240//!    mantissa formatting), then you can build a custom function using the
241//!    provided macro `scale_fn!()`. The existing functions such as
242//!    `bibytes()`, `bytes()`, `seconds()` are all built using this same
243//!    macro.
244//!
245//! ### The high-level API
246//!
247//! The `seconds3()` function parses a number into a `Value` and displays it
248//! using 3 decimals and the appropriate scale for seconds (`UnitAndBelow`),
249//! so that non-sensical scales such as kilo-seconds can't be output. The
250//! `seconds()` function does the same but formats the mantissa with the
251//! default `"{}"`, so no decimals are printed for integer mantissa.
252//!
253//! ```
254//! use si_scale::helpers::{seconds, seconds3};
255//!
256//! let actual = format!("result is {:>15}", seconds(1234.5678));
257//! let expected = "result is     1234.5678 s";
258//! assert_eq!(actual, expected);
259//!
260//! let actual = format!("result is {:>10}", seconds3(12.3e-7));
261//! let expected = "result is   1.230 µs";
262//! assert_eq!(actual, expected);
263//! ```
264//!
265//! The `bytes()` function parses a number into a `Value` _using base 1000_
266//! and displays it using 1 decimal and the appropriate scale for bytes
267//! (`UnitAndAbove`), so that non-sensical scales such as milli-bytes may not
268//! appear.
269//!
270//! ```
271//! use si_scale::helpers::{bytes, bytes1};
272//!
273//! let actual = format!("result is {}", bytes1(12_345_678));
274//! let expected = "result is 12.3 MB";
275//! assert_eq!(actual, expected);
276//!
277//! let actual = format!("result is {:>10}", bytes(16));
278//! let expected = "result is       16 B";
279//! assert_eq!(actual, expected);
280//!
281//! let actual = format!("result is {}", bytes(0.12));
282//! let expected = "result is 0.12 B";
283//! assert_eq!(actual, expected);
284//! ```
285//!
286//! The `bibytes1()` function parses a number into a `Value` _using base 1024_
287//! and displays it using 1 decimal and the appropriate scale for bytes
288//! (`UnitAndAbove`), so that non-sensical scales such as milli-bytes may not
289//! appear.
290//!
291//! ```
292//! use si_scale::helpers::{bibytes, bibytes1};
293//!
294//! let actual = format!("result is {}", bibytes1(12_345_678));
295//! let expected = "result is 11.8 MiB";
296//! assert_eq!(actual, expected);
297//!
298//! let actual = format!("result is {}", bibytes(16 * 1024));
299//! let expected = "result is 16 kiB";
300//! assert_eq!(actual, expected);
301//!
302//! let actual = format!("result is {:>10}", bibytes1(16));
303//! let expected = "result is     16.0 B";
304//! assert_eq!(actual, expected);
305//!
306//! let actual = format!("result is {}", bibytes(0.12));
307//! let expected = "result is 0.12 B";
308//! assert_eq!(actual, expected);
309//! ```
310//!
311//! ### The low-level API
312//!
313//! #### Creating a `Value` with `Value::new()`
314//!
315//! The low-level function [`Value::new()`](`crate::value::Value::new()`)
316//! converts any number convertible to f64 into a `Value` using base 1000. The
317//! `Value` struct implements `From` for common numbers and delegates to
318//! `Value::new()`, so they are equivalent in practice. Here are a few
319//! examples.
320//!
321//! ```rust
322//! use std::convert::From;
323//! use si_scale::prelude::*;
324//!
325//! let actual = Value::from(0.123);
326//! let expected = Value {
327//!     mantissa: 123f64,
328//!     prefix: Prefix::Milli,
329//!     base: Base::B1000,
330//! };
331//! assert_eq!(actual, expected);
332//! assert_eq!(Value::new(0.123), expected);
333//!
334//! let actual: Value = 0.123.into();
335//! assert_eq!(actual, expected);
336//!
337//! let actual: Value = 1300i32.into();
338//! let expected = Value {
339//!     mantissa: 1.3f64,
340//!     prefix: Prefix::Kilo,
341//!     base: Base::B1000,
342//! };
343//! assert_eq!(actual, expected);
344//!
345//! let actual: Vec<Value> = vec![0.123f64, -1.5e28]
346//!     .iter().map(|n| n.into()).collect();
347//! let expected = vec![
348//!     Value {
349//!         mantissa: 123f64,
350//!         prefix: Prefix::Milli,
351//!         base: Base::B1000,
352//!     },
353//!     Value {
354//!         mantissa: -1.5e4f64,
355//!         prefix: Prefix::Yotta,
356//!         base: Base::B1000,
357//!     },
358//! ];
359//! assert_eq!(actual, expected);
360//! ```
361//!
362//! As you can see in the last example, values which scale are outside of the
363//! SI prefixes are represented using the closest SI prefix.
364//!
365//! #### Creating a `Value` with `Value::new_with()`
366//!
367//! The low-level [`Value::new_with()`](`crate::value::Value::new_with()`)
368//! operates similarly to [`Value::new()`](`crate::value::Value::new()`) but
369//! also expects a base and a constraint on the scales you want to use. In
370//! comparison with the simple `Value::new()`, this allows base 1024 scaling
371//! (for kiB, MiB, etc) and preventing upper scales for seconds or lower
372//! scales for integral units such as bytes (e.g. avoid writing 1300 sec as
373//! 1.3 ks or 0.415 B as 415 mB).
374//!
375//! ```rust
376//! use si_scale::prelude::*;
377//!
378//! // Assume this is seconds, no kilo-seconds make sense.
379//! let actual = Value::new_with(1234, Base::B1000, Constraint::UnitAndBelow);
380//! let expected = Value {
381//!     mantissa: 1234f64,
382//!     prefix: Prefix::Unit,
383//!     base: Base::B1000,
384//! };
385//! assert_eq!(actual, expected);
386//! ```
387//!
388//! Don't worry yet about the verbosity, the following parser helps with this.
389//!
390//! #### Formatting values
391//!
392//! In this example, the number `x` is converted into a value and displayed
393//! using the most appropriate SI prefix. The user chose to constrain the
394//! prefix to be anything lower than `Unit` (1) because kilo-seconds make
395//! no sense.
396//!
397//! ```
398//! use si_scale::format_value;
399//! # fn main() {
400//! use si_scale::{value::Value, base::Base, prefix::Constraint};
401//!
402//! let x = 1234.5678;
403//! let v = Value::new_with(x, Base::B1000, Constraint::UnitAndBelow);
404//! let unit = "s";
405//!
406//! let actual = format!(
407//!     "result is {}{u}",
408//!     format_value!(v, "{:.5}", groupings: '_'),
409//!     u = unit
410//! );
411//! let expected = "result is 1_234.567_80 s";
412//! assert_eq!(actual, expected);
413//! # }
414//! ```
415//!
416//! ## Run code-coverage
417//!
418//! Install the llvm-tools-preview component and grcov
419//!
420//! ```sh
421//! rustup component add llvm-tools-preview
422//! cargo install grcov
423//! ```
424//!
425//! Install nightly
426//!
427//! ```sh
428//! rustup toolchain install nightly
429//! ```
430//!
431//! The following make invocation will switch to nigthly run the tests using
432//! Cargo, and output coverage HTML report in `./coverage/`
433//!
434//! ```sh
435//! make coverage
436//! ```
437//!
438//! The coverage report is located in `./coverage/index.html`
439//!
440//! ## License
441//!
442//! Licensed under either of
443//!
444//! - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
445//! - [MIT license](http://opensource.org/licenses/MIT)
446//!
447//! at your option.
448//!
449//! ### Contribution
450//!
451//! Unless you explicitly state otherwise, any contribution intentionally submitted
452//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall
453//! be dual licensed as above, without any additional terms or conditions.
454
455/// Error type used by this crate.
456#[derive(Debug, PartialEq, Eq)]
457pub enum SIUnitsError {
458    /// Indicates an error occurred when parsing the exponent.
459    ExponentParsing(String),
460}
461
462/// Result type used by this crate.
463pub type Result<T> = std::result::Result<T, SIUnitsError>;
464
465pub mod base;
466pub mod format;
467pub mod helpers;
468pub mod prefix;
469pub mod value;
470
471/// Holds first-class citizens of this crate, for convenience.
472pub mod prelude {
473    pub use crate::base::Base;
474    pub use crate::prefix::{Constraint, Prefix};
475    pub use crate::value::Value;
476}