use crate::{Consts, calculation_results::FormatOptions, locale};
use core::fmt;
use factorion_math::{
length,
rug::{Complete, Float, Integer, float::OrdFloat, integer::IntegerExt64, ops::Pow},
};
use std::{borrow::Cow, fmt::Write};
const SINGLES: [&str; 10] = [
"", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem",
];
const SINGLES_LAST: [&str; 10] = [
"", "un", "du", "tr", "quadr", "quint", "sext", "sept", "oct", "non",
];
const TENS: [&str; 10] = [
"",
"dec",
"vigin",
"trigin",
"quadragin",
"quinquagin",
"sexagin",
"septuagin",
"octogin",
"nonagin",
];
const HUNDREDS: [&str; 10] = [
"",
"cen",
"ducen",
"tricen",
"quadringen",
"quingen",
"sescen",
"septingen",
"octingen",
"nongen",
];
const THOUSANDS: [&str; 10] = [
"", "mill", "bill", "trill", "quadrill", "quintill", "sextill", "septill", "octill", "nonill",
];
const TEN_THOUSANDS: [&str; 10] = [
"",
"decill",
"vigintill",
"trigintill",
"quadragintill",
"quinquagintill",
"sexagintill",
"septuagintill",
"octogintill",
"nonagintill",
];
const HUNDRED_THOUSANDS: [&str; 10] = [
"",
"centill",
"ducentill",
"tricentill",
"quadringentill",
"quingentill",
"sescentill",
"septingentill",
"octingentill",
"nongentill",
];
const BINDING_T: [[bool; 10]; 6] = [
[
false, false, false, false, false, false, false, false, false, false,
],
[false, false, true, true, true, true, true, true, true, true],
[false, true, true, true, true, true, true, true, true, true],
[
false, false, false, false, false, false, false, false, false, false,
],
[
false, false, false, false, false, false, false, false, false, false,
],
[
false, false, false, false, false, false, false, false, false, false,
],
];
pub fn get_factorial_level_string<'a>(level: i32, locale: &'a locale::Format<'a>) -> Cow<'a, str> {
if let Some(s) = locale.num_overrides.get(&level) {
return s.as_ref().into();
}
match level {
0 => locale.sub.as_ref().into(),
1 => "{factorial}".into(),
..=999999 if !locale.force_num => {
let singles = if level < 10 { SINGLES_LAST } else { SINGLES };
let mut acc = String::new();
let mut n = level;
let s = n % 10;
n /= 10;
acc.write_str(singles[s as usize]).unwrap();
let t = n % 10;
n /= 10;
acc.write_str(TENS[t as usize]).unwrap();
let h = n % 10;
n /= 10;
acc.write_str(HUNDREDS[h as usize]).unwrap();
let th = n % 10;
n /= 10;
acc.write_str(THOUSANDS[th as usize]).unwrap();
let tth = n % 10;
n /= 10;
acc.write_str(TEN_THOUSANDS[tth as usize]).unwrap();
let hth = n % 10;
acc.write_str(HUNDRED_THOUSANDS[hth as usize]).unwrap();
let last_written = [s, t, h, th, tth, hth]
.iter()
.cloned()
.enumerate()
.rev()
.find(|(_, n)| *n != 0)
.unwrap();
if BINDING_T[last_written.0][last_written.1 as usize] {
acc.write_str("t").unwrap();
}
acc.write_str(&locale.uple).unwrap();
acc.into()
}
_ => {
let mut suffix = String::new();
write!(&mut suffix, "{level}-{{factorial}}").unwrap();
suffix.into()
}
}
}
const EN_SINGLES: [&str; 10] = [
"", "one ", "two ", "three ", "four ", "five ", "six ", "seven ", "eight ", "nine ",
];
const EN_TENS: [&str; 10] = [
"", "ten ", "twenty ", "thirty ", "forty ", "fifty ", "sixty ", "seventy ", "eighty ",
"ninety ",
];
const EN_TENS_SINGLES: [&str; 10] = [
"ten ",
"eleven ",
"twelve ",
"thirteen ",
"fourteen ",
"fifteen ",
"sixteen ",
"seventeen ",
"eighteen ",
"nineteen ",
];
const SINGLES_LAST_ILLION: [&str; 10] = [
"", "m", "b", "tr", "quadr", "quint", "sext", "sept", "oct", "non",
];
pub fn write_out_number(acc: &mut String, num: &Integer, consts: &Consts) -> std::fmt::Result {
if num == &0 {
return acc.write_str("zero");
}
let negative = num < &0;
let num = Float::with_val(consts.float_precision, num).abs();
let ten = Float::with_val(consts.float_precision, 10);
let digit_blocks = num
.clone()
.log10()
.to_u32_saturating_round(factorion_math::rug::float::Round::Down)
.unwrap()
/ 3;
if negative {
acc.write_str("minus ")?;
}
for digit_blocks_left in (digit_blocks.saturating_sub(5)..=digit_blocks).rev() {
let current_digits = Float::to_u32_saturating_round(
&((num.clone() / ten.clone().pow(digit_blocks_left * 3)) % 1000),
factorion_math::rug::float::Round::Down,
)
.unwrap();
let mut n = current_digits;
let s = n % 10;
n /= 10;
let t = n % 10;
n /= 10;
let h = n % 10;
acc.write_str(EN_SINGLES[h as usize])?;
if h != 0 {
acc.write_str("hundred ")?;
}
if t == 1 {
acc.write_str(EN_TENS_SINGLES[s as usize])?;
} else {
acc.write_str(EN_TENS[t as usize])?;
acc.write_str(EN_SINGLES[s as usize])?;
}
if digit_blocks_left > 0 && current_digits != 0 {
let singles = if digit_blocks_left < 10 {
SINGLES_LAST_ILLION
} else {
SINGLES
};
let mut n = digit_blocks_left - 1;
if n > 0 {
let s = n % 10;
n /= 10;
acc.write_str(singles[s as usize]).unwrap();
let t = n % 10;
n /= 10;
acc.write_str(TENS[t as usize]).unwrap();
let h = n % 10;
n /= 10;
acc.write_str(HUNDREDS[h as usize]).unwrap();
let th = n % 10;
n /= 10;
acc.write_str(THOUSANDS[th as usize]).unwrap();
let tth = n % 10;
n /= 10;
acc.write_str(TEN_THOUSANDS[tth as usize]).unwrap();
let hth = n % 10;
acc.write_str(HUNDRED_THOUSANDS[hth as usize]).unwrap();
let last_written = [s, t, h, th, tth, hth]
.iter()
.cloned()
.enumerate()
.rev()
.find(|(_, n)| *n != 0)
.unwrap();
if BINDING_T[last_written.0][last_written.1 as usize] {
acc.write_str("t").unwrap();
}
acc.write_str("illion ")?;
} else {
acc.write_str("thousand ")?;
}
}
}
acc.pop();
Ok(())
}
pub fn round(number: &mut String) -> bool {
if let Some(digit) = number
.pop()
.map(|n| n.to_digit(10).expect("Not a base 10 number"))
&& digit >= 5
{
let mut last_digit = number
.pop()
.and_then(|n| n.to_digit(10))
.expect("Not a base 10 number");
while last_digit == 9 {
let Some(digit) = number.pop() else {
number.push_str("10");
return true;
};
if digit == '.' {
break;
}
let digit = digit.to_digit(10).expect("Not a base 10 number");
last_digit = digit;
}
let _ = write!(number, "{}", last_digit + 1);
}
false
}
pub fn truncate(number: &Integer, consts: &Consts) -> (String, bool) {
let prec = consts.float_precision;
if number == &0 {
return (number.to_string(), false);
}
let negative = number.is_negative();
let orig_number = number;
let number = number.clone().abs();
let length = (Float::with_val(prec, &number).ln() / Float::with_val(prec, 10).ln())
.to_integer_round(crate::rug::float::Round::Down)
.unwrap()
.0;
let ten_exp = Integer::u64_pow_u64(
10,
(length.clone() - consts.number_decimals_scientific - 1u8)
.max(Integer::ZERO)
.to_u64()
.unwrap(),
)
.complete();
let rough = !number.is_divisible(&ten_exp);
let truncated_number: Integer = &number / ten_exp;
let mut truncated_number = truncated_number.to_string();
if truncated_number.len() > consts.number_decimals_scientific {
round(&mut truncated_number);
}
if let Some(mut digit) = truncated_number.pop() {
while digit == '0' {
digit = match truncated_number.pop() {
Some(x) => x,
None => break,
}
}
truncated_number.push(digit);
}
if truncated_number.len() > 1 {
truncated_number.insert(1, '.'); }
if negative {
truncated_number.insert(0, '-');
}
if length > consts.number_decimals_scientific + 1 {
if truncated_number == "1" {
(format!("10^{length}"), rough)
} else {
(format!("{truncated_number} × 10^{length}"), rough)
}
} else {
(orig_number.to_string(), false)
}
}
pub fn format_float(acc: &mut String, number: &Float, consts: &Consts) -> std::fmt::Result {
let mut number = number.clone();
let negative = number.is_sign_negative();
number = number.abs();
if number == 0 {
return acc.write_char('0');
}
let exponent = number
.clone()
.log10()
.to_integer_round(factorion_math::rug::float::Round::Down)
.expect("Could not round exponent")
.0;
if exponent > consts.number_decimals_scientific
|| exponent < -(consts.number_decimals_scientific as isize)
{
number /= Float::with_val(consts.float_precision, &exponent).exp10();
}
let mut whole_number = number
.to_integer_round(factorion_math::rug::float::Round::Down)
.expect("Could not get integer part")
.0;
let decimal_part = number - &whole_number + 1;
let mut decimal_part = format!("{decimal_part}");
decimal_part.remove(0);
decimal_part.remove(0);
decimal_part.truncate(consts.number_decimals_scientific + 1);
if decimal_part.len() > consts.number_decimals_scientific && round(&mut decimal_part) {
decimal_part.clear();
whole_number += 1;
}
if let Some(mut digit) = decimal_part.pop() {
while digit == '0' {
digit = match decimal_part.pop() {
Some(x) => x,
None => break,
}
}
decimal_part.push(digit);
}
if negative {
acc.write_str("-")?;
}
write!(acc, "{whole_number}")?;
if !decimal_part.is_empty() && decimal_part != "0" {
acc.write_str(".")?;
acc.write_str(&decimal_part)?;
}
if exponent > consts.number_decimals_scientific
|| exponent < -(consts.number_decimals_scientific as isize)
{
write!(acc, " × 10^{exponent}")?;
}
Ok(())
}
pub fn replace(s: &mut String, search_start: usize, from: &str, to: &str) -> usize {
if let Some(start) = s[search_start..].find(from) {
let start = start + search_start;
s.replace_range(start..(start + from.len()), to);
start
} else {
s.len()
}
}
pub(crate) fn format_complex_infinity(
acc: &mut String,
opts: FormatOptions,
) -> Result<(), fmt::Error> {
if opts.write_out {
acc.write_str("complex infinity")?;
} else {
acc.write_str("∞\u{0303}")?;
}
Ok(())
}
pub(crate) fn format_approximate_digits_tower(
acc: &mut String,
opts: &FormatOptions,
is_value: bool,
consts: &Consts<'_>,
negative: &bool,
depth: &Integer,
exponent: &Integer,
) -> Result<(), fmt::Error> {
let depth = if is_value {
depth.clone() + 1
} else {
depth.clone()
};
acc.write_str(if *negative { "-" } else { "" })?;
if !opts.agressive_shorten && depth <= usize::MAX && (depth <= 1 || exponent != &1) {
if depth > 0 {
acc.write_str("10^(")?;
}
if depth > 1 {
acc.write_str(&"10\\^".repeat(depth.to_usize().unwrap() - 1))?;
acc.write_str("(")?;
}
if opts.force_shorten {
acc.write_str(&truncate(exponent, consts).0)?;
} else {
write!(acc, "{exponent}")?;
}
if depth > 1 {
acc.write_str("\\)")?;
}
if depth > 0 {
acc.write_str(")")?;
}
} else {
let mut extra = 0;
let mut exponent = Float::with_val(consts.float_precision, exponent);
while exponent >= 10 {
extra += 1;
exponent = exponent.log10();
}
acc.write_str("^(")?;
write!(acc, "{}", depth + extra)?;
acc.write_str(")10")?;
}
Ok(())
}
pub(crate) fn format_approximate_digits(
acc: &mut String,
opts: &FormatOptions,
is_value: bool,
consts: &Consts<'_>,
digits: &Integer,
) -> Result<(), fmt::Error> {
if opts.write_out && !is_value && length(digits, consts.float_precision) < 3000000 {
write_out_number(acc, digits, consts)?;
} else {
if is_value {
acc.write_str("10^(")?;
}
if opts.force_shorten {
acc.write_str(&truncate(digits, consts).0)?;
} else {
write!(acc, "{digits}")?;
}
if is_value {
acc.write_str(")")?;
}
}
Ok(())
}
pub(crate) fn format_approximate(
acc: &mut String,
opts: &FormatOptions,
consts: &Consts<'_>,
base: &OrdFloat,
exponent: &Integer,
) -> Result<(), fmt::Error> {
let base = base.as_float();
format_float(acc, base, consts)?;
acc.write_str(" × 10^")?;
if opts.force_shorten {
acc.write_str("(")?;
acc.write_str(&truncate(exponent, consts).0)?;
acc.write_str(")")?;
} else {
write!(acc, "{exponent}")?;
}
Ok(())
}
pub(crate) fn format_exact(
acc: &mut String,
rough: &mut bool,
opts: &FormatOptions,
consts: &Consts<'_>,
factorial: &Integer,
) -> Result<(), fmt::Error> {
if opts.write_out && length(factorial, consts.float_precision) < 3000000 {
write_out_number(acc, factorial, consts)?;
} else if opts.force_shorten {
let (s, r) = truncate(factorial, consts);
*rough = r;
acc.write_str(&s)?;
} else {
write!(acc, "{factorial}")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rug::Integer;
use std::str::FromStr;
#[test]
fn test_round_down() {
let mut number = String::from("1929472373");
round(&mut number);
assert_eq!(number, "192947237");
}
#[test]
fn test_round_up() {
let mut number = String::from("74836748625");
round(&mut number);
assert_eq!(number, "7483674863");
}
#[test]
fn test_round_carry() {
let mut number = String::from("24999999995");
round(&mut number);
assert_eq!(number, "25");
}
#[test]
fn test_factorial_level_string() {
let en = locale::get_en();
assert_eq!(get_factorial_level_string(1, &en.format), "{factorial}");
assert_eq!(
get_factorial_level_string(2, &en.format),
"double-{factorial}"
);
assert_eq!(
get_factorial_level_string(3, &en.format),
"triple-{factorial}"
);
assert_eq!(
get_factorial_level_string(10, &en.format),
"decuple-{factorial}"
);
assert_eq!(
get_factorial_level_string(45, &en.format),
"quinquadragintuple-{factorial}"
);
assert_eq!(
get_factorial_level_string(50, &en.format),
"quinquagintuple-{factorial}"
);
assert_eq!(
get_factorial_level_string(100, &en.format),
"centuple-{factorial}"
);
assert_eq!(
get_factorial_level_string(521, &en.format),
"unviginquingentuple-{factorial}"
);
assert_eq!(
get_factorial_level_string(1000, &en.format),
"milluple-{factorial}"
);
assert_eq!(
get_factorial_level_string(4321, &en.format),
"unvigintricenquadrilluple-{factorial}"
);
assert_eq!(
get_factorial_level_string(89342, &en.format),
"duoquadragintricennonilloctogintilluple-{factorial}"
);
assert_eq!(
get_factorial_level_string(654321, &en.format),
"unvigintricenquadrillquinquagintillsescentilluple-{factorial}"
);
assert_eq!(
get_factorial_level_string(1000000, &en.format),
"1000000-{factorial}"
);
let de = locale::get_de();
assert_eq!(get_factorial_level_string(1, &de.format), "{factorial}");
assert_eq!(
get_factorial_level_string(2, &de.format),
"doppel{factorial}"
);
assert_eq!(
get_factorial_level_string(3, &de.format),
"trippel{factorial}"
);
assert_eq!(
get_factorial_level_string(45, &de.format),
"quinquadragintupel{factorial}"
);
}
#[test]
fn test_write_out_number() {
let consts = Consts::default();
let mut acc = String::new();
write_out_number(
&mut acc,
&"1234567890123456789000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
.parse()
.unwrap(),
&consts,
)
.unwrap();
assert_eq!(
acc,
"one tredeccentillion two hundred thirty four duodeccentillion five hundred sixty seven undeccentillion eight hundred ninety deccentillion one hundred twenty three novemcentillion four hundred fifty six octocentillion"
);
let mut acc = String::new();
write_out_number(&mut acc, &"123456789".parse().unwrap(), &consts).unwrap();
assert_eq!(
acc,
"one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine"
);
}
#[test]
fn test_truncate() {
let consts = Consts::default();
assert_eq!(truncate(&Integer::from_str("0").unwrap(), &consts,).0, "0");
assert_eq!(
truncate(&Integer::from_str("-1").unwrap(), &consts,).0,
"-1"
);
assert_eq!(
truncate(
&Integer::from_str(&format!("1{}", "0".repeat(300))).unwrap(),
&consts
)
.0,
"10^300"
);
assert!(
!truncate(
&Integer::from_str(&format!("1{}", "0".repeat(300))).unwrap(),
&consts
)
.1
);
assert_eq!(
truncate(
&Integer::from_str(&format!("2{}", "0".repeat(300))).unwrap(),
&consts
)
.0,
"2 × 10^300"
);
assert_eq!(
truncate(
&-Integer::from_str(&format!("2{}", "0".repeat(300))).unwrap(),
&consts
)
.0,
"-2 × 10^300"
);
assert_eq!(
truncate(
&Integer::from_str(&format!("2{}", "0".repeat(2000000))).unwrap(),
&consts
)
.0,
"2 × 10^2000000"
);
}
#[test]
fn test_format_float() {
let consts = Consts::default();
let x = Float::with_val(consts.float_precision, 1.5);
let mut acc = String::new();
format_float(&mut acc, &x, &consts).unwrap();
assert_eq!(acc, "1.5");
let x = Float::with_val(consts.float_precision, -1.5);
let mut acc = String::new();
format_float(&mut acc, &x, &consts).unwrap();
assert_eq!(acc, "-1.5");
let x = Float::with_val(consts.float_precision, 1);
let mut acc = String::new();
format_float(&mut acc, &x, &consts).unwrap();
assert_eq!(acc, "1");
let x = Float::with_val(consts.float_precision, 1.5)
* Float::with_val(consts.float_precision, 50000).exp10();
let mut acc = String::new();
format_float(&mut acc, &x, &consts).unwrap();
assert_eq!(acc, "1.5 × 10^50000");
}
}