use crate::base::{
default_to_cardinal, set_low_numwords, set_mid_numwords, Cards, Lang, N2WError, Result,
};
use crate::currency::{default_to_currency, CurrencyForms, CurrencyValue};
use crate::floatpath::{float2tuple, FloatValue};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, Zero};
use std::collections::HashMap;
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 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),
}
}
fn py_f64_floordiv(x: f64, y: f64) -> f64 {
let m = x % y; let mut div = (x - m) / y;
if m != 0.0 && ((y < 0.0) != (m < 0.0)) {
div -= 1.0;
}
if div != 0.0 {
let floordiv = div.floor();
if div - floordiv > 0.5 {
floordiv + 1.0
} else {
floordiv
}
} else {
0.0f64.copysign(x / y)
}
}
fn py_f64_mod(x: f64, y: f64) -> f64 {
let mut m = x % y;
if m != 0.0 {
if (y < 0.0) != (m < 0.0) {
m += y;
}
} else {
m = 0.0f64.copysign(y);
}
m
}
fn ordinal_num_suffix(m: &BigInt) -> &'static str {
let vaerdte = [0i64, 1, 5, 6, 11, 12];
if (*m >= BigInt::from(30) && *m <= BigInt::from(39))
|| vaerdte.iter().any(|v| *m == BigInt::from(*v))
{
"te"
} else if *m == BigInt::from(2) {
"en"
} else {
"ende"
}
}
fn da_merge(l: (&str, &BigInt), r: (&str, &BigInt), ordflag: bool) -> (String, BigInt) {
let (ctext_in, cnum) = l;
let (ntext_in, nnum) = r;
let mut ctext = ctext_in.to_string();
let mut ntext = ntext_in.to_string();
let mut next_text = ntext_in.to_string();
let next_num = nnum.clone();
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let hundred_thousand = BigInt::from(100_000);
let million = BigInt::from(1_000_000);
if nnum == &hundred || nnum == &thousand {
if cnum == &ten && nnum == &thousand {
return ("ti tusind".to_string(), BigInt::from(10_000));
} else if cnum == &hundred && nnum == &thousand {
return ("ethundrede tusind".to_string(), BigInt::from(100_000));
}
next_text = format!("et{}", ntext_in);
}
if cnum.is_one() {
if nnum < &million || ordflag {
return (next_text, next_num);
}
ctext = "en".to_string();
}
let val: BigInt;
if nnum > cnum {
if nnum >= &million {
ctext.push(' ');
}
val = cnum * nnum;
} else {
if cnum >= &hundred && cnum < &thousand {
ctext.push_str(" og ");
} else if cnum >= &thousand && cnum <= &hundred_thousand {
ctext.push_str("e og ");
}
if nnum < &ten && ten < *cnum && cnum < &hundred {
if nnum.is_one() {
ntext = "en".to_string();
}
let old_ctext = ctext;
ctext = format!("{}og", ntext);
ntext = old_ctext;
} else if cnum >= &million {
ctext.push(' ');
}
val = cnum + nnum;
}
(format!("{}{}", ctext, ntext), val)
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m = HashMap::new();
m.insert(
"DKK",
CurrencyForms::new(&["krone", "kroner"], &["øre", "øre"]),
);
m.insert("EUR", CurrencyForms::new(&["euro", "euro"], &["cent", "cent"]));
m.insert(
"USD",
CurrencyForms::new(&["dollar", "dollars"], &["cent", "cent"]),
);
m.insert(
"GBP",
CurrencyForms::new(&["pund", "pund"], &["penny", "pence"]),
);
m.insert(
"SEK",
CurrencyForms::new(&["krone", "kroner"], &["øre", "øre"]),
);
m.insert(
"NOK",
CurrencyForms::new(&["krone", "kroner"], &["øre", "øre"]),
);
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
let mut m = HashMap::new();
m.insert("AUD", "Australian");
m.insert("BYN", "Belarusian");
m.insert("CAD", "Canadian");
m.insert("EEK", "Estonian");
m.insert("USD", "US");
m.insert("RUB", "Russian");
m.insert("NOK", "Norwegian");
m.insert("MXN", "Mexican");
m.insert("RON", "Romanian");
m.insert("INR", "Indian");
m.insert("HUF", "Hungarian");
m.insert("ISK", "íslenskar");
m.insert("UZS", "Uzbekistan");
m.insert("SAR", "Saudi");
m.insert("JPY", "Japanese");
m.insert("KRW", "Korean");
m
}
pub struct LangDa {
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 LangDa {
fn default() -> Self {
Self::new()
}
}
impl LangDa {
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));
let mut cards = Cards::new();
let cap = 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!("{}illiarder", word), );
cards.insert(
BigInt::from(10u8).pow((n - 3) as u32),
format!("{}illioner", word), );
n -= 6;
}
set_mid_numwords(
&mut cards,
&[
(1000, "tusind"),
(100, "hundrede"),
(90, "halvfems"),
(80, "firs"),
(70, "halvfjerds"),
(60, "treds"),
(50, "halvtreds"),
(40, "fyrre"),
(30, "tredive"),
],
);
set_low_numwords(
&mut cards,
&[
"tyve", "nitten", "atten", "sytten", "seksten", "femten", "fjorten", "tretten",
"tolv", "elleve", "ti", "ni", "otte", "syv", "seks", "fem", "fire", "tre", "to",
"et", "nul",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let ords: Vec<(&'static str, &'static str)> = vec![
("nul", "nul"),
("et", "første"), ("to", "anden"),
("tre", "tredje"),
("fire", "fjerde"),
("fem", "femte"),
("seks", "sjette"),
("syv", "syvende"),
("otte", "ottende"),
("ni", "niende"),
("ti", "tiende"),
("elleve", "ellevte"),
("tolv", "tolvte"),
("tretten", "trett"),
("fjorten", "fjort"),
("femten", "femt"),
("seksten", "sekst"),
("sytten", "sytt"),
("atten", "att"),
("nitten", "nitt"),
("tyve", "tyv"),
];
LangDa {
cards,
maxval,
ords,
exclude_title: vec!["og".into(), "komma".into(), "minus".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)
))),
}
}
fn to_splitnum(&self, val: &BigInt) -> Result<String> {
let (high, low) = val.div_mod_floor(&BigInt::from(100));
let mut out: Vec<String> = Vec::new();
let hightxt = "hundrede";
if !high.is_zero() {
out.push(self.to_cardinal(&high)?);
if !low.is_zero() {
out.push(hightxt.to_string());
} else {
out.push(hightxt.to_string());
}
}
if !low.is_zero() {
out.push(self.to_cardinal(&low)?);
}
Ok(out.join(" "))
}
}
struct DaOrd<'a> {
base: &'a LangDa,
}
impl Lang for DaOrd<'_> {
fn cards(&self) -> &Cards {
&self.base.cards
}
fn maxval(&self) -> &BigInt {
&self.base.maxval
}
fn negword(&self) -> &str {
"minus "
}
fn pointword(&self) -> &str {
"komma"
}
fn exclude_title(&self) -> &[String] {
&self.base.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
da_merge(l, r, true)
}
}
impl Lang for LangDa {
fn default_currency(&self) -> &str {
"DKK"
}
fn default_separator(&self) -> &str {
","
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"minus "
}
fn pointword(&self) -> &str {
"komma"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
da_merge(l, r, false)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let engine = DaOrd { base: self };
let mut outword = default_to_cardinal(&engine, value)?;
for (key, rep) in self.ords.iter() {
if outword.ends_with(key) {
let cut = outword.len() - key.len();
outword = format!("{}{}", &outword[..cut], rep);
break;
}
}
let m = value.mod_floor(&BigInt::from(100));
if (m >= BigInt::from(30) && m <= BigInt::from(39)) || m.is_zero() {
outword.push_str("te");
} else if m > BigInt::from(12) || m.is_zero() {
outword.push_str("ende");
}
Ok(outword)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let m = value.mod_floor(&BigInt::from(100));
Ok(format!("{}{}", value, ordinal_num_suffix(&m)))
}
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)?;
let m = i.mod_floor(&BigInt::from(100));
Ok(format!("{}{}", repr_str, ordinal_num_suffix(&m)))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value: f, .. } => {
if *f == 1.0 {
return Ok("en".to_string());
}
let guard = py_f64_mod(py_f64_floordiv(*f, 100.0), 10.0);
if guard == 0.0 {
return self.cardinal_float_entry(value, None);
}
let (high, low) = float2tuple(value);
let mut out: Vec<String> = Vec::new();
if !high.is_zero() {
out.push(self.to_cardinal(&high)?);
out.push("hundrede".to_string());
}
if !low.is_zero() {
out.push(self.to_cardinal(&low)?);
}
Ok(out.join(" "))
}
FloatValue::Decimal { value: d, .. } => {
if *d == BigDecimal::from(1) {
return Ok("en".to_string());
}
let pre = d.with_scale(0).as_bigint_and_exponent().0;
let q: BigInt = &pre / BigInt::from(100);
if (&q % BigInt::from(10)).is_zero() {
return self.cardinal_float_entry(value, None);
}
let low = d.clone() - BigDecimal::from(&q * BigInt::from(100));
let mut out: Vec<String> = Vec::new();
if !q.is_zero() {
out.push(self.to_cardinal(&q)?);
out.push("hundrede".to_string());
}
if !low.is_zero() {
let (_, exp) = low.as_bigint_and_exponent();
let fv = FloatValue::Decimal {
value: low.clone(),
precision: exp.unsigned_abs() as u32,
};
out.push(self.cardinal_float_entry(&fv, None)?);
}
Ok(out.join(" "))
}
}
}
fn to_year(&self, value: &BigInt) -> Result<String> {
if value.is_one() {
return Ok("en".to_string());
}
if value
.div_floor(&BigInt::from(100))
.mod_floor(&BigInt::from(10))
.is_zero()
{
return self.to_cardinal(value);
}
self.to_splitnum(value)
}
fn lang_name(&self) -> &str {
"Num2Word_DA"
}
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());
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 minus_str = if v.is_negative() { self.negword() } else { "" };
let abs_val = v.abs();
let money_str = self.to_cardinal(&abs_val)?;
let cr1 = &forms.unit;
let currency_str = if abs_val.is_one() {
cr1.first()
} else {
cr1.get(1).or_else(|| cr1.first())
}
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?;
return Ok(format!("{} {} {}", minus_str, money_str, currency_str)
.trim()
.to_string());
}
default_to_currency(self, val, currency, cents, separator, adjective)
}
}