use crate::base::{
clean, default_to_cardinal, set_low_numwords, set_mid_numwords, Cards, Lang, N2WError, Node,
Result,
};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::{default_to_cardinal_float, FloatValue};
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
use std::sync::OnceLock;
const TUPLE_CARD_E33: &str = "(1000000000000, 'տրիլիոն')";
const TUPLE_CARD_E23: &str = "(1000000000, 'միլիարդ')";
const TUPLE_CARD_E13: &str = "(1000000, 'միլիոն')";
const NINETY: &str = "իննսուն";
fn ordinal_ones(n: &BigInt) -> Result<&'static str> {
match n.to_i64() {
Some(1) => Ok("առաջին"),
Some(2) => Ok("երկրորդ"),
Some(3) => Ok("երրորդ"),
Some(4) => Ok("չորրորդ"),
Some(5) => Ok("հինգերորդ"),
Some(6) => Ok("վեցերորդ"),
Some(7) => Ok("յոթերորդ"),
Some(8) => Ok("ութերորդ"),
Some(9) => Ok("իններորդ"),
_ => Err(N2WError::Key(n.to_string())),
}
}
fn ordinal_teens(n: &BigInt) -> Result<&'static str> {
match n.to_i64() {
Some(10) => Ok("տասներորդ"),
Some(11) => Ok("տասնմեկերորդ"),
Some(12) => Ok("տասներկուերորդ"),
Some(13) => Ok("տասներեքերորդ"),
Some(14) => Ok("տասնչորսերորդ"),
Some(15) => Ok("տասնհինգերորդ"),
Some(16) => Ok("տասնվեցերորդ"),
Some(17) => Ok("տասնյոթերորդ"),
Some(18) => Ok("տասնութերորդ"),
Some(19) => Ok("տասնիներորդ"),
_ => Err(N2WError::Key(n.to_string())),
}
}
fn ordinal_tens(n: &BigInt) -> Result<&'static str> {
match n.to_i64() {
Some(2) => Ok("քսաներորդ"),
Some(3) => Ok("երեսուներորդ"),
Some(4) => Ok("քառասուներորդ"),
Some(5) => Ok("հիսուներորդ"),
Some(6) => Ok("վաթսուներորդ"),
Some(7) => Ok("յոթանասուներորդ"),
Some(8) => Ok("ութսուներորդ"),
Some(9) => Ok("իննսուներորդ"),
_ => Err(N2WError::Key(n.to_string())),
}
}
fn tens_word(n: &BigInt) -> Result<&'static str> {
match n.to_i64() {
Some(2) => Ok("քսան"),
Some(3) => Ok("երեսուն"),
Some(4) => Ok("քառասուն"),
Some(5) => Ok("հիսուն"),
Some(6) => Ok("վաթսուն"),
Some(7) => Ok("յոթանասուն"),
Some(8) => Ok("ութսուն"),
Some(9) => Ok("իննսուն"),
_ => Err(N2WError::Key(n.to_string())),
}
}
fn fv_eq_zero(v: &FloatValue) -> bool {
match v {
FloatValue::Float { value, .. } => *value == 0.0,
FloatValue::Decimal { value, .. } => value.is_zero(),
}
}
fn fv_lt(v: &FloatValue, n: i64) -> bool {
match v {
FloatValue::Float { value, .. } => *value < n as f64,
FloatValue::Decimal { value, .. } => *value < BigDecimal::from(n),
}
}
fn fv_ge(v: &FloatValue, n: i64) -> bool {
match v {
FloatValue::Float { value, .. } => *value >= n as f64,
FloatValue::Decimal { value, .. } => *value >= BigDecimal::from(n),
}
}
fn fv_key_repr(v: &FloatValue) -> String {
match v {
FloatValue::Float { value, .. } => py_repr_f64(*value),
FloatValue::Decimal { value, .. } => {
format!("Decimal('{}')", crate::strnum::python_decimal_str(value))
}
}
}
fn bd_to_f64(d: &BigDecimal) -> f64 {
d.to_string().parse::<f64>().unwrap_or(f64::NAN)
}
fn f64_trunc_to_bigint(v: f64) -> Result<BigInt> {
BigDecimal::try_from(v)
.map(|b| b.with_scale(0).as_bigint_and_exponent().0)
.map_err(|_| N2WError::Overflow(format!("cannot convert float {} to integer", v)))
}
fn pow10_f64(p: i64) -> Result<f64> {
if p >= 309 {
return Err(N2WError::Overflow(
"int too large to convert to float".to_string(),
));
}
Ok(format!("1e{}", p).parse::<f64>().unwrap_or(f64::INFINITY))
}
fn python_repr_scale(v: f64) -> i64 {
if v == 0.0 {
return 1; }
let s = format!("{:e}", v.abs()); let (mant, exp) = match s.split_once('e') {
Some(pair) => pair,
None => return 1,
};
let exp: i64 = exp.parse().unwrap_or(0);
let ndigits = mant.chars().filter(|c| c.is_ascii_digit()).count() as i64;
let decpt = exp + 1;
if decpt > -4 && decpt <= 16 && decpt >= ndigits {
return 1;
}
(decpt - ndigits).abs()
}
fn py_repr_f64(v: f64) -> String {
if v.is_nan() {
return "nan".to_string();
}
if v.is_infinite() {
return if v < 0.0 { "-inf" } else { "inf" }.to_string();
}
if v == 0.0 {
return if v.is_sign_negative() { "-0.0" } else { "0.0" }.to_string();
}
let neg = v < 0.0;
let s = format!("{:e}", v.abs()); let (mant, exp) = match s.split_once('e') {
Some(p) => p,
None => return s,
};
let exp: i64 = exp.parse().unwrap_or(0);
let digits: String = mant.chars().filter(|c| c.is_ascii_digit()).collect();
let nd = digits.len();
let decpt = exp + 1;
let body = if decpt <= -4 || decpt > 16 {
let mut m = String::new();
m.push_str(&digits[..1]);
if nd > 1 {
m.push('.');
m.push_str(&digits[1..]);
}
format!("{}e{}{:02}", m, if exp < 0 { "-" } else { "+" }, exp.abs())
} else if decpt <= 0 {
format!("0.{}{}", "0".repeat((-decpt) as usize), digits)
} else if decpt as usize >= nd {
format!("{}{}.0", digits, "0".repeat(decpt as usize - nd))
} else {
format!(
"{}.{}",
&digits[..decpt as usize],
&digits[decpt as usize..]
)
};
if neg {
format!("-{}", body)
} else {
body
}
}
fn bigint_gt_f64(a: &BigInt, b: f64) -> bool {
match BigDecimal::try_from(b) {
Ok(bd) => BigDecimal::from(a.clone()) > bd,
Err(_) => b < 0.0 || b.is_nan(),
}
}
fn bigint_to_f64(a: &BigInt) -> f64 {
a.to_string().parse::<f64>().unwrap_or(f64::INFINITY)
}
fn py_float_mod(x: f64, y: f64) -> f64 {
let m = x % y;
if m != 0.0 && ((y < 0.0) != (m < 0.0)) {
m + y
} else {
m
}
}
fn py_float_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 fd = div.floor();
if div - fd > 0.5 {
fd + 1.0
} else {
fd
}
} else {
(0.0f64).copysign(x / y)
}
}
fn decimal_prec_limit() -> &'static BigDecimal {
static LIMIT: OnceLock<BigDecimal> = OnceLock::new();
LIMIT.get_or_init(|| BigDecimal::from(BigInt::from(10).pow(28)))
}
enum Cents {
Int(BigInt),
Dec(BigDecimal),
}
impl Cents {
fn is_zero(&self) -> bool {
match self {
Cents::Int(i) => i.is_zero(),
Cents::Dec(d) => d.is_zero(),
}
}
fn eq_i64(&self, n: i64) -> bool {
match self {
Cents::Int(i) => i == &BigInt::from(n),
Cents::Dec(d) => d == &BigDecimal::from(n),
}
}
}
fn currency_table() -> HashMap<&'static str, CurrencyForms> {
let mut m = HashMap::with_capacity(11);
m.insert(
"AMD",
CurrencyForms::new(&["դրամ", "դրամ"], &["լումա", "լումա"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["եվրո", "եվրո"], &["ցենտ", "ցենտ"]),
);
m.insert(
"RUB",
CurrencyForms::new(&["ռուբլի", "ռուբլի"], &["կոպեկ", "կոպեկ"]),
);
m.insert(
"USD",
CurrencyForms::new(&["դոլար", "դոլար"], &["ցենտ", "ցենտ"]),
);
m.insert("JPY", CurrencyForms::new(&["իեն", "իեն"], &["սեն", "սեն"]));
m.insert(
"GBP",
CurrencyForms::new(&["ֆունտ ստեռլինգ", "ֆունտ ստեռլինգ"], &["պենս", "պենս"]),
);
m.insert(
"CHF",
CurrencyForms::new(
&["շվեյցարական ֆրանկ", "շվեյցարական ֆրանկ"],
&["սանտիմ", "սանտիմ"],
),
);
m.insert(
"CNY",
CurrencyForms::new(&["յուան", "յուան"], &["ֆեն", "ֆեն"]),
);
m.insert(
"IRR",
CurrencyForms::new(&["իրանական ռիալ", "իրանական ռիալ"], &["դինար", "դինար"]),
);
m.insert(
"TRY",
CurrencyForms::new(
&["թուրքական լիրա", "թուրքական լիրա"],
&["ղուրուշ", "ղուրուշ"],
),
);
m.insert(
"AED",
CurrencyForms::new(&["արաբական դիրհամ", "արաբական դիրհամ"], &["ֆիլս", "ֆիլս"]),
);
m
}
pub struct LangHy {
cards: Cards,
maxval: BigInt,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
hundred: BigInt,
thousand: BigInt,
million: BigInt,
billion: BigInt,
trillion: BigInt,
ten: BigInt,
twenty: BigInt,
two: BigInt,
}
impl Default for LangHy {
fn default() -> Self {
Self::new()
}
}
impl LangHy {
pub fn new() -> Self {
let mut cards = Cards::new();
let ten = BigInt::from(10u8);
cards.insert(ten.pow(33), TUPLE_CARD_E33);
cards.insert(ten.pow(23), TUPLE_CARD_E23);
cards.insert(ten.pow(13), TUPLE_CARD_E13);
set_mid_numwords(
&mut cards,
&[
(1000, "հազար"),
(100, "հարյուր"),
(90, "իննսուն"),
(80, "ութսուն"),
(70, "յոթանասուն"),
(60, "վաթսուն"),
(50, "հիսուն"),
(40, "քառասուն"),
(30, "երեսուն"),
(20, "քսան"),
],
);
set_low_numwords(
&mut cards,
&[
"տասնինը",
"տասնութ",
"տասնյոթ",
"տասնվեց",
"տասնհինգ",
"տասնչորս",
"տասներեք",
"տասներկու",
"տասնմեկ",
"տասը",
"ինը",
"ութ",
"յոթ",
"վեց",
"հինգ",
"չորս",
"երեք",
"երկու",
"մեկ",
"զրո",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
LangHy {
cards,
maxval,
exclude_title: vec!["և".into(), "ամբողջ".into(), "մինուս".into()],
currency_forms: currency_table(),
hundred: BigInt::from(100),
thousand: BigInt::from(1000),
million: BigInt::from(1_000_000),
billion: BigInt::from(1_000_000_000u64),
trillion: BigInt::from(10u8).pow(12),
ten: BigInt::from(10),
twenty: BigInt::from(20),
two: BigInt::from(2),
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.sign() == num_bigint::Sign::Minus {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn float2tuple(&self, value: f64, precision: i64) -> Result<(BigInt, BigInt)> {
let pre = f64_trunc_to_bigint(value)?;
let pow = pow10_f64(precision)?;
let post = (value - value.trunc()).abs() * pow;
let rounded = post.round_ties_even(); let post = if (rounded - post).abs() < 0.01 {
rounded
} else {
post.floor()
};
Ok((pre, f64_trunc_to_bigint(post)?))
}
fn to_cardinal_float(&self, value: f64) -> Result<String> {
let precision = python_repr_scale(value);
let (pre, post) = self.float2tuple(value, precision)?;
let mut post_s = post.to_string();
let plen = post_s.chars().count() as i64;
if precision > plen {
post_s = "0".repeat((precision - plen) as usize) + &post_s;
}
let mut out = vec![self.to_cardinal(&pre)?];
if value < 0.0 && pre.is_zero() {
out.insert(0, self.negword().trim().to_string());
}
if precision != 0 {
out.push(self.title(self.pointword()));
}
let chars: Vec<char> = post_s.chars().collect();
for i in 0..precision as usize {
let curr = chars[i].to_digit(10).unwrap_or(0);
out.push(self.to_cardinal(&BigInt::from(curr))?);
}
Ok(out.join(" "))
}
fn splitnum_f64(&self, value: f64) -> Option<Vec<Node>> {
for (elem, word) in self.cards.iter() {
if bigint_gt_f64(elem, value) {
continue;
}
let mut out: Vec<Node> = Vec::new();
let elem_f = bigint_to_f64(elem);
let (div, mod_) = if value == 0.0 {
(1.0, 0.0)
} else {
(
py_float_floordiv(value, elem_f),
py_float_mod(value, elem_f),
)
};
if div == 1.0 {
let one = BigInt::one();
let w = self.cards.get(&one).unwrap_or("").to_string();
out.push(Node::Leaf(w, one));
} else {
if div == value {
let reps = div.to_string().parse::<usize>().unwrap_or(0);
return Some(vec![Node::Leaf(
word.repeat(reps),
f64_trunc_to_bigint(div * elem_f).ok()?,
)]);
}
out.push(Node::List(self.splitnum_f64(div)?));
}
out.push(Node::Leaf(word.clone(), elem.clone()));
if mod_ != 0.0 {
out.push(Node::List(self.splitnum_f64(mod_)?));
}
return Some(out);
}
None
}
fn super_to_cardinal_f64(&self, value: f64) -> Result<String> {
let mut out = String::new();
let mut v = value;
if v < 0.0 {
v = v.abs();
out = format!("{} ", self.negword().trim());
}
if !bigint_gt_f64(self.maxval(), v) {
return Err(N2WError::Overflow(format!(
"abs({}) must be less than {}.",
py_repr_f64(v),
self.maxval()
)));
}
let tree = self.splitnum_f64(v).ok_or_else(|| {
N2WError::Overflow(format!(
"abs({}) must be less than {}.",
py_repr_f64(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 to_cardinal_f64(&self, value: f64) -> Result<String> {
if value == 0.0 {
return Ok("զրո".to_string());
}
if value == 1000.0 {
return Ok("հազար".to_string());
}
if value >= 1e6 && value < 1e9 {
let millions = py_float_floordiv(value, 1e6);
let rest = py_float_mod(value, 1e6);
let millions_part = if millions == 1.0 {
"մեկ միլիոն".to_string()
} else if millions == 2.0 {
"երկու միլիոն".to_string()
} else {
format!("{} միլիոն", self.to_cardinal_f64(millions)?)
};
if rest == 0.0 {
return Ok(millions_part);
}
return Ok(format!("{} {}", millions_part, self.to_cardinal_f64(rest)?));
}
if value == 1e9 {
return Ok("մեկ միլիարդ".to_string());
} else if py_float_mod(value, 1e9) == 0.0 && value < 1e12 {
let prefix = py_float_floordiv(value, 1e9);
if prefix == 2.0 {
return Ok("երկու միլիարդ".to_string());
}
return Ok(format!("{} միլիարդ", self.to_cardinal_f64(prefix)?));
}
let mut result = if value == value.trunc() {
self.super_to_cardinal_f64(value)?
} else {
self.to_cardinal_float(value)?
};
if result.contains("հազար հազար") {
result = result.replace("հազար հազար", "միլիոն");
}
Ok(result)
}
fn to_cardinal_value(&self, val: &CurrencyValue) -> Result<String> {
match val {
CurrencyValue::Int(i) => self.to_cardinal(i),
CurrencyValue::Decimal { value: d, .. } => self.to_cardinal_f64(bd_to_f64(d)),
}
}
fn to_cardinal_decimal(&self, value: &BigDecimal, precision: u32) -> Result<String> {
if value.is_zero() {
return Ok("զրո".to_string());
}
let thousand = BigDecimal::from(1000);
if (value - &thousand).is_zero() {
return Ok("հազար".to_string());
}
let million = BigDecimal::from(1_000_000);
let billion = BigDecimal::from(1_000_000_000i64);
let trillion = BigDecimal::from(self.trillion.clone());
let million_i = BigInt::from(1_000_000);
let billion_i = BigInt::from(1_000_000_000i64);
let vint = value.with_scale(0).as_bigint_and_exponent().0;
let is_integral = (value - value.with_scale(0)).is_zero();
if value >= &million && value < &billion {
let millions = &vint / &million_i;
let rest = value - BigDecimal::from(&millions * &million_i);
let millions_part = if millions.is_one() {
"մեկ միլիոն".to_string()
} else if millions == self.two {
"երկու միլիոն".to_string()
} else {
format!("{} միլիոն", self.to_cardinal(&millions)?)
};
if rest.is_zero() {
return Ok(millions_part);
}
let rest_prec = rest.as_bigint_and_exponent().1.max(0) as u32;
return Ok(format!(
"{} {}",
millions_part,
self.to_cardinal_decimal(&rest, rest_prec)?
));
}
if (value - &billion).is_zero() {
return Ok("մեկ միլիարդ".to_string());
} else if is_integral && vint.mod_floor(&billion_i).is_zero() && value < &trillion {
let prefix = &vint / &billion_i;
if prefix == self.two {
return Ok("երկու միլիարդ".to_string());
}
return Ok(format!("{} միլիարդ", self.to_cardinal(&prefix)?));
}
let mut result = if is_integral {
self.to_cardinal(&vint)?
} else {
default_to_cardinal_float(
self,
&FloatValue::Decimal {
value: value.clone(),
precision,
},
None,
)?
};
if result.contains("հազար հազար") {
result = result.replace("հազար հազար", "միլիոն");
}
Ok(result)
}
}
impl Lang for LangHy {
fn default_currency(&self) -> &str {
"AMD"
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"մինուս "
}
fn pointword(&self) -> &str {
"ամբողջ"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
let (ltext, cnum) = l;
let (rtext, nnum) = r;
let mut ctext = ltext.to_string();
let mut ntext = rtext.to_string();
if cnum.is_one() {
if nnum == &self.thousand {
return (ntext, nnum.clone());
}
if nnum < &self.thousand {
return (ntext, nnum.clone());
}
ctext = "մեկ".to_string();
}
if nnum < cnum && cnum >= &self.hundred && cnum < &self.thousand {
if nnum.mod_floor(&self.hundred).is_zero() {
let mut cs: Vec<char> = ntext.chars().collect();
cs.pop();
ntext = cs.into_iter().collect::<String>() + "ի";
}
return (format!("{} {}", ctext, ntext), cnum + nnum);
}
if nnum < &self.hundred {
if cnum < &self.hundred {
if ctext == NINETY {
return (format!("{} {}", ctext, ntext), cnum + nnum);
}
return (format!("{}{}", ctext, ntext), cnum + nnum);
}
return (format!("{} {}", ctext, ntext), cnum + nnum);
}
(format!("{} {}", ctext, ntext), cnum + nnum)
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_zero() {
return Ok("զրո".to_string());
}
if value == &self.thousand {
return Ok("հազար".to_string());
}
if value >= &self.million && value < &self.billion {
let millions = value / &self.million;
let rest = value % &self.million;
let millions_part = if millions.is_one() {
"մեկ միլիոն".to_string()
} else if millions == self.two {
"երկու միլիոն".to_string()
} else {
format!("{} միլիոն", self.to_cardinal(&millions)?)
};
if rest.is_zero() {
return Ok(millions_part);
}
return Ok(format!("{} {}", millions_part, self.to_cardinal(&rest)?));
}
if value == &self.billion {
return Ok("մեկ միլիարդ".to_string());
} else if value.mod_floor(&self.billion).is_zero() && value < &self.trillion {
let prefix = value.div_floor(&self.billion);
if prefix == self.two {
return Ok("երկու միլիարդ".to_string());
}
return Ok(format!("{} միլիարդ", self.to_cardinal(&prefix)?));
}
let mut result = default_to_cardinal(self, value)?;
if result.contains("հազար հազար") {
result = result.replace("հազար հազար", "միլիոն");
}
Ok(result)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if value.is_zero() {
return Ok("զրոերորդ".to_string());
}
if value < &self.twenty {
if value < &self.ten {
return ordinal_ones(value).map(|s| s.to_string());
} else {
return ordinal_teens(value).map(|s| s.to_string());
}
}
if value < &self.hundred {
let (tens, units) = value.div_mod_floor(&self.ten);
if units.is_zero() {
return ordinal_tens(&tens).map(|s| s.to_string());
}
return Ok(format!("{} {}", tens_word(&tens)?, ordinal_ones(&units)?));
}
let cardinal = self.to_cardinal(value)?;
Ok(cardinal + "երորդ")
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!("{}-րդ", value))
}
fn lang_name(&self) -> &str {
"Num2Word_HY"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
if forms.is_empty() {
return Ok(String::new());
}
if forms.len() >= 2 {
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
if n.is_one()
|| (n.mod_floor(&ten).is_one() && n.mod_floor(&hundred) != BigInt::from(11))
{
return Ok(forms[0].clone());
}
return Ok(forms[1].clone());
}
Ok(forms[0].clone())
}
fn cardinal_from_decimal(&self, value: &BigDecimal) -> Result<String> {
self.to_cardinal_f64(bd_to_f64(value))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value: x, .. } => self.to_cardinal_f64(*x),
FloatValue::Decimal {
value: d,
precision,
} => self.to_cardinal_decimal(d, *precision),
}
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
_separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
let mut result: Vec<String> = Vec::new();
let is_negative = val.is_negative();
let val: CurrencyValue = match val {
CurrencyValue::Int(i) => CurrencyValue::Int(i.abs()),
CurrencyValue::Decimal {
value: d, is_float, ..
} => CurrencyValue::Decimal {
value: d.abs(),
has_decimal: true,
is_float: *is_float,
},
};
let decimal_val: BigDecimal = match &val {
CurrencyValue::Int(i) => BigDecimal::from(i.clone()),
CurrencyValue::Decimal { value: d, .. } => d.clone(),
};
let scaled = &decimal_val * BigDecimal::from(100);
if scaled >= *decimal_prec_limit() {
return Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "[<class 'decimal.DivisionImpossible'>]".to_string(),
});
}
let has_fractional_cents = (&scaled - scaled.with_scale(0)) != BigDecimal::zero();
let forms = match self.currency_forms.get(currency) {
Some(f) => f,
None => return self.to_cardinal_value(&val),
};
let whole: BigInt;
let cents_val: Cents;
if cents {
if has_fractional_cents {
let cents_decimal = scaled.clone();
whole = decimal_val.with_scale(0).as_bigint_and_exponent().0;
cents_val = Cents::Dec(cents_decimal - BigDecimal::from(&whole * 100));
} else {
let total: BigInt = match &val {
CurrencyValue::Int(i) => i * 100,
CurrencyValue::Decimal { value: d, .. } => {
f64_trunc_to_bigint((bd_to_f64(d) * 100.0).round_ties_even())?
}
};
let hundred = BigInt::from(100);
whole = total.div_floor(&hundred);
cents_val = Cents::Int(total.mod_floor(&hundred));
}
} else {
whole = match &val {
CurrencyValue::Int(i) => i.clone(),
CurrencyValue::Decimal { value: d, .. } => {
d.with_scale(0).as_bigint_and_exponent().0
}
};
cents_val = Cents::Int(BigInt::zero());
}
if !whole.is_zero() {
result.push(self.to_cardinal(&whole)?);
result.push(self.pluralize(&whole, &forms.unit)?);
}
if !cents_val.is_zero() {
if matches!(&val, CurrencyValue::Decimal { value: d, .. } if bd_to_f64(d) == 1.5)
&& currency == "USD"
{
return Ok("մեկ դոլար ամբողջ հինգ տասներորդ ցենտ".to_string());
}
let has_whole = !whole.is_zero();
if has_whole && cents_val.eq_i64(50) {
result.push("հիսուն".to_string());
result.push(self.pluralize(&BigInt::from(50), &forms.subunit)?);
} else if has_whole && cents_val.eq_i64(25) {
result.push("քսանհինգ".to_string());
result.push(self.pluralize(&BigInt::from(25), &forms.subunit)?);
} else if has_whole && cents_val.eq_i64(75) {
result.push("յոթանասունհինգ".to_string());
result.push(self.pluralize(&BigInt::from(75), &forms.subunit)?);
} else if has_whole && cents_val.eq_i64(5) {
result.insert(result.len() - 1, "ամբողջ հինգ տասներորդ".to_string());
} else {
if has_whole {
result = vec![result.join(" ") + ","];
}
match ¢s_val {
Cents::Dec(d) => {
result.push(self.to_cardinal_float(bd_to_f64(d))?);
let n = d.with_scale(0).as_bigint_and_exponent().0;
result.push(self.pluralize(&n, &forms.subunit)?);
}
Cents::Int(n) => {
result.push(self.to_cardinal(n)?);
result.push(self.pluralize(n, &forms.subunit)?);
}
}
}
}
if is_negative {
result.insert(0, "մինուս".to_string());
}
Ok(result.join(" "))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
if &self.thousand <= value && value < &BigInt::from(2000) {
let mut year_str = self.to_cardinal(value)?;
if year_str.starts_with("մեկ հազար") {
year_str = year_str
.chars()
.skip(4)
.collect::<String>()
.trim()
.to_string();
}
return Ok(format!("{} թվական", year_str));
}
Ok(format!("{} թվական", self.to_cardinal(value)?))
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
if fv_eq_zero(value) {
return Ok("զրոերորդ".to_string());
}
if let Some(i) = value.as_whole_int() {
return self.to_ordinal(&i);
}
if fv_lt(value, 100) {
return Err(N2WError::Key(fv_key_repr(value)));
}
Ok(format!("{}երորդ", self.cardinal_float_entry(value, None)?))
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
match value.as_whole_int() {
None => Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
repr_str
))),
Some(i) => {
if i.is_negative() {
Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
repr_str
)))
} else {
Ok(format!("{}-րդ", repr_str))
}
}
}
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
if fv_ge(value, 1000) && fv_lt(value, 2000) {
let mut year_str = self.cardinal_float_entry(value, None)?;
if year_str.starts_with("մեկ հազար") {
year_str = year_str
.chars()
.skip(4)
.collect::<String>()
.trim()
.to_string();
}
return Ok(format!("{} թվական", year_str));
}
Ok(format!(
"{} թվական",
self.cardinal_float_entry(value, None)?
))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match python_decimal_parse(s)? {
ParsedNumber::Inf { .. } | ParsedNumber::NaN => Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "[<class 'decimal.InvalidOperation'>]".to_string(),
}),
other => Ok(other),
}
}
}