use crate::base::{
default_to_cardinal, set_low_numwords, set_mid_numwords, Cards, Kwargs, Lang, N2WError,
Result,
};
use crate::currency::{default_to_currency, CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use num_bigint::BigInt;
use num_traits::{FromPrimitive, One, Signed, Zero};
use std::collections::HashMap;
const GENDER_STEM: &str = "o";
fn pow10(n: u32) -> BigInt {
BigInt::from(10u8).pow(n)
}
fn drop_last_chars(s: &str, n: usize) -> String {
let count = s.chars().count();
s.chars().take(count.saturating_sub(n)).collect()
}
fn py_log1000(v: &BigInt) -> u32 {
if *v >= BigInt::from(999_999_999_999_995_072u64) && *v < pow10(18) {
return 6;
}
if *v >= BigInt::from(999_999_999_999_996u64) && *v < pow10(15) {
return 5;
}
let thousand = BigInt::from(1000);
let mut k = 0u32;
let mut p = BigInt::one();
loop {
let next = &p * &thousand;
if next <= *v {
p = next;
k += 1;
} else {
return k;
}
}
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const CENTIMOS: [&str; 2] = ["céntimo", "céntimos"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("CRC", CurrencyForms::new(&["colón", "colónes"], &CENTIMOS));
m.insert("EUR", CurrencyForms::new(&["euro", "euros"], &CENTIMOS));
m.insert(
"USD",
CurrencyForms::new(&["dólar", "dólares"], &["centavo", "centavos"]),
);
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
[
("AUD", "Australian"),
("BYN", "Belarusian"),
("CAD", "Canadian"),
("EEK", "Estonian"),
("USD", "US"),
("RUB", "Russian"),
("NOK", "Norwegian"),
("MXN", "Mexican"),
("RON", "Romanian"),
("INR", "Indian"),
("HUF", "Hungarian"),
("ISK", "íslenskar"),
("UZS", "Uzbekistan"),
("SAR", "Saudi"),
("JPY", "Japanese"),
("KRW", "Korean"),
]
.into_iter()
.collect()
}
fn es_uno_fixups(s: &str) -> String {
s.replace("veintiuno euro", "veintiún euro")
.replace("veintiuno céntimo", "veintiún céntimo")
.replace("uno euro", "un euro")
.replace("uno céntimo", "un céntimo")
.replace("uno centavo", "un centavo")
.replace("uno dólar", "un dólar")
}
fn float_ordinal_reject(value: &FloatValue) -> N2WError {
if let FloatValue::Float { value: f, .. } = value {
if f.is_nan() {
return N2WError::Value("cannot convert float NaN to integer".into());
}
if f.is_infinite() {
return N2WError::Overflow("cannot convert float infinity to integer".into());
}
}
let shown = match value {
FloatValue::Float { value: f, .. } => format!("{}", f),
FloatValue::Decimal { value: d, .. } => crate::strnum::python_decimal_str(d),
};
N2WError::Type(format!(
"El float {} no puede ser tratado como un ordinal.",
shown
))
}
fn float_trunc_int(value: &FloatValue) -> Result<BigInt> {
match value {
FloatValue::Float { value: f, .. } => {
if f.is_nan() {
return Err(N2WError::Value(
"cannot convert float NaN to integer".into(),
));
}
if f.is_infinite() {
return Err(N2WError::Overflow(
"cannot convert float infinity to integer".into(),
));
}
Ok(BigInt::from_f64(f.trunc()).expect("finite whole f64 converts exactly"))
}
FloatValue::Decimal { value: d, .. } => {
Ok(d.with_scale(0).as_bigint_and_exponent().0)
}
}
}
pub struct LangEsCr {
cards: Cards,
maxval: BigInt,
ords: HashMap<BigInt, &'static str>,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangEsCr {
fn default() -> Self {
Self::new()
}
}
impl LangEsCr {
pub fn new() -> Self {
let lows = [
"non", "oct", "sept", "sext", "quint", "cuatr", "tr", "b", "m",
];
let mut cards = Cards::new();
let cap: u32 = 3 + 6 * lows.len() as u32;
let mut n = cap;
for word in lows.iter() {
if n <= 3 {
break;
}
cards.insert(pow10(n - 3), format!("{}illón", word));
n -= 6;
}
set_mid_numwords(
&mut cards,
&[
(1000, "mil"),
(100, "cien"),
(90, "noventa"),
(80, "ochenta"),
(70, "setenta"),
(60, "sesenta"),
(50, "cincuenta"),
(40, "cuarenta"),
(30, "treinta"),
],
);
set_low_numwords(
&mut cards,
&[
"veintinueve",
"veintiocho",
"veintisiete",
"veintiséis",
"veinticinco",
"veinticuatro",
"veintitrés",
"veintidós",
"veintiuno",
"veinte",
"diecinueve",
"dieciocho",
"diecisiete",
"dieciséis",
"quince",
"catorce",
"trece",
"doce",
"once",
"diez",
"nueve",
"ocho",
"siete",
"seis",
"cinco",
"cuatro",
"tres",
"dos",
"uno",
"cero",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let mut ords: HashMap<BigInt, &'static str> = HashMap::new();
for (k, v) in [
(1i64, "primer"),
(2, "segund"),
(3, "tercer"),
(4, "cuart"),
(5, "quint"),
(6, "sext"),
(7, "séptim"),
(8, "octav"),
(9, "noven"),
(10, "décim"),
(20, "vigésim"),
(30, "trigésim"),
(40, "cuadragésim"),
(50, "quincuagésim"),
(60, "sexagésim"),
(70, "septuagésim"),
(80, "octogésim"),
(90, "nonagésim"),
(100, "centésim"),
(200, "ducentésim"),
(300, "tricentésim"),
(400, "cuadrigentésim"),
(500, "quingentésim"),
(600, "sexcentésim"),
(700, "septigentésim"),
(800, "octigentésim"),
(900, "noningentésim"),
] {
ords.insert(BigInt::from(k), v);
}
ords.insert(pow10(3), "milésim");
ords.insert(pow10(6), "millonésim");
ords.insert(pow10(9), "billonésim");
ords.insert(pow10(12), "trillonésim");
ords.insert(pow10(15), "cuadrillonésim");
LangEsCr {
cards,
maxval,
ords,
exclude_title: vec!["y".into(), "menos".into(), "punto".into()],
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"El número negativo {} no puede ser tratado como un ordinal.",
value
)));
}
Ok(())
}
fn ord_word(&self, key: &BigInt) -> Result<&'static str> {
self.ords
.get(key)
.copied()
.ok_or_else(|| N2WError::Key(format!("{}", key)))
}
fn to_ordinal_gender(&self, value: &BigInt, gender: &str) -> Result<String> {
let mut gender_stem = if gender == "f" { "a" } else { "o" };
self.verify_ordinal(value)?;
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let text: String = if value.is_zero() {
String::new()
} else if *value <= ten {
format!("{}{}", self.ord_word(value)?, gender_stem)
} else if *value <= BigInt::from(29) {
gender_stem = "o";
let dec = (value / &ten) * &ten;
format!(
"{}{}{}",
self.ord_word(&dec)?.replace('é', "e"),
gender_stem,
self.to_ordinal_gender(&(value % &ten), gender)?
)
} else if *value <= hundred {
let dec = (value / &ten) * &ten;
format!(
"{}{} {}",
self.ord_word(&dec)?,
gender_stem,
self.to_ordinal_gender(&(value - &dec), gender)?
)
} else if *value <= thousand {
let cen = (value / &hundred) * &hundred;
format!(
"{}{} {}",
self.ord_word(&cen)?,
gender_stem,
self.to_ordinal_gender(&(value - &cen), gender)?
)
} else if *value < pow10(18) {
let dec = pow10(3 * py_log1000(value));
let high_part = value / &dec;
let low_part = value % &dec;
let cardinal = if !high_part.is_one() {
self.to_cardinal(&high_part)?
} else {
String::new()
};
let ordw = self.ord_word(&dec)?;
if high_part.is_zero() {
return Err(N2WError::Value(format!(
"RecursionError: maximum recursion depth exceeded (to_ordinal({}))",
value
)));
}
format!(
"{}{}{} {}",
cardinal,
ordw,
gender_stem,
self.to_ordinal_gender(&low_part, gender)?
)
} else {
self.to_cardinal(value)?
};
Ok(text.trim().replace("oo", "o"))
}
fn es_to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: &str,
adjective: bool,
) -> Result<String> {
if let CurrencyValue::Int(v) = val {
let forms = match self.currency_forms.get(currency) {
Some(f) => f,
None => {
return default_to_currency(self, val, currency, cents, separator, adjective)
}
};
let cr1 = &forms.unit;
let minus_str = if v.is_negative() { self.negword() } else { "" };
let abs_val = v.abs();
let cardinal = self.to_cardinal(&abs_val)?;
let (money_str, currency_str) = if abs_val.is_one() {
("un".to_string(), cr1.first())
} else {
(cardinal, cr1.get(1).or_else(|| cr1.first()))
};
let currency_str =
currency_str.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?;
return Ok(format!("{} {} {}", minus_str, money_str, currency_str)
.trim()
.to_string());
}
let result = default_to_currency(self, val, currency, cents, separator, adjective)?;
Ok(es_uno_fixups(&result))
}
}
impl Lang for LangEsCr {
fn str_to_number(&self, s: &str) -> crate::base::Result<crate::strnum::ParsedNumber> {
crate::lang_es::es_str_to_number(s)
}
fn default_currency(&self) -> &str {
"CRC"
}
fn default_separator(&self) -> &str {
" y"
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"menos "
}
fn pointword(&self) -> &str {
"punto"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
let (ctext0, cnum) = l;
let (ntext0, nnum) = r;
let mut ctext = ctext0.to_string();
let mut ntext = ntext0.to_string();
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let million = BigInt::from(1_000_000);
if cnum.is_one() {
if *nnum < million {
return (ntext, nnum.clone());
}
ctext = "un".to_string();
} else if *cnum == hundred && !(nnum % &thousand).is_zero() {
ctext.push('t');
ctext.push_str(GENDER_STEM);
}
if nnum < cnum {
if *cnum < hundred {
return (format!("{} y {}", ctext, ntext), cnum + nnum);
}
return (format!("{} {}", ctext, ntext), cnum + nnum);
} else if (nnum % &million).is_zero() && *cnum > BigInt::one() {
ntext = format!("{}lones", drop_last_chars(&ntext, 3));
}
if *nnum == hundred {
if *cnum == BigInt::from(5) {
ctext = "quinien".to_string();
ntext = String::new();
} else if *cnum == BigInt::from(7) {
ctext = "sete".to_string();
} else if *cnum == BigInt::from(9) {
ctext = "nove".to_string();
}
ntext.push('t');
ntext.push_str(GENDER_STEM);
ntext.push('s');
} else {
if *nnum >= thousand {
if ctext.ends_with("veintiuno") {
ctext = format!("{}ún", drop_last_chars(&ctext, 3));
} else if ctext.ends_with("uno") {
ctext = drop_last_chars(&ctext, 1);
}
}
ntext = format!(" {}", ntext);
}
(format!("{}{}", ctext, ntext), cnum * nnum)
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let v = value.abs();
if v >= self.maxval {
return Err(N2WError::Overflow(format!(
"abs({}) deber ser inferior a {}.",
v, self.maxval
)));
}
default_to_cardinal(self, value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.to_ordinal_gender(value, "m")
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!("{}º", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.to_cardinal(value)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value.as_whole_int() {
Some(i) => self.to_ordinal(&i),
None => Err(float_ordinal_reject(value)),
}
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
match value.as_whole_int() {
Some(i) if !i.is_negative() => Ok(format!("{}º", repr_str)),
Some(_) => Err(N2WError::Type(format!(
"El número negativo {} no puede ser tratado como un ordinal.",
repr_str
))),
None => Err(float_ordinal_reject(value)),
}
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
self.to_cardinal(&float_trunc_int(value)?)
}
fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["gender"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let gender = if kw.str("gender") == Some("f") { "f" } else { "m" };
self.to_ordinal_gender(value, gender)
}
fn to_ordinal_num_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["gender"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
self.verify_ordinal(value)?;
let marker = if kw.str("gender") == Some("f") { "ª" } else { "º" };
Ok(format!("{}{}", value, marker))
}
fn to_year_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["suffix", "longval"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
self.to_year(value)
}
fn to_fraction(&self, numerator: &BigInt, denominator: &BigInt) -> Result<String> {
if denominator.is_zero() {
return Err(N2WError::ZeroDivision("denominator must not be zero".into()));
}
if denominator.is_one() || numerator.is_zero() {
return self.to_cardinal(numerator);
}
let is_negative = numerator.is_negative() ^ denominator.is_negative();
let abs_n = numerator.abs();
let abs_d = denominator.abs();
let den_word = if abs_d == BigInt::from(2) {
(if abs_n.is_one() { "medio" } else { "medios" }).to_string()
} else if abs_d == BigInt::from(3) {
(if abs_n.is_one() { "tercio" } else { "tercios" }).to_string()
} else if abs_d == BigInt::from(4) {
(if abs_n.is_one() { "cuarto" } else { "cuartos" }).to_string()
} else {
let mut w = self.to_ordinal(&abs_d)?;
if !abs_n.is_one() && !w.ends_with('s') {
w.push('s');
}
w
};
let num_word = if abs_n.is_one() {
"un".to_string()
} else {
self.to_cardinal(&abs_n)?
};
let sign = if is_negative { "menos " } else { "" };
Ok(format!("{}{} {}", sign, num_word, den_word))
}
fn lang_name(&self) -> &str {
"Num2Word_ES_CR"
}
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 pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let form = if n.is_one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
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 result = self.es_to_currency(val, currency, cents, separator, adjective)?;
Ok(result.replace("uno", "un"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::floatpath::FloatValue;
use bigdecimal::BigDecimal;
use std::str::FromStr;
fn int(s: &str) -> CurrencyValue {
CurrencyValue::parse(s, true, false, false).unwrap()
}
fn flt(value: f64, precision: u32) -> FloatValue {
FloatValue::Float { value, precision }
}
fn dec(s: &str, precision: u32) -> FloatValue {
FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision,
}
}
#[test]
fn cardinal_float_corpus_rows() {
let l = LangEsCr::new();
let rows: Vec<(FloatValue, &str)> = vec![
(flt(0.5, 1), "cero punto cinco"),
(flt(1.5, 1), "uno punto cinco"),
(flt(2.25, 2), "dos punto dos cinco"),
(flt(3.14, 2), "tres punto uno cuatro"),
(flt(0.01, 2), "cero punto cero uno"),
(flt(0.1, 1), "cero punto uno"),
(flt(0.99, 2), "cero punto nueve nueve"),
(flt(1.01, 2), "uno punto cero uno"),
(flt(12.34, 2), "doce punto tres cuatro"),
(flt(99.99, 2), "noventa y nueve punto nueve nueve"),
(flt(100.5, 1), "cien punto cinco"),
(
flt(1234.56, 2),
"mil doscientos treinta y cuatro punto cinco seis",
),
(flt(-0.5, 1), "menos cero punto cinco"),
(flt(-1.5, 1), "menos uno punto cinco"),
(flt(-12.34, 2), "menos doce punto tres cuatro"),
(flt(1.005, 3), "uno punto cero cero cinco"),
(flt(2.675, 3), "dos punto seis siete cinco"),
];
for (v, want) in rows {
let got = l.to_cardinal_float(&v, None).unwrap();
assert_eq!(got, want, "to_cardinal_float({:?})", v);
}
}
#[test]
fn cardinal_dec_corpus_rows() {
let l = LangEsCr::new();
let rows: Vec<(FloatValue, &str)> = vec![
(dec("0.01", 2), "cero punto cero uno"),
(dec("1.10", 2), "uno punto uno cero"),
(dec("12.345", 3), "doce punto tres cuatro cinco"),
(
dec("98746251323029.99", 2),
"noventa y ocho billones setecientos cuarenta y seis mil \
doscientos cincuenta y un millones trescientos veintitrés mil \
veintinueve punto nueve nueve",
),
(dec("0.001", 3), "cero punto cero cero uno"),
];
for (v, want) in rows {
let got = l.to_cardinal_float(&v, None).unwrap();
assert_eq!(got, want, "to_cardinal_float({:?})", v);
}
}
fn float(s: &str) -> CurrencyValue {
CurrencyValue::parse(s, false, true, true).unwrap()
}
#[test]
fn currency_corpus_rows() {
let l = LangEsCr::new();
let rows: Vec<(&str, CurrencyValue, &str)> = vec![
("EUR", int("0"), "cero euros"),
("EUR", int("1"), "un euro"),
("EUR", int("2"), "dos euros"),
("EUR", int("100"), "cien euros"),
("EUR", float("12.34"), "doce euros y treinta y cuatro céntimos"),
("EUR", float("0.01"), "cero euros y un céntimo"),
("EUR", float("1.0"), "un euro y cero céntimos"),
(
"EUR",
float("99.99"),
"noventa y nueve euros y noventa y nueve céntimos",
),
(
"EUR",
float("1234.56"),
"mil doscientos treinta y cuatro euros y cincuenta y seis céntimos",
),
(
"EUR",
float("-12.34"),
"menos doce euros y treinta y cuatro céntimos",
),
("EUR", int("1000000"), "un millón euros"),
("EUR", float("0.5"), "cero euros y cincuenta céntimos"),
("USD", int("0"), "cero dólares"),
("USD", int("1"), "un dólar"),
("USD", int("2"), "dos dólares"),
("USD", int("100"), "cien dólares"),
(
"USD",
float("12.34"),
"doce dólares y treinta y cuatro centavos",
),
("USD", float("0.01"), "cero dólares y un centavo"),
("USD", float("1.0"), "un dólar y cero centavos"),
(
"USD",
float("99.99"),
"noventa y nueve dólares y noventa y nueve centavos",
),
(
"USD",
float("1234.56"),
"mil doscientos treinta y cuatro dólares y cincuenta y seis centavos",
),
(
"USD",
float("-12.34"),
"menos doce dólares y treinta y cuatro centavos",
),
("USD", int("1000000"), "un millón dólares"),
("USD", float("0.5"), "cero dólares y cincuenta centavos"),
];
for (cur, val, want) in rows {
let got = l.to_currency(&val, cur, true, None, false).unwrap();
assert_eq!(got, want, "to_currency({:?}, {})", val, cur);
}
}
#[test]
fn currency_codes_shadowed_away() {
let l = LangEsCr::new();
let args = || {
vec![
int("0"),
int("1"),
int("2"),
int("100"),
float("12.34"),
float("0.01"),
float("1.0"),
float("99.99"),
float("1234.56"),
float("-12.34"),
int("1000000"),
float("0.5"),
]
};
let mut n = 0;
for cur in ["GBP", "JPY", "KWD", "BHD", "INR", "CNY", "CHF"] {
for val in args() {
match l.to_currency(&val, cur, true, None, false) {
Err(N2WError::NotImplemented(m)) => assert_eq!(
m,
format!(
"Currency code \"{}\" not implemented for \"Num2Word_ES_CR\"",
cur
)
),
other => panic!("{} {:?}: want NotImplemented, got {:?}", cur, val, other),
}
n += 1;
}
}
assert_eq!(n, 84, "corpus has 84 NotImplementedError currency rows");
}
#[test]
fn cheque_corpus_rows() {
let l = LangEsCr::new();
let v = BigDecimal::from_str("1234.56").unwrap();
assert_eq!(
l.to_cheque(&v, "EUR").unwrap(),
"MIL DOSCIENTOS TREINTA Y CUATRO AND 56/100 EUROS"
);
assert_eq!(
l.to_cheque(&v, "USD").unwrap(),
"MIL DOSCIENTOS TREINTA Y CUATRO AND 56/100 DÓLARES"
);
for cur in ["GBP", "JPY", "KWD", "BHD", "INR", "CNY", "CHF"] {
assert!(matches!(
l.to_cheque(&v, cur),
Err(N2WError::NotImplemented(_))
));
}
}
#[test]
fn cheque_keeps_uno_and_pluralizes() {
let l = LangEsCr::new();
let cases = [
("1.0", "EUR", "UNO AND 00/100 EUROS"),
("21.21", "EUR", "VEINTIUNO AND 21/100 EUROS"),
("2.0", "CRC", "DOS AND 00/100 COLÓNES"),
("0.01", "CRC", "CERO AND 01/100 COLÓNES"),
("-1234.56", "EUR", "MINUS MIL DOSCIENTOS TREINTA Y CUATRO AND 56/100 EUROS"),
("13.5", "EUR", "TRECE AND 50/100 EUROS"),
];
for (v, cur, want) in cases {
let d = BigDecimal::from_str(v).unwrap();
assert_eq!(l.to_cheque(&d, cur).unwrap(), want, "to_cheque({}, {})", v, cur);
}
}
#[test]
fn negative_int_double_space_but_float_single() {
let l = LangEsCr::new();
for (val, want) in [
(int("-1"), "menos un euro"),
(int("-2"), "menos dos euros"),
(int("-21"), "menos veintiun euros"),
] {
assert_eq!(l.to_currency(&val, "EUR", true, None, false).unwrap(), want);
}
for (val, want) in [
(float("-1.0"), "menos un euro y cero céntimos"),
(float("-0.01"), "menos cero euros y un céntimo"),
] {
assert_eq!(l.to_currency(&val, "EUR", true, None, false).unwrap(), want);
}
}
#[test]
fn uno_fixups_are_substring_replaces() {
let l = LangEsCr::new();
let cases: Vec<(&str, CurrencyValue, &str)> = vec![
("EUR", int("21"), "veintiun euros"),
("EUR", int("31"), "treinta y un euros"),
("EUR", int("101"), "ciento un euros"),
("EUR", int("1000001"), "un millón un euros"),
("EUR", int("21000"), "veintiún mil euros"),
("EUR", int("100000000021"), "cien mil millones veintiun euros"),
("EUR", float("21.21"), "veintiún euros y veintiún céntimos"),
("EUR", float("21.0"), "veintiún euros y cero céntimos"),
("EUR", float("21.01"), "veintiún euros y un céntimo"),
("EUR", float("1.21"), "un euro y veintiún céntimos"),
("USD", float("21.21"), "veintiun dólares y veintiun centavos"),
("USD", float("21.01"), "veintiun dólares y un centavo"),
("USD", float("1.21"), "un dólar y veintiun centavos"),
("CRC", float("21.21"), "veintiun colónes y veintiún céntimos"),
("CRC", float("21.0"), "veintiun colónes y cero céntimos"),
("CRC", float("1.21"), "un colón y veintiún céntimos"),
];
for (cur, val, want) in cases {
let got = l.to_currency(&val, cur, true, None, false).unwrap();
assert_eq!(got, want, "to_currency({:?}, {})", val, cur);
}
}
#[test]
fn defaults_and_the_colones_typo() {
let l = LangEsCr::new();
assert_eq!(l.default_currency(), "CRC");
assert_eq!(l.default_separator(), " y");
let cur = l.default_currency();
for (val, want) in [
(int("1"), "un colón"),
(int("2"), "dos colónes"),
(int("0"), "cero colónes"),
(int("-3"), "menos tres colónes"),
] {
assert_eq!(l.to_currency(&val, cur, true, None, false).unwrap(), want);
}
assert_eq!(
l.to_currency(&float("1.0"), cur, true, None, false).unwrap(),
"un colón y cero céntimos"
);
}
#[test]
fn adjective_only_reaches_the_float_arm() {
let l = LangEsCr::new();
assert_eq!(
l.to_currency(&float("12.34"), "USD", true, None, true).unwrap(),
"doce US dólares y treinta y cuatro centavos"
);
for (val, want) in [(int("2"), "dos dólares"), (int("1"), "un dólar")] {
assert_eq!(l.to_currency(&val, "USD", true, None, true).unwrap(), want);
}
assert_eq!(
l.to_currency(&float("1.0"), "EUR", true, None, true).unwrap(),
"un euro y cero céntimos"
);
}
#[test]
fn cents_terse() {
let l = LangEsCr::new();
assert_eq!(
l.to_currency(&float("1.0"), "EUR", false, None, false).unwrap(),
"un euro y 00 céntimos"
);
assert_eq!(
l.to_currency(&float("21.21"), "EUR", false, None, false).unwrap(),
"veintiún euros y 21 céntimos"
);
assert_eq!(
l.to_currency(&float("12.34"), "USD", false, None, false).unwrap(),
"doce dólares y 34 centavos"
);
}
#[test]
fn float_without_a_dot_in_its_repr_still_prints_cents() {
let l = LangEsCr::new();
assert_eq!(
l.to_currency(&float("1e+21"), "EUR", true, None, false).unwrap(),
"mil trillones euros y cero céntimos"
);
assert_eq!(
l.to_currency(&float("1000000.5"), "EUR", true, None, false).unwrap(),
"un millón euros y cincuenta céntimos"
);
}
#[test]
fn notimplemented_outranks_overflow() {
let l = LangEsCr::new();
let huge = int(&format!("1{}", "0".repeat(60)));
assert!(matches!(
l.to_currency(&huge, "GBP", true, None, false),
Err(N2WError::NotImplemented(_))
));
assert!(matches!(
l.to_currency(&huge, "EUR", true, None, false),
Err(N2WError::Overflow(_))
));
}
}