use crate::base::{Kwargs, KwVal, Lang, N2WError, Result};
use crate::currency::{parse_currency_parts, prefix_currency, CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, python_decimal_str, python_int_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::{Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const ZERO: &str = "тэг";
const NEGWORD: &str = "хасах";
const BCE_PREFIX: &str = "МЭӨ ";
const YEAR_WORD: &str = "он";
const ONES: [(&str, &str); 10] = [
("", ""),
("нэг", "нэг"),
("хоёр", "хоёр"),
("гурав", "гурван"),
("дөрөв", "дөрвөн"),
("тав", "таван"),
("зургаа", "зургаан"),
("долоо", "долоон"),
("найм", "найман"),
("ес", "есөн"),
];
const TEN: (&str, &str) = ("арав", "арван");
const TWENTIES: [(&str, &str); 10] = [
("", ""),
("", ""),
("хорь", "хорин"),
("гуч", "гучин"),
("дөч", "дөчин"),
("тавь", "тавин"),
("жар", "жаран"),
("дал", "далан"),
("ная", "наян"),
("ер", "ерэн"),
];
const HUNDRED: (&str, &str) = ("зуу", "зуун");
const THOUSANDS: [&str; 23] = [
"", "мянга", "сая", "тэрбум", "их наяд", "тунамал", "их ингүүмэл", "ялгаруулагч", "их өөр дээр", "хязгаар үзэгдэл", "их шалтгааны зүйл", "эрхт", "их сайтар хүргэсэн", "живэх тоосон билэг", "их билэг тэмдэг", "тохио мэдэхүй", "их тийн болсон", "асрахуй", "их нигүүлсэнгүй", "тоолшгүй", "өгүүлшгүй", "үлэшгүй", "сэтгэшгүй", ];
const THOUSAND_FREE: &str = "мянга";
const THOUSAND_SUFFIXED: &str = "мянган";
const ORD_DUGAAR: &str = "дугаар";
const ORD_DUGEER: &str = "дүгээр";
const POINT_WORDS: [&str; 7] = [
"", "аравны", "зууны", "мянганы", "арван мянганы", "зуун мянганы", "саяны", ];
#[allow(dead_code)]
fn index_error(msg: &str) -> N2WError {
N2WError::Index(msg.to_string())
}
fn key_error(key: usize) -> N2WError {
N2WError::Key(key.to_string())
}
fn thousands(i: usize) -> Result<&'static str> {
THOUSANDS.get(i).copied().ok_or_else(|| key_error(i))
}
fn splitbyx(s: &str) -> Vec<u16> {
let length = s.len();
let mut out: Vec<u16> = Vec::new();
if length > 3 {
let start = length % 3;
if start > 0 {
out.push(s[..start].parse().expect("ascii digits"));
}
let mut i = start;
while i < length {
out.push(s[i..i + 3].parse().expect("ascii digits"));
i += 3;
}
} else {
out.push(s.parse().expect("ascii digits"));
}
out
}
fn get_digits(x: u16) -> (u16, u16, u16) {
(x % 10, (x / 10) % 10, (x / 100) % 10)
}
fn int2word(n: &BigInt, all_suffixed: bool) -> Result<String> {
if n.is_negative() {
return Ok(format!("{} {}", NEGWORD, int2word(&n.abs(), false)?));
}
if n.is_zero() {
return Ok(ZERO.to_string());
}
let mut words: Vec<&'static str> = Vec::new();
let chunks = splitbyx(&n.to_string());
let mut i = chunks.len();
for x in chunks {
i -= 1;
if x == 0 {
continue;
}
let (n1, n2, n3) = get_digits(x);
let (mut use_suffix1, mut use_suffix2, mut use_suffix3) = (true, true, true);
if !all_suffixed && i == 0 {
if n1 == 0 && n2 == 0 {
use_suffix3 = false;
} else if n1 == 0 {
use_suffix2 = false;
} else {
use_suffix1 = false;
}
}
if n3 > 0 {
if n3 > 1 {
words.push(ONES[n3 as usize].1);
}
words.push(if use_suffix3 { HUNDRED.1 } else { HUNDRED.0 });
}
if n2 == 1 {
words.push(if use_suffix2 { TEN.1 } else { TEN.0 });
} else if n2 > 1 {
let t = TWENTIES[n2 as usize];
words.push(if use_suffix2 { t.1 } else { t.0 });
}
if n1 > 0 {
let o = ONES[n1 as usize];
words.push(if use_suffix1 { o.1 } else { o.0 });
}
if i > 0 {
words.push(thousands(i)?);
}
}
if all_suffixed {
if let Some(last) = words.last_mut() {
if *last == THOUSAND_FREE {
*last = THOUSAND_SUFFIXED;
}
}
}
Ok(words.join(" "))
}
fn ordinal_suffix(number: &BigInt) -> Result<&'static str> {
let number_str = number.to_string();
let chars: Vec<char> = number_str.chars().collect();
let mut suffix = ORD_DUGAAR;
let last = chars[chars.len() - 1];
if last != '0' {
if last == '1' || last == '4' || last == '9' {
suffix = ORD_DUGEER;
}
} else if chars.len() > 1 {
let second_last = chars[chars.len() - 2];
if second_last != '0' && (second_last == '4' || second_last == '9') {
suffix = ORD_DUGEER;
}
}
Ok(suffix)
}
fn f64_trunc_to_bigint(f: f64) -> Result<BigInt> {
format!("{:.0}", f.trunc())
.parse::<BigInt>()
.map_err(|e| N2WError::Value(e.to_string()))
}
fn float_repr_precision(f: f64) -> u32 {
let s = format!("{}", f);
match s.split_once('.') {
Some((_, frac)) if !frac.contains('e') => frac.len() as u32,
_ => 0,
}
}
fn python_float_str(f: f64, precision: u32) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return if f.is_sign_negative() { "-inf" } else { "inf" }.to_string();
}
if f != 0.0 {
let sci = format!("{:e}", f);
let (mant, exp) = sci.split_once('e').expect("LowerExp always has an e");
let exp: i32 = exp.parse().expect("exponent is an integer");
if !(-4..16).contains(&exp) {
return format!(
"{}e{}{:02}",
mant,
if exp < 0 { "-" } else { "+" },
exp.abs()
);
}
}
format!("{:.*}", precision.max(1) as usize, f)
}
fn mn_value_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, precision } => python_float_str(*value, *precision),
FloatValue::Decimal { value, .. } => python_decimal_str(value),
}
}
fn mn_to_cardinal_str(n: &str, all_suffixed: bool) -> Result<String> {
if n.contains('.') {
let is_negative = n.starts_with('-');
let abs_n = if is_negative { &n[1..] } else { n };
let (left, right) = abs_n.split_once('.').expect("checked: contains '.'");
let right_int = right
.parse::<BigInt>()
.map_err(|e| N2WError::Value(e.to_string()))?;
if right_int.is_zero() {
let f: f64 = n
.parse()
.map_err(|e: std::num::ParseFloatError| N2WError::Value(e.to_string()))?;
return int2word(&f64_trunc_to_bigint(f)?, all_suffixed);
}
if right.len() > 6 {
return Err(N2WError::NotImplemented(String::new()));
}
let left_int = left
.parse::<BigInt>()
.map_err(|e| N2WError::Value(e.to_string()))?;
let body = format!(
"{}, {} {}",
int2word(&left_int, all_suffixed)?,
POINT_WORDS[right.len()],
int2word(&right_int, all_suffixed)?,
);
return Ok(if is_negative {
format!("{} {}", NEGWORD, body)
} else {
body
});
}
match python_int_parse(n) {
Some(i) => int2word(&i, all_suffixed),
None => Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
n
))),
}
}
fn mn_to_cardinal(v: &FloatValue, all_suffixed: bool) -> Result<String> {
mn_to_cardinal_str(&mn_value_str(v), all_suffixed)
}
fn mn_trunc_int(v: &FloatValue) -> Result<BigInt> {
match v {
FloatValue::Float { value, .. } => {
if value.is_nan() {
return Err(N2WError::Value(
"cannot convert float NaN to integer".into(),
));
}
if value.is_infinite() {
return Err(N2WError::Overflow(
"cannot convert float infinity to integer".into(),
));
}
f64_trunc_to_bigint(*value)
}
FloatValue::Decimal { value, .. } => Ok(value.with_scale(0).as_bigint_and_exponent().0),
}
}
fn kw_truthy(v: Option<&KwVal>) -> bool {
match v {
None | Some(KwVal::None) => false,
Some(KwVal::Bool(b)) => *b,
Some(KwVal::Int(i)) => *i != 0,
Some(KwVal::Str(s)) => !s.is_empty(),
Some(KwVal::List(l)) => !l.is_empty(),
}
}
const DIVISOR: i64 = 100;
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("AED", CurrencyForms::new(&["дирхам"], &["филс"]));
m.insert("AUD", CurrencyForms::new(&["доллар"], &["цент"]));
m.insert("BGN", CurrencyForms::new(&["лев"], &["стотинка"]));
m.insert("CAD", CurrencyForms::new(&["доллар"], &["цент"]));
m.insert("CHF", CurrencyForms::new(&["франк"], &["раппен"]));
m.insert("CNY", CurrencyForms::new(&["юань"], &["фэнь"]));
m.insert("CZK", CurrencyForms::new(&["крон"], &["галерж"]));
m.insert("DKK", CurrencyForms::new(&["крон"], &["өре"]));
m.insert("EGP", CurrencyForms::new(&["фунт"], &["пиастр"]));
m.insert("EUR", CurrencyForms::new(&["евро"], &["цент"]));
m.insert("GBP", CurrencyForms::new(&["фунт стерлинг"], &["пенс"]));
m.insert("HKD", CurrencyForms::new(&["доллар"], &["цент"]));
m.insert("HUF", CurrencyForms::new(&["форинт"], &["филлер"]));
m.insert("IDR", CurrencyForms::new(&["рупи"], &["сен"]));
m.insert("INR", CurrencyForms::new(&["рупи"], &["пайса"]));
m.insert("JPY", CurrencyForms::new(&["иен"], &["сен"]));
m.insert("KPW", CurrencyForms::new(&["вон"], &["чон"]));
m.insert("KRW", CurrencyForms::new(&["вон"], &["чон"]));
m.insert("KWD", CurrencyForms::new(&["динaр"], &["филс"]));
m.insert("KZT", CurrencyForms::new(&["тенге"], &["тийн"]));
m.insert("MNT", CurrencyForms::new(&["төгрөг"], &["мөнгө"]));
m.insert("MYR", CurrencyForms::new(&["ринггит"], &["сен"]));
m.insert("NOK", CurrencyForms::new(&["крон"], &["өре"]));
m.insert("NPR", CurrencyForms::new(&["рупи"], &["пайса"]));
m.insert("NZD", CurrencyForms::new(&["доллар"], &["цент"]));
m.insert("PLN", CurrencyForms::new(&["злот"], &["грош"]));
m.insert("RUB", CurrencyForms::new(&["рубль"], &["копейк"]));
m.insert("SEK", CurrencyForms::new(&["крон"], &["өре"]));
m.insert("SGD", CurrencyForms::new(&["доллар"], &["цент"]));
m.insert("THB", CurrencyForms::new(&["бат"], &["сатанг"]));
m.insert("TRY", CurrencyForms::new(&["лира"], &["куруш"]));
m.insert("TWD", CurrencyForms::new(&["доллар"], &["сентао"]));
m.insert("UAH", CurrencyForms::new(&["гривн"], &["копейк"]));
m.insert("USD", CurrencyForms::new(&["доллар"], &["цент"]));
m.insert("VND", CurrencyForms::new(&["донг"], &["су"]));
m.insert("ZAR", CurrencyForms::new(&["ранд"], &["сента"]));
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
[
("AUD", "Австралийн"),
("CAD", "Канадын"),
("CZK", "Чехийн "),
("DKK", "Данийн"),
("HKD", "Хонконг"),
("IDR", "Индонезийн"),
("INR", "Энэтхэгийн"),
("KPW", "БНАСАУ-ын"),
("KRW", "БНСУ-ын"),
("NOK", "Норвегийн"),
("NPR", "Непалын"),
("NZD", "Шинэ Зеландын"),
("SEK", "Шведийн"),
("SGD", "Сингапур"),
("TWD", "Тайванийн"),
("USD", "Америк"),
]
.into_iter()
.collect()
}
pub struct LangMn {
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangMn {
fn default() -> Self {
Self::new()
}
}
impl LangMn {
pub fn new() -> Self {
LangMn {
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
}
impl Lang for LangMn {
fn pointword(&self) -> &str {
"(.)"
}
fn default_currency(&self) -> &str {
"MNT"
}
fn default_separator(&self) -> &str {
","
}
fn negword(&self) -> &str {
NEGWORD
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
int2word(value, false)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
let cardinal = self.to_cardinal(value)?;
let suffix = ordinal_suffix(value)?;
Ok(format!("{} {}", cardinal, suffix))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
let suffix = ordinal_suffix(value)?;
Ok(format!("{} {}", value, suffix))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let mut prefix = "";
let mut v = value.clone();
if v.is_negative() {
v = v.abs();
prefix = BCE_PREFIX;
}
Ok(format!("{}{} {}", prefix, int2word(&v, true)?, YEAR_WORD))
}
fn lang_name(&self) -> &str {
"Num2Word_MN"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
self.currency_adjectives.get(code).copied()
}
fn default_adjective(&self) -> bool {
true
}
fn pluralize(&self, _n: &BigInt, forms: &[String]) -> Result<String> {
forms
.first()
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
fn money_verbose(&self, number: &BigInt, _currency: &str) -> Result<String> {
int2word(number, true)
}
fn cents_verbose(&self, number: &BigInt, _currency: &str) -> Result<String> {
int2word(number, true)
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
mn_to_cardinal(value, false)
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
mn_to_cardinal(value, false)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let n = mn_trunc_int(value)?;
self.to_ordinal(&n)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, _repr_str: &str) -> Result<String> {
let n = mn_trunc_int(value)?;
self.to_ordinal_num(&n)
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
let lt_zero = match value {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.is_negative(),
};
let (v, prefix) = if lt_zero {
let abs = match value {
FloatValue::Float { value, precision } => FloatValue::Float {
value: value.abs(),
precision: *precision,
},
FloatValue::Decimal { value, precision } => FloatValue::Decimal {
value: value.abs(),
precision: *precision,
},
};
(abs, BCE_PREFIX)
} else {
(value.clone(), "")
};
Ok(format!("{}{} {}", prefix, mn_to_cardinal(&v, true)?, YEAR_WORD))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match python_decimal_parse(s)? {
ParsedNumber::Inf { negative } => Err(N2WError::Value(format!(
"invalid literal for int() with base 10: '{}Infinity'",
if negative { "-" } else { "" },
))),
other => Ok(other),
}
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["all_suffixed"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
int2word(value, kw_truthy(kw.get("all_suffixed")))
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if !kw.only(&["all_suffixed"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
mn_to_cardinal(value, kw_truthy(kw.get("all_suffixed")))
}
fn cardinal_from_decimal(&self, value: &BigDecimal) -> Result<String> {
let f = value
.to_f64()
.ok_or_else(|| N2WError::Value(format!("cannot represent {} as f64", value)))?;
let precision = float_repr_precision(f);
mn_to_cardinal(&FloatValue::Float { value: f, precision }, false)
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
let d = match val {
CurrencyValue::Int(v) => return int2word(v, false),
CurrencyValue::Decimal { value, .. } => value,
};
let separator = separator.unwrap_or(self.default_separator());
let scaled = d * BigDecimal::from(DIVISOR);
let has_fractional_cents = &scaled - scaled.with_scale(0) != BigDecimal::zero();
let (left, right, is_negative) =
parse_currency_parts(val, false, has_fractional_cents, DIVISOR);
let forms = self.currency_forms(currency).ok_or_else(|| {
N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
))
})?;
let mut cr1 = forms.unit.clone();
let cr2 = forms.subunit.clone();
if adjective {
if let Some(adj) = self.currency_adjective(currency) {
cr1 = prefix_currency(adj, &cr1);
}
}
let minus_str = if is_negative {
format!("{} ", NEGWORD)
} else {
String::new()
};
let money_str = self.money_verbose(&left, currency)?;
if right <= BigDecimal::zero() {
return Ok(format!(
"{}{} {}",
minus_str,
money_str,
self.pluralize(&left, &cr1)?
));
}
let cents_str = if has_fractional_cents {
if cents {
self.cardinal_from_decimal(&right)?
} else {
return Err(N2WError::NotImplemented(format!(
"str(float(...)) repr of fractional sub-units not \
implemented for {} ({})",
self.lang_name(),
right
)));
}
} else {
let right_int = right.as_bigint_and_exponent().0;
if cents {
self.cents_verbose(&right_int, currency)?
} else {
right_int.to_string()
}
};
let right_int = right.as_bigint_and_exponent().0;
Ok(format!(
"{}{} {}{} {} {}",
minus_str,
money_str,
self.pluralize(&left, &cr1)?,
separator,
cents_str,
self.pluralize(&right_int, &cr2)?
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
fn f(v: f64, p: u32) -> Result<String> {
LangMn::new().to_cardinal_float(&FloatValue::Float { value: v, precision: p }, None)
}
fn dec(s: &str, p: u32) -> Result<String> {
LangMn::new().to_cardinal_float(
&FloatValue::Decimal { value: BigDecimal::from_str(s).unwrap(), precision: p },
None,
)
}
#[test]
fn float_cardinal_corpus() {
assert_eq!(f(0.0, 1).unwrap(), "тэг");
assert_eq!(f(0.5, 1).unwrap(), "тэг, аравны тав");
assert_eq!(f(1.0, 1).unwrap(), "нэг");
assert_eq!(f(1.5, 1).unwrap(), "нэг, аравны тав");
assert_eq!(f(2.25, 2).unwrap(), "хоёр, зууны хорин тав");
assert_eq!(f(3.14, 2).unwrap(), "гурав, зууны арван дөрөв");
assert_eq!(f(0.01, 2).unwrap(), "тэг, зууны нэг");
assert_eq!(f(0.1, 1).unwrap(), "тэг, аравны нэг");
assert_eq!(f(0.99, 2).unwrap(), "тэг, зууны ерэн ес");
assert_eq!(f(1.01, 2).unwrap(), "нэг, зууны нэг");
assert_eq!(f(12.34, 2).unwrap(), "арван хоёр, зууны гучин дөрөв");
assert_eq!(f(99.99, 2).unwrap(), "ерэн ес, зууны ерэн ес");
assert_eq!(f(100.5, 1).unwrap(), "зуу, аравны тав");
assert_eq!(
f(1234.56, 2).unwrap(),
"нэг мянга хоёр зуун гучин дөрөв, зууны тавин зургаа"
);
assert_eq!(f(-0.5, 1).unwrap(), "хасах тэг, аравны тав");
assert_eq!(f(-1.5, 1).unwrap(), "хасах нэг, аравны тав");
assert_eq!(f(-12.34, 2).unwrap(), "хасах арван хоёр, зууны гучин дөрөв");
assert_eq!(f(1.005, 3).unwrap(), "нэг, мянганы тав");
assert_eq!(
f(2.675, 3).unwrap(),
"хоёр, мянганы зургаан зуун далан тав"
);
}
#[test]
fn decimal_cardinal_corpus() {
assert_eq!(dec("0.01", 2).unwrap(), "тэг, зууны нэг");
assert_eq!(dec("1.10", 2).unwrap(), "нэг, зууны арав");
assert_eq!(
dec("12.345", 3).unwrap(),
"арван хоёр, мянганы гурван зуун дөчин тав"
);
assert_eq!(
dec("98746251323029.99", 2).unwrap(),
"ерэн найман их наяд долоон зуун дөчин зургаан тэрбум хоёр зуун \
тавин нэг сая гурван зуун хорин гурван мянга хорин ес, зууны ерэн ес"
);
assert_eq!(dec("0.001", 3).unwrap(), "тэг, мянганы нэг");
}
#[test]
fn edge_cases() {
assert_eq!(dec("1.00000000", 8).unwrap(), "нэг");
assert_eq!(dec("1.000000", 6).unwrap(), "нэг");
assert_eq!(dec("12.000", 3).unwrap(), "арван хоёр");
assert!(matches!(
dec("1.10000000", 8),
Err(N2WError::NotImplemented(m)) if m.is_empty()
));
assert!(matches!(
f(1.1234567, 7),
Err(N2WError::NotImplemented(m)) if m.is_empty()
));
assert_eq!(f(-0.0, 1).unwrap(), "тэг");
assert_eq!(
LangMn::new()
.cardinal_from_decimal(&BigDecimal::from_str("67.5").unwrap())
.unwrap(),
"жаран долоо, аравны тав"
);
}
fn fv(v: f64, p: u32) -> FloatValue {
FloatValue::Float { value: v, precision: p }
}
fn dv(s: &str, p: u32) -> FloatValue {
FloatValue::Decimal { value: BigDecimal::from_str(s).unwrap(), precision: p }
}
#[test]
fn cardinal_entry_corpus() {
let l = LangMn::new();
assert!(matches!(
l.cardinal_float_entry(&fv(1e16, 16), None),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '1e+16'"
));
assert!(matches!(
l.cardinal_float_entry(&fv(1e20, 20), None),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '1e+20'"
));
assert!(matches!(
l.cardinal_float_entry(&dv("1E+2", 2), None),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '1E+2'"
));
assert!(matches!(
l.cardinal_float_entry(&dv("1E+20", 20), None),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '1E+20'"
));
assert_eq!(l.cardinal_float_entry(&fv(5.0, 1), None).unwrap(), "тав");
assert_eq!(l.cardinal_float_entry(&dv("5.00", 2), None).unwrap(), "тав");
assert_eq!(l.cardinal_float_entry(&dv("100", 0), None).unwrap(), "зуу");
assert_eq!(l.cardinal_float_entry(&fv(-0.0, 1), None).unwrap(), "тэг");
assert_eq!(
l.cardinal_float_entry(&dv("12345.000", 3), None).unwrap(),
"арван хоёр мянга гурван зуун дөчин тав"
);
}
#[test]
fn ordinal_entry_corpus() {
let l = LangMn::new();
assert_eq!(l.ordinal_float_entry(&fv(2.5, 1)).unwrap(), "хоёр дугаар");
assert_eq!(l.ordinal_float_entry(&fv(-1.5, 1)).unwrap(), "хасах нэг дүгээр");
assert_eq!(l.ordinal_float_entry(&fv(3.25, 2)).unwrap(), "гурав дугаар");
assert_eq!(l.ordinal_float_entry(&fv(100.0, 1)).unwrap(), "зуу дугаар");
assert_eq!(l.ordinal_float_entry(&fv(-1000.0, 1)).unwrap(), "хасах нэг мянга дугаар");
assert_eq!(l.ordinal_float_entry(&fv(1e16, 16)).unwrap(), "арван тунамал дугаар");
assert_eq!(
l.ordinal_float_entry(&dv("1E+20", 20)).unwrap(),
"зуун их ингүүмэл дугаар"
);
assert_eq!(l.ordinal_float_entry(&dv("5.00", 2)).unwrap(), "тав дугаар");
assert_eq!(l.ordinal_float_entry(&fv(0.0, 1)).unwrap(), "тэг дугаар");
assert_eq!(l.ordinal_float_entry(&fv(-0.0, 1)).unwrap(), "тэг дугаар");
assert_eq!(l.ordinal_float_entry(&fv(0.5, 1)).unwrap(), "тэг дугаар");
assert_eq!(l.ordinal_float_entry(&dv("0", 0)).unwrap(), "тэг дугаар");
}
#[test]
fn ordinal_num_entry_corpus() {
let l = LangMn::new();
assert_eq!(
l.ordinal_num_float_entry(&fv(-1000000.0, 1), "-1000000.0").unwrap(),
"-1000000 дугаар"
);
assert_eq!(l.ordinal_num_float_entry(&fv(-1.5, 1), "-1.5").unwrap(), "-1 дүгээр");
assert_eq!(
l.ordinal_num_float_entry(&fv(1e16, 16), "1e+16").unwrap(),
"10000000000000000 дугаар"
);
assert_eq!(
l.ordinal_num_float_entry(&dv("1E+20", 20), "1E+20").unwrap(),
"100000000000000000000 дугаар"
);
assert_eq!(l.ordinal_num_float_entry(&dv("5.00", 2), "5.00").unwrap(), "5 дугаар");
assert_eq!(l.ordinal_num_float_entry(&fv(0.5, 1), "0.5").unwrap(), "0 дугаар");
assert_eq!(l.ordinal_num_float_entry(&fv(-0.0, 1), "-0.0").unwrap(), "0 дугаар");
}
#[test]
fn year_entry_corpus() {
let l = LangMn::new();
assert_eq!(l.year_float_entry(&fv(-1000.0, 1)).unwrap(), "МЭӨ нэг мянган он");
assert_eq!(l.year_float_entry(&fv(-1000000.0, 1)).unwrap(), "МЭӨ нэг сая он");
assert_eq!(l.year_float_entry(&fv(-0.0, 1)).unwrap(), "тэг он");
assert_eq!(l.year_float_entry(&fv(0.5, 1)).unwrap(), "тэг, аравны таван он");
assert_eq!(l.year_float_entry(&fv(-1.5, 1)).unwrap(), "МЭӨ нэг, аравны таван он");
assert_eq!(l.year_float_entry(&fv(3.25, 2)).unwrap(), "гурван, зууны хорин таван он");
assert_eq!(l.year_float_entry(&fv(99.0, 1)).unwrap(), "ерэн есөн он");
assert_eq!(l.year_float_entry(&fv(1234.0, 1)).unwrap(), "нэг мянга хоёр зуун гучин дөрвөн он");
assert_eq!(l.year_float_entry(&dv("5.00", 2)).unwrap(), "таван он");
assert_eq!(l.year_float_entry(&dv("100", 0)).unwrap(), "зуун он");
assert_eq!(l.year_float_entry(&dv("1.50", 2)).unwrap(), "нэг, зууны тавин он");
assert!(matches!(
l.year_float_entry(&fv(1e16, 16)),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '1e+16'"
));
assert!(matches!(
l.year_float_entry(&dv("1E+2", 2)),
Err(N2WError::Value(_))
));
}
#[test]
fn all_suffixed_kwarg() {
let l = LangMn::new();
let t = Kwargs(vec![("all_suffixed".into(), KwVal::Bool(true))]);
let f_ = Kwargs(vec![("all_suffixed".into(), KwVal::Bool(false))]);
let none = Kwargs(vec![("all_suffixed".into(), KwVal::None)]);
let bad = Kwargs(vec![("gender".into(), KwVal::Str("f".into()))]);
assert_eq!(l.to_cardinal_kw(&BigInt::from(3), &t).unwrap(), "гурван");
assert_eq!(l.to_cardinal_kw(&BigInt::from(100), &t).unwrap(), "зуун");
assert_eq!(
l.to_cardinal_kw(&BigInt::from(1234), &t).unwrap(),
"нэг мянга хоёр зуун гучин дөрвөн"
);
assert_eq!(l.to_cardinal_kw(&BigInt::from(-5), &t).unwrap(), "хасах тав");
assert_eq!(l.to_cardinal_kw(&BigInt::from(1234), &f_).unwrap(), "нэг мянга хоёр зуун гучин дөрөв");
assert_eq!(l.to_cardinal_kw(&BigInt::from(3), &none).unwrap(), "гурав");
assert!(matches!(
l.to_cardinal_kw(&BigInt::from(3), &bad),
Err(N2WError::Fallback(_))
));
assert_eq!(
l.to_cardinal_float_kw(&fv(1.5, 1), None, &t).unwrap(),
"нэг, аравны таван"
);
}
#[test]
fn str_to_number_infinity() {
let l = LangMn::new();
assert!(matches!(
l.str_to_number("Infinity"),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: 'Infinity'"
));
assert!(matches!(
l.str_to_number("-Infinity"),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '-Infinity'"
));
assert!(matches!(l.str_to_number("1e3"), Ok(ParsedNumber::Dec(_))));
assert!(matches!(l.str_to_number("NaN"), Ok(ParsedNumber::NaN)));
assert!(matches!(
l.cardinal_float_entry(&dv("1E+3", 3), None),
Err(N2WError::Value(m)) if m == "invalid literal for int() with base 10: '1E+3'"
));
}
}