Skip to main content

hanzo_protocol/
num_format.rs

1use std::cell::OnceCell;
2
3use icu_decimal::DecimalFormatter;
4use icu_decimal::input::Decimal;
5use icu_decimal::options::DecimalFormatterOptions;
6use icu_locale_core::Locale;
7
8fn make_local_formatter() -> Option<DecimalFormatter> {
9    let loc: Locale = sys_locale::get_locale()?.parse().ok()?;
10    DecimalFormatter::try_new(loc.into(), DecimalFormatterOptions::default()).ok()
11}
12
13fn make_en_us_formatter() -> DecimalFormatter {
14    #![allow(clippy::expect_used)]
15    let loc: Locale = "en-US".parse().expect("en-US wasn't a valid locale");
16    DecimalFormatter::try_new(loc.into(), DecimalFormatterOptions::default())
17        .expect("en-US wasn't a valid locale")
18}
19
20fn with_formatter<T>(f: impl FnOnce(&DecimalFormatter) -> T) -> T {
21    thread_local! {
22        static FORMATTER: OnceCell<DecimalFormatter> = const { OnceCell::new() };
23    }
24
25    FORMATTER.with(|cell| {
26        let formatter =
27            cell.get_or_init(|| make_local_formatter().unwrap_or_else(make_en_us_formatter));
28        f(formatter)
29    })
30}
31
32/// Format a u64 with locale-aware digit separators (e.g. "12345" -> "12,345"
33/// for en-US).
34pub fn format_with_separators(n: u64) -> String {
35    with_formatter(|formatter| formatter.format(&Decimal::from(n)).to_string())
36}
37
38fn format_si_suffix_with_formatter(n: u64, formatter: &DecimalFormatter) -> String {
39    if n < 1000 {
40        return formatter.format(&Decimal::from(n)).to_string();
41    }
42
43    // Format `n / scale` with the requested number of fractional digits.
44    let format_scaled = |n: u64, scale: u64, frac_digits: u32| -> String {
45        let value = n as f64 / scale as f64;
46        let scaled: u64 = (value * 10f64.powi(frac_digits as i32)).round() as u64;
47        let mut dec = Decimal::from(scaled);
48        dec.multiply_pow10(-(frac_digits as i16));
49        formatter.format(&dec).to_string()
50    };
51
52    const UNITS: [(u64, &str); 3] = [(1_000, "K"), (1_000_000, "M"), (1_000_000_000, "G")];
53    let f = n as f64;
54    for &(scale, suffix) in &UNITS {
55        if (100.0 * f / scale as f64).round() < 1000.0 {
56            return format!("{}{}", format_scaled(n, scale, 2), suffix);
57        } else if (10.0 * f / scale as f64).round() < 1000.0 {
58            return format!("{}{}", format_scaled(n, scale, 1), suffix);
59        } else if (f / scale as f64).round() < 1000.0 {
60            return format!("{}{}", format_scaled(n, scale, 0), suffix);
61        }
62    }
63
64    // Above 1000G, keep whole‑G precision.
65    format!(
66        "{}G",
67        format_with_separators(((n as f64) / 1e9).round() as u64)
68    )
69}
70
71/// Format token counts to 3 significant figures, using base-10 SI suffixes.
72///
73/// Examples (en-US):
74///   - 999 -> "999"
75///   - 1200 -> "1.20K"
76///   - 123456789 -> "123M"
77pub fn format_si_suffix(n: u64) -> String {
78    with_formatter(|formatter| format_si_suffix_with_formatter(n, formatter))
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn kmg() {
87        let formatter = make_en_us_formatter();
88        let fmt = |n: u64| format_si_suffix_with_formatter(n, &formatter);
89        assert_eq!(fmt(0), "0");
90        assert_eq!(fmt(999), "999");
91        assert_eq!(fmt(1_000), "1.00K");
92        assert_eq!(fmt(1_200), "1.20K");
93        assert_eq!(fmt(10_000), "10.0K");
94        assert_eq!(fmt(100_000), "100K");
95        assert_eq!(fmt(999_500), "1.00M");
96        assert_eq!(fmt(1_000_000), "1.00M");
97        assert_eq!(fmt(1_234_000), "1.23M");
98        assert_eq!(fmt(12_345_678), "12.3M");
99        assert_eq!(fmt(999_950_000), "1.00G");
100        assert_eq!(fmt(1_000_000_000), "1.00G");
101        assert_eq!(fmt(1_234_000_000), "1.23G");
102        // Above 1000G we keep whole‑G precision (no higher unit supported here).
103        assert_eq!(fmt(1_234_000_000_000), "1,234G");
104    }
105}