1use core::convert::Infallible;
2use core::fmt;
3
4use crate::{FastWritable, NO_VALUES, Values};
5
6#[inline]
24pub fn filesizeformat(bytes: u128, precision: u8) -> Result<FilesizeFormatFilter, Infallible> {
25 Ok(FilesizeFormatFilter { bytes, precision })
26}
27
28#[derive(Debug, Clone, Copy)]
29pub struct FilesizeFormatFilter {
30 bytes: u128,
32 precision: u8,
35}
36
37impl fmt::Display for FilesizeFormatFilter {
38 #[inline]
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 Ok(self.write_into(f, NO_VALUES)?)
41 }
42}
43
44impl FastWritable for FilesizeFormatFilter {
45 fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
46 if self.bytes < 1000 {
47 (self.bytes as u32).write_into(dest, values)?;
48 return Ok(dest.write_str(" B")?);
49 }
50
51 let unit = SI_PREFIXES
53 .iter()
54 .take_while(|siprefix| siprefix.lower_bound <= self.bytes)
55 .last()
56 .unwrap_or(&SI_PREFIXES[SI_PREFIXES.len() - 1]);
57 let integer = self.bytes / unit.lower_bound;
59 let remainder = self.bytes % unit.lower_bound;
60
61 let precision = self.precision.min(unit.exponent);
65
66 integer.write_into(dest, values)?;
68 if precision > 0 {
69 let mut scale = 10u128.pow(precision as u32);
70 let mut fractional = remainder.saturating_mul(scale) / unit.lower_bound;
71 if fractional > 0 {
72 '.'.write_into(dest, values)?;
73 for _ in 0..precision {
74 scale /= 10;
75 let digit = (b'0' + (fractional / scale) as u8) as char;
76 digit.write_into(dest, values)?;
77 fractional %= scale;
78 if fractional == 0 {
79 break;
80 }
81 }
82 }
83 }
84 ' '.write_into(dest, values)?;
85 unit.prefix_char.write_into(dest, values)?;
86 'B'.write_into(dest, values)?;
87
88 Ok(())
89 }
90}
91
92struct SiPrefix {
93 prefix_char: char,
95 lower_bound: u128,
98 exponent: u8,
100}
101impl SiPrefix {
102 const fn new(prefix_char: char, exponent: u8) -> Self {
103 Self {
104 prefix_char,
105 lower_bound: 10u128.pow(exponent as u32),
106 exponent,
107 }
108 }
109}
110
111const SI_PREFIXES: &[SiPrefix] = &[
115 SiPrefix::new('k', 3),
116 SiPrefix::new('M', 6),
117 SiPrefix::new('G', 9),
118 SiPrefix::new('T', 12),
119 SiPrefix::new('P', 15),
120 SiPrefix::new('E', 18),
121 SiPrefix::new('Z', 21),
122 SiPrefix::new('Y', 24),
123 SiPrefix::new('R', 27),
124 SiPrefix::new('Q', 30),
125];
126
127#[test]
128#[cfg(feature = "alloc")]
129fn test_filesizeformat_edgecases() {
130 use alloc::string::ToString;
131
132 assert_eq!(filesizeformat(1000, 0).unwrap().to_string(), "1 kB");
133
134 assert_eq!(
135 filesizeformat(954_548_589_125_249_215_468, 0)
136 .unwrap()
137 .to_string(),
138 "954 EB"
139 );
140 assert_eq!(
141 filesizeformat(954_548_589_125_249_215_468, 10)
142 .unwrap()
143 .to_string(),
144 "954.5485891252 EB"
145 );
146 assert_eq!(
147 filesizeformat(954_548_589_125_249_215_468, 255)
148 .unwrap()
149 .to_string(),
150 "954.548589125249215468 EB"
151 );
152}
153
154#[test]
155#[cfg(feature = "alloc")]
156fn test_filesizeformat_prec2() {
157 use alloc::string::ToString;
158
159 assert_eq!(filesizeformat(0, 2).unwrap().to_string(), "0 B");
160 assert_eq!(filesizeformat(999, 2).unwrap().to_string(), "999 B");
161 assert_eq!(filesizeformat(1000, 2).unwrap().to_string(), "1 kB");
162 assert_eq!(filesizeformat(1023, 2).unwrap().to_string(), "1.02 kB");
163 assert_eq!(filesizeformat(1024, 2).unwrap().to_string(), "1.02 kB");
164 assert_eq!(filesizeformat(1025, 2).unwrap().to_string(), "1.02 kB");
165 assert_eq!(filesizeformat(1100, 2).unwrap().to_string(), "1.1 kB");
166 assert_eq!(filesizeformat(9_499_014, 2).unwrap().to_string(), "9.49 MB");
167 assert_eq!(
168 filesizeformat(954_548_589, 2).unwrap().to_string(),
169 "954.54 MB"
170 );
171 assert_eq!(
172 filesizeformat(300_000_000_000, 2).unwrap().to_string(),
173 "300 GB"
174 );
175 assert_eq!(
176 filesizeformat(600_000_000_000, 2).unwrap().to_string(),
177 "600 GB"
178 );
179 assert_eq!(
180 filesizeformat(7_000_000_000_000, 2).unwrap().to_string(),
181 "7 TB"
182 );
183 assert_eq!(
184 filesizeformat(2_300_000_000_000_000, 2)
185 .unwrap()
186 .to_string(),
187 "2.3 PB"
188 );
189 assert_eq!(
190 filesizeformat(9_900_000_000_000_000_000, 2)
191 .unwrap()
192 .to_string(),
193 "9.9 EB"
194 );
195 assert_eq!(
196 filesizeformat(4_500_000_000_000_000_000_000, 2)
197 .unwrap()
198 .to_string(),
199 "4.5 ZB"
200 );
201}
202
203#[test]
204#[cfg(feature = "alloc")]
205fn test_filesizeformat_prec3() {
206 use alloc::string::ToString;
207
208 assert_eq!(filesizeformat(0, 3).unwrap().to_string(), "0 B");
209 assert_eq!(filesizeformat(999, 3).unwrap().to_string(), "999 B");
210 assert_eq!(filesizeformat(1000, 3).unwrap().to_string(), "1 kB");
211 assert_eq!(filesizeformat(1023, 3).unwrap().to_string(), "1.023 kB");
212 assert_eq!(filesizeformat(1024, 3).unwrap().to_string(), "1.024 kB");
213 assert_eq!(filesizeformat(1025, 3).unwrap().to_string(), "1.025 kB");
214 assert_eq!(filesizeformat(1100, 3).unwrap().to_string(), "1.1 kB");
215 assert_eq!(
216 filesizeformat(9_499_014, 3).unwrap().to_string(),
217 "9.499 MB"
218 );
219 assert_eq!(
220 filesizeformat(954_548_589, 3).unwrap().to_string(),
221 "954.548 MB"
222 );
223 assert_eq!(
224 filesizeformat(300_000_000_000, 3).unwrap().to_string(),
225 "300 GB"
226 );
227 assert_eq!(
228 filesizeformat(600_000_000_000, 3).unwrap().to_string(),
229 "600 GB"
230 );
231 assert_eq!(
232 filesizeformat(7_000_000_000_000, 3).unwrap().to_string(),
233 "7 TB"
234 );
235 assert_eq!(
236 filesizeformat(2_300_000_000_000_000, 3)
237 .unwrap()
238 .to_string(),
239 "2.3 PB"
240 );
241 assert_eq!(
242 filesizeformat(9_900_000_000_000_000_000, 3)
243 .unwrap()
244 .to_string(),
245 "9.9 EB"
246 );
247 assert_eq!(
248 filesizeformat(4_500_000_000_000_000_000_000, 3)
249 .unwrap()
250 .to_string(),
251 "4.5 ZB"
252 );
253}