1use std::fmt;
4
5#[allow(clippy::cast_precision_loss)] const fn bytes_as_f64_for_display(bytes: u64) -> f64 {
7 bytes as f64
10}
11
12#[inline]
14#[must_use]
15pub fn format_bytes(bytes: u64) -> String {
16 let mut output = String::new();
17 format_bytes_into(&mut output, bytes).expect("writing to String cannot fail");
18 output
19}
20
21#[inline]
23pub fn format_bytes_into(output: &mut impl fmt::Write, bytes: u64) -> fmt::Result {
24 const KIB: u64 = 1024;
25 const MIB: u64 = KIB * 1024;
26 const GIB: u64 = MIB * 1024;
27 const TIB: u64 = GIB * 1024;
28 const PIB: u64 = TIB * 1024;
29 const KIB_F64: f64 = 1024.0;
30 const MIB_F64: f64 = KIB_F64 * 1024.0;
31 const GIB_F64: f64 = MIB_F64 * 1024.0;
32 const TIB_F64: f64 = GIB_F64 * 1024.0;
33 const PIB_F64: f64 = TIB_F64 * 1024.0;
34
35 if bytes >= PIB {
36 write!(
37 output,
38 "{:.2} PiB",
39 bytes_as_f64_for_display(bytes) / PIB_F64
40 )
41 } else if bytes >= TIB {
42 write!(
43 output,
44 "{:.2} TiB",
45 bytes_as_f64_for_display(bytes) / TIB_F64
46 )
47 } else if bytes >= GIB {
48 write!(
49 output,
50 "{:.2} GiB",
51 bytes_as_f64_for_display(bytes) / GIB_F64
52 )
53 } else if bytes >= MIB {
54 write!(
55 output,
56 "{:.2} MiB",
57 bytes_as_f64_for_display(bytes) / MIB_F64
58 )
59 } else if bytes >= KIB {
60 write!(
61 output,
62 "{:.2} KiB",
63 bytes_as_f64_for_display(bytes) / KIB_F64
64 )
65 } else {
66 write!(output, "{bytes} B")
67 }
68}
69
70#[inline]
72#[must_use]
73pub fn format_count(count: u64) -> String {
74 const UNITS: &[(u64, &str)] = &[
75 (1_000_000_000_000, "T"),
76 (1_000_000_000, "B"),
77 (1_000_000, "M"),
78 (1_000, "K"),
79 ];
80
81 #[allow(clippy::cast_precision_loss)] fn compact(value: u64, divisor: u64, suffix: &str) -> String {
83 let scaled = value as f64 / divisor as f64;
84 format!("{scaled:.1}{suffix}")
85 }
86
87 for (idx, (divisor, suffix)) in UNITS.iter().enumerate() {
88 if count >= *divisor {
89 let formatted = compact(count, *divisor, suffix);
90 if formatted.starts_with("1000.0") && idx > 0 {
91 let (next_divisor, next_suffix) = UNITS[idx - 1];
92 return compact(count, next_divisor, next_suffix);
93 }
94 return formatted;
95 }
96 }
97
98 count.to_string()
99}
100
101#[inline]
103#[must_use]
104pub fn short_id(uuid: &uuid::Uuid) -> String {
105 uuid.to_string()[..8].to_string()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_format_bytes() {
114 assert_eq!(format_bytes(0), "0 B");
115 assert_eq!(format_bytes(512), "512 B");
116 assert_eq!(format_bytes(1024), "1.00 KiB");
117 assert_eq!(format_bytes(1536), "1.50 KiB");
118 assert_eq!(format_bytes(1_048_576), "1.00 MiB");
119 assert_eq!(format_bytes(29_312_178), "27.95 MiB");
120 assert_eq!(format_bytes(1_073_741_824), "1.00 GiB");
121 }
122
123 #[test]
124 fn format_bytes_into_matches_owned_formatter() {
125 let cases = [
126 0,
127 512,
128 1024,
129 1536,
130 1_048_576,
131 29_312_178,
132 1_073_741_824,
133 1_099_511_627_776,
134 1_125_899_906_842_624,
135 ];
136
137 for bytes in cases {
138 let mut output = String::new();
139 format_bytes_into(&mut output, bytes).unwrap();
140 assert_eq!(output, format_bytes(bytes));
141 }
142 }
143
144 #[test]
145 fn test_format_count() {
146 assert_eq!(format_count(0), "0");
147 assert_eq!(format_count(999), "999");
148 assert_eq!(format_count(1_000), "1.0K");
149 assert_eq!(format_count(999_999), "1.0M");
150 assert_eq!(format_count(12_345), "12.3K");
151 assert_eq!(format_count(1_234_567), "1.2M");
152 assert_eq!(format_count(1_234_567_890), "1.2B");
153 assert_eq!(format_count(9_876_543_210_987), "9.9T");
154 }
155
156 #[test]
157 fn test_short_id() {
158 let uuid = uuid::Uuid::parse_str("34925aee-7f65-4670-9adc-d2e95ac97b26").unwrap();
159 assert_eq!(short_id(&uuid), "34925aee");
160 }
161}