Skip to main content

nifty_download/
size.rs

1//! `format_bytes(n)`: a size in bytes the way people read it.
2
3/// Each unit is this many of the one before, as macOS and web browsers count, so a size
4/// reads the same on every system.
5const STEP: u64 = 1000;
6const UNITS: &[&str] = &["KB", "MB", "GB", "TB", "PB", "EB"];
7/// Tenths kept for sizes of a kilobyte or more.
8const TENTHS: u64 = 10;
9
10/// `bytes` as text: `"1 byte"` and `"999 bytes"`, then one decimal place in the largest
11/// unit that keeps the number at least 1, such as `"1.5 MB"`.
12#[must_use]
13pub fn format_bytes(bytes: i64) -> String {
14    let sign = if bytes < 0 { "-" } else { "" };
15    let size = bytes.unsigned_abs();
16    if size < STEP {
17        let noun = if size == 1 { "byte" } else { "bytes" };
18        return format!("{sign}{size} {noun}");
19    }
20    let mut divisor = STEP;
21    let mut unit = 0;
22    // Tenths of the unit, rounded to nearest; rounding may reach the next unit, as
23    // 999,950 bytes does, so that is checked after rounding.
24    let mut tenths = rounded_tenths(size, divisor);
25    while tenths >= STEP * TENTHS && unit + 1 < UNITS.len() {
26        divisor *= STEP;
27        unit += 1;
28        tenths = rounded_tenths(size, divisor);
29    }
30    format!(
31        "{sign}{}.{} {}",
32        tenths / TENTHS,
33        tenths % TENTHS,
34        UNITS[unit]
35    )
36}
37
38fn rounded_tenths(size: u64, divisor: u64) -> u64 {
39    let tenths =
40        (u128::from(size) * u128::from(TENTHS) + u128::from(divisor) / 2) / u128::from(divisor);
41    u64::try_from(tenths).expect("a size in tenths of a kilobyte or more fits a u64")
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn counts_bytes_then_larger_units() {
50        assert_eq!(format_bytes(0), "0 bytes");
51        assert_eq!(format_bytes(1), "1 byte");
52        assert_eq!(format_bytes(999), "999 bytes");
53        assert_eq!(format_bytes(1000), "1.0 KB");
54        assert_eq!(format_bytes(1_540), "1.5 KB");
55        assert_eq!(format_bytes(1_550), "1.6 KB");
56        assert_eq!(format_bytes(15_200_000), "15.2 MB");
57        assert_eq!(format_bytes(2_000_000_000), "2.0 GB");
58    }
59
60    #[test]
61    fn rounding_up_moves_to_the_next_unit() {
62        assert_eq!(format_bytes(999_949), "999.9 KB");
63        assert_eq!(format_bytes(999_950), "1.0 MB");
64    }
65
66    #[test]
67    fn handles_the_ends_of_the_int_range() {
68        assert_eq!(format_bytes(-1_500), "-1.5 KB");
69        assert_eq!(format_bytes(i64::MAX), "9.2 EB");
70        assert_eq!(format_bytes(i64::MIN), "-9.2 EB");
71    }
72}