use crate::base::{KwVal, Kwargs, Lang, N2WError, Result};
use crate::currency::CurrencyValue;
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{Signed, Zero};
const NEGWORD: &str = "މައިނަސް";
const ORDWORD: &str = "ވަނަ";
const BCWORD: &str = "ބީ.ސީ";
const CENTSWORD: &str = "ލާރި";
const HIGH_NUMWORDS: [&str; 9] = [
"ނޮނިލި",
"އޮކްޓިލި",
"ސެޕްޓިލި",
"ސެކްސްޓިލި",
"ކުއިންޓިލި",
"ކުއަޑްރި",
"ޓްރި",
"ބިލި",
"މިލި",
];
const HIGH_SUFFIX: &str = "ޔަން";
const GROUPING: [isize; 13] = [-3, -5, -6, -9, -12, -15, -18, -21, -24, -27, -30, -33, -36];
const CTX_PREC: usize = 28;
const MAXVAL_EXP: usize = 33;
pub struct LangDv {
mult_words_asc: Vec<String>,
maxval: BigInt,
}
pub fn new() -> LangDv {
LangDv::new()
}
impl Default for LangDv {
fn default() -> Self {
LangDv::new()
}
}
impl LangDv {
pub fn new() -> LangDv {
let mut cards_desc: Vec<String> = HIGH_NUMWORDS
.iter()
.map(|w| format!("{}{}", w, HIGH_SUFFIX))
.collect();
cards_desc.push("ލައްކަ".to_string()); cards_desc.push("ހާސް".to_string());
let mut mult_words_asc = cards_desc;
mult_words_asc.reverse();
LangDv {
mult_words_asc,
maxval: pow10(MAXVAL_EXP),
}
}
fn base_stem(&self, k: i64) -> Result<&'static str> {
Ok(match k {
0 => "ސުން",
1 => "އެއް",
2 => "ދެ",
3 => "ތިން",
4 => "ހަތަރު",
5 => "ފަސް",
6 => "ހަ",
7 => "ހަތް",
8 => "އައް",
9 => "ނުވަ",
10 => "ދިހަ",
100 => "ސަތޭކަ",
200 => "ދުއިސައްތަ",
_ => return Err(N2WError::Key(k.to_string())),
})
}
fn base_nominal(&self, k: i64) -> Result<&'static str> {
Ok(match k {
0 => "ސުމެއް",
1 => "އެކެއް",
2 => "ދޭއް",
3 => "ތިނެއް",
4 => "ހަތަރެއް",
5 => "ފަހެއް",
6 => "ހައެއް",
7 => "ހަތެއް",
8 => "އަށެއް",
9 => "ނުވައެއް",
10 => "ދިހައެއް",
11 => "އެގާރަ",
12 => "ބާރަ",
13 => "ތޭރަ",
14 => "ސައުދަ",
15 => "ފަނަރަ",
16 => "ސޯޅަ",
17 => "ސަތާރަ",
18 => "އަށާރަ",
19 => "ނަވާރަ",
20 => "ވިހި",
21 => "އެކާވީސް",
22 => "ބާވީސް",
23 => "ތޭވީސް",
24 => "ސައުވީސް",
25 => "ފަންސަވީސް",
26 => "ސައްބީސް",
27 => "ހަތާވީސް",
28 => "އަށާވީސް",
29 => "ނަވާވީސް",
30 => "ތިރީސް",
40 => "ސާޅީސް",
50 => "ފަންސާސް",
60 => "ފަސްދޮޅަސް",
70 => "ހަތްދިހަ",
80 => "އައްޑިހަ",
90 => "ނުވަދިހަ",
_ => return Err(N2WError::Key(k.to_string())),
})
}
fn convert_decade2nominal(&self, decade: &str) -> Result<&'static str> {
self.base_nominal(py_int(decade)? * 10)
}
fn convert_decade2stem(&self, decade: &str) -> Result<&'static str> {
if py_int(decade)? == 1 {
return self.base_stem(10);
}
self.convert_decade2nominal(decade)
}
fn convert_two2nominal(&self, digits_str: &str) -> Result<String> {
let n = py_int(digits_str)?;
if n < 30 {
return Ok(self.base_nominal(n)?.to_string());
}
let d1 = char_at(digits_str, 1)?;
let d0 = char_at(digits_str, 0)?;
if py_int(&d1)? == 0 {
return Ok(self.convert_decade2stem(&d0)?.to_string());
}
Ok(format!(
"{}{}",
self.convert_decade2stem(&d0)?,
self.base_nominal(py_int(&d1)?)?
))
}
fn convert_two2stem(&self, digits_str: &str) -> Result<String> {
let n = py_int(digits_str)?;
let ds = n.to_string();
if n < 11 {
return Ok(self.base_stem(n)?.to_string());
}
if n < 30 {
return Ok(self.base_nominal(n)?.to_string());
}
let d1 = char_at(&ds, 1)?;
let d0 = char_at(&ds, 0)?;
if py_int(&d1)? == 0 {
return Ok(self.convert_decade2stem(&d0)?.to_string());
}
Ok(format!(
"{}{}",
self.convert_decade2stem(&d0)?,
self.base_stem(py_int(&d1)?)?
))
}
fn convert_three2stem(&self, digits_str: &str) -> Result<String> {
let ds = py_int(digits_str)?.to_string();
if ds.len() < 3 {
return self.convert_two2stem(&ds);
}
let d0 = char_at(&ds, 0)?;
let tail = py_slice(&ds, 1, 3); let tail_n = py_int(&tail)?;
if py_int(&d0)? == 1 {
if tail_n == 0 {
return Ok(self.base_stem(100)?.to_string());
}
return Ok(format!(
"{}{}",
self.base_stem(100)?,
self.convert_two2stem(&tail)?
));
}
if py_int(&d0)? == 2 {
if tail_n == 0 {
return Ok(self.base_stem(200)?.to_string());
}
return Ok(format!(
"{}{}",
self.base_stem(200)?,
self.convert_two2stem(&tail)?
));
}
if tail_n == 0 {
return Ok(format!(
"{}{}",
self.convert_two2stem(&d0)?,
self.base_stem(100)?
));
}
Ok(format!(
"{}{} {}",
self.convert_two2stem(&d0)?,
self.base_stem(100)?,
self.convert_two2stem(&tail)?
))
}
fn convert_three2nominal(&self, digits_str: &str) -> Result<String> {
let ds = py_int(digits_str)?.to_string();
if ds.len() < 3 {
return self.convert_two2nominal(&ds);
}
let d0 = char_at(&ds, 0)?;
let tail = py_slice(&ds, 1, 3);
let tail_n = py_int(&tail)?;
if py_int(&d0)? == 1 {
if tail_n == 0 {
return Ok(self.base_stem(100)?.to_string());
}
return Ok(format!(
"{}{}",
self.base_stem(100)?,
self.convert_two2nominal(&tail)?
));
}
if py_int(&d0)? == 2 {
if tail_n == 0 {
return Ok(self.base_stem(200)?.to_string());
}
return Ok(format!(
"{}{}",
self.base_stem(200)?,
self.convert_two2nominal(&tail)?
));
}
if tail_n == 0 {
return Ok(format!(
"{}{}",
self.convert_two2nominal(&d0)?,
self.base_stem(100)?
));
}
Ok(format!(
"{}{} {}",
self.convert_two2stem(&d0)?,
self.base_stem(100)?,
self.convert_two2nominal(&tail)?
))
}
fn convert_int(&self, digits_str: &str, nominal: bool) -> Result<String> {
if digits_str == "0" {
return Ok(if nominal {
self.base_nominal(0)?.to_string()
} else {
self.base_stem(0)?.to_string()
});
}
let mut bounds: Vec<isize> = Vec::with_capacity(1 + GROUPING.len());
bounds.push(digits_str.chars().count() as isize);
bounds.extend_from_slice(&GROUPING);
let mut parts: Vec<String> = Vec::new();
for w in bounds.windows(2) {
let (end, start) = (w[0], w[1]);
let p = py_slice(digits_str, start, end);
if !p.is_empty() {
parts.push(p);
}
}
let head = match parts.first() {
Some(h) => h.clone(),
None => return Err(N2WError::Index("list index out of range".into())),
};
let mut part_words: Vec<String> = Vec::new();
for (num, word) in parts.iter().skip(1).zip(self.mult_words_asc.iter()) {
if py_int(num)? != 0 {
part_words.push(format!("{}{}", self.convert_three2stem(num)?, word));
}
}
part_words.reverse();
if py_int(&head)? == 0 {
return Ok(part_words.join(" "));
}
let tail = if nominal {
self.convert_three2nominal(&head)?
} else {
self.convert_three2stem(&head)?
};
Ok(format!("{} {}", part_words.join(" "), tail))
}
fn to_cardinal_float(&self, value: &BigInt, nominal: bool) -> Result<String> {
if round_half_even_28(&value.abs()) >= self.maxval {
return Err(N2WError::Overflow(format!(
"abs({}) must be less than {}.",
value, self.maxval
)));
}
let result = self.convert_int(&value.abs().to_string(), nominal)?;
if value.is_negative() {
return Ok(format!("{} {}", NEGWORD, result));
}
Ok(result.trim().to_string())
}
fn to_cardinal_float_dec(&self, value: &BigDecimal, nominal: bool) -> Result<String> {
let (coefficient, scale) = value.as_bigint_and_exponent();
let exponent = -scale;
let digits = coefficient.abs().to_string();
if self.overflows(&coefficient, exponent) {
return Err(N2WError::Overflow(format!(
"abs({}) must be less than {}.",
py_decimal_str(value),
self.maxval
)));
}
let result = self.convert_int(&digits, nominal)?;
if coefficient.is_negative() {
return Ok(format!("{} {}", NEGWORD, result));
}
Ok(result.trim().to_string())
}
fn overflows(&self, coefficient: &BigInt, exponent: i64) -> bool {
if coefficient.is_zero() {
return false;
}
let ndigits = coefficient.abs().to_string().len() as i64;
if exponent + ndigits - 1 >= MAXVAL_EXP as i64 {
return true;
}
let exact = coefficient.abs() * pow10(exponent.max(0) as usize);
round_half_even_28(&exact) >= self.maxval
}
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 convert_discrete(&self, digits_str: &str, nominal: bool) -> Result<String> {
let mut out: Vec<String> = Vec::new();
for ch in digits_str.chars() {
let d = py_int(&ch.to_string())?;
out.push(if nominal {
self.base_nominal(d)?.to_string()
} else {
self.base_stem(d)?.to_string()
});
}
Ok(out.join(" "))
}
fn cardinal_float_body(
&self,
sign_negative: bool,
digit_str: &str,
exponent: i64,
nominal: bool,
) -> Result<String> {
let result = if exponent < 0 {
let n = digit_str.chars().count() as i64;
let cut = n + exponent;
let int_digits: String = if cut > 0 {
digit_str.chars().take(cut as usize).collect()
} else {
String::new()
};
let int_part = self.convert_int(&int_digits, true)?;
let frac_digits: String = if cut > 0 {
digit_str.chars().skip(cut as usize).collect()
} else {
digit_str.chars().collect()
};
let frac_part = self.convert_discrete(&frac_digits, nominal)?;
format!("{} {} {}", int_part, self.pointword(), frac_part)
} else {
self.convert_int(digit_str, nominal)?
};
if sign_negative {
Ok(format!("{} {}", NEGWORD, result))
} else {
Ok(result.trim().to_string())
}
}
fn float_path_overflows(&self, digit_str: &str, exponent: i64) -> bool {
let d = match BigInt::parse_bytes(digit_str.as_bytes(), 10) {
Some(d) if !d.is_zero() => d,
_ => return false,
};
let d = round_half_even_28(&d);
let floored = if exponent >= 0 {
d * pow10(exponent as usize)
} else {
d / pow10((-exponent) as usize) };
floored >= self.maxval
}
fn cardinal_tuple(
&self,
sign_negative: bool,
digit_str: &str,
exponent: i64,
nominal: bool,
) -> Result<String> {
if self.float_path_overflows(digit_str, exponent) {
return Err(N2WError::Overflow(format!(
"abs({}) must be less than {}.",
py_decimal_str_parts(sign_negative, digit_str, exponent),
self.maxval
)));
}
self.cardinal_float_body(sign_negative, digit_str, exponent, nominal)
}
fn cardinal_float_full(&self, value: &FloatValue, nominal: bool) -> Result<String> {
let (sign_negative, digit_str, exponent) = float_value_tuple(value);
self.cardinal_tuple(sign_negative, &digit_str, exponent, nominal)
}
fn cardinal_from_f64(&self, f: f64, nominal: bool) -> Result<String> {
let (sign_negative, digit_str, exponent) = py_float_dec_tuple(f);
self.cardinal_tuple(sign_negative, &digit_str, exponent, nominal)
}
fn verify_ordinal_float(&self, value: &FloatValue) -> Result<()> {
let integral = match value {
FloatValue::Float { value, .. } => *value == value.trunc(),
FloatValue::Decimal { value, .. } => {
let (coeff, scale) = value.as_bigint_and_exponent();
scale <= 0 || (&coeff % pow10(scale as usize)).is_zero()
}
};
if !integral {
return Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
float_value_str(value)
)));
}
let negative = match value {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.as_bigint_and_exponent().0.is_negative(),
};
if negative {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
float_value_str(value)
)));
}
Ok(())
}
fn year_int(&self, value: &BigInt, suffix: &str) -> Result<String> {
let mut v = value.clone();
let mut suffix = suffix.to_string();
if v.is_negative() {
v = v.abs();
if suffix.is_empty() {
suffix = BCWORD.to_string();
}
}
let hundred = BigInt::from(100);
let high = &v / &hundred;
let low = &v % &hundred;
let mut result: Vec<String> = Vec::new();
if v < BigInt::from(2000) && v >= BigInt::from(1100) {
result.push(self.to_cardinal(&high)?);
result.push("ސަތޭކަ".to_string()); result.push(self.to_cardinal(&low)?);
} else {
result.push(self.to_cardinal(&v)?);
}
if !suffix.is_empty() {
result.push(suffix);
}
Ok(result.join(" "))
}
}
impl Lang for LangDv {
fn python_maxval(&self) -> Option<num_bigint::BigInt> {
Some(num_bigint::BigInt::from(10u32).pow(33))
}
fn pointword(&self) -> &str {
"ޕޮއިންޓް"
}
fn lang_name(&self) -> &str {
"Num2Word_DV"
}
fn default_currency(&self) -> &str {
"ރުފިޔާ"
}
fn negword(&self) -> &str {
NEGWORD
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.to_cardinal_float(value, true)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!(
"{} {}",
self.to_cardinal_float(value, false)?,
ORDWORD
))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!("{} {}", value, ORDWORD))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.year_int(value, "")
}
fn to_year_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["suffix"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let suffix = match kw.get("suffix") {
Option::None | Some(KwVal::None) => String::new(),
Some(KwVal::Str(s)) => s.clone(),
Some(_) => return Err(N2WError::Fallback("kwargs".into())),
};
self.year_int(value, &suffix)
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["nominal"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
self.to_cardinal_float(value, kw_nominal(kw))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
self.cardinal_float_full(value, true)
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
self.cardinal_float_full(value, true)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
self.verify_ordinal_float(value)?;
Ok(format!(
"{} {}",
self.cardinal_float_full(value, false)?,
ORDWORD
))
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
self.verify_ordinal_float(value)?;
Ok(format!("{} {}", repr_str, ORDWORD))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value: f, .. } => {
let mut v = *f;
let mut suffix = "";
if v < 0.0 {
v = -v;
suffix = BCWORD;
}
let mut result: Vec<String> = Vec::new();
if v < 2000.0 && v >= 1100.0 {
let low = v % 100.0;
let high = ((v - low) / 100.0).floor();
result.push(self.cardinal_from_f64(high, true)?);
result.push("ސަތޭކަ".to_string());
result.push(self.cardinal_from_f64(low, true)?);
} else {
result.push(self.cardinal_from_f64(v, true)?);
}
if !suffix.is_empty() {
result.push(suffix.to_string());
}
Ok(result.join(" "))
}
FloatValue::Decimal { value: d, .. } => {
let mut v = d.clone();
let mut suffix = "";
if v < BigDecimal::from(0) {
v = v.abs();
suffix = BCWORD;
}
let mut result: Vec<String> = Vec::new();
if v < BigDecimal::from(2000) && v >= BigDecimal::from(1100) {
let (coeff, scale) = v.as_bigint_and_exponent();
let hundred = BigInt::from(100);
let (q, low_coeff, low_exp) = if scale >= 0 {
let div = &hundred * pow10(scale as usize);
let q = &coeff / ÷ let low = &coeff - &q * ÷
(q, low, -scale)
} else {
let full = &coeff * pow10((-scale) as usize);
let q = &full / &hundred;
let low = &full - &q * &hundred;
(q, low, 0)
};
result.push(self.cardinal_tuple(false, &q.to_string(), 0, true)?);
result.push("ސަތޭކަ".to_string());
result.push(self.cardinal_tuple(
false,
&low_coeff.to_string(),
low_exp,
true,
)?);
} else {
let (coeff, scale) = v.as_bigint_and_exponent();
result.push(self.cardinal_tuple(
coeff.is_negative(),
&coeff.abs().to_string(),
-scale,
true,
)?);
}
if !suffix.is_empty() {
result.push(suffix.to_string());
}
Ok(result.join(" "))
}
}
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if !kw.only(&["nominal"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
self.cardinal_float_full(value, kw_nominal(kw))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match python_decimal_parse(s) {
Ok(ParsedNumber::NaN) => Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "comparison involving NaN".into(),
}),
Ok(other) => Ok(other),
Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
..
}) => Err(N2WError::Type(format!("{} is not a valid value.", s))),
Err(e) => Err(e),
}
}
fn to_fraction(&self, _numerator: &BigInt, _denominator: &BigInt) -> Result<String> {
Err(N2WError::Attribute(
"'Num2Word_DV' object has no attribute 'to_fraction'".into(),
))
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
_separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
let decimal_value = match val {
CurrencyValue::Int(i) => BigDecimal::from(i.clone()),
CurrencyValue::Decimal { value, .. } => value.clone(),
};
if decimal_value.is_zero() {
return Ok(format!("{} {}", self.base_stem(0)?, currency));
}
let int_part = to_integral_value(&decimal_value);
let frac_part = to_integral_value(&((&decimal_value - &int_part) * BigDecimal::from(100)));
let mut result: Vec<String> = Vec::new();
if !int_part.is_zero() {
result.push(self.to_cardinal_float_dec(&int_part, false)?);
result.push(currency.to_string());
}
if !frac_part.is_zero() {
result.push(self.to_cardinal_float_dec(&frac_part, false)?);
if !cents {
return Err(N2WError::Type(format!(
"sequence item {}: expected str instance, bool found",
result.len()
)));
}
result.push(CENTSWORD.to_string());
}
Ok(result.join(" "))
}
fn to_cheque(&self, _val: &BigDecimal, _currency: &str) -> Result<String> {
Err(N2WError::Attribute(
"'Num2Word_DV' object has no attribute 'to_cheque'".into(),
))
}
}
fn pow10(n: usize) -> BigInt {
let mut p = BigInt::from(1);
for _ in 0..n {
p *= 10;
}
p
}
fn shortest_digits(f: f64) -> (String, i64) {
let s = format!("{:e}", f); let (mant, exp) = s.split_once('e').expect("LowerExp always contains 'e'");
let digits: String = mant.chars().filter(|c| c.is_ascii_digit()).collect();
let adj: i64 = exp.parse().expect("LowerExp exponent is an integer");
(digits, adj)
}
fn py_float_dec_tuple(f: f64) -> (bool, String, i64) {
let sign_negative = f.is_sign_negative();
let (m, adj) = shortest_digits(f);
let ndig = m.chars().count() as i64;
let e_nat = adj - (ndig - 1);
if adj >= 16 || adj < -4 {
return (sign_negative, m, e_nat);
}
if e_nat >= 0 {
let mut d = m;
for _ in 0..=e_nat {
d.push('0');
}
let trimmed = d.trim_start_matches('0');
let d = if trimmed.is_empty() {
"0".to_string()
} else {
trimmed.to_string()
};
(sign_negative, d, -1)
} else {
(sign_negative, m, e_nat)
}
}
fn py_float_str(f: f64) -> String {
let sign = if f.is_sign_negative() { "-" } else { "" };
let (m, adj) = shortest_digits(f);
if (-4..16).contains(&adj) {
let intlen = adj + 1;
if intlen <= 0 {
format!("{}0.{}{}", sign, "0".repeat((-intlen) as usize), m)
} else if intlen as usize >= m.len() {
format!("{}{}{}.0", sign, m, "0".repeat(intlen as usize - m.len()))
} else {
let (a, b) = m.split_at(intlen as usize);
format!("{}{}.{}", sign, a, b)
}
} else {
let mut chars = m.chars();
let lead = chars.next().unwrap_or('0');
let rest: String = chars.collect();
let coeff = if rest.is_empty() {
lead.to_string()
} else {
format!("{}.{}", lead, rest)
};
format!(
"{}{}e{}{:02}",
sign,
coeff,
if adj < 0 { '-' } else { '+' },
adj.abs()
)
}
}
fn float_value_tuple(value: &FloatValue) -> (bool, String, i64) {
match value {
FloatValue::Float { value, .. } => py_float_dec_tuple(*value),
FloatValue::Decimal { value, .. } => {
let (coeff, scale) = value.as_bigint_and_exponent();
(coeff.is_negative(), coeff.abs().to_string(), -scale)
}
}
}
fn float_value_str(value: &FloatValue) -> String {
match value {
FloatValue::Float { value, .. } => py_float_str(*value),
FloatValue::Decimal { .. } => {
let (sign, digits, exponent) = float_value_tuple(value);
py_decimal_str_parts(sign, &digits, exponent)
}
}
}
fn kw_nominal(kw: &Kwargs) -> bool {
match kw.get("nominal") {
Option::None => true, Some(KwVal::Bool(b)) => *b,
Some(KwVal::Int(i)) => *i != 0,
Some(KwVal::None) => false,
Some(KwVal::Str(s)) => !s.is_empty(),
Some(KwVal::List(l)) => !l.is_empty(),
}
}
fn py_decimal_str_parts(sign_negative: bool, digit_str: &str, exponent: i64) -> String {
let sign = if sign_negative { "-" } else { "" };
let chars: Vec<char> = digit_str.chars().collect();
let ndigits = chars.len() as i64;
let adjusted = exponent + ndigits - 1;
if exponent <= 0 && adjusted >= -6 {
if exponent == 0 {
return format!("{}{}", sign, digit_str);
}
let leftdigits = exponent + ndigits; let (intpart, fracpart) = if leftdigits <= 0 {
(
"0".to_string(),
format!(".{}{}", "0".repeat((-leftdigits) as usize), digit_str),
)
} else {
let l = leftdigits as usize;
(
chars[..l].iter().collect::<String>(),
format!(".{}", chars[l..].iter().collect::<String>()),
)
};
return format!("{}{}{}", sign, intpart, fracpart);
}
let coeff = if ndigits == 1 {
chars[0].to_string()
} else {
format!("{}.{}", chars[0], chars[1..].iter().collect::<String>())
};
let esign = if adjusted < 0 { "-" } else { "+" };
format!("{}{}E{}{}", sign, coeff, esign, adjusted.abs())
}
fn round_half_even_28(a: &BigInt) -> BigInt {
let ndigits = a.to_string().len(); if ndigits <= CTX_PREC {
return a.clone();
}
let divisor = pow10(ndigits - CTX_PREC);
let (mut q, r) = a.div_rem(&divisor);
let twice = &r * 2;
if twice > divisor {
q += 1;
} else if twice == divisor {
if !q.is_even() {
q += 1;
}
}
q * divisor
}
fn to_integral_value(d: &BigDecimal) -> BigDecimal {
let (coefficient, scale) = d.as_bigint_and_exponent();
if scale <= 0 {
return d.clone();
}
let divisor = pow10(scale as usize);
let negative = coefficient.is_negative();
let (mut q, r) = coefficient.abs().div_rem(&divisor);
let twice = &r * 2;
if twice > divisor || (twice == divisor && !q.is_even()) {
q += 1;
}
if negative {
q = -q;
}
BigDecimal::new(q, 0)
}
fn py_decimal_str(d: &BigDecimal) -> String {
let (coefficient, scale) = d.as_bigint_and_exponent();
let exponent = -scale;
let digits = coefficient.abs().to_string();
let sign = if coefficient.is_negative() { "-" } else { "" };
if exponent <= 0 {
return format!("{}{}", sign, digits);
}
let adjusted = exponent + digits.chars().count() as i64 - 1;
let mut chars = digits.chars();
let lead = chars.next().unwrap_or('0');
let rest: String = chars.collect();
let coeff = if rest.is_empty() {
lead.to_string()
} else {
format!("{}.{}", lead, rest)
};
let esign = if adjusted < 0 { "-" } else { "+" };
format!("{}{}E{}{}", sign, coeff, esign, adjusted.abs())
}
fn py_int(s: &str) -> Result<i64> {
s.parse::<i64>()
.map_err(|_| N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s)))
}
fn char_at(s: &str, i: usize) -> Result<String> {
s.chars()
.nth(i)
.map(|c| c.to_string())
.ok_or_else(|| N2WError::Index("string index out of range".into()))
}
fn py_slice(s: &str, start: isize, end: isize) -> String {
let chars: Vec<char> = s.chars().collect();
let len = chars.len() as isize;
let norm = |i: isize| -> usize {
if i < 0 {
let j = len + i;
if j < 0 {
0
} else {
j as usize
}
} else if i > len {
len as usize
} else {
i as usize
}
};
let (a, b) = (norm(start), norm(end));
if a >= b {
return String::new();
}
chars[a..b].iter().collect()
}
#[cfg(test)]
mod float_tests {
use super::*;
use std::str::FromStr;
fn f(value: f64, precision: u32) -> Result<String> {
Lang::to_cardinal_float(
&LangDv::new(),
&FloatValue::Float { value, precision },
None,
)
}
fn d(s: &str) -> Result<String> {
Lang::to_cardinal_float(
&LangDv::new(),
&FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision: s.split_once('.').map_or(0, |(_, fr)| fr.len() as u32),
},
None,
)
}
fn is_index(r: &Result<String>) -> bool {
matches!(r, Err(N2WError::Index(_)))
}
#[test]
fn float_cardinal_matches_corpus_and_interpreter() {
assert!(is_index(&f(0.5, 1)));
assert_eq!(f(1.5, 1).unwrap(), "އެކެއް ޕޮއިންޓް ފަހެއް");
assert_eq!(f(3.14, 2).unwrap(), "ތިނެއް ޕޮއިންޓް އެކެއް ހަތަރެއް");
assert_eq!(f(12.34, 2).unwrap(), "ބާރަ ޕޮއިންޓް ތިނެއް ހަތަރެއް");
assert_eq!(f(-12.34, 2).unwrap(), "މައިނަސް ބާރަ ޕޮއިންޓް ތިނެއް ހަތަރެއް");
assert_eq!(f(1.005, 3).unwrap(), "އެކެއް ޕޮއިންޓް ސުމެއް ސުމެއް ފަހެއް");
assert_eq!(f(2.675, 3).unwrap(), "ދޭއް ޕޮއިންޓް ހައެއް ހަތެއް ފަހެއް");
assert!(is_index(&f(0.01, 2)));
assert_eq!(f(99.99, 2).unwrap(), "ނުވަދިހަނުވައެއް ޕޮއިންޓް ނުވައެއް ނުވައެއް");
assert_eq!(f(1.0, 1).unwrap(), "އެކެއް ޕޮއިންޓް ސުމެއް");
assert!(is_index(&f(0.0, 1)));
assert!(is_index(&f(-0.5, 1)));
assert_eq!(f(100.5, 1).unwrap(), "ސަތޭކަ ޕޮއިންޓް ފަހެއް");
assert_eq!(f(1234.56, 2).unwrap(), "އެއްހާސް ދުއިސައްތަތިރީސްހަތަރެއް ޕޮއިންޓް ފަހެއް ހައެއް");
assert_eq!(f(-1.5, 1).unwrap(), "މައިނަސް އެކެއް ޕޮއިންޓް ފަހެއް");
assert!(is_index(&f(0.1, 1)));
assert!(is_index(&f(0.99, 2)));
assert_eq!(f(1.01, 2).unwrap(), "އެކެއް ޕޮއިންޓް ސުމެއް އެކެއް");
assert_eq!(f(2.25, 2).unwrap(), "ދޭއް ޕޮއިންޓް ދޭއް ފަހެއް");
}
#[test]
fn decimal_cardinal_matches_corpus() {
assert!(is_index(&d("0.01")));
assert_eq!(d("1.10").unwrap(), "އެކެއް ޕޮއިންޓް އެކެއް ސުމެއް");
assert_eq!(d("12.345").unwrap(), "ބާރަ ޕޮއިންޓް ތިނެއް ހަތަރެއް ފަހެއް");
assert_eq!(
d("98746251323029.99").unwrap(),
"ނުވަދިހައައްޓްރިޔަން ހަތްސަތޭކަ ސާޅީސްހަބިލިޔަން ދުއިސައްތަފަންސާސްއެއްމިލިޔަން ތިންލައްކަ ތޭވީސްހާސް ނަވާވީސް ޕޮއިންޓް ނުވައެއް ނުވައެއް"
);
assert!(is_index(&d("0.001")));
assert_eq!(d("1.005").unwrap(), "އެކެއް ޕޮއިންޓް ސުމެއް ސުމެއް ފަހެއް");
assert_eq!(d("2.675").unwrap(), "ދޭއް ޕޮއިންޓް ހައެއް ހަތެއް ފަހެއް");
assert!(is_index(&d("0.50")));
}
#[test]
fn whole_float_entries_match_corpus() {
let l = LangDv::new();
let fv = |v: f64| FloatValue::Float {
value: v,
precision: 1,
};
assert_eq!(
l.cardinal_float_entry(&fv(5.0), None).unwrap(),
"ފަހެއް ޕޮއިންޓް ސުމެއް"
);
assert_eq!(
l.cardinal_float_entry(&fv(-2.0), None).unwrap(),
"މައިނަސް ދޭއް ޕޮއިންޓް ސުމެއް"
);
assert_eq!(
l.cardinal_float_entry(&fv(-1000.0), None).unwrap(),
"މައިނަސް އެއްހާސް ޕޮއިންޓް ސުމެއް"
);
assert_eq!(l.cardinal_float_entry(&fv(1e16), None).unwrap(), "އެކެއް");
assert_eq!(l.cardinal_float_entry(&fv(1e20), None).unwrap(), "އެކެއް");
assert!(matches!(
l.cardinal_float_entry(&fv(0.0), None),
Err(N2WError::Index(_))
));
assert!(matches!(
l.cardinal_float_entry(&fv(-0.0), None),
Err(N2WError::Index(_))
));
assert_eq!(l.ordinal_float_entry(&fv(1.0)).unwrap(), "އެކެއް ޕޮއިންޓް ސުން ވަނަ");
assert_eq!(l.ordinal_float_entry(&fv(1e16)).unwrap(), "އެއް ވަނަ");
assert!(matches!(
l.ordinal_float_entry(&fv(2.5)),
Err(N2WError::Type(_))
));
assert!(matches!(
l.ordinal_float_entry(&fv(-1.5)),
Err(N2WError::Type(_))
));
assert!(matches!(
l.ordinal_float_entry(&fv(-1.0)),
Err(N2WError::Type(_))
));
assert!(matches!(
l.ordinal_float_entry(&fv(-0.0)),
Err(N2WError::Index(_))
));
assert_eq!(
l.ordinal_num_float_entry(&fv(-0.0), "-0.0").unwrap(),
"-0.0 ވަނަ"
);
assert!(matches!(
l.ordinal_num_float_entry(&fv(-1.0), "-1.0"),
Err(N2WError::Type(_))
));
assert_eq!(l.year_float_entry(&fv(-1.5)).unwrap(), "އެކެއް ޕޮއިންޓް ފަހެއް ބީ.ސީ");
assert_eq!(
l.year_float_entry(&fv(1234.0)).unwrap(),
"ބާރަ ޕޮއިންޓް ސުމެއް ސަތޭކަ ތިރީސްހަތަރެއް ޕޮއިންޓް ސުމެއް"
);
assert_eq!(l.year_float_entry(&fv(1e16)).unwrap(), "އެކެއް");
assert!(matches!(
l.year_float_entry(&fv(-0.0)),
Err(N2WError::Index(_))
));
}
#[test]
fn decimal_entries_match_corpus() {
let l = LangDv::new();
let dv = |s: &str| FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision: 0,
};
assert_eq!(l.cardinal_float_entry(&dv("1E+2"), None).unwrap(), "އެކެއް");
assert_eq!(l.cardinal_float_entry(&dv("1E+20"), None).unwrap(), "އެކެއް");
assert_eq!(l.ordinal_float_entry(&dv("0")).unwrap(), "ސުން ވަނަ");
assert_eq!(l.ordinal_float_entry(&dv("5")).unwrap(), "ފަސް ވަނަ");
assert_eq!(l.ordinal_float_entry(&dv("100")).unwrap(), "ސަތޭކަ ވަނަ");
assert_eq!(
l.ordinal_float_entry(&dv("5.00")).unwrap(),
"ފަހެއް ޕޮއިންޓް ސުން ސުން ވަނަ"
);
assert_eq!(l.ordinal_float_entry(&dv("1E+2")).unwrap(), "އެއް ވަނަ");
assert!(matches!(
l.ordinal_float_entry(&dv("-3.0")),
Err(N2WError::Type(_))
));
assert_eq!(l.year_float_entry(&dv("-3.0")).unwrap(), "ތިނެއް ޕޮއިންޓް ސުމެއް ބީ.ސީ");
assert_eq!(l.year_float_entry(&dv("1E+2")).unwrap(), "އެކެއް");
assert_eq!(
l.year_float_entry(&dv("12345.000")).unwrap(),
"ބާރަހާސް ތިންސަތޭކަ ސާޅީސްފަހެއް ޕޮއިންޓް ސުމެއް ސުމެއް ސުމެއް"
);
}
#[test]
fn strings_fraction_and_kwargs_match_corpus() {
use crate::base::{KwVal, Kwargs};
let l = LangDv::new();
assert!(matches!(l.str_to_number("abc"), Err(N2WError::Type(_))));
assert!(matches!(l.str_to_number(""), Err(N2WError::Type(_))));
assert!(matches!(
l.str_to_number("NaN"),
Err(N2WError::Custom {
class: "InvalidOperation",
..
})
));
assert!(matches!(l.str_to_number("1e3"), Ok(ParsedNumber::Dec(_))));
assert!(matches!(
l.to_fraction(&BigInt::from(1), &BigInt::from(0)),
Err(N2WError::Attribute(_))
));
let kw = |v: KwVal| Kwargs(vec![("nominal".to_string(), v)]);
assert_eq!(
l.to_cardinal_kw(&BigInt::from(1234), &kw(KwVal::Bool(false)))
.unwrap(),
"އެއްހާސް ދުއިސައްތަތިރީސްހަތަރު"
);
assert_eq!(
l.to_cardinal_kw(&BigInt::from(-5), &kw(KwVal::Bool(false)))
.unwrap(),
"މައިނަސް ފަސް"
);
assert_eq!(
l.to_cardinal_kw(&BigInt::from(0), &kw(KwVal::Bool(false)))
.unwrap(),
"ސުން"
);
assert_eq!(
l.to_cardinal_kw(&BigInt::from(0), &kw(KwVal::Bool(true)))
.unwrap(),
"ސުމެއް"
);
assert!(matches!(
l.to_cardinal_kw(
&BigInt::from(1),
&Kwargs(vec![("case".to_string(), KwVal::Str("dative".into()))])
),
Err(N2WError::Fallback(_))
));
let skw = Kwargs(vec![("suffix".to_string(), KwVal::Str("AD".into()))]);
assert_eq!(l.to_year_kw(&BigInt::from(5), &skw).unwrap(), "ފަހެއް AD");
assert_eq!(l.to_year_kw(&BigInt::from(-17), &skw).unwrap(), "ސަތާރަ AD");
assert_eq!(
l.to_year_kw(
&BigInt::from(-17),
&Kwargs(vec![("suffix".to_string(), KwVal::None)])
)
.unwrap(),
"ސަތާރަ ބީ.ސީ"
);
}
#[test]
fn overflow_boundary_matches_python() {
let overflow = |s: &str| matches!(d(s), Err(N2WError::Overflow(_)));
assert!(overflow(&"9".repeat(33))); assert!(overflow(&format!("1{}", "0".repeat(33)))); assert!(overflow(&(10u128.pow(33) - 50000).to_string())); assert!(d(&(10u128.pow(33) - 50001).to_string()).is_ok()); assert!(d(&"9".repeat(28)).is_ok()); assert!(d(&format!("1{}", "0".repeat(32))).is_ok()); assert!(d(&format!("{}.5", "9".repeat(32))).is_ok());
}
}