number_prefix/
lib.rs

1#![deny(unsafe_code)]
2#![warn(missing_copy_implementations)]
3#![warn(missing_debug_implementations)]
4#![warn(missing_docs)]
5#![warn(nonstandard_style)]
6#![warn(trivial_numeric_casts)]
7#![warn(unreachable_pub)]
8#![warn(unused)]
9
10
11//! This is a library for formatting numbers with numeric prefixes, such as
12//! turning “3000 metres” into “3 kilometres”, or “8705 bytes” into “8.5 KiB”.
13//!
14//!
15//! # Usage
16//!
17//! The function [`NumberPrefix::decimal`](enum.NumberPrefix.html#method.decimal)
18//! returns either a pair of the resulting number and its prefix, or a
19//! notice that the number was too small to have any prefix applied to it. For
20//! example:
21//!
22//! ```
23//! use number_prefix::NumberPrefix;
24//!
25//! let amount = 8542_f32;
26//! let result = match NumberPrefix::decimal(amount) {
27//!     NumberPrefix::Standalone(bytes) => {
28//!         format!("The file is {} bytes in size", bytes)
29//!     }
30//!     NumberPrefix::Prefixed(prefix, n) => {
31//!         format!("The file is {:.1} {}B in size", n, prefix)
32//!     }
33//! };
34//!
35//! assert_eq!("The file is 8.5 kB in size", result);
36//! ```
37//!
38//! The `{:.1}` part of the formatting string tells it to restrict the
39//! output to only one decimal place. This value is calculated by repeatedly
40//! dividing the number by 1000 until it becomes less than that, which in this
41//! case results in 8.542, which gets rounded down. Because only one division
42//! had to take place, the function also returns the decimal prefix `Kilo`,
43//! which gets converted to its internationally-recognised symbol when
44//! formatted as a string.
45//!
46//! If the value is too small to have any prefixes applied to it — in this case,
47//! if it’s under 1000 — then the standalone value will be returned:
48//!
49//! ```
50//! use number_prefix::NumberPrefix;
51//!
52//! let amount = 705_f32;
53//! let result = match NumberPrefix::decimal(amount) {
54//!     NumberPrefix::Standalone(bytes) => {
55//!         format!("The file is {} bytes in size", bytes)
56//!     }
57//!     NumberPrefix::Prefixed(prefix, n) => {
58//!         format!("The file is {:.1} {}B in size", n, prefix)
59//!     }
60//! };
61//!
62//! assert_eq!("The file is 705 bytes in size", result);
63//! ```
64//!
65//! In this particular example, the user expects different formatting for
66//! both bytes and kilobytes: while prefixed values are given more precision,
67//! there’s no point using anything other than whole numbers for just byte
68//! amounts. This is why the function pays attention to values without any
69//! prefixes — they often need to be special-cased.
70//!
71//!
72//! ## Binary Prefixes
73//!
74//! This library also allows you to use the *binary prefixes*, which use the
75//! number 1024 (2<sup>10</sup>) as the multiplier, rather than the more common 1000
76//! (10<sup>3</sup>). This uses the
77//! [`NumberPrefix::binary`](enum.NumberPrefix.html#method.binary) function.
78//! For example:
79//!
80//! ```
81//! use number_prefix::NumberPrefix;
82//!
83//! let amount = 8542_f32;
84//! let result = match NumberPrefix::binary(amount) {
85//!     NumberPrefix::Standalone(bytes) => {
86//!         format!("The file is {} bytes in size", bytes)
87//!     }
88//!     NumberPrefix::Prefixed(prefix, n) => {
89//!         format!("The file is {:.1} {}B in size", n, prefix)
90//!     }
91//! };
92//!
93//! assert_eq!("The file is 8.3 KiB in size", result);
94//! ```
95//!
96//! A kibibyte is slightly larger than a kilobyte, so the number is smaller
97//! in the result; but other than that, it works in exactly the same way, with
98//! the binary prefix being converted to a symbol automatically.
99//!
100//!
101//! ## Which type of prefix should I use?
102//!
103//! There is no correct answer this question! Common practice is to use
104//! the binary prefixes for numbers of *bytes*, while still using the decimal
105//! prefixes for everything else. Computers work with powers of two, rather than
106//! powers of ten, and by using the binary prefixes, you get a more accurate
107//! representation of the amount of data.
108//!
109//!
110//! ## Prefix Names
111//!
112//! If you need to describe your unit in actual words, rather than just with the
113//! symbol, use one of the `upper`, `caps`, `lower`, or `symbol`, which output the
114//! prefix in a variety of formats. For example:
115//!
116//! ```
117//! use number_prefix::NumberPrefix;
118//!
119//! let amount = 8542_f32;
120//! let result = match NumberPrefix::decimal(amount) {
121//!     NumberPrefix::Standalone(bytes) => {
122//!         format!("The file is {} bytes in size", bytes)
123//!     }
124//!     NumberPrefix::Prefixed(prefix, n) => {
125//!         format!("The file is {:.1} {}bytes in size", n, prefix.lower())
126//!     }
127//! };
128//!
129//! assert_eq!("The file is 8.5 kilobytes in size", result);
130//! ```
131//!
132//!
133//! ## String Parsing
134//!
135//! There is a `FromStr` implementation for `NumberPrefix` that parses
136//! strings containing numbers and trailing prefixes, such as `7.5E`.
137//!
138//! Currently, the only supported units are `b` and `B` for bytes, and `m` for
139//! metres. Whitespace is allowed between the number and the rest of the string.
140//!
141//! ```
142//! use number_prefix::{NumberPrefix, Prefix};
143//!
144//! assert_eq!("7.05E".parse::<NumberPrefix<_>>(),
145//!            Ok(NumberPrefix::Prefixed(Prefix::Exa, 7.05_f64)));
146//!
147//! assert_eq!("7.05".parse::<NumberPrefix<_>>(),
148//!            Ok(NumberPrefix::Standalone(7.05_f64)));
149//!
150//! assert_eq!("7.05 GiB".parse::<NumberPrefix<_>>(),
151//!            Ok(NumberPrefix::Prefixed(Prefix::Gibi, 7.05_f64)));
152//! ```
153
154
155#![cfg_attr(not(feature = "std"), no_std)]
156
157#[cfg(feature = "std")]
158mod parse;
159
160#[cfg(not(feature = "std"))]
161use core::ops::{Neg, Div};
162
163#[cfg(feature = "std")]
164use std::{fmt, ops::{Neg, Div}};
165
166
167/// A numeric prefix, either binary or decimal.
168#[derive(PartialEq, Eq, Clone, Copy, Debug)]
169pub enum Prefix {
170
171    /// _kilo_, 10<sup>3</sup> or 1000<sup>1</sup>.
172    /// From the Greek ‘χίλιοι’ (‘chilioi’), meaning ‘thousand’.
173    Kilo,
174
175    /// _mega_, 10<sup>6</sup> or 1000<sup>2</sup>.
176    /// From the Ancient Greek ‘μέγας’ (‘megas’), meaning ‘great’.
177    Mega,
178
179    /// _giga_, 10<sup>9</sup> or 1000<sup>3</sup>.
180    /// From the Greek ‘γίγας’ (‘gigas’), meaning ‘giant’.
181    Giga,
182
183    /// _tera_, 10<sup>12</sup> or 1000<sup>4</sup>.
184    /// From the Greek ‘τέρας’ (‘teras’), meaning ‘monster’.
185    Tera,
186
187    /// _peta_, 10<sup>15</sup> or 1000<sup>5</sup>.
188    /// From the Greek ‘πέντε’ (‘pente’), meaning ‘five’.
189    Peta,
190
191    /// _exa_, 10<sup>18</sup> or 1000<sup>6</sup>.
192    /// From the Greek ‘ἕξ’ (‘hex’), meaning ‘six’.
193    Exa,
194
195    /// _zetta_, 10<sup>21</sup> or 1000<sup>7</sup>.
196    /// From the Latin ‘septem’, meaning ‘seven’.
197    Zetta,
198
199    /// _yotta_, 10<sup>24</sup> or 1000<sup>8</sup>.
200    /// From the Green ‘οκτώ’ (‘okto’), meaning ‘eight’.
201    Yotta,
202
203    /// _kibi_, 2<sup>10</sup> or 1024<sup>1</sup>.
204    /// The binary version of _kilo_.
205    Kibi,
206
207    /// _mebi_, 2<sup>20</sup> or 1024<sup>2</sup>.
208    /// The binary version of _mega_.
209    Mebi,
210
211    /// _gibi_, 2<sup>30</sup> or 1024<sup>3</sup>.
212    /// The binary version of _giga_.
213    Gibi,
214
215    /// _tebi_, 2<sup>40</sup> or 1024<sup>4</sup>.
216    /// The binary version of _tera_.
217    Tebi,
218
219    /// _pebi_, 2<sup>50</sup> or 1024<sup>5</sup>.
220    /// The binary version of _peta_.
221    Pebi,
222
223    /// _exbi_, 2<sup>60</sup> or 1024<sup>6</sup>.
224    /// The binary version of _exa_.
225    Exbi,
226    // you can download exa binaries at https://exa.website/#installation
227
228    /// _zebi_, 2<sup>70</sup> or 1024<sup>7</sup>.
229    /// The binary version of _zetta_.
230    Zebi,
231
232    /// _yobi_, 2<sup>80</sup> or 1024<sup>8</sup>.
233    /// The binary version of _yotta_.
234    Yobi,
235}
236
237
238/// The result of trying to apply a prefix to a floating-point value.
239#[derive(PartialEq, Eq, Clone, Debug)]
240pub enum NumberPrefix<F> {
241
242	/// A **standalone** value is returned when the number is too small to
243	/// have any prefixes applied to it. This is commonly a special case, so
244	/// is handled separately.
245    Standalone(F),
246
247    /// A **prefixed** value *is* large enough for prefixes. This holds the
248    /// prefix, as well as the resulting value.
249    Prefixed(Prefix, F),
250}
251
252impl<F: Amounts> NumberPrefix<F> {
253
254    /// Formats the given floating-point number using **decimal** prefixes.
255    ///
256    /// This function accepts both `f32` and `f64` values. If you’re trying to
257    /// format an integer, you’ll have to cast it first.
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use number_prefix::{Prefix, NumberPrefix};
263    ///
264    /// assert_eq!(NumberPrefix::decimal(1_000_000_000_f32),
265    ///            NumberPrefix::Prefixed(Prefix::Giga, 1_f32));
266    /// ```
267    pub fn decimal(amount: F) -> Self {
268        use self::Prefix::*;
269        Self::format_number(amount, Amounts::NUM_1000, [Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta])
270    }
271
272    /// Formats the given floating-point number using **binary** prefixes.
273    ///
274    /// This function accepts both `f32` and `f64` values. If you’re trying to
275    /// format an integer, you’ll have to cast it first.
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use number_prefix::{Prefix, NumberPrefix};
281    ///
282    /// assert_eq!(NumberPrefix::binary(1_073_741_824_f64),
283    ///            NumberPrefix::Prefixed(Prefix::Gibi, 1_f64));
284    /// ```
285    pub fn binary(amount: F) -> Self {
286        use self::Prefix::*;
287        Self::format_number(amount, Amounts::NUM_1024, [Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi])
288    }
289
290    fn format_number(mut amount: F, kilo: F, prefixes: [Prefix; 8]) -> Self {
291
292        // For negative numbers, flip it to positive, do the processing, then
293        // flip it back to negative again afterwards.
294        let was_negative = if amount.is_negative() { amount = -amount; true } else { false };
295
296        let mut prefix = 0;
297        while amount >= kilo && prefix < 8 {
298            amount = amount / kilo;
299            prefix += 1;
300        }
301
302        if was_negative {
303            amount = -amount;
304        }
305
306        if prefix == 0 {
307            NumberPrefix::Standalone(amount)
308        }
309        else {
310            NumberPrefix::Prefixed(prefixes[prefix - 1], amount)
311        }
312    }
313}
314
315#[cfg(feature = "std")]
316impl fmt::Display for Prefix {
317	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
318		write!(f, "{}", self.symbol())
319	}
320}
321
322impl Prefix {
323
324	/// Returns the name in uppercase, such as “KILO”.
325	///
326	/// # Examples
327	///
328	/// ```
329	/// use number_prefix::Prefix;
330	///
331	/// assert_eq!("GIGA", Prefix::Giga.upper());
332	/// assert_eq!("GIBI", Prefix::Gibi.upper());
333	/// ```
334    pub fn upper(self) -> &'static str {
335        use self::Prefix::*;
336        match self {
337            Kilo => "KILO",  Mega => "MEGA",  Giga  => "GIGA",   Tera  => "TERA",
338            Peta => "PETA",  Exa  => "EXA",   Zetta => "ZETTA",  Yotta => "YOTTA",
339            Kibi => "KIBI",  Mebi => "MEBI",  Gibi  => "GIBI",   Tebi  => "TEBI",
340            Pebi => "PEBI",  Exbi => "EXBI",  Zebi  => "ZEBI",   Yobi  => "YOBI",
341        }
342    }
343
344    /// Returns the name with the first letter capitalised, such as “Mega”.
345    ///
346	/// # Examples
347	///
348	/// ```
349	/// use number_prefix::Prefix;
350	///
351	/// assert_eq!("Giga", Prefix::Giga.caps());
352	/// assert_eq!("Gibi", Prefix::Gibi.caps());
353	/// ```
354    pub fn caps(self) -> &'static str {
355        use self::Prefix::*;
356        match self {
357            Kilo => "Kilo",  Mega => "Mega",  Giga  => "Giga",   Tera  => "Tera",
358            Peta => "Peta",  Exa  => "Exa",   Zetta => "Zetta",  Yotta => "Yotta",
359            Kibi => "Kibi",  Mebi => "Mebi",  Gibi  => "Gibi",   Tebi  => "Tebi",
360            Pebi => "Pebi",  Exbi => "Exbi",  Zebi  => "Zebi",   Yobi  => "Yobi",
361        }
362    }
363
364    /// Returns the name in lowercase, such as “giga”.
365    ///
366    /// # Examples
367    ///
368    /// ```
369    /// use number_prefix::Prefix;
370    ///
371    /// assert_eq!("giga", Prefix::Giga.lower());
372    /// assert_eq!("gibi", Prefix::Gibi.lower());
373    /// ```
374    pub fn lower(self) -> &'static str {
375        use self::Prefix::*;
376        match self {
377            Kilo => "kilo",  Mega => "mega",  Giga  => "giga",   Tera  => "tera",
378            Peta => "peta",  Exa  => "exa",   Zetta => "zetta",  Yotta => "yotta",
379            Kibi => "kibi",  Mebi => "mebi",  Gibi  => "gibi",   Tebi  => "tebi",
380            Pebi => "pebi",  Exbi => "exbi",  Zebi  => "zebi",   Yobi  => "yobi",
381        }
382    }
383
384    /// Returns the short-hand symbol, such as “T” (for “tera”).
385    ///
386	/// # Examples
387	///
388	/// ```
389	/// use number_prefix::Prefix;
390	///
391	/// assert_eq!("G",  Prefix::Giga.symbol());
392	/// assert_eq!("Gi", Prefix::Gibi.symbol());
393	/// ```
394    pub fn symbol(self) -> &'static str {
395        use self::Prefix::*;
396        match self {
397            Kilo => "k",   Mega => "M",   Giga  => "G",   Tera  => "T",
398            Peta => "P",   Exa  => "E",   Zetta => "Z",   Yotta => "Y",
399            Kibi => "Ki",  Mebi => "Mi",  Gibi  => "Gi",  Tebi  => "Ti",
400            Pebi => "Pi",  Exbi => "Ei",  Zebi  => "Zi",  Yobi  => "Yi",
401        }
402    }
403}
404
405/// Traits for floating-point values for both the possible multipliers. They
406/// need to be Copy, have defined 1000 and 1024s, and implement a bunch of
407/// operators.
408pub trait Amounts: Copy + Sized + PartialOrd + Div<Output=Self> + Neg<Output=Self> {
409
410    /// The constant representing 1000, for decimal prefixes.
411    const NUM_1000: Self;
412
413    /// The constant representing 1024, for binary prefixes.
414    const NUM_1024: Self;
415
416    /// Whether this number is negative.
417    /// This is used internally.
418    fn is_negative(self) -> bool;
419}
420
421impl Amounts for f32 {
422    const NUM_1000: Self = 1000_f32;
423    const NUM_1024: Self = 1024_f32;
424
425    fn is_negative(self) -> bool {
426        self.is_sign_negative()
427    }
428}
429
430impl Amounts for f64 {
431    const NUM_1000: Self = 1000_f64;
432    const NUM_1024: Self = 1024_f64;
433
434    fn is_negative(self) -> bool {
435        self.is_sign_negative()
436    }
437}
438
439
440#[cfg(test)]
441mod test {
442    use super::{NumberPrefix, Prefix};
443
444	#[test]
445	fn decimal_minus_one_billion() {
446	    assert_eq!(NumberPrefix::decimal(-1_000_000_000_f64),
447	               NumberPrefix::Prefixed(Prefix::Giga, -1f64))
448	}
449
450    #[test]
451    fn decimal_minus_one() {
452        assert_eq!(NumberPrefix::decimal(-1f64),
453                   NumberPrefix::Standalone(-1f64))
454    }
455
456    #[test]
457    fn decimal_0() {
458        assert_eq!(NumberPrefix::decimal(0f64),
459                   NumberPrefix::Standalone(0f64))
460    }
461
462    #[test]
463    fn decimal_999() {
464        assert_eq!(NumberPrefix::decimal(999f32),
465                   NumberPrefix::Standalone(999f32))
466    }
467
468    #[test]
469    fn decimal_1000() {
470        assert_eq!(NumberPrefix::decimal(1000f32),
471                   NumberPrefix::Prefixed(Prefix::Kilo, 1f32))
472    }
473
474    #[test]
475    fn decimal_1030() {
476        assert_eq!(NumberPrefix::decimal(1030f32),
477                   NumberPrefix::Prefixed(Prefix::Kilo, 1.03f32))
478    }
479
480    #[test]
481    fn decimal_1100() {
482        assert_eq!(NumberPrefix::decimal(1100f64),
483                   NumberPrefix::Prefixed(Prefix::Kilo, 1.1f64))
484    }
485
486    #[test]
487    fn decimal_1111() {
488        assert_eq!(NumberPrefix::decimal(1111f64),
489                   NumberPrefix::Prefixed(Prefix::Kilo, 1.111f64))
490    }
491
492    #[test]
493    fn binary_126456() {
494        assert_eq!(NumberPrefix::binary(126_456f32),
495                   NumberPrefix::Prefixed(Prefix::Kibi, 123.492188f32))
496    }
497
498    #[test]
499    fn binary_1048576() {
500        assert_eq!(NumberPrefix::binary(1_048_576f64),
501                   NumberPrefix::Prefixed(Prefix::Mebi, 1f64))
502    }
503
504    #[test]
505    fn binary_1073741824() {
506        assert_eq!(NumberPrefix::binary(2_147_483_648f32),
507                   NumberPrefix::Prefixed(Prefix::Gibi, 2f32))
508    }
509
510    #[test]
511    fn giga() {
512    	assert_eq!(NumberPrefix::decimal(1_000_000_000f64),
513    	           NumberPrefix::Prefixed(Prefix::Giga, 1f64))
514    }
515
516    #[test]
517    fn tera() {
518    	assert_eq!(NumberPrefix::decimal(1_000_000_000_000f64),
519    	           NumberPrefix::Prefixed(Prefix::Tera, 1f64))
520    }
521
522    #[test]
523    fn peta() {
524    	assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000f64),
525    	           NumberPrefix::Prefixed(Prefix::Peta, 1f64))
526    }
527
528    #[test]
529    fn exa() {
530    	assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000f64),
531    	           NumberPrefix::Prefixed(Prefix::Exa, 1f64))
532    }
533
534    #[test]
535    fn zetta() {
536    	assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000_000f64),
537    	           NumberPrefix::Prefixed(Prefix::Zetta, 1f64))
538    }
539
540    #[test]
541    fn yotta() {
542    	assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000_000_000f64),
543    	           NumberPrefix::Prefixed(Prefix::Yotta, 1f64))
544    }
545
546    #[test]
547    #[allow(overflowing_literals)]
548    fn and_so_on() {
549    	// When you hit yotta, don't keep going
550		assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000_000_000_000f64),
551		           NumberPrefix::Prefixed(Prefix::Yotta, 1000f64))
552    }
553}