Skip to main content

hanzo_protocol/
num_format.rs

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