use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const POINTWORD: &str = "ni";
const ONES: [&str; 10] = [
"fu",
"kelen",
"fila",
"saba",
"naani",
"duuru",
"wɔɔrɔ",
"wolonfila",
"segi",
"kɔnɔntɔn",
];
const TENS: [&str; 10] = [
"",
"tan",
"mugan",
"bisaba",
"binaani",
"biduuru",
"biwɔɔrɔ",
"biwolonfila",
"bisegi",
"bikɔnɔntɔn",
];
const HUNDRED: &str = "kɛmɛ";
const THOUSAND: &str = "ba";
const MILLION: &str = "miliyɔn";
const NEGWORD: &str = "dɔgɔ ";
const SEPARATOR_DEFAULT: &str = " ";
const SEPARATOR_UNSET: &str = ",";
const FALLBACK_CODE: &str = "XOF";
fn split_currency(val: &CurrencyValue) -> Result<(BigInt, BigInt)> {
let d = match val {
CurrencyValue::Int(v) => return Ok((v.abs(), BigInt::zero())),
CurrencyValue::Decimal { value: d, .. } => d.abs(),
};
let (digits, scale) = d.as_bigint_and_exponent();
if scale < 0 {
return Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
d
)));
}
if scale == 0 {
return Ok((digits, BigInt::zero()));
}
let s = digits.abs().to_string();
let scale = scale as usize;
let (int_part, frac_part) = if s.len() > scale {
let (a, b) = s.split_at(s.len() - scale);
(a.to_string(), b.to_string())
} else {
("0".to_string(), format!("{:0>width$}", s, width = scale))
};
let left = int_part
.parse::<BigInt>()
.unwrap_or_else(|_| BigInt::zero());
let head: String = frac_part.chars().take(2).collect();
let right = format!("{:0<2}", head)
.parse::<BigInt>()
.unwrap_or_else(|_| BigInt::zero());
Ok((left, right))
}
fn int_to_word(number: &BigInt) -> String {
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let million = BigInt::from(1_000_000);
let billion = BigInt::from(1_000_000_000);
if number.is_zero() {
return ONES[0].to_string();
}
if number < &ten {
let i = number.to_usize().expect("guarded: 0 < number < 10");
return ONES[i].to_string();
}
if number < &hundred {
let (t, o) = number.div_rem(&ten);
let ti = t.to_usize().expect("guarded: 1 <= t <= 9");
let oi = o.to_usize().expect("guarded: 0 <= o <= 9");
let tail = if oi != 0 {
format!(" ni {}", ONES[oi])
} else {
String::new()
};
return format!("{}{}", TENS[ti], tail);
}
if number < &thousand {
let (h, r) = number.div_rem(&hundred);
let hi = h.to_usize().expect("guarded: 1 <= h <= 9");
let base = if hi > 1 {
format!("{} {}", HUNDRED, ONES[hi])
} else {
HUNDRED.to_string()
};
let tail = if !r.is_zero() {
format!(" ni {}", int_to_word(&r))
} else {
String::new()
};
return format!("{}{}", base, tail);
}
if number < &million {
let (t, r) = number.div_rem(&thousand);
let base = if t > BigInt::from(1) {
format!("{} {}", THOUSAND, int_to_word(&t))
} else {
THOUSAND.to_string()
};
let tail = if !r.is_zero() {
format!(" ni {}", int_to_word(&r))
} else {
String::new()
};
return format!("{}{}", base, tail);
}
if number < &billion {
let (m, r) = number.div_rem(&million);
let base = if m > BigInt::from(1) {
format!("{} {}", MILLION, int_to_word(&m))
} else {
MILLION.to_string()
};
let tail = if !r.is_zero() {
format!(" ni {}", int_to_word(&r))
} else {
String::new()
};
return format!("{}{}", base, tail);
}
number.to_string()
}
fn python_float_repr(v: f64) -> String {
if v.is_nan() {
return "nan".to_string();
}
if v.is_infinite() {
return (if v.is_sign_negative() { "-inf" } else { "inf" }).to_string();
}
let sign = if v.is_sign_negative() { "-" } else { "" };
let a = v.abs();
let s = format!("{:e}", a);
let (mant, exp) = s.split_once('e').expect("LowerExp always emits an 'e'");
let exp: i32 = exp.parse().expect("LowerExp emits an integer exponent");
let mut digits: String = mant.chars().filter(|c| *c != '.').collect();
let mut decpt = exp + 1;
let frac_digits = digits.chars().count() as i32 - decpt;
if frac_digits > 0 && a != 0.0 {
let t = format!("{:.*}", frac_digits as usize, a);
let (ip, fp) = t.split_once('.').expect("frac_digits > 0 forces a point");
let all = format!("{}{}", ip, fp);
let trimmed = all.trim_start_matches('0');
if !trimmed.is_empty() {
let lead = all.chars().count() - trimmed.chars().count();
digits = trimmed.to_string();
decpt = ip.chars().count() as i32 - lead as i32;
}
}
let n = digits.chars().count() as i32;
if decpt <= -4 || decpt > 16 {
let e = decpt - 1;
let mut out = String::from(sign);
let mut it = digits.chars();
out.push(it.next().expect("a finite double has at least one digit"));
if n > 1 {
out.push('.');
out.push_str(it.as_str());
}
out.push('e');
out.push(if e < 0 { '-' } else { '+' });
out.push_str(&format!("{:02}", (e as i64).abs()));
out
} else if decpt <= 0 {
format!("{}0.{}{}", sign, "0".repeat((-decpt) as usize), digits)
} else if decpt >= n {
format!("{}{}{}.0", sign, digits, "0".repeat((decpt - n) as usize))
} else {
let k = decpt as usize;
format!(
"{}{}.{}",
sign,
digits.chars().take(k).collect::<String>(),
digits.chars().skip(k).collect::<String>()
)
}
}
fn python_decimal_str(d: &BigDecimal) -> String {
let (unscaled, scale) = d.as_bigint_and_exponent();
let sign = if unscaled.is_negative() { "-" } else { "" };
let int_digits = unscaled.abs().to_string();
let exp: i64 = -scale;
let ndig = int_digits.chars().count() as i64;
let leftdigits = exp + ndig;
let dotplace = if exp <= 0 && leftdigits > -6 {
leftdigits
} else {
1
};
let (intpart, fracpart) = if dotplace <= 0 {
(
"0".to_string(),
format!(".{}{}", "0".repeat((-dotplace) as usize), int_digits),
)
} else if dotplace >= ndig {
(
format!("{}{}", int_digits, "0".repeat((dotplace - ndig) as usize)),
String::new(),
)
} else {
let k = dotplace as usize;
(
int_digits.chars().take(k).collect::<String>(),
format!(".{}", int_digits.chars().skip(k).collect::<String>()),
)
};
let exp_part = if leftdigits == dotplace {
String::new()
} else {
format!("E{:+}", leftdigits - dotplace)
};
format!("{}{}{}{}", sign, intpart, fracpart, exp_part)
}
fn python_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, .. } => python_float_repr(*value),
FloatValue::Decimal { value, .. } => python_decimal_str(value),
}
}
fn python_int(s: &str) -> Result<BigInt> {
s.parse::<BigInt>()
.map_err(|_| N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s)))
}
pub struct LangBm {
forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangBm {
fn default() -> Self {
Self::new()
}
}
impl LangBm {
pub fn new() -> Self {
let mut forms = HashMap::with_capacity(3);
forms.insert(
"XOF",
CurrencyForms::new(&["seefa", "seefa"], &["santimu", "santimu"]),
);
forms.insert(
"USD",
CurrencyForms::new(&["dolari", "dolari"], &["santimu", "santimu"]),
);
forms.insert(
"EUR",
CurrencyForms::new(&["ero", "ero"], &["santimu", "santimu"]),
);
LangBm { forms }
}
fn cardinal_from_str(&self, n: &str) -> Result<String> {
let n = n.trim();
if let Some(rest) = n.strip_prefix('-') {
let inner = self.cardinal_from_str(rest)?;
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
if let Some((left, right)) = n.split_once('.') {
let mut ret = format!("{} {}", int_to_word(&python_int(left)?), POINTWORD);
for ch in right.chars() {
let d = ch.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", ch))
})? as usize;
ret.push(' ');
ret.push_str(ONES[d]);
}
return Ok(ret.trim().to_string());
}
Ok(int_to_word(&python_int(n)?))
}
}
impl Lang for LangBm {
fn cardinal_float_entry(
&self,
value: &crate::floatpath::FloatValue,
precision_override: Option<u32>,
) -> crate::base::Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
Ok(format!("{}nan", self.to_cardinal_float(value, None)?))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}nan", repr_str))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match python_decimal_parse(s)? {
ParsedNumber::Inf { .. } => Err(N2WError::Value(
"invalid literal for int() with base 10: 'Infinity'".into(),
)),
other => Ok(other),
}
}
fn default_currency(&self) -> &str {
"XOF"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
POINTWORD
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
self.cardinal_from_str(&python_str(value))
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
let inner = self.to_cardinal(&value.abs())?;
return Ok(format!("{}{}", NEGWORD, inner).trim().to_string());
}
Ok(int_to_word(value))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}nan", self.to_cardinal(value)?))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}nan", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.to_cardinal(value)
}
fn lang_name(&self) -> &str {
"Num2Word_BM"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
if forms.is_empty() {
return Ok(String::new());
}
Ok(if n.is_one() {
forms[0].clone()
} else {
forms[forms.len() - 1].clone()
})
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
let separator = separator.unwrap_or(self.default_separator());
let separator = if separator == SEPARATOR_UNSET {
SEPARATOR_DEFAULT
} else {
separator
};
let is_negative = val.is_negative();
let (left, right) = split_currency(val)?;
let forms = self
.forms
.get(currency)
.or_else(|| self.forms.get(FALLBACK_CODE))
.expect("FALLBACK_CODE is inserted by new()");
let one = BigInt::one();
let unit = if left != one {
&forms.unit[1]
} else {
&forms.unit[0]
};
let mut result = format!("{} {}", int_to_word(&left), unit);
if cents && !right.is_zero() {
let subunit = if right != one {
&forms.subunit[1]
} else {
&forms.subunit[0]
};
result.push_str(&format!("{}{} {}", separator, int_to_word(&right), subunit));
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use bigdecimal::BigDecimal;
use std::str::FromStr;
fn cur(arg: &str, code: &str) -> String {
let is_int = !arg.contains('.') && !arg.to_lowercase().contains('e');
let v = CurrencyValue::parse(arg, is_int, !is_int, !is_int).unwrap();
LangBm::new()
.to_currency(&v, code, true, None, false)
.unwrap()
}
fn cheque(arg: &str, code: &str) -> Result<String> {
LangBm::new().to_cheque(&BigDecimal::from_str(arg).unwrap(), code)
}
fn c(n: i64) -> String {
LangBm::new().to_cardinal(&BigInt::from(n)).unwrap()
}
fn o(n: i64) -> String {
LangBm::new().to_ordinal(&BigInt::from(n)).unwrap()
}
fn on(n: i64) -> String {
LangBm::new().to_ordinal_num(&BigInt::from(n)).unwrap()
}
fn y(n: i64) -> String {
LangBm::new().to_year(&BigInt::from(n)).unwrap()
}
#[test]
fn corpus_cardinal() {
assert_eq!(c(0), "fu");
assert_eq!(c(1), "kelen");
assert_eq!(c(9), "kɔnɔntɔn");
assert_eq!(c(10), "tan");
assert_eq!(c(11), "tan ni kelen");
assert_eq!(c(100), "kɛmɛ");
assert_eq!(c(999), "kɛmɛ kɔnɔntɔn ni bikɔnɔntɔn ni kɔnɔntɔn");
assert_eq!(c(1000), "ba");
assert_eq!(c(1_000_000), "miliyɔn");
assert_eq!(c(1_000_001), "miliyɔn ni kelen");
assert_eq!(
c(1_234_567),
"miliyɔn ni ba kɛmɛ fila ni bisaba ni naani ni kɛmɛ duuru ni biwɔɔrɔ ni wolonfila"
);
}
#[test]
fn corpus_negative() {
assert_eq!(c(-1), "dɔgɔ kelen");
assert_eq!(c(-21), "dɔgɔ mugan ni kelen");
assert_eq!(c(-100), "dɔgɔ kɛmɛ");
assert_eq!(o(-1), "dɔgɔ kelennan");
assert_eq!(on(-999), "-999nan");
}
#[test]
fn corpus_ordinal_and_year() {
assert_eq!(o(0), "funan");
assert_eq!(o(10), "tannan");
assert_eq!(on(0), "0nan");
assert_eq!(y(-500), "dɔgɔ kɛmɛ duuru");
assert_eq!(y(-44), "dɔgɔ binaani ni naani");
}
#[test]
fn digit_fallback_above_billion() {
assert_eq!(c(1_000_000_000), "1000000000");
assert_eq!(o(1_000_000_000), "1000000000nan");
assert_eq!(on(1_000_000_000), "1000000000nan");
let big: BigInt = "1000000000000000000000".parse().unwrap();
let lang = LangBm::new();
assert_eq!(lang.to_cardinal(&big).unwrap(), "1000000000000000000000");
assert_eq!(lang.to_ordinal(&big).unwrap(), "1000000000000000000000nan");
}
#[test]
fn negative_past_fallback_sign_asymmetry() {
assert_eq!(c(-1_000_000_000), "dɔgɔ 1000000000");
assert_eq!(o(-1_000_000_000), "dɔgɔ 1000000000nan");
assert_eq!(on(-1_000_000_000), "-1000000000nan");
assert_eq!(y(-1_000_000_000), "dɔgɔ 1000000000");
}
#[test]
fn corpus_currency_known_codes() {
assert_eq!(cur("0", "EUR"), "fu ero");
assert_eq!(cur("1", "EUR"), "kelen ero");
assert_eq!(cur("2", "EUR"), "fila ero");
assert_eq!(cur("100", "EUR"), "kɛmɛ ero");
assert_eq!(cur("1000000", "EUR"), "miliyɔn ero");
assert_eq!(
cur("12.34", "EUR"),
"tan ni fila ero bisaba ni naani santimu"
);
assert_eq!(cur("0.01", "EUR"), "fu ero kelen santimu");
assert_eq!(
cur("99.99", "EUR"),
"bikɔnɔntɔn ni kɔnɔntɔn ero bikɔnɔntɔn ni kɔnɔntɔn santimu"
);
assert_eq!(
cur("1234.56", "EUR"),
"ba ni kɛmɛ fila ni bisaba ni naani ero biduuru ni wɔɔrɔ santimu"
);
assert_eq!(cur("0.5", "EUR"), "fu ero biduuru santimu");
assert_eq!(cur("1.0", "EUR"), "kelen ero");
assert_eq!(
cur("-12.34", "EUR"),
"dɔgɔ tan ni fila ero bisaba ni naani santimu"
);
assert_eq!(cur("1", "USD"), "kelen dolari");
assert_eq!(
cur("12.34", "USD"),
"tan ni fila dolari bisaba ni naani santimu"
);
assert_eq!(cur("1", "XOF"), "kelen seefa");
}
#[test]
fn corpus_currency_unknown_codes_fall_back_to_xof() {
for code in ["GBP", "JPY", "KWD", "BHD", "INR", "CNY", "CHF", "ZZZ"] {
assert_eq!(cur("0", code), "fu seefa");
assert_eq!(cur("100", code), "kɛmɛ seefa");
assert_eq!(cur("1.0", code), "kelen seefa");
assert_eq!(cur("0.01", code), "fu seefa kelen santimu");
assert_eq!(
cur("1234.56", code),
"ba ni kɛmɛ fila ni bisaba ni naani seefa biduuru ni wɔɔrɔ santimu"
);
assert_eq!(
cur("-12.34", code),
"dɔgɔ tan ni fila seefa bisaba ni naani santimu"
);
}
}
#[test]
fn currency_edges() {
assert_eq!(cur("1000000000.0", "EUR"), "1000000000 ero");
assert_eq!(
cur("12.349", "EUR"),
"tan ni fila ero bisaba ni naani santimu"
);
assert_eq!(cur("0.005", "EUR"), "fu ero");
assert_eq!(cur("-0.001", "EUR"), "dɔgɔ fu ero");
assert_eq!(cur("-0.0", "EUR"), "fu ero");
assert_eq!(cur("2.5", "EUR"), "fila ero biduuru santimu");
assert_eq!(cur("-100", "EUR"), "dɔgɔ kɛmɛ ero");
let v = CurrencyValue::parse("12.34", false, true, true).unwrap();
assert_eq!(
LangBm::new()
.to_currency(&v, "EUR", false, None, false)
.unwrap(),
"tan ni fila ero"
);
let v = CurrencyValue::parse("12.34", false, true, true).unwrap();
assert_eq!(
LangBm::new()
.to_currency(&v, "EUR", true, Some(" ni "), false)
.unwrap(),
"tan ni fila ero ni bisaba ni naani santimu"
);
}
#[test]
fn currency_exponent_literal_raises_value_error() {
let v = CurrencyValue::parse("1e+16", false, true, true).unwrap();
assert!(matches!(
LangBm::new().to_currency(&v, "EUR", true, None, false),
Err(N2WError::Value(_))
));
assert_eq!(cur("10000000000000000", "EUR"), "10000000000000000 ero");
}
#[test]
fn corpus_cheque() {
assert_eq!(
cheque("1234.56", "EUR").unwrap(),
"BA NI KƐMƐ FILA NI BISABA NI NAANI AND 56/100 ERO"
);
assert_eq!(
cheque("1234.56", "USD").unwrap(),
"BA NI KƐMƐ FILA NI BISABA NI NAANI AND 56/100 DOLARI"
);
for code in ["GBP", "JPY", "KWD", "BHD", "INR", "CNY", "CHF"] {
match cheque("1234.56", code) {
Err(N2WError::NotImplemented(m)) => assert_eq!(
m,
format!(
"Currency code \"{}\" not implemented for \"Num2Word_BM\"",
code
)
),
other => panic!("{}: expected NotImplemented, got {:?}", code, other),
}
}
}
#[test]
fn cheque_edges() {
assert_eq!(
cheque("-1.0", "USD").unwrap(),
"MINUS KELEN AND 00/100 DOLARI"
);
assert_eq!(cheque("0", "XOF").unwrap(), "FU AND 00/100 SEEFA");
}
fn f(v: f64) -> Result<String> {
LangBm::new().to_cardinal_float(
&FloatValue::Float {
value: v,
precision: 99,
},
None,
)
}
fn d(s: &str) -> Result<String> {
LangBm::new().to_cardinal_float(
&FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision: 99,
},
None,
)
}
#[test]
fn corpus_cardinal_float() {
assert_eq!(f(0.0).unwrap(), "fu ni fu");
assert_eq!(f(0.5).unwrap(), "fu ni duuru");
assert_eq!(f(1.0).unwrap(), "kelen ni fu");
assert_eq!(f(1.5).unwrap(), "kelen ni duuru");
assert_eq!(f(2.25).unwrap(), "fila ni fila duuru");
assert_eq!(f(3.14).unwrap(), "saba ni kelen naani");
assert_eq!(f(0.01).unwrap(), "fu ni fu kelen");
assert_eq!(f(0.1).unwrap(), "fu ni kelen");
assert_eq!(f(0.99).unwrap(), "fu ni kɔnɔntɔn kɔnɔntɔn");
assert_eq!(f(1.01).unwrap(), "kelen ni fu kelen");
assert_eq!(f(12.34).unwrap(), "tan ni fila ni saba naani");
assert_eq!(
f(99.99).unwrap(),
"bikɔnɔntɔn ni kɔnɔntɔn ni kɔnɔntɔn kɔnɔntɔn"
);
assert_eq!(f(100.5).unwrap(), "kɛmɛ ni duuru");
assert_eq!(
f(1234.56).unwrap(),
"ba ni kɛmɛ fila ni bisaba ni naani ni duuru wɔɔrɔ"
);
assert_eq!(f(-0.5).unwrap(), "dɔgɔ fu ni duuru");
assert_eq!(f(-1.5).unwrap(), "dɔgɔ kelen ni duuru");
assert_eq!(f(-12.34).unwrap(), "dɔgɔ tan ni fila ni saba naani");
}
#[test]
fn corpus_cardinal_decimal() {
assert_eq!(d("0.01").unwrap(), "fu ni fu kelen");
assert_eq!(d("1.10").unwrap(), "kelen ni kelen fu");
assert_eq!(d("12.345").unwrap(), "tan ni fila ni saba naani duuru");
assert_eq!(
d("98746251323029.99").unwrap(),
"98746251323029 ni kɔnɔntɔn kɔnɔntɔn"
);
assert_eq!(d("0.001").unwrap(), "fu ni fu fu kelen");
}
#[test]
fn float_artefacts_are_not_reachable() {
assert_eq!(f(2.675).unwrap(), "fila ni wɔɔrɔ wolonfila duuru");
assert_eq!(f(1.005).unwrap(), "kelen ni fu fu duuru");
assert_eq!(f(-1.005).unwrap(), "dɔgɔ kelen ni fu fu duuru");
assert_eq!(f(2.5).unwrap(), "fila ni duuru");
assert_eq!(f(0.5).unwrap(), "fu ni duuru");
assert_eq!(f(0.005).unwrap(), "fu ni fu fu duuru");
}
#[test]
fn shortest_repr_ties_go_to_even() {
assert_eq!(f(670352580196876.2).unwrap(), "670352580196876 ni fila");
assert_eq!(
f(161834668665500.12).unwrap(),
"161834668665500 ni kelen fila"
);
assert_eq!(
f(-29489152302236.812).unwrap(),
"dɔgɔ 29489152302236 ni segi kelen fila"
);
}
#[test]
fn float_repr_placement() {
assert_eq!(f(1.0).unwrap(), "kelen ni fu");
assert_eq!(f(-1.0).unwrap(), "dɔgɔ kelen ni fu");
assert_eq!(f(-0.0).unwrap(), "dɔgɔ fu ni fu");
assert_eq!(f(1000000000.5).unwrap(), "1000000000 ni duuru");
assert_eq!(f(1e15).unwrap(), "1000000000000000 ni fu");
assert_eq!(f(9999999999999998.0).unwrap(), "9999999999999998 ni fu");
assert_eq!(f(0.0001).unwrap(), "fu ni fu fu fu kelen");
}
#[test]
fn exponent_notation_raises_value_error() {
for (v, lit) in [
(1e16, "1e+16"),
(1e21, "1e+21"),
(1e-5, "1e-05"),
(1e300, "1e+300"),
(-1e16, "1e+16"),
(-1e-5, "1e-05"),
(f64::INFINITY, "inf"),
(f64::NEG_INFINITY, "inf"),
] {
match f(v) {
Err(N2WError::Value(m)) => assert_eq!(
m,
format!("invalid literal for int() with base 10: '{}'", lit)
),
other => panic!("{}: expected Value, got {:?}", lit, other),
}
}
match f(f64::NAN) {
Err(N2WError::Value(m)) => {
assert_eq!(m, "invalid literal for int() with base 10: 'nan'")
}
other => panic!("nan: expected Value, got {:?}", other),
}
for v in [1.5e16, 1.5e-5] {
match f(v) {
Err(N2WError::Value(m)) => {
assert_eq!(m, "invalid literal for int() with base 10: 'e'")
}
other => panic!("{}: expected Value, got {:?}", v, other),
}
}
}
#[test]
fn decimal_str_rules() {
assert_eq!(d("0.00").unwrap(), "fu ni fu fu");
assert_eq!(d("5.00").unwrap(), "duuru ni fu fu");
assert_eq!(d("5").unwrap(), "duuru");
assert_eq!(d("0").unwrap(), "fu");
assert_eq!(d("-1").unwrap(), "dɔgɔ kelen");
assert_eq!(d("-0.5").unwrap(), "dɔgɔ fu ni duuru");
assert_eq!(
d("1.000000000000000000001").unwrap(),
"kelen ni fu fu fu fu fu fu fu fu fu fu fu fu fu fu fu fu fu fu fu fu kelen"
);
for (s, lit) in [("1E+16", "1E+16"), ("0.0000001", "1E-7"), ("-0E+2", "0E+2")] {
match d(s) {
Err(N2WError::Value(m)) => assert_eq!(
m,
format!("invalid literal for int() with base 10: '{}'", lit)
),
other => panic!("{}: expected Value, got {:?}", s, other),
}
}
match d("12E2") {
Err(N2WError::Value(m)) => {
assert_eq!(m, "invalid literal for int() with base 10: 'E'")
}
other => panic!("12E2: expected Value, got {:?}", other),
}
assert_eq!(d("0.000001").unwrap(), "fu ni fu fu fu fu fu kelen");
}
#[test]
fn precision_override_is_ignored() {
let l = LangBm::new();
let v = FloatValue::Float {
value: 2.675,
precision: 3,
};
assert_eq!(
l.to_cardinal_float(&v, Some(1)).unwrap(),
"fila ni wɔɔrɔ wolonfila duuru"
);
assert_eq!(
l.to_cardinal_float(&v, Some(9)).unwrap(),
"fila ni wɔɔrɔ wolonfila duuru"
);
let z = FloatValue::Float {
value: 0.5,
precision: 1,
};
assert_eq!(l.to_cardinal_float(&z, Some(5)).unwrap(), "fu ni duuru");
}
#[test]
fn pluralize_forms() {
let l = LangBm::new();
let forms = vec!["a".to_string(), "b".to_string()];
assert_eq!(l.pluralize(&BigInt::from(1), &forms).unwrap(), "a");
assert_eq!(l.pluralize(&BigInt::from(2), &forms).unwrap(), "b");
assert_eq!(l.pluralize(&BigInt::from(2), &[]).unwrap(), "");
}
}