net-bytes 0.3.0

A Rust library for handling file sizes, download speeds, and download acceleration with support for both SI and IEC standards
Documentation
use std::time::Duration;

use crate::{FileSizeFormat, format_parts_scaled, SCALE};

/// Download speed in bytes per second (fixed-point representation)
///
/// Internally stores `bytes_per_second * SCALE` for precision.
///
/// 表示下载速度(单位:字节每秒,定点数表示)
///
/// 内部存储 `bytes_per_second * SCALE` 以保持精度。
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct DownloadSpeed {
    /// Scaled value: actual_bytes_per_second * SCALE
    scaled_bps: u128,
}

impl DownloadSpeed {
    /// Create a new download speed from raw bytes per second
    ///
    /// # Parameters
    /// - `bytes_per_second`: Speed in bytes per second (can be zero)
    ///
    /// 从原始字节/秒创建一个新的下载速度实例
    ///
    /// # 参数
    /// - `bytes_per_second`: 字节/秒速度(可以为零)
    #[inline]
    pub fn from_raw(bytes_per_second: u64) -> Self {
        Self {
            scaled_bps: (bytes_per_second as u128) * SCALE,
        }
    }

    /// Create a new download speed from bytes downloaded and time taken
    ///
    /// Uses pure integer arithmetic with nanosecond precision.
    ///
    /// # Parameters
    /// - `bytes`: Number of bytes downloaded (can be zero)
    /// - `duration`: Time taken for the download
    ///
    /// 从下载的字节数和所用时间创建一个新的下载速度实例
    ///
    /// 使用纯整数运算,保持纳秒级精度。
    ///
    /// # 参数
    /// - `bytes`: 下载的字节数(可以为零)
    /// - `duration`: 下载所用的时间
    pub fn new(bytes: u64, duration: Duration) -> Self {
        let nanos = duration.as_nanos();
        if nanos == 0 {
            return Self { scaled_bps: 0 };
        }

        // bytes_per_second = bytes / (nanos / 1e9) = bytes * 1e9 / nanos
        // scaled_bps = bytes_per_second * SCALE = bytes * 1e9 * SCALE / nanos
        // To avoid overflow, we compute: (bytes * SCALE) * 1e9 / nanos
        // But for very large bytes, we need to be careful about overflow
        // u128 max is ~3.4e38, bytes max is ~1.8e19, SCALE is 1e6, 1e9
        // So bytes * SCALE * 1e9 could be ~1.8e34 which fits in u128
        let scaled_bps = (bytes as u128) * SCALE * 1_000_000_000 / nanos;
        
        Self { scaled_bps }
    }

    /// Get the internal scaled value (for advanced usage)
    ///
    /// 获取内部缩放值(高级用途)
    #[inline]
    pub fn as_scaled(&self) -> u128 {
        self.scaled_bps
    }

    /// Get the speed in bytes per second as `u64` (floored)
    ///
    /// 以 `u64` 的形式获取字节每秒(向下取整)
    #[inline]
    pub fn as_u64(&self) -> u64 {
        (self.scaled_bps / SCALE) as u64
    }

    /// Get the speed as f64 (for compatibility)
    ///
    /// 以 f64 的形式获取速度(兼容用途)
    #[inline]
    pub fn as_f64(&self) -> f64 {
        self.scaled_bps as f64 / SCALE as f64
    }
}

impl FileSizeFormat for DownloadSpeed {
    /// Returns the formatted value and unit in SI (base-1000) standard
    ///
    /// 返回 SI (base-1000) 标准的 (formatted_value, unit)
    fn get_si_parts(&self) -> (String, &'static str) {
        const UNITS: &[&str] = &[
            "B/s", "KB/s", "MB/s", "GB/s", "TB/s", "PB/s", "EB/s", "ZB/s", "YB/s",
        ];
        format_parts_scaled(self.scaled_bps, 1000, UNITS)
    }

