1#![allow(clippy::cast_precision_loss)]
2
3use std::fmt::Display;
4
5pub fn humanize(bytes: u64) -> String {
6 Unit::from(bytes).to_string()
7}
8
9const BASE: u64 = 1_000;
10
11enum Unit {
12 B(u64),
13 KB(f64),
14 MB(f64),
15 GB(f64),
16 TB(f64),
17 PB(f64),
18}
19
20impl Display for Unit {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 Unit::B(v) => write!(f, "{v} B"),
24 Unit::KB(v) => write!(f, "{v:.0} KB"),
25 Unit::MB(v) => write!(f, "{v:.1} MB"),
26 Unit::GB(v) => write!(f, "{v:.2} GB"),
27 Unit::TB(v) => write!(f, "{v:.2} TB"),
28 Unit::PB(v) => write!(f, "{v:.2} PB"),
29 }
30 }
31}
32
33impl From<u64> for Unit {
34 fn from(value: u64) -> Self {
35 if value < BASE {
36 return Unit::B(value);
37 }
38 let exponent = value.ilog10() / BASE.ilog10();
39 let value = value as f64 / BASE.pow(exponent) as f64;
40 match exponent {
41 1 => Unit::KB(value),
42 2 => Unit::MB(value),
43 3 => Unit::GB(value),
44 4 => Unit::TB(value),
45 _ => Unit::PB(value),
46 }
47 }
48}
49
50#[cfg(test)]
51mod test {
52 use super::humanize;
53
54 #[test]
55 fn humanize_returns_bytes_in_a_human_readable_format() {
56 assert_eq!(humanize(0), "0 B");
57 assert_eq!(humanize(256), "256 B");
58 assert_eq!(humanize(512), "512 B");
59 assert_eq!(humanize(1_000), "1 KB");
60 assert_eq!(humanize(2_650), "3 KB");
61 assert_eq!(humanize(737_525), "738 KB");
62 assert_eq!(humanize(1_000_000), "1.0 MB");
63 assert_eq!(humanize(1_240_000), "1.2 MB");
64 assert_eq!(humanize(1_250_000), "1.2 MB");
65 assert_eq!(humanize(1_260_000), "1.3 MB");
66 assert_eq!(humanize(10_525_000), "10.5 MB");
67 assert_eq!(humanize(2_886_000_000), "2.89 GB");
68 assert_eq!(humanize(200_500_150_001), "200.50 GB");
69 assert_eq!(humanize(50_000_000_000_000), "50.00 TB");
70 assert_eq!(humanize(1_421_000_000_000_000), "1.42 PB");
71 }
72}