use crate::base::{
clean, set_low_numwords, set_mid_numwords, splitnum, Cards, Lang, N2WError, Node, Result,
};
use crate::currency::CurrencyForms;
use crate::floatpath::FloatValue;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{FromPrimitive, One, Signed, Zero};
use std::collections::HashMap;
const BIG_UNITS: &[&str] = &[
"trilliard",
"trillion",
"billiard",
"billion",
"milliard",
"million",
];
fn gen_high_numwords(units: &[&str], tens: &[&str], lows: &[&str]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for t in tens {
for u in units {
out.push(format!("{}{}", u, t));
}
}
out.reverse();
const REPLACEMENTS: &[(&str, &str)] = &[
("novemn", "noven"),
("novemo", "novo"),
("octoo", "octo"),
("quintd", "quind"),
("quintn", "quin"),
("quintq", "quinq"),
("quints", "quins"),
("quintt", "quint"),
("quintv", "quinv"),
("septenn", "septen"),
("septent", "sept"),
("sexn", "sen"),
("sexs", "ses"),
("tresd", "tred"),
("tresn", "tren"),
("tress", "tres"),
("tresv", "trev"),
("unno", "uno"),
];
for (k, v) in REPLACEMENTS {
out = out.iter().map(|o| o.replace(k, v)).collect();
}
out.extend(lows.iter().map(|s| s.to_string()));
out
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"DZD",
CurrencyForms::new(&["dinar", "dinars"], &["centime", "centimes"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["centime", "centimes"]),
);
m.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cents"]),
);
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 py_float_str(f: f64) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return if f.is_sign_positive() {
"inf".to_string()
} else {
"-inf".to_string()
};
}
let a = f.abs();
if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let s = format!("{:e}", f);
if let Some((m, e)) = s.split_once('e') {
let (sign, digits) = match e.strip_prefix('-') {
Some(d) => ("-", d.to_string()),
None => ("+", e.to_string()),
};
let digits = if digits.len() < 2 {
format!("0{}", digits)
} else {
digits
};
return format!("{}e{}{}", m, sign, digits);
}
s
} else if f.fract() == 0.0 {
format!("{:.1}", f)
} else {
format!("{}", f)
}
}
fn py_num_str(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, .. } => py_float_str(*value),
FloatValue::Decimal { value, .. } => crate::strnum::python_decimal_str(value),
}
}
pub struct LangFrDz {
cards: Cards,
maxval: BigInt,
ords: Vec<(&'static str, &'static str)>,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangFrDz {
fn default() -> Self {
Self::new()
}
}
impl LangFrDz {
pub fn new() -> Self {
let lows = [
"non", "oct", "sept", "sext", "quint", "quadr", "tr", "b", "m",
];
let units = [
"", "un", "duo", "tres", "quattuor", "quint", "sex", "septen", "octo", "novem",
];
let tens = [
"dec",
"vigint",
"trigint",
"quadragint",
"quinquagint",
"sexagint",
"septuagint",
"octogint",
"nonagint",
];
let mut high = vec!["cent".to_string()];
high.extend(gen_high_numwords(&units, &tens, &lows));
debug_assert_eq!(high.len(), 100);
let mut cards = Cards::new();
let cap: i64 = 3 + 6 * high.len() as i64;
let mut n = cap;
for word in high.iter() {
if n <= 3 {
break;
}
cards.insert(BigInt::from(10u8).pow(n as u32), format!("{}illiard", word));
cards.insert(
BigInt::from(10u8).pow((n - 3) as u32),
format!("{}illion", word),
);
n -= 6;
}
set_mid_numwords(
&mut cards,
&[
(1000, "mille"),
(100, "cent"),
(80, "quatre-vingts"),
(60, "soixante"),
(50, "cinquante"),
(40, "quarante"),
(30, "trente"),
],
);
set_low_numwords(
&mut cards,
&[
"vingt", "dix-neuf", "dix-huit", "dix-sept", "seize", "quinze", "quatorze",
"treize", "douze", "onze", "dix", "neuf", "huit", "sept", "six", "cinq", "quatre",
"trois", "deux", "un", "zéro",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
LangFrDz {
cards,
maxval,
ords: vec![("cinq", "cinquième"), ("neuf", "neuvième")],
exclude_title: vec!["et".into(), "virgule".into(), "moins".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!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn verify_ordinal_float(&self, value: &FloatValue) -> Result<BigInt> {
match value.as_whole_int() {
Some(i) => {
if i.is_negative() {
Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
py_num_str(value)
)))
} else {
Ok(i)
}
}
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
py_num_str(value)
))),
}
}
}
impl Lang for LangFrDz {
fn default_currency(&self) -> &str {
"DZD"
}
fn default_separator(&self) -> &str {
" et"
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"moins "
}
fn pointword(&self) -> &str {
"virgule"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
let mut out = String::new();
let mut v = value.clone();
if v.is_negative() {
v = v.abs();
out = format!("{} ", self.negword().trim());
}
if &v >= self.maxval() {
return Err(N2WError::Overflow(format!(
"Nombre trop grand pour être converti en mots (abs({}) > {}).",
v,
self.maxval()
)));
}
let tree = splitnum(self, &v).ok_or_else(|| {
N2WError::Overflow(format!(
"Nombre trop grand pour être converti en mots (abs({}) > {}).",
v,
self.maxval()
))
})?;
let words = match clean(self, tree) {
Node::Leaf(t, _) => t,
Node::List(_) => return Err(N2WError::Type("clean did not reduce".into())),
};
Ok(self.title(&format!("{}{}", out, words)))
}
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 ten = BigInt::from(10);
let eighty = BigInt::from(80);
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());
}
} else {
let drop_s = (cnum - &eighty).mod_floor(&hundred).is_zero()
|| (cnum.mod_floor(&hundred).is_zero() && cnum < &thousand);
if drop_s && nnum < &million && ctext.chars().last() == Some('s') {
ctext.pop(); }
if cnum < &thousand
&& nnum != &thousand
&& ntext.chars().last() != Some('s')
&& nnum.mod_floor(&hundred).is_zero()
{
ntext.push('s');
}
}
if nnum < cnum && cnum < &hundred {
if nnum.mod_floor(&ten).is_one() && cnum != &eighty {
return (format!("{}-et-{}", ctext, ntext), cnum + nnum);
}
return (format!("{}-{}", ctext, ntext), cnum + nnum);
}
if nnum > cnum {
return (format!("{} {}", ctext, ntext), cnum * nnum);
}
(format!("{} {}", ctext, ntext), cnum + nnum)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
if value.is_one() {
return Ok("premier".to_string());
}
let mut word = self.to_cardinal(value)?;
for unit in BIG_UNITS {
if word == format!("un {}", unit) || word.ends_with(&format!(" {}s", unit)) {
let plural = word.ends_with('s');
let mut stripped = word.trim_end_matches('s').to_string();
if let Some(rest) = stripped.strip_prefix("un ") {
stripped = rest.to_string();
}
return Ok(format!("{}ième{}", stripped, if plural { "s" } else { "" }));
}
if word == *unit {
return Ok(format!("{}ième", unit));
}
}
let mut matched = false;
for (src, repl) in &self.ords {
if word.ends_with(src) {
word = format!("{}{}", &word[..word.len() - src.len()], repl);
matched = true;
break;
}
}
if !matched {
if word.chars().last() == Some('e') {
word.pop(); }
if word.ends_with("ts") || word.ends_with("ents") {
word.pop();
}
word.push_str("ième");
}
Ok(word)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let mut out = value.to_string();
out.push_str(if value.is_one() { "er" } else { "me" });
Ok(out)
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.to_cardinal(value)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let i = self.verify_ordinal_float(value)?;
self.to_ordinal(&i)
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
let i = self.verify_ordinal_float(value)?;
Ok(format!(
"{}{}",
repr_str,
if i.is_one() { "er" } else { "me" }
))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
let i = 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(),
));
}
BigInt::from_f64(f.trunc())
.ok_or_else(|| N2WError::Value("cannot convert float NaN to integer".into()))?
}
FloatValue::Decimal { value: d, .. } => d.with_scale(0).as_bigint_and_exponent().0,
};
self.to_cardinal(&i)
}
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() { "demi" } else { "demis" }).to_string()
} else if abs_d == BigInt::from(3) {
"tiers".to_string()
} else if abs_d == BigInt::from(4) {
(if abs_n.is_one() { "quart" } else { "quarts" }).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 sign = if is_negative {
format!("{} ", self.negword().trim())
} else {
String::new()
};
Ok(format!(
"{}{} {}",
sign,
self.to_cardinal(&abs_n)?,
den_word
))
}
fn lang_name(&self) -> &str {
"Num2Word_FR_DZ"
}
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.abs() <= BigInt::one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
}