1use rust_decimal::Decimal;
9
10use crate::FileSizeFormat;
11use std::num::NonZeroU64;
12use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
13
14#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
18pub struct NonZeroFileSize {
19 bytes: NonZeroU64,
20}
21
22impl NonZeroFileSize {
23 #[inline]
27 pub fn new(bytes: NonZeroU64) -> Self {
28 Self { bytes }
29 }
30
31 #[inline]
35 pub fn get_nonzero(&self) -> NonZeroU64 {
36 self.bytes
37 }
38
39 #[inline]
43 pub fn as_u64(&self) -> u64 {
44 self.bytes.get()
45 }
46}
47
48impl FileSizeFormat for NonZeroFileSize {
50 #[inline]
58 fn get_si_parts(&self) -> (String, &'static str) {
59 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
60 crate::format_parts(
61 rust_decimal::Decimal::from(self.bytes.get()),
62 rust_decimal::Decimal::from(1000),
63 UNITS,
64 )
65 }
66
67 #[inline]
75 fn get_iec_parts(&self) -> (String, &'static str) {
76 format_iec_parts(self.bytes.get())
77 }
78}
79
80#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
84pub struct FileSize {
85 bytes: u64,
86}
87
88impl FileSize {
89 #[inline]
93 pub const fn new(bytes: u64) -> Self {
94 Self { bytes }
95 }
96
97 #[inline]
101 pub const fn as_u64(&self) -> u64 {
102 self.bytes
103 }
104
105 #[inline]
109 pub fn to_nonzero(&self) -> Option<NonZeroFileSize> {
110 NonZeroU64::new(self.bytes).map(NonZeroFileSize::new)
111 }
112}
113
114impl Add for FileSize {
116 type Output = Self;
117
118 #[inline]
119 fn add(self, rhs: Self) -> Self::Output {
120 Self {
121 bytes: self.bytes + rhs.bytes,
122 }
123 }
124}
125
126impl Sub for FileSize {
127 type Output = Self;
128
129 #[inline]
130 fn sub(self, rhs: Self) -> Self::Output {
131 Self {
132 bytes: self.bytes.saturating_sub(rhs.bytes),
133 }
134 }
135}
136
137impl Mul<u64> for FileSize {
138 type Output = Self;
139
140 #[inline]
141 fn mul(self, rhs: u64) -> Self::Output {
142 Self {
143 bytes: self.bytes.saturating_mul(rhs),
144 }
145 }
146}
147
148impl Div<u64> for FileSize {
149 type Output = Self;
150
151 #[inline]
152 fn div(self, rhs: u64) -> Self::Output {
153 Self {
154 bytes: if rhs == 0 { 0 } else { self.bytes / rhs },
155 }
156 }
157}
158
159impl AddAssign for FileSize {
161 #[inline]
162 fn add_assign(&mut self, rhs: Self) {
163 self.bytes = self.bytes.saturating_add(rhs.bytes);
164 }
165}
166
167impl SubAssign for FileSize {
168 #[inline]
169 fn sub_assign(&mut self, rhs: Self) {
170 self.bytes = self.bytes.saturating_sub(rhs.bytes);
171 }
172}
173
174impl MulAssign<u64> for FileSize {
175 #[inline]
176 fn mul_assign(&mut self, rhs: u64) {
177 self.bytes = self.bytes.saturating_mul(rhs);
178 }
179}
180
181impl DivAssign<u64> for FileSize {
182 #[inline]
183 fn div_assign(&mut self, rhs: u64) {
184 if rhs != 0 {
185 self.bytes /= rhs;
186 } else {
187 self.bytes = 0;
188 }
189 }
190}
191
192impl From<u64> for FileSize {
194 #[inline]
195 fn from(bytes: u64) -> Self {
196 Self::new(bytes)
197 }
198}
199
200impl From<NonZeroFileSize> for FileSize {
201 #[inline]
202 fn from(size: NonZeroFileSize) -> Self {
203 Self::new(size.as_u64())
204 }
205}
206
207impl TryFrom<FileSize> for NonZeroFileSize {
208 type Error = std::num::TryFromIntError;
209
210 #[inline]
211 fn try_from(value: FileSize) -> Result<Self, Self::Error> {
212 NonZeroU64::try_from(value.bytes).map(NonZeroFileSize::new)
213 }
214}
215
216impl FileSizeFormat for FileSize {
218 #[inline]
226 fn get_si_parts(&self) -> (String, &'static str) {
227 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
228 crate::format_parts(Decimal::from(self.bytes), Decimal::from(1000), UNITS)
229 }
230
231 #[inline]
239 fn get_iec_parts(&self) -> (String, &'static str) {
240 format_iec_parts(self.bytes)
241 }
242}
243
244fn format_iec_parts(bytes: u64) -> (String, &'static str) {
248 const UNITS: [&str; 9] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
249 const BASE: u64 = 1024;
250
251 let mut value = bytes as f64;
252 let mut unit_index = 0;
253
254 while value >= (BASE as f64) && unit_index < UNITS.len() - 1 {
255 value /= BASE as f64;
256 unit_index += 1;
257 }
258
259 let formatted = if value < 10.0 {
261 format!("{:.2}", value)
263 } else {
264 format!("{:.1}", value)
266 };
267
268 (formatted, UNITS[unit_index])
269}
270
271#[cfg(test)]
273mod tests {
274 use crate::{FileSizeFormat, FormattedValue, SizeStandard};
275
276 use super::{FileSize, NonZeroFileSize};
277 use std::num::NonZeroU64;
278
279 fn format_test_si(bytes: u64) -> String {
283 let nz = NonZeroU64::new(bytes).expect("测试值不能为零");
284 FormattedValue::new(NonZeroFileSize::new(nz), SizeStandard::SI).to_string()
285 }
286
287 fn format_test_iec(bytes: u64) -> String {
291 let nz = NonZeroU64::new(bytes).expect("测试值不能为零");
292 FormattedValue::new(NonZeroFileSize::new(nz), SizeStandard::IEC).to_string()
293 }
294
295 #[test]
298 fn test_si_bytes() {
299 assert_eq!(format_test_si(512), "512.0 B");
300 assert_eq!(format_test_si(999), "999.0 B");
301 }
302
303 #[test]
304 fn test_si_kilobytes() {
305 assert_eq!(format_test_si(1000), "1.00 KB");
306 assert_eq!(format_test_si(1024), "1.02 KB");
308 assert_eq!(format_test_si(9999), "10.00 KB");
310 assert_eq!(format_test_si(10000), "10.0 KB");
312 assert_eq!(format_test_si(100000), "100.0 KB");
314 }
315
316 #[test]
317 fn test_si_megabytes() {
318 assert_eq!(format_test_si(1_000_000), "1.00 MB");
319 }
320
321 #[test]
324 fn test_iec_bytes() {
325 assert_eq!(format_test_iec(512), "512.0 B");
326 assert_eq!(format_test_iec(1023), "1023.0 B");
327 }
328
329 #[test]
330 fn test_iec_kibibytes() {
331 assert_eq!(format_test_iec(1024), "1.00 KiB");
332 assert_eq!(format_test_iec(1500), "1.46 KiB");
334 let bytes_near_10 = (9.999 * 1024.0) as u64;
336 assert_eq!(format_test_iec(bytes_near_10), "10.00 KiB");
337 assert_eq!(format_test_iec(10 * 1024), "10.0 KiB");
339 assert_eq!(format_test_iec(100 * 1024), "100.0 KiB");
341 }
342
343 #[test]
344 fn test_iec_mebibytes() {
345 assert_eq!(format_test_iec(1024 * 1024), "1.00 MiB");
346 }
347
348 #[test]
351 fn test_filesize_arithmetic() {
352 let size1 = FileSize::new(1024);
353 let size2 = FileSize::new(2048);
354
355 assert_eq!((size1 + size2).as_u64(), 3072);
357
358 assert_eq!((size2 - size1).as_u64(), 1024);
360 assert_eq!((size1 - size2).as_u64(), 0); assert_eq!((size1 * 2).as_u64(), 2048);
364
365 assert_eq!((size2 / 2).as_u64(), 1024);
367
368 assert_eq!((size1 / 0).as_u64(), 0);
370 }
371
372 #[test]
373 fn test_filesize_formatting() {
374 let size = FileSize::new(1500);
375
376 let (value, unit) = size.get_si_parts();
378 assert_eq!(&value, "1.50");
379 assert_eq!(unit, "KB");
380
381 let (value, unit) = size.get_iec_parts();
383 assert_eq!(&value, "1.46");
384 assert_eq!(unit, "KiB");
385 }
386
387 #[test]
388 fn test_filesize_conversions() {
389 let size = FileSize::from(1024u64);
391 assert_eq!(size.as_u64(), 1024);
392
393 let non_zero = NonZeroFileSize::new(NonZeroU64::new(2048).unwrap());
395 let size = FileSize::from(non_zero);
396 assert_eq!(size.as_u64(), 2048);
397
398 let size = FileSize::new(1024);
400 let non_zero = NonZeroFileSize::try_from(size);
401 assert!(non_zero.is_ok());
402 assert_eq!(non_zero.unwrap().as_u64(), 1024);
403
404 let zero = FileSize::new(0);
406 let result = NonZeroFileSize::try_from(zero);
407 assert!(result.is_err());
408 }
409}