#[must_use]
pub fn format_rational(num: f64, digits: u32) -> String {
let (x, y) = get_rational(num, digits);
format!("{x}/{y}")
}
pub(crate) fn get_rational(num: f64, digits: u32) -> (i64, u64) {
if num == 0.0 {
(0, 1)
} else if num == 1.0 {
(1, 1)
} else if num < 0.0 {
let (x, y) = get_rational2(-num, digits);
(-x.cast_signed(), y)
} else {
let (x, y) = get_rational2(num, digits);
(x.cast_signed(), y)
}
}
fn get_rational2(num: f64, digits: u32) -> (u64, u64) {
assert!(num > 0.0);
assert!(num != 1.0);
let slop = 10.0f64.powi(-(digits.cast_signed()));
if num <= 1.0 { get_rational3(num, slop) } else { get_rational4(num, slop) }
}
fn good_enough(numer: f64, denom: f64, num: f64, slop: f64) -> bool {
let guess = numer / denom;
(num - guess).abs() <= slop * num
}
fn get_rational3(num: f64, slop: f64) -> (u64, u64) {
assert!(num > 0.0);
assert!(num < 1.0);
let mut numer = 1.0;
loop {
let denom = (numer / num).round();
if good_enough(numer, denom, num, slop) {
return (numer.round() as u64, denom.round() as u64);
}
numer += 1.0;
}
}
fn get_rational4(num: f64, slop: f64) -> (u64, u64) {
assert!(num > 1.0);
let mut denom = 1.0;
loop {
let numer = (num * denom).round();
if good_enough(numer, denom, num, slop) {
return (numer.round() as u64, denom.round() as u64);
}
denom += 1.0;
}
}
#[must_use]
pub fn format_power2f(num: f64) -> String {
if num < 0.0 {
let mut ret = "-".to_string();
ret.push_str(&format_inner(-num, false));
ret
} else {
format_inner(num, false)
}
}
#[must_use]
pub fn format_power10f(num: f64) -> String {
if num < 0.0 {
let mut ret = "-".to_string();
ret.push_str(&format_inner(-num, true));
ret
} else {
format_inner(num, true)
}
}
#[must_use]
pub fn format_power2(num: u64) -> String {
#[expect(clippy::cast_precision_loss, reason = "precision loss ok")]
format_power2f(num as f64)
}
#[must_use]
pub fn format_power10(num: u64) -> String {
#[expect(clippy::cast_precision_loss, reason = "precision loss ok")]
format_power10f(num as f64)
}
#[must_use]
fn format_inner(num: f64, is_10: bool) -> String {
if num < 1000.0 {
return format!("{num:.0}");
}
let mut curr_exp = 1f64;
let mut exp =
if is_10 { crate::num::P10_LETTERS.iter() } else { crate::num::P2_LETTERS.iter() };
let bump = if is_10 { 1000.0 } else { 1024.0 };
loop {
let e = exp.next().unwrap();
if num <= (999.0 * curr_exp) {
if num > (9.9 * curr_exp) {
return format!("{:.0}{}", (num / curr_exp).ceil(), *e as char);
}
if num > curr_exp {
return format!("{:.1}{}", (num * 10.0 / curr_exp).ceil() / 10.0, *e as char);
}
return format!("1{}", *e as char);
}
if *e == b'Q' {
return format!("{:.0}Q", num / curr_exp);
}
curr_exp *= bump;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format2() {
assert_eq!(format_power2(0), "0");
assert_eq!(format_power2(1), "1");
assert_eq!(format_power2f(-1f64), "-1");
assert_eq!(format_power2(999), "999");
assert_eq!(format_power2(1000), "1K");
assert_eq!(format_power2(1024), "1K");
assert_eq!(format_power2(1025), "1.1K");
assert_eq!(format_power2(10137), "9.9K");
assert_eq!(format_power2(10138), "10K");
assert_eq!(format_power2(97 * 1024), "97K");
assert_eq!(format_power2(97 * 1024 + 1), "98K");
assert_eq!(format_power2(999 * 1024), "999K");
assert_eq!(format_power2(999 * 1024 + 1), "1M");
}
#[test]
fn test_format10() {
assert_eq!(format_power10(0), "0");
assert_eq!(format_power10(1), "1");
assert_eq!(format_power10f(-1f64), "-1");
assert_eq!(format_power10(999), "999");
assert_eq!(format_power10(1000), "1k");
assert_eq!(format_power10(1001), "1.1k");
assert_eq!(format_power10(9900), "9.9k");
assert_eq!(format_power10(9901), "10k");
assert_eq!(format_power10(10000), "10k");
assert_eq!(format_power10(97000), "97k");
assert_eq!(format_power10(97001), "98k");
assert_eq!(format_power10(999_000), "999k");
assert_eq!(format_power10(999_001), "1m");
}
}