    /// Returns the formatted value and unit in IEC (base-1024) standard
    ///
    /// 返回 IEC (base-1024) 标准的 (formatted_value, unit)
    fn get_iec_parts(&self) -> (String, &'static str) {
        const UNITS: &[&str] = &[
            "B/s", "KiB/s", "MiB/s", "GiB/s", "TiB/s", "PiB/s", "EiB/s", "ZiB/s", "YiB/s",
        ];
        format_parts_scaled(self.scaled_bps, 1024, UNITS)
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use super::DownloadSpeed;
    use crate::{SizeStandard, FileSizeFormat};

    /// Helper function - SI standard
    ///
    /// 辅助函数 - SI 标准
    fn format_test_si(bytes: u64) -> String {
        DownloadSpeed::from_raw(bytes).to_formatted(SizeStandard::SI).to_string()
    }

    /// Helper function - IEC standard
    ///
    /// 辅助函数 - IEC 标准
    fn format_test_iec(bytes: u64) -> String {
        DownloadSpeed::from_raw(bytes).to_formatted(SizeStandard::IEC).to_string()
    }

    // --- Tests for SI (base-1000) standard ---
    // --- SI (base-1000) 测试 ---
    #[test]
    fn test_si_speed() {
        assert_eq!(format_test_si(512), "512.0 B/s");
        assert_eq!(format_test_si(1000), "1.00 KB/s");
        assert_eq!(format_test_si(1024), "1.02 KB/s");
        assert_eq!(format_test_si(9999), "10.00 KB/s");
        assert_eq!(format_test_si(10_000), "10.0 KB/s");
        assert_eq!(format_test_si(100_000), "100.0 KB/s");
    }

    // --- Tests for IEC (base-1024) standard ---
    // --- IEC (base-1024) 测试 ---
    #[test]
    fn test_iec_speed() {
        assert_eq!(format_test_iec(0), "0.00 B/s");
        assert_eq!(format_test_iec(1024), "1.00 KiB/s");
        assert_eq!(format_test_iec(1500), "1.46 KiB/s");

        let bytes_near_10 = (9.999 * 1024.0) as u64;
        assert_eq!(format_test_iec(bytes_near_10), "10.00 KiB/s");

        assert_eq!(format_test_iec(10 * 1024), "10.0 KiB/s");
        assert_eq!(format_test_iec(100 * 1024), "100.0 KiB/s");
    }

    #[test]
    fn test_zero_speed() {
        let zero_speed = DownloadSpeed::from_raw(0);
        assert_eq!(zero_speed.as_u64(), 0);
        assert_eq!(zero_speed.as_scaled(), 0);

        let formatted = zero_speed.to_formatted(SizeStandard::SI).to_string();
        assert_eq!(formatted, "0.00 B/s");

        let zero_duration = Duration::from_secs(0);
        let zero_speed = DownloadSpeed::new(1000, zero_duration);
        assert_eq!(zero_speed.as_u64(), 0);
    }

    // --- Tests for `new` function ---
    // --- `new` 函数测试 ---
    #[test]
    fn test_new_basic() {
        // 1000 bytes in 1 second = 1000 B/s
        let speed = DownloadSpeed::new(1000, Duration::from_secs(1));
        assert_eq!(speed.as_u64(), 1000);

        // 2000 bytes in 2 seconds = 1000 B/s
        let speed = DownloadSpeed::new(2000, Duration::from_secs(2));
        assert_eq!(speed.as_u64(), 1000);

        // 1000 bytes in 0.5 seconds = 2000 B/s
        let speed = DownloadSpeed::new(1000, Duration::from_millis(500));
        assert_eq!(speed.as_u64(), 2000);
    }

    #[test]
    fn test_new_with_subsec_nanos() {
        // 1000 bytes in 1.5 seconds = 666 B/s (floored)
        let speed = DownloadSpeed::new(1000, Duration::from_millis(1500));
        assert_eq!(speed.as_u64(), 666);

        // 1000 bytes in 100ms = 10000 B/s
        let speed = DownloadSpeed::new(1000, Duration::from_millis(100));
        assert_eq!(speed.as_u64(), 10000);

        // Precise nanosecond calculation: 1_000_000 bytes in 1.000_000_001 seconds
        let speed = DownloadSpeed::new(1_000_000, Duration::new(1, 1));
        assert!(speed.as_u64() >= 999_999); // approximately 1_000_000 B/s
    }

    #[test]
    fn test_new_zero_bytes() {
        // 0 bytes in any duration = 0 B/s
        let speed = DownloadSpeed::new(0, Duration::from_secs(10));
        assert_eq!(speed.as_u64(), 0);
        assert_eq!(speed.as_scaled(), 0);
    }

    #[test]
    fn test_new_zero_duration() {
        // Any bytes in 0 duration = 0 B/s (prevent division by zero)
        let speed = DownloadSpeed::new(1000, Duration::ZERO);
        assert_eq!(speed.as_u64(), 0);
        assert_eq!(speed.as_scaled(), 0);
    }

    #[test]
    fn test_new_large_values() {
        // 1 GB in 1 second = 1 GB/s
        let speed = DownloadSpeed::new(1_000_000_000, Duration::from_secs(1));
        assert_eq!(speed.as_u64(), 1_000_000_000);

        // 10 GB in 10 seconds = 1 GB/s
        let speed = DownloadSpeed::new(10_000_000_000, Duration::from_secs(10));
        assert_eq!(speed.as_u64(), 1_000_000_000);
    }

    #[test]
    fn test_new_extreme_values() {
        // 1 byte in 1 second = 1 B/s
        let speed = DownloadSpeed::new(1, Duration::from_secs(1));
        assert_eq!(speed.as_u64(), 1);

        // 1 byte in 100 years = 0 B/s (floored)
        let speed = DownloadSpeed::new(1, Duration::from_secs(100 * 365 * 24 * 60 * 60));
        assert_eq!(speed.as_u64(), 0);
    }

    #[test]
    fn test_new_edge_cases() {
        // 1 byte in 1 nanosecond = 1_000_000_000 B/s
        let speed = DownloadSpeed::new(1, Duration::from_nanos(1));
        assert_eq!(speed.as_u64(), 1_000_000_000);

        // 1 byte in 1 second = 1 B/s
        let speed = DownloadSpeed::new(1, Duration::from_secs(1));
        assert_eq!(speed.as_u64(), 1);
    }
}