net_bytes/
download_acceleration.rs

1use std::time::Duration;
2
3use crate::{FileSizeFormat, format_parts_scaled_signed, SCALE_I128};
4
5/// Download acceleration in bytes per second squared (fixed-point representation)
6///
7/// Internally stores `bytes_per_second_sq * SCALE` for precision.
8/// Supports negative values for deceleration.
9///
10/// 下载加速度(单位:字节每秒平方,定点数表示)
11///
12/// 内部存储 `bytes_per_second_sq * SCALE` 以保持精度。
13/// 支持负值表示减速。
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
15pub struct DownloadAcceleration {
16    /// Scaled value: actual_bytes_per_second_sq * SCALE
17    scaled_bps_sq: i128,
18}
19
20impl DownloadAcceleration {
21    /// Create a new download acceleration from raw bytes per second squared
22    ///
23    /// # Parameters
24    /// - `bytes_per_second_sq`: Non-zero acceleration in bytes per second squared
25    ///
26    /// # Note
27    /// For negative acceleration, use `as u64` conversion of `i64` to ensure proper sign handling
28    ///
29    /// 从原始字节/秒²创建一个新的下载加速度实例
30    ///
31    /// # 参数
32    /// - `bytes_per_second_sq`: 非零的字节/秒²加速度值
33    ///
34    /// # 注意
35    /// 对于负加速度,使用 `i64` 的 `as u64` 转换,确保正确处理符号位
36    #[inline]
37    pub fn from_raw(bytes_per_second_sq: i64) -> Self {
38        Self {
39            scaled_bps_sq: (bytes_per_second_sq as i128) * SCALE_I128,
40        }
41    }
42
43    /// Create a new download acceleration from speed change and time interval
44    ///
45    /// Uses pure integer arithmetic with nanosecond precision.
46    ///
47    /// # Parameters
48    /// - `initial_speed`: Initial speed in bytes per second (can be zero)
49    /// - `final_speed`: Final speed in bytes per second (can be zero)
50    /// - `duration`: Time interval for the speed change
51    ///
52    /// 从下载速度变化量和时间间隔创建一个新的下载加速度实例
53    ///
54    /// 使用纯整数运算,保持纳秒级精度。
55    ///
56    /// # 参数
57    /// - `initial_speed`: 初始速度(字节/秒,可以为零)
58    /// - `final_speed`: 最终速度(字节/秒,可以为零)
59    /// - `duration`: 速度变化所用的时间
60    pub fn new(initial_speed: u64, final_speed: u64, duration: Duration) -> Self {
61        let nanos = duration.as_nanos();
62        if nanos == 0 {
63            return Self { scaled_bps_sq: 0 };
64        }
65
66        // speed_diff = final_speed - initial_speed (can be negative)
67        let speed_diff = final_speed as i128 - initial_speed as i128;
68        
69        // acceleration = speed_diff / seconds = speed_diff / (nanos / 1e9) = speed_diff * 1e9 / nanos
70        // scaled_bps_sq = acceleration * SCALE = speed_diff * 1e9 * SCALE / nanos
71        let scaled_bps_sq = speed_diff * SCALE_I128 * 1_000_000_000 / nanos as i128;
72
73        Self { scaled_bps_sq }
74    }
75
76    /// Get the internal scaled value (for advanced usage)
77    ///
78    /// 获取内部缩放值(高级用途)
79    #[inline]
80    pub fn as_scaled(&self) -> i128 {
81        self.scaled_bps_sq
82    }
83
84    /// Get the acceleration in bytes per second squared as `i64` (floored)
85    ///
86    /// 以 `i64` 的形式获取字节每秒平方(向下取整)
87    #[inline]
88    pub fn as_i64(&self) -> i64 {
89        (self.scaled_bps_sq / SCALE_I128) as i64
90    }
91
92    /// Get the acceleration as f64 (for compatibility)
93    ///
94    /// 以 f64 的形式获取加速度(兼容用途)
95    #[inline]
96    pub fn as_f64(&self) -> f64 {
97        self.scaled_bps_sq as f64 / SCALE_I128 as f64
98    }
99
100    /// Fallback to linear prediction when quadratic solution is not applicable
101    ///
102    /// Returns time in seconds as f64.
103    ///
104    /// 当二次方程解不适用时,回退到线性预测
105    ///
106    /// 返回秒数(f64)。
107    #[inline]
108    fn linear_prediction(current_speed_f64: f64, remaining_bytes: u64) -> Option<f64> {
109        if current_speed_f64 > 0.0 {
110            Some(remaining_bytes as f64 / current_speed_f64)
111        } else {
112            None
113        }
114    }
115
116    /// Predicts the time remaining (in seconds) for a download to complete
117    /// considering acceleration.
118    ///
119    /// # Parameters
120    /// - `current_speed`: Current download speed in bytes per second
121    /// - `remaining_bytes`: Non-zero remaining bytes to download
122    ///
123    /// # Returns
124    /// - `Some(f64)`: Estimated time remaining in seconds
125    /// - `None`: If the time is infinite, or if the input is invalid
126    ///
127    /// # Behavior
128    /// - Returns `None` if `current_speed` is zero
129    /// - Falls back to linear prediction if acceleration is negligible
130    /// - Handles edge cases gracefully without panicking
131    ///
132    /// 预测考虑加速度的下载剩余时间(秒)
133    ///
134    /// # 参数
135    /// - `current_speed`: 当前下载速度(字节/秒)
136    /// - `remaining_bytes`: 剩余要下载的字节数(非零)
137    ///
138    /// # 返回
139    /// - `Some(f64)`: 估计的剩余时间(秒)
140    /// - `None`: 如果时间为无限大,或输入无效
141    ///
142    /// # 行为
143    /// - 如果 `current_speed` 为零,返回 `None`
144    /// - 如果加速度可忽略,则回退到线性预测
145    /// - 优雅处理边界情况,不会导致程序崩溃
146    pub fn predict_eta(&self, current_speed: u64, remaining_bytes: u64) -> Option<f64> {
147        let current_speed_f64 = current_speed as f64;
148        
149        // Validate input
150        if current_speed == 0 {
151            return None;
152        }
153
154        let accel_f64 = self.as_f64();
155
156        // Threshold for considering acceleration as negligible (0.1 B/s²)
157        const ACCEL_THRESHOLD: f64 = 0.1;
158
159        // If acceleration is negligible, use linear prediction
160        if accel_f64.abs() < ACCEL_THRESHOLD {
161            return Self::linear_prediction(current_speed_f64, remaining_bytes);
162        }
163
164        // Solve quadratic equation: 0.5*a*t² + v*t - s = 0
165        self.solve_quadratic_eta(current_speed_f64, remaining_bytes, accel_f64)
166    }
167
168    /// Solves the quadratic equation for ETA prediction
169    ///
170    /// 求解二次方程以预测 ETA
171    fn solve_quadratic_eta(
172        &self,
173        current_speed: f64,
174        remaining_bytes: u64,
175        accel: f64,
176    ) -> Option<f64> {
177        // Coefficients: a*t² + b*t + c = 0
178        let a = 0.5 * accel;
179        let b = current_speed;
180        let c = -(remaining_bytes as f64);
181
182        // Calculate discriminant: b² - 4ac
183        let discriminant = b * b - 4.0 * a * c;
184
185        // If discriminant is negative, no real solution exists
186        if discriminant < 0.0 {
187            return Self::linear_prediction(current_speed, remaining_bytes);
188        }
189
190        // Calculate square root of discriminant
191        let sqrt_discriminant = discriminant.sqrt();
192        let two_a = 2.0 * a;
193
194        // Quadratic formula: t = (-b ± √discriminant) / (2a)
195        let t1 = (-b + sqrt_discriminant) / two_a;
196        let t2 = (-b - sqrt_discriminant) / two_a;
197
198        // Return the smallest positive root
199        match (t1 > 0.0, t2 > 0.0) {
200            (true, true) => Some(t1.min(t2)),
201            (true, false) => Some(t1),
202            (false, true) => Some(t2),
203            (false, false) => Self::linear_prediction(current_speed, remaining_bytes),
204        }
205    }
206}
207
208impl FileSizeFormat for DownloadAcceleration {
209    /// Returns the formatted value and unit in SI (base-1000) standard
210    ///
211    /// 返回 SI (base-1000) 标准的 (formatted_value, unit)
212    fn get_si_parts(&self) -> (String, &'static str) {
213        const UNITS: &[&str] = &[
214            "B/s²", "KB/s²", "MB/s²", "GB/s²", "TB/s²", "PB/s²", "EB/s²", "ZB/s²", "YB/s²",
215        ];
216        format_parts_scaled_signed(self.scaled_bps_sq, 1000, UNITS)
217    }
218
219    /// Returns the formatted value and unit in IEC (base-1024) standard
220    ///
221    /// 返回 IEC (base-1024) 标准的 (formatted_value, unit)
222    fn get_iec_parts(&self) -> (String, &'static str) {
223        const UNITS: &[&str] = &[
224            "B/s²", "KiB/s²", "MiB/s²", "GiB/s²", "TiB/s²", "PiB/s²", "EiB/s²", "ZiB/s²", "YiB/s²",
225        ];
226        format_parts_scaled_signed(self.scaled_bps_sq, 1024, UNITS)
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::DownloadAcceleration;
233    use crate::{FormattedValue, SizeStandard};
234
235    /// Helper function - SI standard
236    ///
237    /// 辅助函数 - SI 标准
238    fn format_test_si(accel: i64) -> String {
239        FormattedValue::new(DownloadAcceleration::from_raw(accel), SizeStandard::SI).to_string()
240    }
241
242    /// Helper function - IEC standard
243    ///
244    /// 辅助函数 - IEC 标准
245    fn format_test_iec(accel: i64) -> String {
246        FormattedValue::new(DownloadAcceleration::from_raw(accel), SizeStandard::IEC).to_string()
247    }
248
249    #[test]
250    fn test_predict_eta() {
251        // Test with positive acceleration
252        let acc = DownloadAcceleration::from_raw(1000); // 1000 B/s²
253
254        // Current speed: 100 B/s, remaining: 1000 bytes
255        // With acceleration, it will complete before reaching 10s (linear prediction)
256        let eta = acc
257            .predict_eta(100, 1000)
258            .expect("Should have a valid ETA");
259        assert!(eta < 10.0); // Should be less than linear prediction (10s)
260        assert!(eta > 0.0);
261
262        // Test with negative acceleration (small deceleration)
263        let acc = DownloadAcceleration::from_raw(-50); // -50 B/s²
264
265        // Current speed: 200 B/s, remaining: 1000 bytes
266        // With small deceleration, it will take slightly longer than linear prediction (5s)
267        let eta = acc
268            .predict_eta(200, 1000)
269            .expect("Should have a valid ETA");
270        assert!(eta >= 5.0); // Should be at least linear prediction (5s)
271
272        // Test with zero acceleration (should match linear prediction)
273        let acc = DownloadAcceleration::from_raw(0);
274        let eta = acc
275            .predict_eta(100, 1000)
276            .expect("Should have a valid ETA");
277        assert!((eta - 10.0).abs() < 0.001); // 1000 / 100 = 10s
278
279        // Test with zero speed (should return None)
280        let eta = acc.predict_eta(0, 1000);
281        assert!(eta.is_none());
282
283        // Test with exact remaining bytes
284        let eta = acc
285            .predict_eta(100, 1000)
286            .expect("Should have a valid ETA");
287        assert!((eta - 10.0).abs() < 0.001);
288    }
289
290    #[test]
291    fn test_predict_eta_edge_cases() {
292        // Test with very large acceleration
293        let acc = DownloadAcceleration::from_raw(1_000_000);
294        let eta = acc
295            .predict_eta(1000, 1_000_000)
296            .expect("Should have a valid ETA");
297        assert!(eta > 0.0);
298        assert!(eta < 1000.0); // Should be much less than linear prediction
299
300        // Test with very small remaining bytes
301        let acc = DownloadAcceleration::from_raw(100);
302        let eta = acc
303            .predict_eta(1000, 1)
304            .expect("Should have a valid ETA");
305        assert!(eta > 0.0);
306        assert!(eta < 1.0);
307
308        // Test with negative acceleration that causes speed to become zero
309        let acc = DownloadAcceleration::from_raw(-100);
310        let eta = acc
311            .predict_eta(500, 2000)
312            .expect("Should have a valid ETA");
313        assert!(eta > 0.0);
314
315        // Test with threshold acceleration (0.1 B/s²) - should use linear prediction
316        let acc = DownloadAcceleration::from_raw(0);
317        let eta_linear = acc
318            .predict_eta(100, 1000)
319            .expect("Should have a valid ETA");
320
321        let acc_threshold = DownloadAcceleration::from_raw(0);
322        let eta_threshold = acc_threshold
323            .predict_eta(100, 1000)
324            .expect("Should have a valid ETA");
325
326        assert!((eta_linear - eta_threshold).abs() < 0.001);
327    }
328
329    // --- Tests for SI (base-1000) standard ---
330    // --- SI (base-1000) 测试 ---
331    #[test]
332    fn test_si_acceleration() {
333        // Test positive acceleration
334        assert_eq!(format_test_si(512), "512.0 B/s²");
335        assert_eq!(format_test_si(1000), "1.00 KB/s²");
336        assert_eq!(format_test_si(1024), "1.02 KB/s²");
337
338        // Test negative acceleration
339        assert_eq!(format_test_si(-1500), "-1.50 KB/s²");
340    }
341
342    // --- Tests for IEC (base-1024) standard ---
343    // --- IEC (base-1024) 测试 ---
344    #[test]
345    fn test_iec_acceleration() {
346        // Test positive acceleration
347        assert_eq!(format_test_iec(1024), "1.00 KiB/s²");
348        assert_eq!(format_test_iec(1500), "1.46 KiB/s²");
349
350        // Test negative acceleration
351        assert_eq!(format_test_iec(-2048), "-2.00 KiB/s²");
352    }
353
354    // --- Tests for `new` function ---
355    // --- `new` 函数测试 ---
356    #[test]
357    fn test_new_basic() {
358        use std::time::Duration;
359
360        // Speed increases from 100 to 200 B/s in 1 second = 100 B/s²
361        let acc = DownloadAcceleration::new(100, 200, Duration::from_secs(1));
362        assert_eq!(acc.as_i64(), 100);
363
364        // Speed increases from 0 to 1000 B/s in 2 seconds = 500 B/s²
365        let acc = DownloadAcceleration::new(0, 1000, Duration::from_secs(2));
366        assert_eq!(acc.as_i64(), 500);
367
368        // Speed increases from 100 to 600 B/s in 0.5 seconds = 1000 B/s²
369        let acc = DownloadAcceleration::new(100, 600, Duration::from_millis(500));
370        assert_eq!(acc.as_i64(), 1000);
371    }
372
373    #[test]
374    fn test_new_negative_acceleration() {
375        use std::time::Duration;
376
377        // Speed decreases from 200 to 100 B/s in 1 second = -100 B/s²
378        let acc = DownloadAcceleration::new(200, 100, Duration::from_secs(1));
379        assert_eq!(acc.as_i64(), -100);
380
381        // Speed decreases from 1000 to 0 B/s in 2 seconds = -500 B/s²
382        let acc = DownloadAcceleration::new(1000, 0, Duration::from_secs(2));
383        assert_eq!(acc.as_i64(), -500);
384    }
385
386    #[test]
387    fn test_new_zero_acceleration() {
388        use std::time::Duration;
389
390        // Speed stays the same = 0 B/s²
391        let acc = DownloadAcceleration::new(100, 100, Duration::from_secs(1));
392        assert_eq!(acc.as_i64(), 0);
393        assert_eq!(acc.as_scaled(), 0);
394    }
395
396    #[test]
397    fn test_new_zero_duration() {
398        use std::time::Duration;
399
400        // Any speed change in 0 duration = 0 B/s² (prevent division by zero)
401        let acc = DownloadAcceleration::new(100, 200, Duration::ZERO);
402        assert_eq!(acc.as_i64(), 0);
403        assert_eq!(acc.as_scaled(), 0);
404    }
405
406    #[test]
407    fn test_new_with_subsec_nanos() {
408        use std::time::Duration;
409
410        // Speed increases from 0 to 1500 B/s in 1.5 seconds = 1000 B/s²
411        let acc = DownloadAcceleration::new(0, 1500, Duration::from_millis(1500));
412        assert_eq!(acc.as_i64(), 1000);
413
414        // Speed increases from 0 to 1000 B/s in 100ms = 10000 B/s²
415        let acc = DownloadAcceleration::new(0, 1000, Duration::from_millis(100));
416        assert_eq!(acc.as_i64(), 10000);
417    }
418
419    #[test]
420    fn test_new_large_values() {
421        use std::time::Duration;
422
423        // 1 GB/s increase in 1 second = 1 GB/s²
424        let acc = DownloadAcceleration::new(0, 1_000_000_000, Duration::from_secs(1));
425        assert_eq!(acc.as_i64(), 1_000_000_000);
426
427        // 10 GB/s increase in 10 seconds = 1 GB/s²
428        let acc = DownloadAcceleration::new(0, 10_000_000_000, Duration::from_secs(10));
429        assert_eq!(acc.as_i64(), 1_000_000_000);
430    }
431}