codex_protocol/
num_format.rs1use std::sync::OnceLock;
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 formatter() -> &'static DecimalFormatter {
21 static FORMATTER: OnceLock<DecimalFormatter> = OnceLock::new();
22 FORMATTER.get_or_init(|| make_local_formatter().unwrap_or_else(make_en_us_formatter))
23}
24
25pub fn format_with_separators(n: i64) -> String {
28 formatter().format(&Decimal::from(n)).to_string()
29}
30
31fn format_si_suffix_with_formatter(n: i64, formatter: &DecimalFormatter) -> String {
32 let n = n.max(0);
33 if n < 1000 {
34 return formatter.format(&Decimal::from(n)).to_string();
35 }
36
37 let format_scaled = |n: i64, scale: i64, frac_digits: u32| -> String {
39 let value = n as f64 / scale as f64;
40 let scaled: i64 = (value * 10f64.powi(frac_digits as i32)).round() as i64;
41 let mut dec = Decimal::from(scaled);
42 dec.multiply_pow10(-(frac_digits as i16));
43 formatter.format(&dec).to_string()
44 };
45
46 const UNITS: [(i64, &str); 3] = [(1_000, "K"), (1_000_000, "M"), (1_000_000_000, "G")];
47 let f = n as f64;
48 for &(scale, suffix) in &UNITS {
49 if (100.0 * f / scale as f64).round() < 1000.0 {
50 return format!("{}{}", format_scaled(n, scale, 2), suffix);
51 } else if (10.0 * f / scale as f64).round() < 1000.0 {
52 return format!("{}{}", format_scaled(n, scale, 1), suffix);
53 } else if (f / scale as f64).round() < 1000.0 {
54 return format!("{}{}", format_scaled(n, scale, 0), suffix);
55 }
56 }
57
58 format!(
60 "{}G",
61 format_with_separators(((n as f64) / 1e9).round() as i64)
62 )
63}
64
65pub fn format_si_suffix(n: i64) -> String {
72 format_si_suffix_with_formatter(n, formatter())
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn kmg() {
81 let formatter = make_en_us_formatter();
82 let fmt = |n: i64| format_si_suffix_with_formatter(n, &formatter);
83 assert_eq!(fmt(0), "0");
84 assert_eq!(fmt(999), "999");
85 assert_eq!(fmt(1_000), "1.00K");
86 assert_eq!(fmt(1_200), "1.20K");
87 assert_eq!(fmt(10_000), "10.0K");
88 assert_eq!(fmt(100_000), "100K");
89 assert_eq!(fmt(999_500), "1.00M");
90 assert_eq!(fmt(1_000_000), "1.00M");
91 assert_eq!(fmt(1_234_000), "1.23M");
92 assert_eq!(fmt(12_345_678), "12.3M");
93 assert_eq!(fmt(999_950_000), "1.00G");
94 assert_eq!(fmt(1_000_000_000), "1.00G");
95 assert_eq!(fmt(1_234_000_000), "1.23G");
96 assert_eq!(fmt(1_234_000_000_000), "1,234G");
98 }
99}