net_bytes/
file_size.rs

1//! File size formatting utilities
2//!
3//! Provides functionality for formatting file sizes with support for both
4//! SI (base-1000) and IEC (base-1024) standards.
5//!
6//! 提供文件大小格式化功能,支持 SI (base-1000) 和 IEC (base-1024) 两种标准。
7
8use crate::{FileSizeFormat, format_parts_scaled, SCALE};
9use std::num::NonZeroU64;
10use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
11
12/// A non-zero file size representation
13///
14/// 一个表示非零文件大小的结构体。
15#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
16pub struct NonZeroFileSize {
17    bytes: NonZeroU64,
18}
19
20impl NonZeroFileSize {
21    /// Create a new FileSize instance
22    ///
23    /// 创建一个新的 FileSize 实例
24    #[inline]
25    pub fn new(bytes: NonZeroU64) -> Self {
26        Self { bytes }
27    }
28
29    /// Get the inner NonZeroU64 byte value
30    ///
31    /// 获取内部的 NonZeroU64 字节值
32    #[inline]
33    pub fn get_nonzero(&self) -> NonZeroU64 {
34        self.bytes
35    }
36
37    /// Get the byte value as u64
38    ///
39    /// 以 u64 的形式获取字节值
40    #[inline]
41    pub fn as_u64(&self) -> u64 {
42        self.bytes.get()
43    }
44}
45
46// Implement FileSizeFormat for FileSize
47impl FileSizeFormat for NonZeroFileSize {
48    /// Returns the formatted value and unit in SI (base-1000) standard
49    ///
50    /// The formatted_value is returned as String to ensure correct decimal places (e.g., "1.00")
51    ///
52    /// 返回 SI (base-1000) 标准的 (formatted_value, unit)
53    ///
54    /// formatted_value 作为 String 返回,以保证正确的小数位数 (例如 "1.00")
55    #[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    /// Returns the formatted value and unit in IEC (base-1024) standard
62    ///
63    /// The formatted_value is returned as String to ensure correct decimal places (e.g., "1.00")
64    ///
65    /// 返回 IEC (base-1024) 标准的 (formatted_value, unit)
66    ///
67    /// formatted_value 作为 String 返回,以保证正确的小数位数 (例如 "1.00")
68    #[inline]
69    fn get_iec_parts(&self) -> (String, &'static str) {
70        format_iec_parts(self.bytes.get())
71    }
72}
73
74/// A file size representation that can be zero
75///
76/// 一个可以表示零的文件大小结构体。
77#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
78pub struct FileSize {
79    bytes: u64,
80}
81
82impl FileSize {
83    /// Create a new FileSize instance from bytes
84    ///
85    /// 从字节数创建一个新的 FileSize 实例
86    #[inline]
87    pub const fn new(bytes: u64) -> Self {
88        Self { bytes }
89    }
90
91    /// Get the byte value as u64
92    ///
93    /// 以 u64 的形式获取字节值
94    #[inline]
95    pub const fn as_u64(&self) -> u64 {
96        self.bytes
97    }
98
99    /// Convert to NonZeroFileSize if the value is not zero
100    ///
101    /// 如果值不为零,则转换为 NonZeroFileSize
102    #[inline]
103    pub fn to_nonzero(&self) -> Option<NonZeroFileSize> {
104        NonZeroU64::new(self.bytes).map(NonZeroFileSize::new)
105    }
106}
107
108// Implement arithmetic operations for FileSize
109impl 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
153// Implement assignment operators
154impl 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
186// Implement From and TryFrom for conversions
187impl 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
210// Implement FileSizeFormat for FileSize
211impl FileSizeFormat for FileSize {
212    /// Returns the formatted value and unit in SI (base-1000) standard
213    ///
214    /// The formatted_value is returned as String to ensure correct decimal places (e.g., "1.00")
215    ///
216    /// 返回 SI (base-1000) 标准的 (formatted_value, unit)
217    ///
218    /// formatted_value 作为 String 返回,以保证正确的小数位数 (例如 "1.00")
219    #[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    /// Returns the formatted value and unit in IEC (base-1024) standard
226    ///
227    /// The formatted_value is returned as String to ensure correct decimal places (e.g., "1.00")
228    ///
229    /// 返回 IEC (base-1024) 标准的 (formatted_value, unit)
230    ///
231    /// formatted_value 作为 String 返回,以保证正确的小数位数 (例如 "1.00")
232    #[inline]
233    fn get_iec_parts(&self) -> (String, &'static str) {
234        format_iec_parts(self.bytes)
235    }
236}
237
238/// Format bytes using IEC (base-1024) units
239///
240/// 使用 IEC (base-1024) 单位格式化字节数
241fn 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    // Format with appropriate decimal places
254    let formatted = if value < 10.0 {
255        // Always show 2 decimal places for values < 10
256        format!("{:.2}", value)
257    } else {
258        // Always show 1 decimal place for values >= 10
259        format!("{:.1}", value)
260    };
261
262    (formatted, UNITS[unit_index])
263}
264
265// 单元测试
266#[cfg(test)]
267mod tests {
268    use crate::{FileSizeFormat, FormattedValue, SizeStandard};
269
270    use super::{FileSize, NonZeroFileSize};
271    use std::num::NonZeroU64;
272
273    /// Helper function - SI standard
274    ///
275    /// 辅助函数 - SI 标准
276    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    /// Helper function - IEC standard
282    ///
283    /// 辅助函数 - IEC 标准
284    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    // --- Tests for SI (base-1000) standard ---
290    // --- SI (base-1000) 测试 ---
291    #[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        // 1.024 -> 1.02 (2 位小数)
301        assert_eq!(format_test_si(1024), "1.02 KB");
302        // 9.999 -> 10.00 (2 位小数, 舍入)
303        assert_eq!(format_test_si(9999), "10.00 KB");
304        // 10.0 (1 位小数)
305        assert_eq!(format_test_si(10000), "10.0 KB");
306        // 100.0 (1 位小数)
307        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    // --- Tests for IEC (base-1024) standard ---
316    // --- IEC (base-1024) 测试 ---
317    #[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        // 1.464... -> 1.46 (2 位小数)
327        assert_eq!(format_test_iec(1500), "1.46 KiB");
328        // 9.999... -> 10.00 (2 位小数, 舍入)
329        let bytes_near_10 = (9.999 * 1024.0) as u64;
330        assert_eq!(format_test_iec(bytes_near_10), "10.00 KiB");
331        // 10.0 (1 位小数)
332        assert_eq!(format_test_iec(10 * 1024), "10.0 KiB");
333        // 100.0 (1 位小数)
334        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    // --- Tests for FileSize ---
343    // --- FileSize 测试 ---
344    #[test]
345    fn test_filesize_arithmetic() {
346        let size1 = FileSize::new(1024);
347        let size2 = FileSize::new(2048);
348
349        // Addition
350        assert_eq!((size1 + size2).as_u64(), 3072);
351
352        // Subtraction
353        assert_eq!((size2 - size1).as_u64(), 1024);
354        assert_eq!((size1 - size2).as_u64(), 0); // Saturating sub
355
356        // Multiplication
357        assert_eq!((size1 * 2).as_u64(), 2048);
358
359        // Division
360        assert_eq!((size2 / 2).as_u64(), 1024);
361
362        // Division by zero
363        assert_eq!((size1 / 0).as_u64(), 0);
364    }
365
366    #[test]
367    fn test_filesize_formatting() {
368        let size = FileSize::new(1500);
369
370        // Test SI formatting
371        let (value, unit) = size.get_si_parts();
372        assert_eq!(&value, "1.50");
373        assert_eq!(unit, "KB");
374
375        // Test IEC formatting
376        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        // From u64
384        let size = FileSize::from(1024u64);
385        assert_eq!(size.as_u64(), 1024);
386
387        // From NonZeroFileSize
388        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        // To NonZeroFileSize
393        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        // Zero to NonZeroFileSize should fail
399        let zero = FileSize::new(0);
400        let result = NonZeroFileSize::try_from(zero);
401        assert!(result.is_err());
402    }
403}