1use crate::{FileSizeFormat, format_parts_scaled, SCALE};
9use std::num::NonZeroU64;
10use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
11
12#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
16pub struct NonZeroFileSize {
17 bytes: NonZeroU64,
18}
19
20impl NonZeroFileSize {
21 #[inline]
25 pub fn new(bytes: NonZeroU64) -> Self {
26 Self { bytes }
27 }
28
29 #[inline]
33 pub fn get_nonzero(&self) -> NonZeroU64 {
34 self.bytes
35 }
36
37 #[inline]
41 pub fn as_u64(&self) -> u64 {
42 self.bytes.get()
43 }
44}
45
46impl FileSizeFormat for NonZeroFileSize {
48 #[inline]
56 fn get_si_parts(&self) -> (String, &'static str) {
57 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
58 format_parts_scaled((self.bytes.get() as u128) * SCALE, 1000, UNITS)
59 }
60
61 #[inline]
69 fn get_iec_parts(&self) -> (String, &'static str) {
70 format_iec_parts(self.bytes.get())
71 }
72}
73
74#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
78pub struct FileSize {
79 bytes: u64,
80}
81
82impl FileSize {
83 #[inline]
87 pub const fn new(bytes: u64) -> Self {
88 Self { bytes }
89 }
90
91 #[inline]
95 pub const fn as_u64(&self) -> u64 {
96 self.bytes
97 }
98
99 #[inline]
103 pub fn to_nonzero(&self) -> Option<NonZeroFileSize> {
104 NonZeroU64::new(self.bytes).map(NonZeroFileSize::new)
105 }
106}
107
108impl Add for FileSize {
110 type Output = Self;
111
112 #[inline]
113 fn add(self, rhs: Self) -> Self::Output {
114 Self {
115 bytes: self.bytes + rhs.bytes,
116 }
117 }
118}
119
120impl Sub for FileSize {
121 type Output = Self;
122
123 #[inline]
124 fn sub(self, rhs: Self) -> Self::Output {
125 Self {
126 bytes: self.bytes.saturating_sub(rhs.bytes),
127 }
128 }
129}
130
131impl Mul<u64> for FileSize {
132 type Output = Self;
133
134 #[inline]
135 fn mul(self, rhs: u64) -> Self::Output {
136 Self {
137 bytes: self.bytes.saturating_mul(rhs),
138 }
139 }
140}
141
142impl Div<u64> for FileSize {
143 type Output = Self;
144
145 #[inline]
146 fn div(self, rhs: u64) -> Self::Output {
147 Self {
148 bytes: if rhs == 0 { 0 } else { self.bytes / rhs },
149 }
150 }
151}
152
153impl AddAssign for FileSize {
155 #[inline]
156 fn add_assign(&mut self, rhs: Self) {
157 self.bytes = self.bytes.saturating_add(rhs.bytes);
158 }
159}
160
161impl SubAssign for FileSize {
162 #[inline]
163 fn sub_assign(&mut self, rhs: Self) {
164 self.bytes = self.bytes.saturating_sub(rhs.bytes);
165 }
166}
167
168impl MulAssign<u64> for FileSize {
169 #[inline]
170 fn mul_assign(&mut self, rhs: u64) {
171 self.bytes = self.bytes.saturating_mul(rhs);
172 }
173}
174
175impl DivAssign<u64> for FileSize {
176 #[inline]
177 fn div_assign(&mut self, rhs: u64) {
178 if rhs != 0 {
179 self.bytes /= rhs;
180 } else {
181 self.bytes = 0;
182 }
183 }
184}
185
186impl From<u64> for FileSize {
188 #[inline]
189 fn from(bytes: u64) -> Self {
190 Self::new(bytes)
191 }
192}
193
194impl From<NonZeroFileSize> for FileSize {
195 #[inline]
196 fn from(size: NonZeroFileSize) -> Self {
197 Self::new(size.as_u64())
198 }
199}
200
201impl TryFrom<FileSize> for NonZeroFileSize {
202 type Error = std::num::TryFromIntError;
203
204 #[inline]
205 fn try_from(value: FileSize) -> Result<Self, Self::Error> {
206 NonZeroU64::try_from(value.bytes).map(NonZeroFileSize::new)
207 }
208}
209
210impl FileSizeFormat for FileSize {
212 #[inline]
220 fn get_si_parts(&self) -> (String, &'static str) {
221 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
222 format_parts_scaled((self.bytes as u128) * SCALE, 1000, UNITS)
223 }
224
225 #[inline]
233 fn get_iec_parts(&self) -> (String, &'static str) {
234 format_iec_parts(self.bytes)
235 }
236}
237
238fn format_iec_parts(bytes: u64) -> (String, &'static str) {
242 const UNITS: [&str; 9] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
243 const BASE: u64 = 1024;
244
245 let mut value = bytes as f64;
246 let mut unit_index = 0;
247
248 while value >= (BASE as f64) && unit_index < UNITS.len() - 1 {
249 value /= BASE as f64;
250 unit_index += 1;
251 }
252
253 let formatted = if value < 10.0 {
255 format!("{:.2}", value)
257 } else {
258 format!("{:.1}", value)
260 };
261
262 (formatted, UNITS[unit_index])
263}
264
265#[cfg(test)]
267mod tests {
268 use crate::{FileSizeFormat, FormattedValue, SizeStandard};
269
270 use super::{FileSize, NonZeroFileSize};
271 use std::num::NonZeroU64;
272
273 fn format_test_si(bytes: u64) -> String {
277 let nz = NonZeroU64::new(bytes).expect("测试值不能为零");
278 FormattedValue::new(NonZeroFileSize::new(nz), SizeStandard::SI).to_string()
279 }
280
281 fn format_test_iec(bytes: u64) -> String {
285 let nz = NonZeroU64::new(bytes).expect("测试值不能为零");
286 FormattedValue::new(NonZeroFileSize::new(nz), SizeStandard::IEC).to_string()
287 }
288
289 #[test]
292 fn test_si_bytes() {
293 assert_eq!(format_test_si(512), "512.0 B");
294 assert_eq!(format_test_si(999), "999.0 B");
295 }
296
297 #[test]
298 fn test_si_kilobytes() {
299 assert_eq!(format_test_si(1000), "1.00 KB");
300 assert_eq!(format_test_si(1024), "1.02 KB");
302 assert_eq!(format_test_si(9999), "10.00 KB");
304 assert_eq!(format_test_si(10000), "10.0 KB");
306 assert_eq!(format_test_si(100000), "100.0 KB");
308 }
309
310 #[test]
311 fn test_si_megabytes() {
312 assert_eq!(format_test_si(1_000_000), "1.00 MB");
313 }
314
315 #[test]
318 fn test_iec_bytes() {
319 assert_eq!(format_test_iec(512), "512.0 B");
320 assert_eq!(format_test_iec(1023), "1023.0 B");
321 }
322
323 #[test]
324 fn test_iec_kibibytes() {
325 assert_eq!(format_test_iec(1024), "1.00 KiB");
326 assert_eq!(format_test_iec(1500), "1.46 KiB");
328 let bytes_near_10 = (9.999 * 1024.0) as u64;
330 assert_eq!(format_test_iec(bytes_near_10), "10.00 KiB");
331 assert_eq!(format_test_iec(10 * 1024), "10.0 KiB");
333 assert_eq!(format_test_iec(100 * 1024), "100.0 KiB");
335 }
336
337 #[test]
338 fn test_iec_mebibytes() {
339 assert_eq!(format_test_iec(1024 * 1024), "1.00 MiB");
340 }
341
342 #[test]
345 fn test_filesize_arithmetic() {
346 let size1 = FileSize::new(1024);
347 let size2 = FileSize::new(2048);
348
349 assert_eq!((size1 + size2).as_u64(), 3072);
351
352 assert_eq!((size2 - size1).as_u64(), 1024);
354 assert_eq!((size1 - size2).as_u64(), 0); assert_eq!((size1 * 2).as_u64(), 2048);
358
359 assert_eq!((size2 / 2).as_u64(), 1024);
361
362 assert_eq!((size1 / 0).as_u64(), 0);
364 }
365
366 #[test]
367 fn test_filesize_formatting() {
368 let size = FileSize::new(1500);
369
370 let (value, unit) = size.get_si_parts();
372 assert_eq!(&value, "1.50");
373 assert_eq!(unit, "KB");
374
375 let (value, unit) = size.get_iec_parts();
377 assert_eq!(&value, "1.46");
378 assert_eq!(unit, "KiB");
379 }
380
381 #[test]
382 fn test_filesize_conversions() {
383 let size = FileSize::from(1024u64);
385 assert_eq!(size.as_u64(), 1024);
386
387 let non_zero = NonZeroFileSize::new(NonZeroU64::new(2048).unwrap());
389 let size = FileSize::from(non_zero);
390 assert_eq!(size.as_u64(), 2048);
391
392 let size = FileSize::new(1024);
394 let non_zero = NonZeroFileSize::try_from(size);
395 assert!(non_zero.is_ok());
396 assert_eq!(non_zero.unwrap().as_u64(), 1024);
397
398 let zero = FileSize::new(0);
400 let result = NonZeroFileSize::try_from(zero);
401 assert!(result.is_err());
402 }
403}