const KNOWN_SUFFIXES: &[&str] = &[
"", "k", "M", "B", "T", "Qa", "Qi", "Sx", "Sp", "Oc", "No", "Dc",
];
pub fn big_mag(m: crate::bignum::Mag) -> String {
if m.is_zero() {
return "0".into();
}
if m.log10 < 3.0 {
return big(m.to_f64());
}
let raw_group = (m.log10 / 3.0).floor();
if !raw_group.is_finite() || raw_group > 1e15 {
return format!("10^{:.0}", m.log10);
}
let group = raw_group as usize;
let suffix = if group < KNOWN_SUFFIXES.len() {
KNOWN_SUFFIXES[group].to_string()
} else {
alpha_suffix(group - KNOWN_SUFFIXES.len())
};
let scaled = 10f64.powf(m.log10 - (group as f64) * 3.0);
format!("{scaled:.2}{suffix}")
}
pub fn big(n: f64) -> String {
if n.is_nan() || n.is_infinite() {
return "?".into();
}
if n.abs() < 1000.0 {
return format!("{}", n.floor() as i64);
}
let mag = (n.abs().log10() / 3.0).floor() as i32;
let mag = mag.max(0) as usize;
let suffix = if mag < KNOWN_SUFFIXES.len() {
KNOWN_SUFFIXES[mag].to_string()
} else {
alpha_suffix(mag - KNOWN_SUFFIXES.len())
};
let scaled = n / 10f64.powi((mag * 3) as i32);
format!("{scaled:.2}{suffix}")
}
pub fn alpha_suffix(mut n: usize) -> String {
let mut len: u32 = 2;
loop {
let phase_entries = 26usize.pow(len);
if n < phase_entries {
return digits_to_letters(n, len as usize, false);
}
n -= phase_entries;
if n < phase_entries {
return digits_to_letters(n, len as usize, true);
}
n -= phase_entries;
len += 1;
}
}
fn digits_to_letters(mut idx: usize, len: usize, cap_first: bool) -> String {
let mut digits = vec![0u8; len];
for i in (0..len).rev() {
digits[i] = (idx % 26) as u8;
idx /= 26;
}
digits
.iter()
.enumerate()
.map(|(i, &d)| {
let base = if i == 0 && cap_first { b'A' } else { b'a' };
(base + d) as char
})
.collect()
}
pub fn mul_magnitude(v: f64) -> String {
let delta = (v - 1.0).abs();
if delta == 0.0 || !v.is_finite() {
return format!("×{v:.2}");
}
let need = (-delta.log10()).ceil() as i32 + 2;
let prec = need.clamp(2, 8) as usize;
format!("×{v:.*}", prec)
}
pub fn percent_magnitude(v: f64) -> String {
let p = v * 100.0;
let mag = p.abs();
let prec = if mag >= 1.0 {
1
} else if mag >= 0.1 {
2
} else if mag >= 0.01 {
3
} else {
4
};
format!("{p:+.*}%", prec)
}
pub fn flat_magnitude(v: f64) -> String {
let m = v.abs();
let prec = if m >= 10.0 {
1
} else if m >= 1.0 {
2
} else if m >= 0.1 {
3
} else {
4
};
format!("{v:+.*}", prec)
}
pub fn rate(n: f64) -> String {
if n < 10.0 {
format!("{n:.2}")
} else if n < 100.0 {
format!("{n:.1}")
} else {
big(n)
}
}
pub fn duration(secs: u64) -> String {
let h = secs / 3600;
let m = (secs % 3600) / 60;
let s = secs % 60;
if h > 0 {
format!("{h}h {m:02}m {s:02}s")
} else if m > 0 {
format!("{m}m {s:02}s")
} else {
format!("{s}s")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_numbers_are_plain_integers() {
assert_eq!(big(0.0), "0");
assert_eq!(big(42.7), "42");
assert_eq!(big(999.0), "999");
}
#[test]
fn known_suffix_range_uses_short_names() {
assert_eq!(big(1_500.0), "1.50k");
assert_eq!(big(2.5e6), "2.50M");
assert_eq!(big(7.0e33), "7.00Dc");
}
#[test]
fn beyond_decillion_uses_alpha_suffix() {
assert_eq!(big(1.0e36), "1.00aa");
assert_eq!(big(3.5e36), "3.50aa");
assert_eq!(big(1.0e39), "1.00ab");
assert_eq!(big(1.0e42), "1.00ac");
}
#[test]
fn alpha_suffix_phase_transitions() {
assert_eq!(alpha_suffix(0), "aa");
assert_eq!(alpha_suffix(675), "zz");
assert_eq!(alpha_suffix(676), "Aa");
assert_eq!(alpha_suffix(1351), "Zz");
assert_eq!(alpha_suffix(1352), "aaa");
}
#[test]
fn alpha_suffix_within_phase_is_base26() {
assert_eq!(alpha_suffix(1), "ab");
assert_eq!(alpha_suffix(25), "az");
assert_eq!(alpha_suffix(26), "ba");
}
#[test]
fn infinity_and_nan_render_as_question_mark() {
assert_eq!(big(f64::INFINITY), "?");
assert_eq!(big(f64::NEG_INFINITY), "?");
assert_eq!(big(f64::NAN), "?");
}
#[test]
fn big_mag_handles_unbounded_values() {
use crate::bignum::Mag;
assert_eq!(big_mag(Mag::from_f64(1.0e36)), "1.00aa");
let huge = Mag { log10: 999.0 };
let s = big_mag(huge);
assert!(!s.contains('?'), "got {s}");
assert!(s.starts_with("1.00"), "got {s}");
}
#[test]
fn big_mag_handles_zero_and_small() {
use crate::bignum::Mag;
assert_eq!(big_mag(Mag::ZERO), "0");
assert_eq!(big_mag(Mag::from_f64(42.0)), "42");
assert_eq!(big_mag(Mag::from_f64(1500.0)), "1.50k");
}
#[test]
fn huge_but_finite_number_does_not_print_question_mark() {
let s = big(1.0e200);
assert!(!s.contains('?'), "got {s}");
assert!(s.ends_with(char::is_alphabetic), "got {s}");
}
}