1use core::{fmt, write};
2
3use crate::ByteSize;
4
5#[derive(Debug, Clone, Copy)]
7pub(crate) enum Format {
8 Iec,
9 IecShort,
10 Si,
11 SiShort,
12}
13
14impl Format {
15 fn unit(self) -> u64 {
16 match self {
17 Format::Iec | Format::IecShort => crate::KIB,
18 Format::Si | Format::SiShort => crate::KB,
19 }
20 }
21
22 fn unit_base(self) -> f64 {
23 match self {
24 Format::Iec | Format::IecShort => crate::LN_KIB,
25 Format::Si | Format::SiShort => crate::LN_KB,
26 }
27 }
28
29 fn unit_prefixes(self) -> &'static [u8] {
30 match self {
31 Format::Iec | Format::IecShort => crate::UNITS_IEC.as_bytes(),
32 Format::Si | Format::SiShort => crate::UNITS_SI.as_bytes(),
33 }
34 }
35
36 fn unit_separator(self) -> &'static str {
37 match self {
38 Format::Iec | Format::Si => " ",
39 Format::IecShort | Format::SiShort => "",
40 }
41 }
42
43 fn unit_suffix(self) -> &'static str {
44 match self {
45 Format::Iec => "iB",
46 Format::Si => "B",
47 Format::IecShort | Format::SiShort => "",
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
71pub struct Display {
72 pub(crate) byte_size: ByteSize,
73 pub(crate) format: Format,
74}
75
76impl Display {
77 #[must_use]
81 #[doc(alias = "binary")]
82 pub fn iec(mut self) -> Self {
83 self.format = Format::Iec;
84 self
85 }
86
87 #[must_use]
93 #[doc(alias = "binary")]
94 pub fn iec_short(mut self) -> Self {
95 self.format = Format::IecShort;
96 self
97 }
98
99 #[must_use]
103 #[doc(alias = "decimal")]
104 pub fn si(mut self) -> Self {
105 self.format = Format::Si;
106 self
107 }
108
109 #[must_use]
113 #[doc(alias = "decimal")]
114 pub fn si_short(mut self) -> Self {
115 self.format = Format::SiShort;
116 self
117 }
118}
119
120impl fmt::Display for Display {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 let bytes = self.byte_size.as_u64();
123
124 let unit = self.format.unit();
125 #[allow(unused_variables)] let unit_base = self.format.unit_base();
127
128 let unit_prefixes = self.format.unit_prefixes();
129 let unit_separator = self.format.unit_separator();
130 let unit_suffix = self.format.unit_suffix();
131 let precision = f.precision().unwrap_or(1);
132
133 if bytes < unit {
134 write!(f, "{bytes}{unit_separator}B")?;
135 } else {
136 let size = bytes as f64;
137
138 #[cfg(feature = "std")]
139 let exp = ideal_unit_std(size, unit_base);
140
141 #[cfg(not(feature = "std"))]
142 let exp = ideal_unit_no_std(size, unit);
143
144 let unit_prefix = unit_prefixes[exp - 1] as char;
145
146 write!(
147 f,
148 "{:.precision$}{unit_separator}{unit_prefix}{unit_suffix}",
149 (size / unit.pow(exp as u32) as f64),
150 )?;
151 }
152
153 Ok(())
154 }
155}
156
157#[allow(dead_code)] fn ideal_unit_no_std(size: f64, unit: u64) -> usize {
159 assert!(size >= unit as f64, "only called when bytes >= unit");
160
161 let mut ideal_prefix = 0;
162 let mut ideal_size = size;
163
164 loop {
165 ideal_prefix += 1;
166 ideal_size /= unit as f64;
167
168 if ideal_size < unit as f64 {
169 break;
170 }
171 }
172
173 ideal_prefix
174}
175
176#[cfg(feature = "std")]
177#[allow(dead_code)] fn ideal_unit_std(size: f64, unit_base: f64) -> usize {
179 assert!(size.ln() >= unit_base, "only called when bytes >= unit");
180
181 match (size.ln() / unit_base) as usize {
182 0 => unreachable!(),
183 e => e,
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use alloc::{format, string::ToString as _};
190
191 use super::*;
192
193 #[cfg(feature = "std")]
194 quickcheck::quickcheck! {
195 #[test]
196 fn ideal_unit_selection_std_no_std_iec(bytes: ByteSize) -> bool {
197 if bytes.0 < 1025 {
198 return true;
199 }
200
201 let size = bytes.0 as f64;
202
203 ideal_unit_std(size, crate::LN_KIB) == ideal_unit_no_std(size, crate::KIB)
204 }
205
206 #[test]
207 fn ideal_unit_selection_std_no_std_si(bytes: ByteSize) -> bool {
208 if bytes.0 < 1025 {
209 return true;
210 }
211
212 let size = bytes.0 as f64;
213
214 ideal_unit_std(size, crate::LN_KB) == ideal_unit_no_std(size, crate::KB)
215 }
216 }
217
218 #[test]
219 fn to_string_iec() {
220 let display = Display {
221 byte_size: ByteSize::gib(1),
222 format: Format::Iec,
223 };
224 assert_eq!("1.0 GiB", display.to_string());
225
226 let display = Display {
227 byte_size: ByteSize::gb(1),
228 format: Format::Iec,
229 };
230 assert_eq!("953.7 MiB", display.to_string());
231 }
232
233 #[test]
234 fn to_string_si() {
235 let display = Display {
236 byte_size: ByteSize::gib(1),
237 format: Format::Si,
238 };
239 assert_eq!("1.1 GB", display.to_string());
240
241 let display = Display {
242 byte_size: ByteSize::gb(1),
243 format: Format::Si,
244 };
245 assert_eq!("1.0 GB", display.to_string());
246 }
247
248 #[test]
249 fn to_string_short() {
250 let display = Display {
251 byte_size: ByteSize::gib(1),
252 format: Format::IecShort,
253 };
254 assert_eq!("1.0G", display.to_string());
255
256 let display = Display {
257 byte_size: ByteSize::gb(1),
258 format: Format::IecShort,
259 };
260 assert_eq!("953.7M", display.to_string());
261 }
262
263 #[track_caller]
264 fn assert_to_string(expected: &str, byte_size: ByteSize, format: Format) {
265 assert_eq!(expected, Display { byte_size, format }.to_string());
266 }
267
268 #[test]
269 fn test_to_string_as() {
270 assert_to_string("215 B", ByteSize::b(215), Format::Iec);
271 assert_to_string("215 B", ByteSize::b(215), Format::Si);
272
273 assert_to_string("1.0 KiB", ByteSize::kib(1), Format::Iec);
274 assert_to_string("1.0 kB", ByteSize::kib(1), Format::Si);
275
276 assert_to_string("293.9 KiB", ByteSize::kb(301), Format::Iec);
277 assert_to_string("301.0 kB", ByteSize::kb(301), Format::Si);
278
279 assert_to_string("1.0 MiB", ByteSize::mib(1), Format::Iec);
280 assert_to_string("1.0 MB", ByteSize::mib(1), Format::Si);
281
282 assert_to_string("1.9 GiB", ByteSize::mib(1907), Format::Iec);
283 assert_to_string("2.0 GB", ByteSize::mib(1908), Format::Si);
284
285 assert_to_string("399.6 MiB", ByteSize::mb(419), Format::Iec);
286 assert_to_string("419.0 MB", ByteSize::mb(419), Format::Si);
287
288 assert_to_string("482.4 GiB", ByteSize::gb(518), Format::Iec);
289 assert_to_string("518.0 GB", ByteSize::gb(518), Format::Si);
290
291 assert_to_string("741.2 TiB", ByteSize::tb(815), Format::Iec);
292 assert_to_string("815.0 TB", ByteSize::tb(815), Format::Si);
293
294 assert_to_string("540.9 PiB", ByteSize::pb(609), Format::Iec);
295 assert_to_string("609.0 PB", ByteSize::pb(609), Format::Si);
296 }
297
298 #[test]
299 fn precision() {
300 let size = ByteSize::mib(1908);
301 assert_eq!("1.9 GiB".to_string(), format!("{size}"));
302 assert_eq!("2 GiB".to_string(), format!("{size:.0}"));
303 assert_eq!("1.86328 GiB".to_string(), format!("{size:.5}"));
304 }
305}