use crate::base::{set_low_numwords, set_mid_numwords, Cards, Lang, N2WError, Result};
use crate::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 inflect(value: &BigInt, text: &str) -> String {
let parts: Vec<&str> = text.split('/').collect();
if value.is_one() {
parts[0].to_string()
} else {
parts.concat()
}
}
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 build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"NOK",
CurrencyForms::new(&["krone", "kroner"], &["\u{f8}re", "\u{f8}re"]),
);
m.insert("EUR", CurrencyForms::new(&["euro", "euro"], &["cent", "cent"]));
m.insert(
"USD",
CurrencyForms::new(&["dollar", "dollar"], &["cent", "cent"]),
);
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", "\u{ed}slenskar"),
("UZS", "Uzbekistan"),
("SAR", "Saudi"),
("JPY", "Japanese"),
("KRW", "Korean"),
]
.into_iter()
.collect()
}
pub struct LangNb {
cards: Cards,
maxval: BigInt,
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
ords_pl: Vec<(&'static str, &'static str)>,
ords_sg: Vec<(&'static str, &'static str)>,
exclude_title: Vec<String>,
}
impl Default for LangNb {
fn default() -> Self {
Self::new()
}
}
impl LangNb {
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: 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, "tusen"),
(100, "hundre"),
(90, "nitti"),
(80, "\u{e5}tti"),
(70, "sytti"),
(60, "seksti"),
(50, "femti"),
(40, "f\u{f8}rti"),
(30, "tretti"),
],
);
set_low_numwords(
&mut cards,
&[
"tjue",
"nitten",
"atten",
"sytten",
"seksten",
"femten",
"fjorten",
"tretten",
"tolv",
"elleve",
"ti",
"ni",
"\u{e5}tte",
"syv",
"seks",
"fem",
"fire",
"tre",
"to",
"en",
"null",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let ords_pl: Vec<(&'static str, &'static str)> = vec![
("to", "andre"),
("tre", "tredje"),
("fire", "fjerde"),
("fem", "femte"),
("seks", "sjette"),
("syv", "syvende"),
("\u{e5}tte", "\u{e5}ttende"),
("ni", "niende"),
("ti", "tiende"),
("elleve", "ellevte"),
("tolv", "tolvte"),
("fjorten", "fjortende"),
("femten", "femtende"),
("seksten", "sekstende"),
("sytten", "syttende"),
("atten", "attende"),
("nitten", "nittende"),
("tjue", "tjuende"),
("hundre", "hundrede"),
("tusen", "tusende"),
("million", "millionte"),
];
let ords_sg: Vec<(&'static str, &'static str)> = vec![("en", "f\u{f8}rste")];
LangNb {
cards,
maxval,
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
ords_pl,
ords_sg,
exclude_title: vec!["og".into(), "komma".into(), "minus".into()],
}
}
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)
))),
}
}
#[allow(clippy::too_many_arguments)]
fn to_splitnum(
&self,
val: &BigInt,
hightxt: &str,
lowtxt: &str,
jointxt: &str,
divisor: &BigInt,
longval: bool,
cents: bool,
) -> Result<String> {
let mut out: Vec<String> = Vec::new();
let (high, low) = val.div_mod_floor(divisor);
if !high.is_zero() {
let hightxt = self.title(&inflect(&high, hightxt));
out.push(self.to_cardinal(&high)?);
if !low.is_zero() {
if longval {
if !hightxt.is_empty() {
out.push(hightxt);
}
if !jointxt.is_empty() {
out.push(self.title(jointxt));
}
}
} else if !hightxt.is_empty() {
out.push(hightxt);
}
}
if !low.is_zero() {
if cents {
out.push(self.to_cardinal(&low)?);
} else {
let s = low.to_string();
out.push(if s.len() < 2 {
format!("{:0>2}", s)
} else {
s
});
}
if !lowtxt.is_empty() && longval {
out.push(self.title(&inflect(&low, lowtxt)));
}
}
Ok(out.join(" "))
}
}
impl Lang for LangNb {
fn default_currency(&self) -> &str {
"NOK"
}
fn default_separator(&self) -> &str {
" og"
}
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) {
let (ltext, lnum) = l;
let (rtext, rnum) = r;
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let million = BigInt::from(10u8).pow(6);
let milliard = BigInt::from(10u8).pow(9);
if lnum.is_one() && rnum < &hundred {
(rtext.to_string(), rnum.clone())
} else if &hundred > lnum && lnum > rnum {
(format!("{}{}", ltext, rtext), lnum + rnum)
} else if lnum >= &hundred && &hundred > rnum {
(format!("{} og {}", ltext, rtext), lnum + rnum)
} else if rnum > lnum {
if lnum.is_one() && (rnum == &hundred || rnum == &thousand) {
(format!("ett {}", rtext), lnum * rnum)
} else if lnum == &hundred && rnum == &thousand {
("hundre tusen".to_string(), BigInt::from(100000))
} else if rnum == &million {
if lnum.is_one() {
("en million".to_string(), lnum * rnum)
} else {
(format!("{} millioner", ltext), lnum * rnum)
}
} else if rnum == &milliard {
if lnum.is_one() {
("en milliard".to_string(), lnum * rnum)
} else {
(format!("{} milliarder", ltext), lnum * rnum)
}
} else {
(format!("{} {}", ltext, rtext), lnum * rnum)
}
} else {
(format!("{} {}", ltext, rtext), lnum + rnum)
}
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let mut outword = self.to_cardinal(value)?;
for (key, val) in self.ords_pl.iter() {
if let Some(stem) = outword.strip_suffix(*key) {
outword = format!("{}{}", stem, val);
break;
}
}
for (key, val) in self.ords_sg.iter() {
if let Some(stem) = outword.strip_suffix(*key) {
outword = format!("{}{}", stem, val);
break;
}
}
Ok(outword)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!("{}.", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let hundred = BigInt::from(100);
let ten = BigInt::from(10);
if value.div_floor(&hundred).mod_floor(&ten).is_zero() {
return self.to_cardinal(value);
}
self.to_splitnum(value, "hundre", "", "og", &hundred, true, true)
}
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> {
self.verify_ordinal_float(value)?;
Ok(format!("{}.", repr_str))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value: f, .. } => {
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("hundre".to_string());
if !low.is_zero() {
out.push("og".to_string());
}
}
if !low.is_zero() {
out.push(self.to_cardinal(&low)?);
}
Ok(out.join(" "))
}
FloatValue::Decimal { value: d, .. } => {
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("hundre".to_string());
if !low.is_zero() {
out.push("og".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 lang_name(&self) -> &str {
"Num2Word_NO"
}
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 = crate::currency::default_to_currency(
self, val, currency, cents, separator, adjective,
)?;
Ok(result.replace(" og null \u{f8}re", ""))
}
}