1#![allow(clippy::cast_precision_loss)]
9
10#[must_use]
29pub fn format_bytes_si(bytes: u64) -> String {
30 const UNITS: &[&str] = &["B", "K", "M", "G", "T", "P", "E"];
31 const THRESHOLD: f64 = 1000.0;
32
33 if bytes == 0 {
34 return "0B".to_string();
35 }
36
37 let mut value = bytes as f64;
38 let mut unit_idx = 0;
39
40 while value >= THRESHOLD && unit_idx < UNITS.len() - 1 {
41 value /= THRESHOLD;
42 unit_idx += 1;
43 }
44
45 if unit_idx == 0 {
46 format!("{bytes}B")
47 } else if value >= 100.0 {
48 format!("{value:.0}{}", UNITS[unit_idx])
49 } else if value >= 10.0 {
50 format!("{value:.1}{}", UNITS[unit_idx])
51 } else {
52 format!("{value:.2}{}", UNITS[unit_idx])
53 }
54}
55
56#[must_use]
66pub fn format_bytes_iec(bytes: u64) -> String {
67 const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
68 const THRESHOLD: f64 = 1024.0;
69
70 if bytes == 0 {
71 return "0B".to_string();
72 }
73
74 let mut value = bytes as f64;
75 let mut unit_idx = 0;
76
77 while value >= THRESHOLD && unit_idx < UNITS.len() - 1 {
78 value /= THRESHOLD;
79 unit_idx += 1;
80 }
81
82 if unit_idx == 0 {
83 format!("{bytes}B")
84 } else {
85 format!("{value:.2}{}", UNITS[unit_idx])
86 }
87}
88
89#[must_use]
103pub fn format_bytes(bytes: u64) -> String {
104 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
105 let mut size = bytes as f64;
106 let mut unit_index = 0;
107
108 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
109 size /= 1024.0;
110 unit_index += 1;
111 }
112
113 if unit_index == 0 {
114 format!("{bytes} {}", UNITS[unit_index])
115 } else {
116 format!("{size:.1} {}", UNITS[unit_index])
117 }
118}
119
120#[must_use]
135pub fn format_bytes_compact(bytes: u64) -> String {
136 const KB: u64 = 1024;
137 const MB: u64 = KB * 1024;
138 const GB: u64 = MB * 1024;
139 const TB: u64 = GB * 1024;
140
141 if bytes >= TB {
142 format!("{:.1}T", bytes as f64 / TB as f64)
143 } else if bytes >= GB {
144 format!("{:.1}G", bytes as f64 / GB as f64)
145 } else if bytes >= MB {
146 format!("{:.1}M", bytes as f64 / MB as f64)
147 } else {
148 format!("{:.1}K", bytes as f64 / KB as f64)
149 }
150}
151
152#[must_use]
169pub fn format_bytes_full(bytes: u64) -> String {
170 const KB: u64 = 1024;
171 const MB: u64 = KB * 1024;
172 const GB: u64 = MB * 1024;
173 const TB: u64 = GB * 1024;
174
175 if bytes >= TB {
176 format!("{:.2} TB", bytes as f64 / TB as f64)
177 } else if bytes >= GB {
178 format!("{:.2} GB", bytes as f64 / GB as f64)
179 } else if bytes >= MB {
180 format!("{:.1} MB", bytes as f64 / MB as f64)
181 } else if bytes >= KB {
182 format!("{:.1} KB", bytes as f64 / KB as f64)
183 } else {
184 format!("{bytes} B")
185 }
186}
187
188#[must_use]
196#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
197pub fn format_bytes_rate(bytes_per_sec: f64) -> String {
198 format!("{}/s", format_bytes_si(bytes_per_sec as u64))
199}
200
201#[must_use]
214pub fn format_percent(value: f64) -> String {
215 format!("{value:.1}%")
216}
217
218#[must_use]
227pub fn format_percent_clamped(value: f64) -> String {
228 format_percent(value.clamp(0.0, 100.0))
229}
230
231#[must_use]
239pub fn format_percent_fixed(value: f64, decimals: usize) -> String {
240 format!("{value:.decimals$}%")
241}
242
243#[must_use]
252pub fn usage_percent(used: u64, total: u64) -> String {
253 if total == 0 {
254 return "0.0%".to_string();
255 }
256 format_percent(used as f64 / total as f64 * 100.0)
257}
258
259#[must_use]
274pub fn format_duration(seconds: u64) -> String {
275 const MINUTE: u64 = 60;
276 const HOUR: u64 = 60 * MINUTE;
277 const DAY: u64 = 24 * HOUR;
278
279 if seconds < MINUTE {
280 format!("{seconds}s")
281 } else if seconds < HOUR {
282 let mins = seconds / MINUTE;
283 let secs = seconds % MINUTE;
284 if secs == 0 {
285 format!("{mins}m")
286 } else {
287 format!("{mins}m {secs}s")
288 }
289 } else if seconds < DAY {
290 let hours = seconds / HOUR;
291 let mins = (seconds % HOUR) / MINUTE;
292 if mins == 0 {
293 format!("{hours}h")
294 } else {
295 format!("{hours}h {mins}m")
296 }
297 } else {
298 let days = seconds / DAY;
299 let hours = (seconds % DAY) / HOUR;
300 if hours == 0 {
301 format!("{days}d")
302 } else {
303 format!("{days}d {hours}h")
304 }
305 }
306}
307
308#[must_use]
317pub fn format_duration_compact(seconds: u64) -> String {
318 const MINUTE: u64 = 60;
319 const HOUR: u64 = 60 * MINUTE;
320 const DAY: u64 = 24 * HOUR;
321
322 if seconds < MINUTE {
323 format!("{seconds:>5}s")
324 } else if seconds < HOUR {
325 let mins = seconds / MINUTE;
326 let secs = seconds % MINUTE;
327 format!("{mins:>2}m{secs:02}s")
328 } else if seconds < DAY {
329 let hours = seconds / HOUR;
330 let mins = (seconds % HOUR) / MINUTE;
331 format!("{hours:>2}h{mins:02}m")
332 } else {
333 let days = seconds / DAY;
334 let hours = (seconds % DAY) / HOUR;
335 format!("{days:>2}d{hours:02}h")
336 }
337}
338
339#[must_use]
353pub fn format_number(n: u64) -> String {
354 let s = n.to_string();
355 let mut result = String::with_capacity(s.len() + s.len() / 3);
356 let chars: Vec<char> = s.chars().collect();
357
358 for (i, c) in chars.iter().enumerate() {
359 if i > 0 && (chars.len() - i).is_multiple_of(3) {
360 result.push(',');
361 }
362 result.push(*c);
363 }
364
365 result
366}
367
368#[must_use]
377pub fn format_freq_mhz(mhz: u32) -> String {
378 if mhz >= 1000 {
379 format!("{:.1}GHz", f64::from(mhz) / 1000.0)
380 } else {
381 format!("{mhz}MHz")
382 }
383}
384
385#[cfg(test)]
390mod tests {
391 use super::*;
392
393 #[test]
394 fn test_format_bytes_si_zero() {
395 assert_eq!(format_bytes_si(0), "0B");
396 }
397
398 #[test]
399 fn test_format_bytes_si_small() {
400 assert_eq!(format_bytes_si(1), "1B");
401 assert_eq!(format_bytes_si(999), "999B");
402 }
403
404 #[test]
405 fn test_format_bytes_si_kilobytes() {
406 assert_eq!(format_bytes_si(1000), "1.00K");
407 assert_eq!(format_bytes_si(1500), "1.50K");
408 assert_eq!(format_bytes_si(10_000), "10.0K");
409 assert_eq!(format_bytes_si(100_000), "100K");
410 }
411
412 #[test]
413 fn test_format_bytes_si_megabytes() {
414 assert_eq!(format_bytes_si(1_000_000), "1.00M");
415 assert_eq!(format_bytes_si(1_500_000), "1.50M");
416 }
417
418 #[test]
419 fn test_format_bytes_si_gigabytes() {
420 assert_eq!(format_bytes_si(1_000_000_000), "1.00G");
421 assert_eq!(format_bytes_si(1_500_000_000), "1.50G");
422 }
423
424 #[test]
425 fn test_format_bytes_si_terabytes() {
426 assert_eq!(format_bytes_si(1_000_000_000_000), "1.00T");
427 }
428
429 #[test]
430 fn test_format_bytes_iec_zero() {
431 assert_eq!(format_bytes_iec(0), "0B");
432 }
433
434 #[test]
435 fn test_format_bytes_iec_kib() {
436 assert_eq!(format_bytes_iec(1024), "1.00KiB");
437 assert_eq!(format_bytes_iec(1536), "1.50KiB");
438 }
439
440 #[test]
441 fn test_format_bytes_iec_mib() {
442 assert_eq!(format_bytes_iec(1_048_576), "1.00MiB");
443 }
444
445 #[test]
446 fn test_format_bytes_human() {
447 assert_eq!(format_bytes(0), "0 B");
448 assert_eq!(format_bytes(1023), "1023 B");
449 assert_eq!(format_bytes(1024), "1.0 KB");
450 assert_eq!(format_bytes(1_048_576), "1.0 MB");
451 assert_eq!(format_bytes(1_073_741_824), "1.0 GB");
452 }
453
454 #[test]
455 fn test_format_bytes_compact() {
456 assert_eq!(format_bytes_compact(0), "0.0K");
457 assert_eq!(format_bytes_compact(512), "0.5K");
458 assert_eq!(format_bytes_compact(1024), "1.0K");
459 assert_eq!(format_bytes_compact(1_048_576), "1.0M");
460 assert_eq!(format_bytes_compact(1_073_741_824), "1.0G");
461 assert_eq!(format_bytes_compact(1_099_511_627_776), "1.0T");
462 }
463
464 #[test]
465 fn test_format_bytes_full() {
466 assert_eq!(format_bytes_full(500), "500 B");
467 assert_eq!(format_bytes_full(1024), "1.0 KB");
468 assert_eq!(format_bytes_full(1_048_576), "1.0 MB");
469 assert_eq!(format_bytes_full(1_073_741_824), "1.00 GB");
470 assert_eq!(format_bytes_full(1_099_511_627_776), "1.00 TB");
471 }
472
473 #[test]
474 fn test_format_bytes_rate() {
475 assert_eq!(format_bytes_rate(1500.0), "1.50K/s");
476 assert_eq!(format_bytes_rate(0.0), "0B/s");
477 }
478
479 #[test]
480 fn test_format_percent() {
481 assert_eq!(format_percent(45.3), "45.3%");
482 assert_eq!(format_percent(0.0), "0.0%");
483 assert_eq!(format_percent(100.0), "100.0%");
484 }
485
486 #[test]
487 fn test_format_percent_clamped() {
488 assert_eq!(format_percent_clamped(150.0), "100.0%");
489 assert_eq!(format_percent_clamped(-10.0), "0.0%");
490 assert_eq!(format_percent_clamped(50.0), "50.0%");
491 }
492
493 #[test]
494 fn test_format_percent_fixed() {
495 assert_eq!(format_percent_fixed(45.333, 2), "45.33%");
496 assert_eq!(format_percent_fixed(5.0, 0), "5%");
497 }
498
499 #[test]
500 fn test_usage_percent() {
501 assert_eq!(usage_percent(50, 200), "25.0%");
502 assert_eq!(usage_percent(100, 100), "100.0%");
503 assert_eq!(usage_percent(0, 0), "0.0%");
504 assert_eq!(usage_percent(0, 100), "0.0%");
505 }
506
507 #[test]
508 fn test_format_duration_seconds() {
509 assert_eq!(format_duration(0), "0s");
510 assert_eq!(format_duration(45), "45s");
511 assert_eq!(format_duration(59), "59s");
512 }
513
514 #[test]
515 fn test_format_duration_minutes() {
516 assert_eq!(format_duration(60), "1m");
517 assert_eq!(format_duration(125), "2m 5s");
518 assert_eq!(format_duration(3599), "59m 59s");
519 }
520
521 #[test]
522 fn test_format_duration_hours() {
523 assert_eq!(format_duration(3600), "1h");
524 assert_eq!(format_duration(3725), "1h 2m");
525 assert_eq!(format_duration(86399), "23h 59m");
526 }
527
528 #[test]
529 fn test_format_duration_days() {
530 assert_eq!(format_duration(86400), "1d");
531 assert_eq!(format_duration(90061), "1d 1h");
532 }
533
534 #[test]
535 fn test_format_duration_compact() {
536 assert_eq!(format_duration_compact(45), " 45s");
537 assert_eq!(format_duration_compact(125), " 2m05s");
538 assert_eq!(format_duration_compact(3725), " 1h02m");
539 assert_eq!(format_duration_compact(90061), " 1d01h");
540 }
541
542 #[test]
543 fn test_format_number() {
544 assert_eq!(format_number(0), "0");
545 assert_eq!(format_number(999), "999");
546 assert_eq!(format_number(1000), "1,000");
547 assert_eq!(format_number(1_234_567), "1,234,567");
548 }
549
550 #[test]
551 fn test_format_freq_mhz() {
552 assert_eq!(format_freq_mhz(800), "800MHz");
553 assert_eq!(format_freq_mhz(3200), "3.2GHz");
554 assert_eq!(format_freq_mhz(1000), "1.0GHz");
555 }
556}