pub fn apply_format(n: f64, fmt_id: u32, fmt_str: Option<&str>) -> String {
if n.is_nan() {
return "NaN".to_string();
}
if n.is_infinite() {
return if n < 0.0 {
"-Infinity".to_string()
} else {
"Infinity".to_string()
};
}
match fmt_id {
0 | 49 => return format_general(n), 1 => return format_integer(n), 2 => return format_fixed(n, 2), 3 => return format_commas(n, 0), 4 => return format_commas(n, 2), 5 | 6 => return format_currency(n, "$", 0), 7 | 8 => return format_currency(n, "$", 2), 9 => return format_percent(n, 0), 10 => return format_percent(n, 2), 11 => return format_scientific(n), 12 => return format_general(n), 13 => return format_general(n), 37 | 38 => return format_commas(n, 0), 39 | 40 => return format_commas(n, 2), 41..=44 => return format_commas(n, 2), _ => {},
}
if let Some(fmt) = fmt_str {
let fmt = fmt.trim();
if !fmt.is_empty() && fmt != "General" && fmt != "@" {
return apply_custom(n, fmt);
}
}
format_general(n)
}
pub fn format_general(n: f64) -> String {
if n == n.trunc() && n.abs() < 1e15 {
format!("{}", n as i64)
} else {
let s = format!("{}", n);
s
}
}
fn format_integer(n: f64) -> String {
format!("{}", n.round() as i64)
}
fn format_fixed(n: f64, decimals: u8) -> String {
format!("{:.prec$}", n, prec = decimals as usize)
}
pub fn format_commas(n: f64, decimals: u8) -> String {
let negative = n < 0.0;
let abs = n.abs();
let sign = if negative { "-" } else { "" };
let factor = 10f64.powi(decimals as i32);
let scaled = (abs * factor).round();
if !scaled.is_finite() || scaled >= u64::MAX as f64 {
return format!("{}{:.prec$}", sign, abs, prec = decimals as usize);
}
let scaled_int = scaled as u64;
if decimals == 0 {
format!("{}{}", sign, insert_commas(scaled_int))
} else {
let divisor = factor as u64;
let int_part = scaled_int / divisor;
let frac = scaled_int % divisor;
format!(
"{}{}.{:0>width$}",
sign,
insert_commas(int_part),
frac,
width = decimals as usize
)
}
}
fn format_currency(n: f64, symbol: &str, decimals: u8) -> String {
if n < 0.0 {
format!("-{}{}", symbol, format_commas(n.abs(), decimals))
} else {
format!("{}{}", symbol, format_commas(n, decimals))
}
}
pub fn format_percent(n: f64, decimals: u8) -> String {
let pct = n * 100.0;
if decimals == 0 {
format!("{}%", pct.round() as i64)
} else {
format!("{:.prec$}%", pct, prec = decimals as usize)
}
}
fn format_scientific(n: f64) -> String {
format!("{:.2E}", n)
}
fn insert_commas(n: u64) -> String {
let s = n.to_string();
let bytes = s.as_bytes();
let len = bytes.len();
let mut out = String::with_capacity(len + len / 3);
for (i, &b) in bytes.iter().enumerate() {
if i > 0 && (len - i).is_multiple_of(3) {
out.push(',');
}
out.push(b as char);
}
out
}
fn apply_custom(n: f64, fmt: &str) -> String {
let section = fmt.split(';').next().unwrap_or(fmt);
let mut currency_prefix = String::new();
let mut suffix = String::new(); let mut has_percent = false;
let mut has_comma_in_num = false;
let mut decimal_zeros = 0u8; let mut _decimal_hashes = 0u8; let mut has_scientific = false;
let mut in_decimal = false;
let mut in_num_part = false;
let mut chars = section.chars().peekable();
while let Some(c) = chars.next() {
match c {
'[' => {
let mut inner = String::new();
for ch in chars.by_ref() {
if ch == ']' {
break;
}
inner.push(ch);
}
if let Some(rest) = inner.strip_prefix('$') {
let sym: String = rest.chars().take_while(|&ch| ch != '-').collect();
if !sym.is_empty() {
currency_prefix = sym;
}
}
},
'"' => {
for ch in chars.by_ref() {
if ch == '"' {
break;
}
suffix.push(ch);
}
},
'\\' => {
chars.next();
},
'_' => {
chars.next();
},
'*' => {
chars.next();
},
'%' => {
has_percent = true;
in_num_part = true;
},
'.' => {
in_decimal = true;
in_num_part = true;
},
'0' => {
in_num_part = true;
if in_decimal {
decimal_zeros += 1;
}
},
'#' => {
in_num_part = true;
if in_decimal {
_decimal_hashes += 1;
}
},
',' => {
if in_num_part {
has_comma_in_num = true;
}
},
'E' | 'e' => {
if matches!(chars.peek(), Some('+') | Some('-')) {
has_scientific = true;
chars.next(); while chars.peek().is_some_and(|c| c.is_ascii_digit()) {
chars.next();
}
} else if !in_num_part {
currency_prefix.push(c);
} else {
suffix.push(c);
}
},
'$' => {
currency_prefix = "$".to_string();
in_num_part = true;
},
c if !in_num_part && !c.is_ascii_whitespace() => {
currency_prefix.push(c);
},
_ => {},
}
}
let decimals = decimal_zeros;
let value = if has_percent { n * 100.0 } else { n };
let body = if has_scientific {
format_scientific(value)
} else if has_comma_in_num {
format_commas(value, decimals)
} else if in_decimal && decimals > 0 {
format_fixed(value, decimals)
} else if in_num_part {
format_integer(value)
} else {
format_general(value)
};
let pct_suffix = if has_percent { "%" } else { "" };
format!("{}{}{}{}", currency_prefix, body, suffix, pct_suffix)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_general() {
assert_eq!(apply_format(42.0, 0, None), "42");
assert_eq!(apply_format(4.25, 0, None), "4.25");
}
#[test]
fn builtin_integer() {
assert_eq!(apply_format(42.7, 1, None), "43");
}
#[test]
fn builtin_fixed_two() {
assert_eq!(apply_format(4.25678, 2, None), "4.26");
}
#[test]
fn builtin_commas_zero() {
assert_eq!(apply_format(1234567.0, 3, None), "1,234,567");
}
#[test]
fn builtin_commas_two() {
assert_eq!(apply_format(1234567.891, 4, None), "1,234,567.89");
}
#[test]
fn builtin_percent_zero() {
assert_eq!(apply_format(0.75, 9, None), "75%");
}
#[test]
fn builtin_percent_two() {
assert_eq!(apply_format(0.1234, 10, None), "12.34%");
}
#[test]
fn builtin_currency_usd() {
assert_eq!(apply_format(1234.5, 7, None), "$1,234.50");
}
#[test]
fn custom_thousands() {
assert_eq!(apply_format(1234567.0, 164, Some("#,##0")), "1,234,567");
}
#[test]
fn custom_thousands_two_decimals() {
assert_eq!(apply_format(1234.5, 164, Some("#,##0.00")), "1,234.50");
}
#[test]
fn custom_percent() {
assert_eq!(apply_format(0.5, 164, Some("0%")), "50%");
}
#[test]
fn custom_percent_decimals() {
assert_eq!(apply_format(0.1256, 164, Some("0.00%")), "12.56%");
}
#[test]
fn custom_euro() {
let result = apply_format(1234.5, 164, Some("[$€-407]#,##0.00"));
assert!(result.contains("€"), "expected euro symbol, got: {result}");
assert!(result.contains("1,234.50"), "expected formatted number, got: {result}");
}
#[test]
fn custom_dollar_prefix() {
assert_eq!(apply_format(99.9, 164, Some("$#,##0.00")), "$99.90");
}
#[test]
fn negative_commas() {
assert_eq!(apply_format(-1234.5, 4, None), "-1,234.50");
}
#[test]
fn zero_percent() {
assert_eq!(apply_format(0.0, 9, None), "0%");
}
#[test]
fn large_commas() {
assert_eq!(apply_format(1_000_000_000.0, 3, None), "1,000,000,000");
}
#[test]
fn nan_renders_as_label() {
assert_eq!(apply_format(f64::NAN, 0, None), "NaN");
}
#[test]
fn infinity_renders_as_label() {
assert_eq!(apply_format(f64::INFINITY, 0, None), "Infinity");
assert_eq!(apply_format(f64::NEG_INFINITY, 0, None), "-Infinity");
}
#[test]
fn zero_renders_uniformly() {
assert_eq!(apply_format(0.0, 0, None), "0");
assert_eq!(apply_format(0.0, 2, None), "0.00");
assert_eq!(apply_format(0.0, 4, None), "0.00");
}
#[test]
fn negative_percent() {
assert_eq!(apply_format(-0.25, 9, None), "-25%");
assert_eq!(apply_format(-0.1234, 10, None), "-12.34%");
}
#[test]
fn negative_currency() {
assert_eq!(apply_format(-99.5, 7, None), "-$99.50");
}
#[test]
fn scientific_builtin() {
let s = apply_format(12345.6789, 11, None);
assert!(s.contains('E'), "scientific got: {s}");
}
#[test]
fn accounting_alias() {
assert_eq!(apply_format(1234.0, 37, None), "1,234");
assert_eq!(apply_format(1234.5, 39, None), "1,234.50");
}
#[test]
fn accounting_paren_range() {
for id in 41u32..=44 {
assert_eq!(apply_format(1234.5, id, None), "1,234.50", "fmt id {id}");
}
}
#[test]
fn fraction_falls_back_to_general() {
assert_eq!(apply_format(1.5, 12, None), "1.5");
assert_eq!(apply_format(2.0, 13, None), "2");
}
#[test]
fn custom_general_falls_through_to_default() {
assert_eq!(apply_format(42.5, 164, Some("General")), "42.5");
assert_eq!(apply_format(42.0, 164, Some("@")), "42");
}
#[test]
fn custom_blank_falls_back_to_general() {
assert_eq!(apply_format(4.25, 164, Some("")), "4.25");
assert_eq!(apply_format(4.25, 164, Some(" ")), "4.25");
}
#[test]
fn custom_multi_section_uses_first() {
assert_eq!(apply_format(1234.5, 164, Some("#,##0.00;-#,##0.00")), "1,234.50");
}
#[test]
fn custom_with_quoted_literal_suffix() {
let result = apply_format(42.0, 164, Some(r#"0" units""#));
assert!(result.contains("42"), "got: {result}");
assert!(result.contains("units"), "got: {result}");
}
#[test]
fn custom_color_directive_is_stripped() {
let result = apply_format(123.0, 164, Some("[Red]#,##0"));
assert!(!result.contains("Red"));
assert!(result.contains("123"));
}
#[test]
fn format_general_keeps_integers_unsuffixed() {
assert_eq!(format_general(42.0), "42");
assert_eq!(format_general(-7.0), "-7");
assert_eq!(format_general(0.0), "0");
}
#[test]
fn format_general_keeps_decimal_for_fraction() {
assert_eq!(format_general(4.25), "4.25");
assert_eq!(format_general(-2.5), "-2.5");
}
#[test]
fn format_commas_negative_with_decimals() {
assert_eq!(format_commas(-1234.5, 2), "-1,234.50");
}
#[test]
fn format_commas_zero() {
assert_eq!(format_commas(0.0, 0), "0");
assert_eq!(format_commas(0.0, 2), "0.00");
}
#[test]
fn format_percent_negative() {
assert_eq!(format_percent(-0.5, 0), "-50%");
}
#[test]
fn format_percent_zero_decimals() {
assert_eq!(format_percent(0.5, 0), "50%");
}
}