use crate::base::{Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::python_decimal_str;
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const NEGWORD: &str = "ngaphantsi kwe ";
const POINTWORD: &str = "ichaphaza";
const ZERO_WORD: &str = "iqanda";
const ONES: [&str; 10] = [
"",
"nye",
"mbini",
"ntathu",
"ne",
"ntlanu",
"ntandathu",
"sixhenxe",
"sibhozo",
"lithoba",
];
const TENS: [&str; 10] = [
"",
"lishumi",
"amashumi amabini",
"amashumi amathathu",
"amashumi amane",
"amashumi amahlanu",
"amashumi amathandathu",
"amashumi asixhenxe",
"amashumi asibhozo",
"amashumi alithoba",
];
const HUNDRED: &str = "ikhulu";
const THOUSAND: &str = "iwaka";
const MILLION: &str = "isigidi";
fn digit(n: &BigInt) -> usize {
n.to_usize()
.expect("XH: table index provably in 0..=9 by the enclosing bound")
}
fn int_to_word(number: &BigInt) -> String {
if number.is_zero() {
return ZERO_WORD.to_string();
}
let ten = BigInt::from(10);
let hundred = BigInt::from(100);
let thousand = BigInt::from(1000);
let million = BigInt::from(1_000_000);
let billion = BigInt::from(1_000_000_000);
let one = BigInt::one();
if number < &ten {
return ONES[digit(number)].to_string();
}
if number < &hundred {
let (t, o) = number.div_mod_floor(&ten);
let mut out = TENS[digit(&t)].to_string();
if !o.is_zero() {
out.push_str(" ana ");
out.push_str(ONES[digit(&o)]);
}
return out;
}
if number < &thousand {
let (h, r) = number.div_mod_floor(&hundred);
let mut out = String::new();
if h > one {
out.push_str(ONES[digit(&h)]);
out.push(' ');
}
out.push_str(HUNDRED);
if !r.is_zero() {
out.push_str(" na ");
out.push_str(&int_to_word(&r));
}
return out;
}
if number < &million {
let (t, r) = number.div_mod_floor(&thousand);
let mut out = String::new();
if t > one {
out.push_str(&int_to_word(&t));
out.push(' ');
}
out.push_str(THOUSAND);
if !r.is_zero() {
out.push_str(" na ");
out.push_str(&int_to_word(&r));
}
return out;
}
if number < &billion {
let (m, r) = number.div_mod_floor(&million);
let mut out = String::new();
if m > one {
out.push_str(&int_to_word(&m));
out.push(' ');
}
out.push_str(MILLION);
if !r.is_zero() {
out.push_str(" na ");
out.push_str(&int_to_word(&r));
}
return out;
}
number.to_string()
}
fn float_unsigned_repr(abs: f64, precision: u32) -> String {
if abs.is_nan() {
return "nan".to_string();
}
if abs.is_infinite() {
return "inf".to_string();
}
if abs != 0.0 && abs < 1e-4 {
let s = format!("{}", abs);
let frac = &s[2..];
let zeros = frac.bytes().take_while(|&b| b == b'0').count();
let sig = &frac[zeros..];
let coeff = if sig.len() == 1 {
sig.to_string()
} else {
format!("{}.{}", &sig[..1], &sig[1..])
};
return format!("{}e-{:02}", coeff, zeros + 1);
}
if abs >= 1e16 {
let s = format!("{}", abs);
let sig = s.trim_end_matches('0');
let coeff = if sig.len() == 1 {
sig.to_string()
} else {
format!("{}.{}", &sig[..1], &sig[1..])
};
return format!("{}e+{:02}", coeff, s.len() - 1);
}
format!("{:.*}", precision as usize, abs)
}
fn xh_string_to_words(n: &str) -> Result<String> {
if let Some((left, right)) = n.split_once('.') {
let left_int = left.parse::<BigInt>().map_err(|_| {
N2WError::Value(format!(
"invalid literal for int() with base 10: '{}'",
left
))
})?;
let mut ret = int_to_word(&left_int);
ret.push(' ');
ret.push_str(POINTWORD);
for ch in right.chars() {
let d = ch.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", ch))
})? as usize;
ret.push(' ');
if ONES[d].is_empty() {
ret.push_str("qanda");
} else {
ret.push_str(ONES[d]);
}
}
Ok(ret)
} else {
let v = n.parse::<BigInt>().map_err(|_| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", n))
})?;
Ok(int_to_word(&v))
}
}
fn split_value(val: &CurrencyValue) -> (bool, BigInt, BigInt) {
match val {
CurrencyValue::Int(v) => (v.is_negative(), v.abs(), BigInt::zero()),
CurrencyValue::Decimal {
value, has_decimal, ..
} => {
let is_negative = value.is_negative();
let abs = value.abs();
let left_dec = abs.with_scale(0);
let left = left_dec.as_bigint_and_exponent().0;
let right = if *has_decimal {
let frac = &abs - &left_dec;
(frac * BigDecimal::from(100))
.with_scale(0)
.as_bigint_and_exponent()
.0
} else {
BigInt::zero()
};
(is_negative, left, right)
}
}
}
fn indexed_form(forms: &[String], n: &BigInt) -> Result<String> {
let i = if n.is_one() { 0 } else { 1 };
forms
.get(i)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
pub struct LangXh {
currency_forms: HashMap<&'static str, CurrencyForms>,
fallback_forms: CurrencyForms,
}
impl Default for LangXh {
fn default() -> Self {
Self::new()
}
}
impl LangXh {
pub fn new() -> Self {
const ISENTI: [&str; 2] = ["isenti", "isenti"];
let entries = [
("ZAR", CurrencyForms::new(&["randi", "randi"], &ISENTI)),
("USD", CurrencyForms::new(&["idola", "idola"], &ISENTI)),
("EUR", CurrencyForms::new(&["iyuro", "iyuro"], &ISENTI)),
];
let fallback_forms = entries[0].1.clone();
LangXh {
currency_forms: entries.into_iter().collect(),
fallback_forms,
}
}
}
impl Lang for LangXh {
fn default_currency(&self) -> &str {
"ZAR"
}
fn default_separator(&self) -> &str {
" "
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"ichaphaza"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
if value.is_negative() {
let words = int_to_word(&value.abs());
return Ok(format!("{}{}", NEGWORD, words).trim().to_string());
}
Ok(int_to_word(value))
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
if value.is_one() {
return Ok("okokuqala".to_string());
}
Ok(format!("we{}", self.to_cardinal(value)?))
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
Ok(format!("we{}", value))
}
fn to_year(&self, value: &BigInt) -> Result<String> {
self.to_cardinal(value)
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let (is_negative, unsigned) = match value {
FloatValue::Float { value, precision } => (
value.is_sign_negative(),
float_unsigned_repr(value.abs(), *precision),
),
FloatValue::Decimal { value, .. } => {
(value.is_negative(), python_decimal_str(&value.abs()))
}
};
let body = xh_string_to_words(&unsigned)?;
if is_negative {
Ok(format!("{}{}", NEGWORD, body).trim().to_string())
} else {
Ok(body)
}
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
if value.as_whole_int().is_some_and(|i| i.is_one()) {
return Ok("okokuqala".to_string());
}
Ok(format!("we{}", self.cardinal_float_entry(value, None)?))
}
fn ordinal_num_float_entry(&self, _value: &FloatValue, repr_str: &str) -> Result<String> {
Ok(format!("we{}", repr_str))
}
fn inf_result(&self, negative: bool, to: &str) -> Result<String> {
match to {
"ordinal_num" => Ok(format!("we{}Infinity", if negative { "-" } else { "" })),
_ => Err(N2WError::Value(
"invalid literal for int() with base 10: 'Infinity'".into(),
)),
}
}
fn nan_result(&self, to: &str) -> Result<String> {
match to {
"ordinal_num" => Ok("weNaN".into()),
_ => Err(N2WError::Value(
"invalid literal for int() with base 10: 'NaN'".into(),
)),
}
}
fn lang_name(&self) -> &str {
"Num2Word_XH"
}
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());
}
Ok(if n.is_one() {
forms[0].clone()
} else {
forms[forms.len() - 1].clone()
})
}
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 (is_negative, left, right) = split_value(val);
let forms = self
.currency_forms
.get(currency)
.unwrap_or(&self.fallback_forms);
let mut result = format!(
"{} {}",
int_to_word(&left),
indexed_form(&forms.unit, &left)?
);
if cents && !right.is_zero() {
result.push_str(separator);
result.push_str(&int_to_word(&right));
result.push(' ');
result.push_str(&indexed_form(&forms.subunit, &right)?);
}
if is_negative {
result = format!("{}{}", NEGWORD, result);
}
Ok(result.trim().to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use bigdecimal::BigDecimal;
use std::str::FromStr;
fn cur_sep(arg: &str, code: &str, cents: bool, sep: Option<&str>) -> String {
let is_int = !arg.contains('.') && !arg.to_lowercase().contains('e');
let v = CurrencyValue::parse(arg, is_int, !is_int, !is_int).unwrap();
LangXh::new()
.to_currency(&v, code, cents, sep, false)
.unwrap()
}
fn cur(arg: &str, code: &str) -> String {
cur_sep(arg, code, true, None)
}
fn cheque(arg: &str, code: &str) -> Result<String> {
LangXh::new().to_cheque(&BigDecimal::from_str(arg).unwrap(), code)
}
#[test]
fn corpus_currency_known_codes() {
for (code, unit) in [("EUR", "iyuro"), ("USD", "idola")] {
assert_eq!(cur("0", code), format!("iqanda {unit}"));
assert_eq!(cur("1", code), format!("nye {unit}"));
assert_eq!(cur("2", code), format!("mbini {unit}"));
assert_eq!(cur("100", code), format!("ikhulu {unit}"));
assert_eq!(cur("1000000", code), format!("isigidi {unit}"));
assert_eq!(cur("1.0", code), format!("nye {unit}"));
assert_eq!(
cur("12.34", code),
format!("lishumi ana mbini {unit} amashumi amathathu ana ne isenti")
);
assert_eq!(cur("0.01", code), format!("iqanda {unit} nye isenti"));
assert_eq!(
cur("0.5", code),
format!("iqanda {unit} amashumi amahlanu isenti")
);
assert_eq!(
cur("99.99", code),
format!(
"amashumi alithoba ana lithoba {unit} \
amashumi alithoba ana lithoba isenti"
)
);
assert_eq!(
cur("1234.56", code),
format!(
"iwaka na mbini ikhulu na amashumi amathathu ana ne {unit} \
amashumi amahlanu ana ntandathu isenti"
)
);
assert_eq!(
cur("-12.34", code),
format!("ngaphantsi kwe lishumi ana mbini {unit} amashumi amathathu ana ne isenti")
);
}
}
#[test]
fn corpus_currency_unknown_codes_fall_back_to_zar() {
for code in ["GBP", "JPY", "KWD", "BHD", "INR", "CNY", "CHF"] {
assert_eq!(cur("0", code), "iqanda randi");
assert_eq!(cur("1", code), "nye randi");
assert_eq!(cur("2", code), "mbini randi");
assert_eq!(cur("100", code), "ikhulu randi");
assert_eq!(cur("1000000", code), "isigidi randi");
assert_eq!(cur("1.0", code), "nye randi");
assert_eq!(
cur("12.34", code),
"lishumi ana mbini randi amashumi amathathu ana ne isenti"
);
assert_eq!(cur("0.01", code), "iqanda randi nye isenti");
assert_eq!(cur("0.5", code), "iqanda randi amashumi amahlanu isenti");
assert_eq!(
cur("99.99", code),
"amashumi alithoba ana lithoba randi amashumi alithoba ana lithoba isenti"
);
assert_eq!(
cur("1234.56", code),
"iwaka na mbini ikhulu na amashumi amathathu ana ne randi \
amashumi amahlanu ana ntandathu isenti"
);
assert_eq!(
cur("-12.34", code),
"ngaphantsi kwe lishumi ana mbini randi amashumi amathathu ana ne isenti"
);
}
assert_eq!(cur("12.34", "ZAR"), cur("12.34", "GBP"));
}
#[test]
fn python_parity_edge_cases() {
assert_eq!(cur("1.005", "ZAR"), "nye randi");
assert_eq!(
cur("1.999", "ZAR"),
"nye randi amashumi alithoba ana lithoba isenti"
);
assert_eq!(
cur("12.349", "ZAR"),
"lishumi ana mbini randi amashumi amathathu ana ne isenti"
);
assert_eq!(cur("-0.0", "ZAR"), "iqanda randi");
assert_eq!(cur("-1", "ZAR"), "ngaphantsi kwe nye randi");
assert_eq!(cur("1000000000", "ZAR"), "1000000000 randi");
let five = CurrencyValue::parse("5", false, false, false).unwrap();
assert_eq!(
LangXh::new()
.to_currency(&five, "ZAR", true, None, false)
.unwrap(),
"ntlanu randi"
);
let five_00 = CurrencyValue::parse("5.00", false, true, true).unwrap();
assert_eq!(
LangXh::new()
.to_currency(&five_00, "ZAR", true, None, false)
.unwrap(),
"ntlanu randi"
);
assert_eq!(
cur_sep("12.34", "EUR", false, None),
"lishumi ana mbini iyuro"
);
assert_eq!(
cur_sep("12.34", "EUR", true, Some(" na")),
"lishumi ana mbini iyuro naamashumi amathathu ana ne isenti"
);
}
fn card_float(value: f64, precision: u32) -> String {
LangXh::new()
.to_cardinal_float(&FloatValue::Float { value, precision }, None)
.unwrap()
}
fn card_dec(s: &str) -> String {
let precision = s.split_once('.').map(|(_, f)| f.len() as u32).unwrap_or(0);
let value = BigDecimal::from_str(s).unwrap();
LangXh::new()
.to_cardinal_float(&FloatValue::Decimal { value, precision }, None)
.unwrap()
}
#[test]
fn corpus_cardinal_float() {
assert_eq!(card_float(0.0, 1), "iqanda ichaphaza qanda");
assert_eq!(card_float(0.5, 1), "iqanda ichaphaza ntlanu");
assert_eq!(card_float(1.0, 1), "nye ichaphaza qanda");
assert_eq!(card_float(1.5, 1), "nye ichaphaza ntlanu");
assert_eq!(card_float(2.25, 2), "mbini ichaphaza mbini ntlanu");
assert_eq!(card_float(3.14, 2), "ntathu ichaphaza nye ne");
assert_eq!(card_float(0.01, 2), "iqanda ichaphaza qanda nye");
assert_eq!(card_float(0.1, 1), "iqanda ichaphaza nye");
assert_eq!(card_float(0.99, 2), "iqanda ichaphaza lithoba lithoba");
assert_eq!(card_float(1.01, 2), "nye ichaphaza qanda nye");
assert_eq!(
card_float(12.34, 2),
"lishumi ana mbini ichaphaza ntathu ne"
);
assert_eq!(
card_float(99.99, 2),
"amashumi alithoba ana lithoba ichaphaza lithoba lithoba"
);
assert_eq!(card_float(100.5, 1), "ikhulu ichaphaza ntlanu");
assert_eq!(
card_float(1234.56, 2),
"iwaka na mbini ikhulu na amashumi amathathu ana ne ichaphaza ntlanu ntandathu"
);
assert_eq!(
card_float(-0.5, 1),
"ngaphantsi kwe iqanda ichaphaza ntlanu"
);
assert_eq!(card_float(-1.5, 1), "ngaphantsi kwe nye ichaphaza ntlanu");
assert_eq!(
card_float(-12.34, 2),
"ngaphantsi kwe lishumi ana mbini ichaphaza ntathu ne"
);
assert_eq!(card_float(1.005, 3), "nye ichaphaza qanda qanda ntlanu");
assert_eq!(
card_float(2.675, 3),
"mbini ichaphaza ntandathu sixhenxe ntlanu"
);
}
#[test]
fn corpus_cardinal_decimal() {
assert_eq!(card_dec("0.01"), "iqanda ichaphaza qanda nye");
assert_eq!(card_dec("1.10"), "nye ichaphaza nye qanda");
assert_eq!(
card_dec("12.345"),
"lishumi ana mbini ichaphaza ntathu ne ntlanu"
);
assert_eq!(
card_dec("98746251323029.99"),
"98746251323029 ichaphaza lithoba lithoba"
);
assert_eq!(card_dec("0.001"), "iqanda ichaphaza qanda qanda nye");
assert_eq!(card_dec("1.5000"), "nye ichaphaza ntlanu qanda qanda qanda");
assert_eq!(
card_dec("-12.34"),
"ngaphantsi kwe lishumi ana mbini ichaphaza ntathu ne"
);
}
#[test]
fn negative_zero_float_gets_negword() {
assert_eq!(card_float(-0.0, 1), "ngaphantsi kwe iqanda ichaphaza qanda");
assert_eq!(card_float(0.0, 1), "iqanda ichaphaza qanda");
}
fn card_float_err(value: f64, precision: u32) -> N2WError {
LangXh::new()
.to_cardinal_float(&FloatValue::Float { value, precision }, None)
.unwrap_err()
}
fn card_dec_p(s: &str, precision: u32) -> Result<String> {
LangXh::new().to_cardinal_float(
&FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision,
},
None,
)
}
fn assert_value_err(err: N2WError, token: &str) {
match err {
N2WError::Value(msg) => assert_eq!(
msg,
format!("invalid literal for int() with base 10: '{token}'")
),
other => panic!("expected ValueError, got {other:?}"),
}
}
#[test]
fn scientific_notation_str_raises_valueerror() {
assert_value_err(card_float_err(1e-05, 5), "1e-05");
assert_value_err(card_float_err(1.5e-05, 6), "e");
assert_value_err(card_float_err(-1.5e-05, 6), "e");
assert_value_err(card_float_err(9.999999999999999e-05, 20), "e");
assert_value_err(card_float_err(5e-324, 324), "5e-324");
assert_eq!(
card_float(0.0001, 4),
"iqanda ichaphaza qanda qanda qanda nye"
);
assert_value_err(card_dec_p("1E-7", 7).unwrap_err(), "1E-7");
assert_value_err(card_dec_p("0.0000001", 7).unwrap_err(), "1E-7");
assert_value_err(card_dec_p("1.5E-7", 8).unwrap_err(), "E");
assert_value_err(card_dec_p("-1.5E-7", 8).unwrap_err(), "E");
assert_eq!(
card_dec_p("0.000001", 6).unwrap(),
"iqanda ichaphaza qanda qanda qanda qanda qanda nye"
);
assert_eq!(
card_dec_p("0.0000010", 7).unwrap(),
"iqanda ichaphaza qanda qanda qanda qanda qanda nye qanda"
);
}
#[test]
fn large_values_fixed_window() {
assert_eq!(card_float(1234567890.5, 1), "1234567890 ichaphaza ntlanu");
assert_eq!(
card_dec_p("12345678901234567.89", 2).unwrap(),
"12345678901234567 ichaphaza sibhozo lithoba"
);
}
#[test]
fn corpus_float_entry_routing() {
let l = LangXh::new();
let f = |v: f64, p: u32| FloatValue::Float {
value: v,
precision: p,
};
let d = |s: &str, p: u32| FloatValue::Decimal {
value: BigDecimal::from_str(s).unwrap(),
precision: p,
};
assert_eq!(
l.cardinal_float_entry(&f(5.0, 1), None).unwrap(),
"ntlanu ichaphaza qanda"
);
assert_eq!(
l.cardinal_float_entry(&f(-0.0, 1), None).unwrap(),
"ngaphantsi kwe iqanda ichaphaza qanda"
);
assert_eq!(
l.cardinal_float_entry(&d("5.00", 2), None).unwrap(),
"ntlanu ichaphaza qanda qanda"
);
assert_eq!(l.cardinal_float_entry(&d("5", 0), None).unwrap(), "ntlanu");
assert_value_err(
l.cardinal_float_entry(&f(1e16, 16), None).unwrap_err(),
"1e+16",
);
assert_value_err(
l.cardinal_float_entry(&f(1e20, 20), None).unwrap_err(),
"1e+20",
);
assert_value_err(
l.cardinal_float_entry(&d("1E+2", 2), None).unwrap_err(),
"1E+2",
);
assert_eq!(l.ordinal_float_entry(&f(1.0, 1)).unwrap(), "okokuqala");
assert_eq!(
l.ordinal_float_entry(&f(5.0, 1)).unwrap(),
"wentlanu ichaphaza qanda"
);
assert_eq!(
l.ordinal_float_entry(&f(-0.0, 1)).unwrap(),
"wengaphantsi kwe iqanda ichaphaza qanda"
);
assert_eq!(l.ordinal_float_entry(&d("5", 0)).unwrap(), "wentlanu");
assert_eq!(l.ordinal_float_entry(&d("0", 0)).unwrap(), "weiqanda");
assert_value_err(l.ordinal_float_entry(&d("1E+20", 20)).unwrap_err(), "1E+20");
assert_eq!(
l.ordinal_num_float_entry(&f(5.0, 1), "5.0").unwrap(),
"we5.0"
);
assert_eq!(
l.ordinal_num_float_entry(&f(1e16, 16), "1e+16").unwrap(),
"we1e+16"
);
assert_eq!(
l.year_float_entry(&f(-3.0, 1)).unwrap(),
"ngaphantsi kwe ntathu ichaphaza qanda"
);
}
#[test]
fn inf_nan_results_native() {
let l = LangXh::new();
for to in ["cardinal", "ordinal", "year"] {
assert_value_err(l.inf_result(false, to).unwrap_err(), "Infinity");
assert_value_err(l.inf_result(true, to).unwrap_err(), "Infinity");
assert_value_err(l.nan_result(to).unwrap_err(), "NaN");
}
assert_eq!(l.inf_result(false, "ordinal_num").unwrap(), "weInfinity");
assert_eq!(l.inf_result(true, "ordinal_num").unwrap(), "we-Infinity");
assert_eq!(l.nan_result("ordinal_num").unwrap(), "weNaN");
}
#[test]
fn corpus_cheque() {
assert_eq!(
cheque("1234.56", "EUR").unwrap(),
"IWAKA NA MBINI IKHULU NA AMASHUMI AMATHATHU ANA NE AND 56/100 IYURO"
);
assert_eq!(
cheque("1234.56", "USD").unwrap(),
"IWAKA NA MBINI IKHULU NA AMASHUMI AMATHATHU ANA NE AND 56/100 IDOLA"
);
for code in ["GBP", "JPY", "KWD", "BHD", "INR", "CNY", "CHF"] {
match cheque("1234.56", code) {
Err(N2WError::NotImplemented(msg)) => assert_eq!(
msg,
format!("Currency code \"{code}\" not implemented for \"Num2Word_XH\"")
),
other => panic!("{code}: expected NotImplemented, got {other:?}"),
}
}
assert_eq!(
cheque("1234.56", "ZAR").unwrap(),
"IWAKA NA MBINI IKHULU NA AMASHUMI AMATHATHU ANA NE AND 56/100 RANDI"
);
assert_eq!(
cheque("-5.5", "USD").unwrap(),
"MINUS NTLANU AND 50/100 IDOLA"
);
assert_eq!(cheque("1.0", "USD").unwrap(), "NYE AND 00/100 IDOLA");
assert_eq!(cheque("0.01", "USD").unwrap(), "IQANDA AND 01/100 IDOLA");
}
}