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