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}