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}
554