use crate::base::{Lang, N2WError, Result};
use crate::currency::{parse_currency_parts, CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const ZERO: &str = "பூஜ்ஜியம்";
const NEGWORD: &str = "கழித்தல் ";
const ORDINAL_SUFFIX: &str = "ஆவது";
const ORDINAL_NUM_SUFFIX: &str = "-வது";
const YEAR_THOUSAND: &str = "ஆயிரத்து";
const ONES: [&str; 10] = [
"",
"ஒன்று",
"இரண்டு",
"மூன்று",
"நான்கு",
"ஐந்து",
"ஆறு",
"ஏழு",
"எட்டு",
"ஒன்பது",
];
const TENS: [&str; 10] = [
"",
"பத்து",
"இருபது",
"முப்பது",
"நாற்பது",
"ஐம்பது",
"அறுபது",
"எழுபது",
"எண்பது",
"தொண்ணூறு",
];
const TEENS: [&str; 9] = [
"பதினொன்று",
"பன்னிரண்டு",
"பதின்மூன்று",
"பதினான்கு",
"பதினைந்து",
"பதினாறு",
"பதினேழு",
"பதினெட்டு",
"பத்தொன்பது",
];
const HUNDREDS_SPECIAL: [&str; 9] = [
"நூறு",
"இருநூறு",
"முன்னூறு",
"நானூறு",
"ஐநூறு",
"அறுநூறு",
"எழுநூறு",
"எண்ணூறு",
"தொள்ளாயிரம்",
];
const ORDINALS: [&str; 10] = [
"முதல்",
"இரண்டாம்",
"மூன்றாம்",
"நான்காம்",
"ஐந்தாம்",
"ஆறாம்",
"ஏழாம்",
"எட்டாம்",
"ஒன்பதாம்",
"பத்தாம்",
];
fn crore() -> BigInt {
BigInt::from(10_000_000u32)
}
fn lakh() -> BigInt {
BigInt::from(100_000u32)
}
fn thousand() -> BigInt {
BigInt::from(1_000u32)
}
fn hundred() -> BigInt {
BigInt::from(100u32)
}
fn int_to_word(n: &BigInt) -> String {
if n.is_zero() {
return ZERO.to_string();
}
let mut n = n.clone();
let mut parts: Vec<String> = Vec::new();
let c = crore();
if n >= c {
let crores = n.div_floor(&c);
if crores.is_one() {
parts.push("ஒரு கோடி".to_string());
} else {
parts.push(format!("{} கோடி", int_to_word(&crores)));
}
n = n.mod_floor(&c);
}
let l = lakh();
if n >= l {
let lakhs = n.div_floor(&l);
if lakhs.is_one() {
parts.push("ஒரு இலட்சம்".to_string());
} else {
parts.push(format!("{} இலட்சம்", int_to_word(&lakhs)));
}
n = n.mod_floor(&l);
}
let t = thousand();
if n >= t {
let thousands = n.div_floor(&t);
if thousands.is_one() {
parts.push("ஆயிரம்".to_string());
} else {
parts.push(format!("{} ஆயிரம்", int_to_word(&thousands)));
}
n = n.mod_floor(&t);
}
let h = hundred();
if n >= h {
let hundreds = n.div_floor(&h);
let h_idx = hundreds.to_usize().unwrap_or(usize::MAX);
if (1..=9).contains(&h_idx) {
parts.push(HUNDREDS_SPECIAL[h_idx - 1].to_string());
} else {
parts.push(format!("{} நூறு", ONES[h_idx % 10]));
}
n = n.mod_floor(&h);
}
let mut small = n.to_u32().unwrap_or(0);
if 10 < small && small < 20 {
parts.push(TEENS[(small - 11) as usize].to_string());
} else {
if small >= 10 {
let tens_val = small / 10;
parts.push(TENS[tens_val as usize].to_string());
small %= 10;
}
if small > 0 {
parts.push(ONES[small as usize].to_string());
}
}
parts.join(" ")
}
fn int_to_cardinal(n: &BigInt) -> String {
if n.is_zero() {
return ZERO.to_string();
}
if n.sign() == num_bigint::Sign::Minus {
return format!("{}{}", NEGWORD, int_to_word(&(-n)));
}
int_to_word(n)
}
fn int_to_ordinal(n: &BigInt) -> String {
if n.is_zero() {
return ZERO.to_string();
}
if let Some(i) = n.to_usize() {
if (1..=10).contains(&i) {
return ORDINALS[i - 1].to_string();
}
}
format!("{}{}", int_to_cardinal(n), ORDINAL_SUFFIX)
}
fn cardinal_from_fractional_cents(right: &BigDecimal) -> String {
let truncated = right.to_f64().map(f64::trunc).unwrap_or(0.0);
int_to_cardinal(&BigInt::from(truncated as i64))
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const RUPEE: [&str; 2] = ["ரூபாய்", "ரூபாய்"];
const DOLLAR: [&str; 2] = ["டாலர்", "டாலர்"];
const CENT: [&str; 2] = ["சென்ட்", "சென்ட்"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("INR", CurrencyForms::new(&RUPEE, &["பைசா", "பைசா"]));
m.insert("LKR", CurrencyForms::new(&RUPEE, &["சதம்", "சதம்"]));
m.insert("USD", CurrencyForms::new(&DOLLAR, &CENT));
m.insert("EUR", CurrencyForms::new(&["யூரோ", "யூரோ"], &CENT));
m.insert(
"GBP",
CurrencyForms::new(&["பவுண்ட்", "பவுண்ட்"], &["பென்னி", "பென்னி"]),
);
m.insert("SGD", CurrencyForms::new(&DOLLAR, &CENT));
m.insert(
"MYR",
CurrencyForms::new(&["ரிங்கிட்", "ரிங்கிட்"], &["சென்", "சென்"]),
);
m
}
pub struct LangTa {
currency_forms: HashMap<&'static str, CurrencyForms>,
decimal_ctx_limit: BigInt,
}
impl LangTa {
pub fn new() -> Self {
LangTa {
currency_forms: build_currency_forms(),
decimal_ctx_limit: BigInt::from(10u8).pow(26),
}
}
}
impl Default for LangTa {
fn default() -> Self {
Self::new()
}
}
impl Lang for LangTa {
fn default_currency(&self) -> &str {
"INR"
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"புள்ளி"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
Ok(int_to_cardinal(value))
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let truncated = match value {
FloatValue::Float { value, .. } => {
BigInt::from_f64(value.trunc()).unwrap_or_else(BigInt::zero)
}
FloatValue::Decimal { value, .. } => value.with_scale(0).as_bigint_and_exponent().0,
};
Ok(int_to_cardinal(&truncated))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
Ok(int_to_ordinal(value))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("{}{}", value, ORDINAL_NUM_SUFFIX))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
let t = thousand();
if value < &t {
return Ok(int_to_cardinal(value));
}
let remainder = value.mod_floor(&t);
if value < &BigInt::from(2000u32) {
let mut result = YEAR_THOUSAND.to_string();
if is_positive(&remainder) {
result.push(' ');
result.push_str(&int_to_cardinal(&remainder));
}
return Ok(result);
}
let thousands = value.div_floor(&t);
let mut result = format!("{} {}", int_to_cardinal(&thousands), YEAR_THOUSAND);
if is_positive(&remainder) {
result.push(' ');
result.push_str(&int_to_cardinal(&remainder));
}
Ok(result)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
Ok(int_to_ordinal(&ta_trunc(value)))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("{}{}", repr_str, ORDINAL_NUM_SUFFIX))
}
fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
let (x, is_decimal) = match value {
FloatValue::Float { value, .. } => (
BigDecimal::from_f64(*value).ok_or_else(|| {
N2WError::Value(format!("cannot convert {} to Decimal", value))
})?,
false,
),
FloatValue::Decimal { value, .. } => (value.clone(), true),
};
ta_year_sim(&x, is_decimal)
}
fn lang_name(&self) -> &str {
"Num2Word_TA"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
_cents: bool,
_separator: Option<&str>,
_adjective: bool,
) -> Result<String> {
if let CurrencyValue::Int(v) = val {
if v.abs() >= self.decimal_ctx_limit {
return Ok(format!("{} {}", v, currency));
}
}
let has_fractional_cents = match val {
CurrencyValue::Int(_) => false,
CurrencyValue::Decimal { value, .. } => {
let scaled = value * BigDecimal::from(100);
&scaled - scaled.with_scale(0) != BigDecimal::zero()
}
};
let (left, right, is_negative) =
parse_currency_parts(val, false, has_fractional_cents, 100);
let forms = self.currency_forms.get(currency).ok_or_else(|| {
N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
))
})?;
let mut result: Vec<String> = Vec::new();
if is_negative {
result.push(NEGWORD.trim().to_string());
}
result.push(int_to_cardinal(&left));
result.push(forms.unit[0].clone());
if right > BigDecimal::zero() {
let right_words = if has_fractional_cents {
cardinal_from_fractional_cents(&right)
} else {
int_to_cardinal(&right.as_bigint_and_exponent().0)
};
result.push(right_words);
result.push(forms.subunit[0].clone());
}
Ok(result.join(" "))
}
}
fn is_positive(n: &BigInt) -> bool {
n.sign() == num_bigint::Sign::Plus
}
fn ta_trunc(value: &FloatValue) -> BigInt {
match value {
FloatValue::Float { value, .. } => {
BigInt::from_f64(value.trunc()).unwrap_or_else(BigInt::zero)
}
FloatValue::Decimal { value, .. } => value.with_scale(0).as_bigint_and_exponent().0,
}
}
fn ta_type_error(is_decimal: bool) -> N2WError {
N2WError::Type(format!(
"list indices must be integers or slices, not {}",
if is_decimal {
"decimal.Decimal"
} else {
"float"
}
))
}
fn ta_floordiv(a: &BigDecimal, b: &BigDecimal) -> BigInt {
(a / b).with_scale(0).as_bigint_and_exponent().0
}
fn ta_mod(a: &BigDecimal, b: &BigDecimal) -> BigDecimal {
a - BigDecimal::from(ta_floordiv(a, b)) * b
}
fn ta_word_sim(n: &BigDecimal, is_decimal: bool) -> Result<String> {
if n.is_zero() {
return Ok(ZERO.to_string());
}
let mut n = n.clone();
let mut parts: Vec<String> = Vec::new();
let crore_bd = BigDecimal::from(10_000_000u32);
if n >= crore_bd {
let crores = BigDecimal::from(ta_floordiv(&n, &crore_bd));
if crores == BigDecimal::from(1u32) {
parts.push("ஒரு கோடி".to_string());
} else {
parts.push(format!("{} கோடி", ta_word_sim(&crores, is_decimal)?));
}
n = ta_mod(&n, &crore_bd);
}
let lakh_bd = BigDecimal::from(100_000u32);
if n >= lakh_bd {
let lakhs = BigDecimal::from(ta_floordiv(&n, &lakh_bd));
if lakhs == BigDecimal::from(1u32) {
parts.push("ஒரு இலட்சம்".to_string());
} else {
parts.push(format!("{} இலட்சம்", ta_word_sim(&lakhs, is_decimal)?));
}
n = ta_mod(&n, &lakh_bd);
}
let thousand_bd = BigDecimal::from(1000u32);
if n >= thousand_bd {
let thousands = BigDecimal::from(ta_floordiv(&n, &thousand_bd));
if thousands == BigDecimal::from(1u32) {
parts.push("ஆயிரம்".to_string());
} else {
parts.push(format!("{} ஆயிரம்", ta_word_sim(&thousands, is_decimal)?));
}
n = ta_mod(&n, &thousand_bd);
}
let hundred_bd = BigDecimal::from(100u32);
if n >= hundred_bd {
let h = ta_floordiv(&n, &hundred_bd);
let h_idx = h.to_usize().unwrap_or(0);
debug_assert!((1..=9).contains(&h_idx));
parts.push(HUNDREDS_SPECIAL[h_idx - 1].to_string());
n = ta_mod(&n, &hundred_bd);
}
let ten_bd = BigDecimal::from(10u32);
let twenty_bd = BigDecimal::from(20u32);
if n > ten_bd && n < twenty_bd {
if n.is_integer() {
let i = n
.with_scale(0)
.as_bigint_and_exponent()
.0
.to_usize()
.expect("teens index 11..=19 fits usize");
parts.push(TEENS[i - 11].to_string());
} else {
return Err(N2WError::Key(format!("{}", n)));
}
} else {
if n >= ten_bd {
return Err(ta_type_error(is_decimal));
}
if n > BigDecimal::from(0u32) {
return Err(ta_type_error(is_decimal));
}
}
Ok(parts.join(" "))
}
fn ta_cardinal_sim(n: &BigDecimal, is_decimal: bool) -> Result<String> {
if n.is_zero() {
return Ok(ZERO.to_string());
}
if n.is_negative() {
let pos = -n.clone();
return Ok(format!("{}{}", NEGWORD, ta_word_sim(&pos, is_decimal)?));
}
ta_word_sim(n, is_decimal)
}
fn ta_year_sim(n: &BigDecimal, is_decimal: bool) -> Result<String> {
let thousand_bd = BigDecimal::from(1000u32);
if *n < thousand_bd {
return ta_cardinal_sim(n, is_decimal);
}
let remainder = ta_mod(n, &thousand_bd);
if *n < BigDecimal::from(2000u32) {
let mut result = YEAR_THOUSAND.to_string();
if remainder > BigDecimal::from(0u32) {
result.push(' ');
result.push_str(&ta_cardinal_sim(&remainder, is_decimal)?);
}
return Ok(result);
}
let thousands = BigDecimal::from(ta_floordiv(n, &thousand_bd));
let mut result = format!(
"{} {}",
ta_cardinal_sim(&thousands, is_decimal)?,
YEAR_THOUSAND
);
if remainder > BigDecimal::from(0u32) {
result.push(' ');
result.push_str(&ta_cardinal_sim(&remainder, is_decimal)?);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn cur(arg: &str, currency: &str) -> Result<String> {
let is_int = !arg.contains('.') && !arg.to_lowercase().contains('e');
let val = CurrencyValue::parse(arg, is_int, !is_int, !is_int).unwrap();
LangTa::new().to_currency(&val, currency, true, None, false)
}
fn cheque(arg: &str, currency: &str) -> Result<String> {
use std::str::FromStr;
LangTa::new().to_cheque(&BigDecimal::from_str(arg).unwrap(), currency)
}
#[test]
fn corpus_currency() {
for (arg, want) in [
("0", "பூஜ்ஜியம் யூரோ"),
("1", "ஒன்று யூரோ"),
("2", "இரண்டு யூரோ"),
("100", "நூறு யூரோ"),
("12.34", "பன்னிரண்டு யூரோ முப்பது நான்கு சென்ட்"),
("0.01", "பூஜ்ஜியம் யூரோ ஒன்று சென்ட்"),
("1.0", "ஒன்று யூரோ"),
("99.99", "தொண்ணூறு ஒன்பது யூரோ தொண்ணூறு ஒன்பது சென்ட்"),
("1234.56", "ஆயிரம் இருநூறு முப்பது நான்கு யூரோ ஐம்பது ஆறு சென்ட்"),
("-12.34", "கழித்தல் பன்னிரண்டு யூரோ முப்பது நான்கு சென்ட்"),
("1000000", "பத்து இலட்சம் யூரோ"),
("0.5", "பூஜ்ஜியம் யூரோ ஐம்பது சென்ட்"),
] {
assert_eq!(cur(arg, "EUR").unwrap(), want, "EUR {}", arg);
}
assert_eq!(
cur("12.34", "INR").unwrap(),
"பன்னிரண்டு ரூபாய் முப்பது நான்கு பைசா"
);
assert_eq!(
cur("12.34", "USD").unwrap(),
"பன்னிரண்டு டாலர் முப்பது நான்கு சென்ட்"
);
assert_eq!(
cur("12.34", "GBP").unwrap(),
"பன்னிரண்டு பவுண்ட் முப்பது நான்கு பென்னி"
);
assert_eq!(cur("1.05", "LKR").unwrap(), "ஒன்று ரூபாய் ஐந்து சதம்");
assert_eq!(cur("1.05", "MYR").unwrap(), "ஒன்று ரிங்கிட் ஐந்து சென்");
assert_eq!(cur("1.05", "SGD").unwrap(), "ஒன்று டாலர் ஐந்து சென்ட்");
}
#[test]
fn corpus_currency_not_implemented() {
for code in ["JPY", "KWD", "BHD", "CNY", "CHF"] {
match cur("12.34", code) {
Err(N2WError::NotImplemented(m)) => assert_eq!(
m,
format!(
"Currency code \"{}\" not implemented for \"Num2Word_TA\"",
code
)
),
other => panic!("{}: expected NotImplemented, got {:?}", code, other),
}
}
}
#[test]
fn corpus_cheque() {
for (code, want) in [
("EUR", "ஆயிரம் இருநூறு முப்பது நான்கு AND 56/100 யூரோ"),
("USD", "ஆயிரம் இருநூறு முப்பது நான்கு AND 56/100 டாலர்"),
("GBP", "ஆயிரம் இருநூறு முப்பது நான்கு AND 56/100 பவுண்ட்"),
("INR", "ஆயிரம் இருநூறு முப்பது நான்கு AND 56/100 ரூபாய்"),
] {
assert_eq!(cheque("1234.56", code).unwrap(), want, "cheque {}", code);
}
for code in ["JPY", "KWD", "BHD", "CNY", "CHF"] {
assert!(matches!(
cheque("1234.56", code),
Err(N2WError::NotImplemented(_))
));
}
assert_eq!(
cheque("-1234.56", "INR").unwrap(),
"MINUS ஆயிரம் இருநூறு முப்பது நான்கு AND 56/100 ரூபாய்"
);
assert_eq!(cheque("1.05", "INR").unwrap(), "ஒன்று AND 05/100 ரூபாய்");
}
#[test]
fn negatives_take_one_stripped_negword() {
assert_eq!(cur("-0.5", "INR").unwrap(), "கழித்தல் பூஜ்ஜியம் ரூபாய் ஐம்பது பைசா");
assert_eq!(cur("-5", "INR").unwrap(), "கழித்தல் ஐந்து ரூபாய்");
assert_eq!(cur("-1.0", "INR").unwrap(), "கழித்தல் ஒன்று ரூபாய்");
assert_eq!(cur("-0.01", "INR").unwrap(), "கழித்தல் பூஜ்ஜியம் ரூபாய் ஒன்று பைசா");
}
#[test]
fn fractional_cents_truncate() {
assert_eq!(cur("2.675", "INR").unwrap(), "இரண்டு ரூபாய் அறுபது ஏழு பைசா");
assert_eq!(cur("0.655", "INR").unwrap(), "பூஜ்ஜியம் ரூபாய் அறுபது ஐந்து பைசா");
assert_eq!(
cur("12.345", "INR").unwrap(),
"பன்னிரண்டு ரூபாய் முப்பது நான்கு பைசா"
);
assert_eq!(cur("1.155", "INR").unwrap(), "ஒன்று ரூபாய் பதினைந்து பைசா");
assert_eq!(cur("1.005", "INR").unwrap(), "ஒன்று ரூபாய் பூஜ்ஜியம் பைசா");
assert_eq!(cur("1.00001", "INR").unwrap(), "ஒன்று ரூபாய் பூஜ்ஜியம் பைசா");
}
#[test]
fn fractional_cents_go_through_float() {
assert_eq!(
cur("1.029999999999999999999", "INR").unwrap(),
"ஒன்று ரூபாய் மூன்று பைசா"
);
assert_eq!(
cur("1.999999999999999999999", "INR").unwrap(),
"ஒன்று ரூபாய் நூறு பைசா"
);
}
#[test]
fn int_decimal_context_limit() {
let below = "99999999999999999999999999"; assert!(cur(below, "INR")
.unwrap()
.starts_with("தொண்ணூறு ஒன்பது ஆயிரம்"));
assert_eq!(
cur("100000000000000000000000000", "INR").unwrap(),
"100000000000000000000000000 INR"
);
assert_eq!(
cur("-100000000000000000000000000", "INR").unwrap(),
"-100000000000000000000000000 INR"
);
assert!(cur("-99999999999999999999999999", "INR")
.unwrap()
.starts_with("கழித்தல்"));
assert_eq!(
cur("1000000000000000000000000000000", "JPY").unwrap(),
"1000000000000000000000000000000 JPY"
);
assert!(matches!(
cur("10000000000000000000000000", "JPY"),
Err(N2WError::NotImplemented(_))
));
}
#[test]
fn corpus_cardinal_float() {
let l = LangTa::new();
let cases: &[(f64, u32, &str)] = &[
(0.0, 1, "பூஜ்ஜியம்"),
(0.5, 1, "பூஜ்ஜியம்"),
(1.0, 1, "ஒன்று"),
(1.5, 1, "ஒன்று"),
(2.25, 2, "இரண்டு"),
(3.14, 2, "மூன்று"),
(0.01, 2, "பூஜ்ஜியம்"),
(0.1, 1, "பூஜ்ஜியம்"),
(0.99, 2, "பூஜ்ஜியம்"),
(1.01, 2, "ஒன்று"),
(12.34, 2, "பன்னிரண்டு"),
(99.99, 2, "தொண்ணூறு ஒன்பது"),
(100.5, 1, "நூறு"),
(1234.56, 2, "ஆயிரம் இருநூறு முப்பது நான்கு"),
(-0.5, 1, "பூஜ்ஜியம்"),
(-1.5, 1, "கழித்தல் ஒன்று"),
(-12.34, 2, "கழித்தல் பன்னிரண்டு"),
(1.005, 3, "ஒன்று"),
(2.675, 3, "இரண்டு"),
];
for (v, p, want) in cases {
let fv = FloatValue::Float {
value: *v,
precision: *p,
};
assert_eq!(
l.to_cardinal_float(&fv, None).unwrap(),
*want,
"float {}",
v
);
}
}
#[test]
fn corpus_cardinal_decimal() {
use std::str::FromStr;
let l = LangTa::new();
let cases: &[(&str, u32, &str)] = &[
("0.01", 2, "பூஜ்ஜியம்"),
("1.10", 2, "ஒன்று"),
("12.345", 3, "பன்னிரண்டு"),
(
"98746251323029.99",
2,
"தொண்ணூறு எட்டு இலட்சம் எழுபது நான்கு ஆயிரம் அறுநூறு இருபது ஐந்து கோடி பதின்மூன்று இலட்சம் இருபது மூன்று ஆயிரம் இருபது ஒன்பது",
),
("0.001", 3, "பூஜ்ஜியம்"),
("-1.5", 1, "கழித்தல் ஒன்று"),
("-0.5", 1, "பூஜ்ஜியம்"),
];
for (s, p, want) in cases {
let fv = FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision: *p,
};
assert_eq!(
l.to_cardinal_float(&fv, None).unwrap(),
*want,
"decimal {}",
s
);
}
}
#[test]
fn extra_kwargs_are_inert() {
let l = LangTa::new();
let v = CurrencyValue::parse("12.34", false, true, true).unwrap();
let want = "பன்னிரண்டு ரூபாய் முப்பது நான்கு பைசா";
assert_eq!(l.to_currency(&v, "INR", true, None, false).unwrap(), want);
assert_eq!(
l.to_currency(&v, "INR", false, Some(" X"), true).unwrap(),
want
);
}
}