use rug::Integer;
use std::fmt::{Formatter, Write};
use super::{FormatOptions, NumberFormat, Scientific, Separator};
pub fn should_use_scientific(n: &Integer) -> bool {
*n.as_abs() >= 1_000_000_000_000_i64
}
fn round(mut s: String, max_digits: usize) -> String {
let negative = s.starts_with('-');
let bytes = unsafe {
if negative {
&mut s.as_bytes_mut()[1..]
} else {
s.as_bytes_mut()
}
};
for (i, byte) in bytes.iter().copied().enumerate() {
if !byte.is_ascii_digit() {
panic!(
"invalid character in numeric string: '{}' ({}) found at byte position {}",
byte as char,
byte,
i,
);
}
}
if max_digits >= bytes.len() {
return s;
}
let start_idx = max_digits;
let mut carry = None;
for byte in bytes.iter_mut().take(start_idx + 1).rev() {
match carry {
Some(true) => match *byte {
b'9' => *byte = b'0',
_ => {
*byte += 1;
carry = Some(false);
}
},
Some(false) => break,
None => carry = Some(*byte >= b'5'),
}
}
unsafe {
bytes.as_mut_ptr()
.add(start_idx)
.write_bytes(b'0', bytes.len() - start_idx);
}
if let Some(true) = carry {
s.insert(if negative { 1 } else { 0 }, '1');
}
s
}
pub fn insert_separators(s: &mut String) {
let decimal = s.find('.').unwrap_or(s.len());
s.reserve(s.len() / 3);
let mut i = decimal.saturating_sub(3);
while i > 0 {
s.insert(i, ',');
i = i.saturating_sub(3);
}
}
pub fn fmt_decimal(f: &mut Formatter<'_>, n: &Integer, options: FormatOptions) -> std::fmt::Result {
let mut s = n.to_string_radix(10);
if let Some(max_digits) = options.precision {
if max_digits < s.len() {
return fmt_scientific(f, n, options);
}
}
if options.separators == Separator::Always {
insert_separators(&mut s);
}
write!(f, "{}", s)
}
pub fn fmt_scientific(f: &mut Formatter<'_>, n: &Integer, options: FormatOptions) -> std::fmt::Result {
let mut s = n.to_string_radix(10);
if let Some(max_digits) = options.precision {
s = round(s, max_digits);
}
let first_non_zero = s.find(|c: char| c.is_ascii_digit() && c != '0').unwrap();
let exponent = s.len() - first_non_zero - 1;
if exponent == 0 {
write!(f, "{}", s)
} else {
s.insert(first_non_zero + 1, '.');
let s = s.trim_end_matches('0').trim_end_matches('.');
match options.scientific {
Scientific::Times => write!(f, "{} × 10 ^ {}", s, exponent),
Scientific::E => write!(f, "{}E{}", s, exponent),
}
}
}
const INT_NUM_NAMES: [&str; 21] = [
"thousand",
"million",
"billion",
"trillion",
"quadrillion",
"quintillion",
"sextillion",
"septillion",
"octillion",
"nonillion",
"decillion",
"undecillion",
"duodecillion",
"tredecillion",
"quattuordecillion",
"quindecillion",
"sexdecillion",
"septendecillion",
"octodecillion",
"novemdecillion",
"vigintillion",
];
pub fn fmt_word_str(f: &mut Formatter<'_>, input: &str) -> std::fmt::Result {
let chars = input.chars().collect::<Vec<_>>();
let mut chunks = chars.rchunks(3).rev().enumerate();
let num_chunks = input.len() / 3 + if input.len() % 3 == 0 { 0 } else { 1 };
let mut parts = Vec::with_capacity(num_chunks);
if num_chunks > INT_NUM_NAMES.len() + 1 {
let mut packed = String::new();
for packed_chunk in chunks.by_ref().take(num_chunks - INT_NUM_NAMES.len()) {
packed_chunk.1.iter().for_each(|&c| packed.push(c));
}
write!(packed, " {}", INT_NUM_NAMES.last().unwrap())?;
parts.push(packed);
}
for (chunk_index, chunk) in chunks {
let num = chunk.iter().collect::<String>().parse::<u16>().unwrap();
if num == 0 {
continue;
}
let mut part = String::new();
write!(part, "{}", num)?;
let place_index = (num_chunks - chunk_index).checked_sub(2);
if let Some(place_index) = place_index {
if place_index < INT_NUM_NAMES.len() {
write!(part, " {}", INT_NUM_NAMES[place_index])?;
}
}
parts.push(part);
}
write!(f, "{}", parts.join(" "))?;
Ok(())
}
fn fmt_word(f: &mut Formatter<'_>, n: &Integer, options: FormatOptions) -> std::fmt::Result {
let mut s = n.to_string_radix(10);
if let Some(max_digits) = options.precision {
s = round(s, max_digits);
}
fmt_word_str(f, &s)
}
pub fn fmt(f: &mut Formatter<'_>, n: &Integer, 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 | NumberFormat::Fraction => fmt_decimal(f, n, options),
NumberFormat::Scientific => fmt_scientific(f, n, options),
NumberFormat::Word => fmt_word(f, n, options),
}
}