use crate::approx::approximate_rational;
use rug::{float::Round, Float};
use std::{cmp::Ordering, fmt::Formatter};
use super::{integer, FormatOptions, NumberFormat, Scientific, Separator};
pub fn should_use_scientific(n: &Float) -> bool {
let abs = n.as_abs();
*abs <= 1e-6 || *abs >= 1e+12
}
fn trim_trailing(s: &str) -> &str {
if s.contains('.') {
s.trim_end_matches('0').trim_end_matches('.')
} else {
s
}
}
fn fmt_non_normal_decimal<F: std::fmt::Write>(f: &mut F, n: &Float) -> std::fmt::Result {
if n.is_normal() {
panic!("fmt_non_normal_decimal called with a normal float: {}", n);
}
if n.is_nan() {
return write!(f, "NaN");
}
if n.is_infinite() {
return write!(f, "{}∞", if n.is_sign_negative() { "-" } else { "" });
}
if n.is_zero() {
return write!(f, "0");
}
unreachable!()
}
fn fmt_decimal<F: std::fmt::Write>(f: &mut F, n: &Float, options: FormatOptions) -> std::fmt::Result {
if !n.is_normal() {
return fmt_non_normal_decimal(f, n);
}
let (sign, mut s, exponent) = n.to_sign_string_exp_round(10, options.precision, Round::Nearest);
let exponent = exponent.unwrap();
match exponent.cmp(&0) {
Ordering::Less => s.insert_str(0, &format!("0.{}", "0".repeat(-exponent as usize))),
Ordering::Equal => s.insert_str(0, "0."),
Ordering::Greater => {
let exponent = exponent as usize;
match s.len().cmp(&exponent) {
Ordering::Less => s.push_str(&"0".repeat(exponent - s.len())),
Ordering::Greater => s.insert(exponent , '.'),
Ordering::Equal => {},
}
},
}
if options.separators == Separator::Always {
integer::insert_separators(&mut s);
}
write!(f, "{}{}", if sign { "-" } else { "" }, trim_trailing(&s))
}
pub fn fmt_scientific(f: &mut Formatter<'_>, n: &Float, options: FormatOptions) -> std::fmt::Result {
if !n.is_normal() {
return fmt_non_normal_decimal(f, n);
}
let (sign, mut s, exponent) = n.to_sign_string_exp_round(10, options.precision, Round::Nearest);
let mut exponent = exponent.unwrap();
s.insert(1, '.');
exponent -= 1;
write!(f, "{}{}{}{}",
if sign { "-" } else { "" },
trim_trailing(&s),
match options.scientific {
Scientific::Times => " × 10 ^ ",
Scientific::E => "E",
},
exponent,
)
}
fn fmt_fraction(f: &mut Formatter<'_>, n: &Float, options: FormatOptions) -> std::fmt::Result {
if !n.is_normal() {
return fmt_non_normal_decimal(f, n);
}
let options = options.into_builder()
.number(NumberFormat::Auto)
.build();
if n.is_integer() {
return fmt(f, n, options);
}
let (numerator, denominator) = approximate_rational(n).into_numer_denom();
integer::fmt(f, &numerator, options)?;
write!(f, " / ")?;
let expected_format = if integer::should_use_scientific(&denominator) {
NumberFormat::Scientific
} else {
NumberFormat::Decimal
};
if expected_format == NumberFormat::Scientific {
write!(f, "(")?;
integer::fmt_scientific(f, &denominator, options)?;
write!(f, ")")?;
} else {
integer::fmt_decimal(f, &denominator, options)?;
}
Ok(())
}
const DEC_NUM_NAMES: [&str; 30] = [
"tenth",
"hundredth",
"thousandth",
"ten-thousandth",
"hundred-thousandth",
"millionth",
"ten-millionth",
"hundred-millionth",
"billionth",
"ten-billionth",
"hundred-billionth",
"trillionth",
"ten-trillionth",
"hundred-trillionth",
"quintillionth",
"ten-quintillionth",
"hundred-quintillionth",
"sextillionth",
"ten-sextillionth",
"hundred-sextillionth",
"septillionth",
"ten-septillionth",
"hundred-septillionth",
"octillionth",
"ten-octillionth",
"hundred-octillionth",
"nonillionth",
"ten-nonillionth",
"hundred-nonillionth",
"decillionth",
];
fn fmt_word(f: &mut Formatter<'_>, n: &Float, options: FormatOptions) -> std::fmt::Result {
if !n.is_normal() {
return fmt_non_normal_decimal(f, n);
}
let mut s = String::new();
fmt_decimal(&mut s, n, Separator::Never.inside(options))?;
let mut parts = s.split('.');
if let Some(integer) = parts.next() {
integer::fmt_word_str(f, integer)?;
}
if let Some(decimal) = parts.next() {
write!(f, " and ")?;
if decimal.len() > DEC_NUM_NAMES.len() {
integer::fmt_word_str(f, decimal)?;
write!(f, " 0.{} {}", &decimal[DEC_NUM_NAMES.len()..], DEC_NUM_NAMES.last().unwrap())?;
} else {
integer::fmt_word_str(f, decimal)?;
write!(f, " {}", DEC_NUM_NAMES[decimal.len() - 1])?;
}
if decimal != "1" {
write!(f, "s")?;
}
}
Ok(())
}
pub fn fmt(f: &mut Formatter<'_>, n: &Float, options: FormatOptions) -> std::fmt::Result {
match options.number {
NumberFormat::Auto => {
if should_use_scientific(n) {
fmt_scientific(f, n, options)
} else {
fmt_decimal(f, n, options)
}
}
NumberFormat::Decimal => fmt_decimal(f, n, options),
NumberFormat::Scientific => fmt_scientific(f, n, options),
NumberFormat::Fraction => fmt_fraction(f, n, options),
NumberFormat::Word => fmt_word(f, n, options),
}
}