Skip to main content

ferroui_base/utilities/
byte_size_helper.rs

1use super::CultureInfo;
2use crate::data::converters::composite_format::{format_with, FormatArg};
3
4const FORMAT_TEMPLATE_SEPARATED: &str = "{0}{1:0.#} {2}";
5const FORMAT_TEMPLATE: &str = "{0}{1:0.#}{2}";
6
7const PREFIXES: [&str; 9] = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
8
9/// Formats a size in bytes as text with a decimal unit prefix (`1.5 KB`).
10pub struct ByteSizeHelper;
11
12impl ByteSizeHelper {
13    /// The text of `bytes` with the largest decimal unit that keeps the
14    /// number at least one, with at most one fractional digit, formatted
15    /// with the conventions of the current culture.
16    ///
17    /// `separate` puts a space between the number and the unit; as in the
18    /// original, only a size of zero honours it.
19    pub fn to_string(bytes: u64, separate: bool) -> String {
20        let culture = CultureInfo::current_culture();
21
22        if bytes == 0 {
23            let template = if separate { FORMAT_TEMPLATE_SEPARATED } else { FORMAT_TEMPLATE };
24            return format_with(template, &[FormatArg::Null, FormatArg::I32(0), FormatArg::Str(PREFIXES[0])], &culture)
25                .expect("the size format is valid");
26        }
27
28        let abs_size = (bytes as f64).abs();
29        let fp_power = abs_size.ln() / 1000f64.ln();
30        let int_power = fp_power as i32;
31        let i_unit = if int_power >= PREFIXES.len() as i32 { PREFIXES.len() - 1 } else { int_power as usize };
32        let norm_size = abs_size / 1000f64.powi(i_unit as i32);
33
34        // An unsigned size is never negative: the sign argument is always null.
35        format_with(FORMAT_TEMPLATE, &[FormatArg::Null, FormatArg::F64(norm_size), FormatArg::Str(PREFIXES[i_unit])], &culture)
36            .expect("the size format is valid")
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    // Not from upstream: the upstream project has no tests of this helper.
43    use super::*;
44
45    #[test]
46    fn zero_honours_the_separator() {
47        assert_eq!("0 B", ByteSizeHelper::to_string(0, true));
48        assert_eq!("0B", ByteSizeHelper::to_string(0, false));
49    }
50
51    #[test]
52    fn non_zero_sizes_use_decimal_units_without_separator() {
53        assert_eq!("999B", ByteSizeHelper::to_string(999, true));
54        assert_eq!("1KB", ByteSizeHelper::to_string(1000, true));
55        assert_eq!("1.5KB", ByteSizeHelper::to_string(1500, false));
56        assert_eq!("2.5MB", ByteSizeHelper::to_string(2_500_000, true));
57        assert_eq!("18.4EB", ByteSizeHelper::to_string(u64::MAX, true));
58    }
59}