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" )] |
158 | mod parse; |
159 | |
160 | #[cfg (not(feature = "std" ))] |
161 | use core::ops::{Neg, Div}; |
162 | |
163 | #[cfg (feature = "std" )] |
164 | use std::{fmt, ops::{Neg, Div}}; |
165 | |
166 | |
167 | /// A numeric prefix, either binary or decimal. |
168 | #[derive (PartialEq, Eq, Clone, Copy, Debug)] |
169 | pub 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)] |
240 | pub 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 | |
252 | impl<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" )] |
316 | impl fmt::Display for Prefix { |
317 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
318 | write!(f, " {}" , self.symbol()) |
319 | } |
320 | } |
321 | |
322 | impl 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. |
408 | pub 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 | |
421 | impl 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 | |
430 | impl 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)] |
441 | mod 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 | } |
554 | |