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 crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{FromPrimitive, One, Signed, Zero};
use std::collections::HashMap;
const HUNDREDS: [&str; 10] = [
"", "cento",
"duzentos",
"trezentos",
"quatrocentos",
"quinhentos",
"seiscentos",
"setecentos",
"oitocentos",
"novecentos",
];
const ORDS: [[&str; 10]; 3] = [
[
"", "primeiro", "segundo", "terceiro", "quarto", "quinto", "sexto", "sétimo", "oitavo",
"nono",
],
[
"",
"décimo",
"vigésimo",
"trigésimo",
"quadragésimo",
"quinquagésimo",
"sexagésimo",
"septuagésimo",
"octogésimo",
"nonagésimo",
],
[
"",
"centésimo",
"ducentésimo",
"tricentésimo",
"quadrigentésimo",
"quingentésimo",
"seiscentésimo",
"septigentésimo",
"octigentésimo",
"nongentésimo",
],
];
fn is_word_char(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}
fn fix_milhao(s: &str) -> String {
s.replace("milião", "milhão").replace("miliões", "milhões")
}
fn pt_match(s: &str, ext: &str) -> bool {
let chars: Vec<char> = s.chars().collect();
let pat: Vec<char> = format!("{} e ", ext).chars().collect();
if chars.len() < pat.len() {
return false;
}
for start in 0..=(chars.len() - pat.len()) {
if chars[start..start + pat.len()] != pat[..] {
continue;
}
let j = start + pat.len();
let mut k = j;
while k < chars.len() && is_word_char(chars[k]) {
k += 1;
}
if k >= chars.len() || chars[k] != ' ' {
continue;
}
let w: String = chars[j..k].iter().collect();
if !(w.ends_with("ento") || w.ends_with("entos")) {
continue;
}
if chars[k + 1..].iter().any(|&c| c == 'e') {
return true;
}
}
false
}
fn pt_drops_e(result: &str, ext: &str) -> String {
result.replace(&format!("{} e", ext), ext)
}
fn ptbr_comma(s: &str, ext: &str) -> Option<String> {
let chars: Vec<char> = s.chars().collect();
let pat: Vec<char> = format!("{} ", ext).chars().collect();
if chars.len() < pat.len() {
return None;
}
for start in 0..=(chars.len() - pat.len()) {
if chars[start..start + pat.len()] != pat[..] {
continue;
}
let j = start + pat.len();
let mut k = j;
while k < chars.len() && is_word_char(chars[k]) {
k += 1;
}
let w: String = chars[j..k].iter().collect();
if !w.contains("ento") {
continue;
}
let mut out: String = chars[..j - 1].iter().collect();
out.push_str(", ");
out.extend(chars[j..].iter());
return Some(out);
}
None
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"BRL",
CurrencyForms::new(&["real", "reais"], &["centavo", "centavos"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["euro", "euros"], &["cêntimo", "cêntimos"]),
);
m.insert(
"USD",
CurrencyForms::new(&["dólar", "dólares"], &["centavo", "centavos"]),
);
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()
}
const PT_DE_EXTS: [&str; 6] = [
"milhão",
"milhões",
"bilião",
"biliões",
"trilião",
"triliões",
];
const PT_BR_DE_EXTS: [&str; 6] = [
"milhão",
"milhões",
"bilhão",
"bilhões",
"trilhão",
"trilhões",
];
fn insert_de(result: &str, ext: &str, currency_str: &str) -> Option<String> {
if result.contains(&format!("{} {}", ext, currency_str)) {
Some(result.replacen(ext, &format!("{} de", ext), 1))
} else {
None
}
}
fn collapse_ws(s: &str) -> String {
let mut out = String::new();
let mut in_ws = false;
for c in s.chars() {
if c.is_whitespace() {
if !in_ws {
out.push(' ');
in_ws = true;
}
} else {
out.push(c);
in_ws = false;
}
}
out
}
fn float_repr_precision(f: f64) -> u32 {
let s = format!("{}", f);
match s.split_once('.') {
Some((_, frac)) if !frac.contains('e') => frac.len() as u32,
_ => 0,
}
}
fn float_trunc_int(value: &FloatValue) -> Result<BigInt> {
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(),
));
}
Ok(BigInt::from_f64(f.trunc()).expect("finite whole f64 converts exactly"))
}
FloatValue::Decimal { value: d, .. } => {
Ok(d.with_scale(0).as_bigint_and_exponent().0)
}
}
}
fn fv_numeric_negative(v: &FloatValue) -> bool {
match v {
FloatValue::Float { value, .. } => *value < 0.0,
FloatValue::Decimal { value, .. } => value.is_negative(),
}
}
fn fv_abs(v: &FloatValue) -> FloatValue {
match v {
FloatValue::Float { value, precision } => FloatValue::Float {
value: value.abs(),
precision: *precision,
},
FloatValue::Decimal { value, precision } => FloatValue::Decimal {
value: value.abs(),
precision: *precision,
},
}
}
pub struct LangPtBr {
cards: Cards,
maxval: BigInt,
hundreds: [&'static str; 10],
thousand_separators: HashMap<usize, &'static str>,
exclude_title: Vec<String>,
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangPtBr {
fn default() -> Self {
Self::new()
}
}
impl LangPtBr {
pub fn new() -> Self {
let high = [
"non", "oct", "sept", "sext", "quint", "quatr", "tr", "b", "m",
];
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 - 3) as u32),
format!("{}ilião", word),
);
n -= 6;
}
set_mid_numwords(
&mut cards,
&[
(1000, "mil"),
(100, "cem"),
(90, "noventa"),
(80, "oitenta"),
(70, "setenta"),
(60, "sessenta"),
(50, "cinquenta"),
(40, "quarenta"),
(30, "trinta"),
],
);
set_low_numwords(
&mut cards,
&[
"vinte",
"dezenove",
"dezoito",
"dezessete",
"dezesseis",
"quinze",
"catorze",
"treze",
"doze",
"onze",
"dez",
"nove",
"oito",
"sete",
"seis",
"cinco",
"quatro",
"três",
"dois",
"um",
"zero",
],
);
let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
let thousand_separators: HashMap<usize, &'static str> = [
(3usize, "milésimo"),
(6, "milionésimo"),
(9, "bilionésimo"),
(12, "trilionésimo"),
(15, "quatrilionésimo"),
]
.into_iter()
.collect();
LangPtBr {
cards,
maxval,
hundreds: HUNDREDS,
thousand_separators,
exclude_title: vec!["e".into(), "vírgula".into(), "menos".into()],
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
fn hundreds_word(&self, cnum: &BigInt) -> &'static str {
let idx = u32::try_from(cnum).unwrap_or(0) as usize;
if idx < self.hundreds.len() {
self.hundreds[idx]
} else {
""
}
}
fn super_to_cardinal(&self, value: &BigInt) -> Result<String> {
let mut result = default_to_cardinal(self, value)?;
for ext in [
"mil",
"milhão",
"milhões",
"mil milhões",
"bilião",
"biliões",
"mil biliões",
] {
if pt_match(&result, ext) {
result = pt_drops_e(&result, ext);
}
}
Ok(result)
}
fn super_float(&self, fv: &FloatValue, pointword: &str) -> Result<String> {
let precision = fv.precision();
let (pre, post) = float2tuple(fv);
let pre = pre.abs();
let post_str = post.to_string();
let post_str = format!(
"{}{}",
"0".repeat((precision as usize).saturating_sub(post_str.len())),
post_str
);
let mut out = vec![self.to_cardinal(&pre)?];
if precision > 0 {
out.push(self.title(pointword));
}
for ch in post_str.chars().take(precision as usize) {
let d = ch
.to_digit(10)
.ok_or_else(|| N2WError::Value(format!("non-digit {:?} in fractional part", ch)))?;
out.push(self.to_cardinal(&BigInt::from(d))?);
}
let mut result = out.join(" ");
for ext in [
"mil",
"milhão",
"milhões",
"mil milhões",
"bilião",
"biliões",
"mil biliões",
] {
if pt_match(&result, ext) {
result = pt_drops_e(&result, ext);
}
}
Ok(result)
}
fn fv_divmod(&self, fv: &FloatValue, pow: u32) -> (BigInt, FloatValue) {
match fv {
FloatValue::Float { value, .. } => {
let d = 10f64.powi(pow as i32);
let rem = value % d;
let q = ((value - rem) / d).round();
(
BigInt::from(q as i128),
FloatValue::Float {
value: rem,
precision: float_repr_precision(rem),
},
)
}
FloatValue::Decimal { value, precision } => {
let divisor = BigInt::from(10).pow(pow);
let pre = value.with_scale(0).as_bigint_and_exponent().0;
let q = &pre / &divisor;
let rem = value - BigDecimal::from(&q * &divisor);
(
q,
FloatValue::Decimal {
value: rem,
precision: *precision,
},
)
}
}
}
fn frac_cardinal(&self, fv: &FloatValue, pointword: &str) -> Result<String> {
let hundred = BigInt::from(100);
let e5 = BigInt::from(100_000u32);
let e6 = BigInt::from(1_000_000u32);
let e9 = BigInt::from(1_000_000_000u32);
let e12 = BigInt::from(1_000_000_000_000u64);
let pre = float2tuple(fv).0.abs();
if pre >= e9 {
if pre >= e12 {
let (trillions, remainder) = self.fv_divmod(fv, 12);
let mut result = if trillions.is_one() {
"um trilhão".to_string()
} else {
format!("{} trilhões", self.to_cardinal(&trillions)?)
};
let rem_pre = float2tuple(&remainder).0.abs();
if rem_pre >= e9 {
let (billions, rest) = self.fv_divmod(&remainder, 9);
if billions.is_one() {
result.push_str(", um bilhão");
} else {
result.push_str(&format!(", {} bilhões", self.to_cardinal(&billions)?));
}
let rest_str = fix_milhao(&self.super_float(&rest, pointword)?);
result.push_str(&format!(" e {}", rest_str));
} else {
let remainder_str = fix_milhao(&self.super_float(&remainder, pointword)?);
result.push_str(&format!(" e {}", remainder_str));
}
return Ok(result);
}
let (billions, remainder) = self.fv_divmod(fv, 9);
let mut result = if billions.is_one() {
"um bilhão".to_string()
} else {
format!("{} bilhões", self.to_cardinal(&billions)?)
};
let rem_pre = float2tuple(&remainder).0.abs();
let remainder_str = fix_milhao(&self.super_float(&remainder, pointword)?);
if rem_pre >= e5 {
result.push_str(&format!(", {}", remainder_str));
} else {
result.push_str(&format!(" e {}", remainder_str));
}
return Ok(result);
}
if pre >= e6 {
let (millions, remainder) = self.fv_divmod(fv, 6);
let mut result = if millions.is_one() {
"um milhão".to_string()
} else {
format!("{} milhões", self.super_to_cardinal(&millions)?)
};
let rem_pre = float2tuple(&remainder).0.abs();
let remainder_str = self.frac_cardinal(&remainder, pointword)?;
if rem_pre >= hundred {
result.push_str(&format!(", {}", remainder_str));
} else {
result.push_str(&format!(" e {}", remainder_str));
}
return Ok(result);
}
let mut result = fix_milhao(&self.super_float(fv, pointword)?);
for ext in [
"mil",
"milhão",
"milhões",
"bilhão",
"bilhões",
"trilhão",
"trilhões",
"quatrilhão",
"quatrilhões",
] {
if let Some(rewritten) = ptbr_comma(&result, ext) {
result = rewritten;
}
}
Ok(result)
}
fn cardinal_float_with_pointword(&self, value: &FloatValue, pointword: &str) -> Result<String> {
let abs_fv = fv_abs(value);
let body = self.frac_cardinal(&abs_fv, pointword)?;
if value.is_negative() {
Ok(format!("{}{}", self.negword(), body))
} else {
Ok(body)
}
}
fn pt_to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: &str,
adjective: bool,
) -> Result<String> {
if let CurrencyValue::Int(v) = val {
let cr1 = match self.currency_forms.get(currency) {
Some(f) => &f.unit,
None => return self.to_cardinal(v),
};
let minus_str = if v.is_negative() {
format!("{} ", self.negword().trim())
} else {
String::new()
};
let abs_val = v.abs();
let money_str = self.to_cardinal(&abs_val)?;
let currency_str = if abs_val.is_one() || cr1.len() <= 1 {
cr1.first()
} else {
cr1.get(1)
}
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?
.clone();
let mut result = format!("{}{} {}", minus_str, money_str, currency_str);
for ext in PT_DE_EXTS {
if let Some(rewritten) = insert_de(&result, ext, ¤cy_str) {
result = rewritten;
}
}
return Ok(result.trim().to_string());
}
let mut result = default_to_currency(self, val, currency, cents, separator, adjective)?;
let cr1 = match self.currency_forms.get(currency) {
Some(f) => &f.unit,
None => return Err(N2WError::Key(format!("'{}'", currency))),
};
let plural = cr1
.get(1)
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?
.clone();
for ext in PT_DE_EXTS {
if let Some(rewritten) = insert_de(&result, ext, &plural) {
result = rewritten;
}
}
Ok(result)
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
}
impl Lang for LangPtBr {
fn default_currency(&self) -> &str {
"BRL"
}
fn default_separator(&self) -> &str {
" e"
}
fn cards(&self) -> &Cards {
&self.cards
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
"menos "
}
fn pointword(&self) -> &str {
"vírgula"
}
fn exclude_title(&self) -> &[String] {
&self.exclude_title
}
fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
let (ctext, cnum) = l;
let (ntext, nnum) = r;
let mut ctext = ctext.to_string();
let mut ntext = ntext.to_string();
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, cnum * nnum);
}
ctext = "um".to_string();
} else if cnum == &hundred && nnum > &BigInt::zero() && nnum < &thousand {
ctext = "cento".to_string();
}
if nnum > cnum {
if (nnum % &million).is_zero() && cnum > &BigInt::one() {
if nnum == &million {
ntext = "milhões".to_string();
} else {
let chars: Vec<char> = ntext.chars().collect();
let keep: String = chars[..chars.len().saturating_sub(4)].iter().collect();
ntext = format!("{}lhões", keep);
}
}
if nnum == &hundred {
ctext = self.hundreds_word(cnum).to_string();
ntext = String::new();
} else {
ntext = format!(" {}", ntext);
}
(format!("{}{}", ctext, ntext), cnum * nnum)
} else {
(format!("{} e {}", ctext, ntext), cnum + nnum)
}
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{}{}",
self.negword(),
self.to_cardinal(&(-value))?
));
}
let hundred = BigInt::from(100);
let e5 = BigInt::from(100_000u32);
let e6 = BigInt::from(1_000_000u32);
let e9 = BigInt::from(1_000_000_000u32);
let e12 = BigInt::from(1_000_000_000_000u64);
if value >= &e9 {
if value >= &e12 {
let (trillions, remainder) = value.div_rem(&e12);
let mut result = if trillions.is_one() {
"um trilhão".to_string()
} else {
format!("{} trilhões", self.to_cardinal(&trillions)?)
};
if !remainder.is_zero() {
if remainder >= e9 {
let (billions, rest) = remainder.div_rem(&e9);
if billions.is_one() {
result.push_str(", um bilhão");
} else {
result.push_str(&format!(", {} bilhões", self.to_cardinal(&billions)?));
}
if !rest.is_zero() {
let rest_str = fix_milhao(&self.super_to_cardinal(&rest)?);
result.push_str(&format!(" e {}", rest_str));
}
} else {
let remainder_str = fix_milhao(&self.super_to_cardinal(&remainder)?);
result.push_str(&format!(" e {}", remainder_str));
}
}
return Ok(result);
}
let (billions, remainder) = value.div_rem(&e9);
let mut result = if billions.is_one() {
"um bilhão".to_string()
} else {
format!("{} bilhões", self.to_cardinal(&billions)?)
};
if !remainder.is_zero() {
let remainder_str = fix_milhao(&self.super_to_cardinal(&remainder)?);
if remainder >= e5 {
result.push_str(&format!(", {}", remainder_str));
} else {
result.push_str(&format!(" e {}", remainder_str));
}
}
return Ok(result);
}
if value >= &e6 {
let (millions, remainder) = value.div_rem(&e6);
let mut result = if millions.is_one() {
"um milhão".to_string()
} else {
format!("{} milhões", self.super_to_cardinal(&millions)?)
};
if !remainder.is_zero() {
let remainder_str = self.to_cardinal(&remainder)?;
if remainder >= hundred {
result.push_str(&format!(", {}", remainder_str));
} else {
result.push_str(&format!(" e {}", remainder_str));
}
}
return Ok(result);
}
let mut result = fix_milhao(&self.super_to_cardinal(value)?);
for ext in [
"mil",
"milhão",
"milhões",
"bilhão",
"bilhões",
"trilhão",
"trilhões",
"quatrilhão",
"quatrilhões",
] {
if let Some(rewritten) = ptbr_comma(&result, ext) {
result = rewritten;
}
}
Ok(result)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let s = value.to_string(); let mut result: Vec<String> = Vec::new();
let mut thousand_separator: &str = "";
for (idx, ch) in s.chars().rev().enumerate() {
if idx != 0 && idx % 3 == 0 {
thousand_separator = match self.thousand_separators.get(&idx) {
Some(t) => *t,
None => return Err(N2WError::Key(idx.to_string())),
};
}
if ch != '0' && !thousand_separator.is_empty() {
result.push(thousand_separator.to_string());
thousand_separator = "";
}
let d = ch.to_digit(10).unwrap_or(0) as usize;
result.push(ORDS[idx % 3][d].to_string());
}
result.reverse();
let joined = result.join(" ");
let result = collapse_ws(joined.trim());
if result.starts_with("primeiro") && s != "1" {
return Ok(result.chars().skip(9).collect());
}
Ok(result)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
Ok(format!("{}º", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
return Ok(format!(
"{} antes de Cristo",
self.to_cardinal(&value.abs())?
));
}
self.to_cardinal(value)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
self.to_ordinal(&float_trunc_int(value)?)
}
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
))),
Some(_) => Ok(format!("{}º", repr_str)),
}
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
let fractional_decimal =
matches!(value, FloatValue::Decimal { .. }) && value.as_whole_int().is_none();
let render = |v: &FloatValue| -> Result<String> {
if fractional_decimal {
self.cardinal_float_with_pointword(v, "ponto")
} else {
self.cardinal_float_entry(v, None)
}
};
if fv_numeric_negative(value) {
return Ok(format!("{} antes de Cristo", render(&fv_abs(value))?));
}
render(value)
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
let stripped = s.trim();
let us_style = stripped.contains('.') && !stripped.contains(',');
match python_decimal_parse(s)? {
ParsedNumber::Dec(value) if us_style => Ok(ParsedNumber::DecPoint {
value,
pointword: "ponto",
}),
ParsedNumber::Inf { .. } | ParsedNumber::NaN => Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "[<class 'decimal.InvalidOperation'>]".into(),
}),
other => Ok(other),
}
}
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() { "meio" } else { "meios" }.to_string()
} else if abs_d == BigInt::from(3) {
if abs_n.is_one() { "terço" } else { "terços" }.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 num_word = if abs_n.is_one() {
"um".to_string()
} else {
self.to_cardinal(&abs_n)?
};
let sign = if is_negative {
format!("{} ", self.negword().trim())
} else {
String::new()
};
Ok(format!("{}{} {}", sign, num_word, den_word))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
self.cardinal_float_with_pointword(value, self.pointword())
}
fn cardinal_with_pointword(
&self,
value: &FloatValue,
pointword: &str,
_precision_override: Option<u32>,
) -> Result<String> {
if let Some(i) = value.as_whole_int() {
return self.to_cardinal(&i);
}
self.cardinal_float_with_pointword(value, pointword)
}
fn lang_name(&self) -> &str {
"Num2Word_PT_BR"
}
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 val: CurrencyValue = match val {
CurrencyValue::Decimal { value, .. } if value.is_integer() => {
CurrencyValue::Int(value.with_scale(0).as_bigint_and_exponent().0)
}
other => other.clone(),
};
let mut result = self.pt_to_currency(&val, currency, cents, separator, adjective)?;
if let CurrencyValue::Int(v) = &val {
if let Some(forms) = self.currency_forms.get(currency) {
let cr1 = &forms.unit;
let abs_val = v.abs();
let currency_str = if !abs_val.is_one() && cr1.len() > 1 {
cr1.get(1)
} else {
cr1.first()
}
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))?
.clone();
for ext in PT_BR_DE_EXTS {
if let Some(rewritten) = insert_de(&result, ext, ¤cy_str) {
result = rewritten;
}
}
}
}
Ok(result)
}
}