use crate::base::{KwVal, Kwargs, Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, python_decimal_str, ParsedNumber};
use bigdecimal::{BigDecimal, RoundingMode};
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, Zero};
use std::collections::HashMap;
const ZERO: &str = "sıfır";
const ZEROTH: &str = "sıfırıncı";
const NEGWORD: &str = "eksi";
const CARDINAL_HUNDRED: &str = "yüz";
const ORDINAL_HUNDRED: &str = "yüzüncü";
const CURRENCY_UNIT: &str = "lira";
const ERRMSG_NONNUM: &str = "Sadece sayılar yazıya çevrilebilir.";
fn errmsg_toobig(value: &BigInt, maxval: &BigInt) -> String {
format!(
"abs({}) sayı yazıya çevirmek için çok büyük. \
Yazıya çevrilebilecek en büyük rakam {}.",
value, maxval
)
}
fn cardinal_ones(d: u8) -> &'static str {
match d {
b'1' => "bir",
b'2' => "iki",
b'3' => "üç",
b'4' => "dört",
b'5' => "beş",
b'6' => "altı",
b'7' => "yedi",
b'8' => "sekiz",
b'9' => "dokuz",
_ => "",
}
}
fn ordinal_ones(d: u8) -> &'static str {
match d {
b'1' => "birinci",
b'2' => "ikinci",
b'3' => "üçüncü",
b'4' => "dördüncü",
b'5' => "beşinci",
b'6' => "altıncı",
b'7' => "yedinci",
b'8' => "sekizinci",
b'9' => "dokuzuncu",
_ => "",
}
}
fn cardinal_tens(d: u8) -> &'static str {
match d {
b'1' => "on",
b'2' => "yirmi",
b'3' => "otuz",
b'4' => "kırk",
b'5' => "elli",
b'6' => "altmış",
b'7' => "yetmiş",
b'8' => "seksen",
b'9' => "doksan",
_ => "",
}
}
fn ordinal_tens(d: u8) -> &'static str {
match d {
b'1' => "onuncu",
b'2' => "yirminci",
b'3' => "otuzuncu",
b'4' => "kırkıncı",
b'5' => "ellinci",
b'6' => "altmışıncı",
b'7' => "yetmişinci",
b'8' => "sekseninci",
b'9' => "doksanıncı",
_ => "",
}
}
fn hundreds(d: u8) -> &'static str {
match d {
b'2' => "iki",
b'3' => "üç",
b'4' => "dört",
b'5' => "beş",
b'6' => "altı",
b'7' => "yedi",
b'8' => "sekiz",
b'9' => "dokuz",
_ => "",
}
}
fn cardinal_triplet(i: usize) -> Result<&'static str> {
match i {
1 => Ok("bin"),
2 => Ok("milyon"),
3 => Ok("milyar"),
4 => Ok("trilyon"),
5 => Ok("katrilyon"),
6 => Ok("kentilyon"),
_ => Err(N2WError::Key(i.to_string())),
}
}
fn ordinal_triplet(i: usize) -> Result<&'static str> {
match i {
1 => Ok("bininci"),
2 => Ok("milyonuncu"),
3 => Ok("milyarıncı"),
4 => Ok("trilyonuncu"),
5 => Ok("katrilyonuncu"),
6 => Ok("kentilyon"),
_ => Err(N2WError::Key(i.to_string())),
}
}
fn python_float_int(n: &BigInt) -> BigInt {
if n.is_zero() {
return BigInt::zero();
}
let bits = n.bits();
if bits <= 53 {
return n.clone();
}
let shift = (bits - 53) as usize;
let mut q = n >> shift;
let rem = n - (&q << shift);
let half = BigInt::one() << (shift - 1);
if rem > half || (rem == half && q.is_odd()) {
q += 1;
}
q << shift
}
struct SplitNum {
digits: Vec<u8>,
total_triplets_to_read: usize,
total_digits_outside_triplets: usize,
order_of_last_zero_digit: usize,
}
impl SplitNum {
fn new(digits: Vec<u8>) -> Self {
let l = digits.len();
let total_triplets_to_read = if l % 3 > 0 { l / 3 + 1 } else { l / 3 };
let total_digits_outside_triplets = l % 3;
let mut order_of_last_zero_digit = 0usize;
let mut found = false;
for i in 0..l.saturating_sub(1) {
if digits[l - 1 - i] == b'0' && !found {
order_of_last_zero_digit = i + 1;
} else {
found = true;
}
}
SplitNum {
digits,
total_triplets_to_read,
total_digits_outside_triplets,
order_of_last_zero_digit,
}
}
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"TRY",
CurrencyForms::new(&["lira", "lira"], &["kuruş", "kuruş"]),
);
m.insert(
"EUR",
CurrencyForms::new(&["avro", "avro"], &["sent", "sent"]),
);
m.insert(
"USD",
CurrencyForms::new(&["dolar", "dolar"], &["sent", "sent"]),
);
m
}
pub struct LangTr {
maxval: BigInt,
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangTr {
fn default() -> Self {
Self::new()
}
}
impl LangTr {
pub fn new() -> Self {
LangTr {
maxval: BigInt::from(10u8).pow(21u32) - 1,
currency_forms: build_currency_forms(),
}
}
fn verify_cardinal(&self, value: &BigInt) -> Result<()> {
if value.abs() >= self.maxval {
return Err(N2WError::Overflow(errmsg_toobig(value, &self.maxval)));
}
Ok(())
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(ERRMSG_NONNUM.to_string()));
}
if value.abs() >= self.maxval {
return Err(N2WError::Overflow(errmsg_toobig(value, &self.maxval)));
}
Ok(())
}
fn to_cardinal_impl(&self, value: &BigInt) -> Result<String> {
self.verify_cardinal(value)?;
let (pre_word, magnitude) = if value.is_negative() {
(NEGWORD, value.abs())
} else {
("", value.clone())
};
let digits = python_float_int(&magnitude).to_string().into_bytes();
let sn = SplitNum::new(digits);
self.read_cardinal(&sn, pre_word)
}
fn read_cardinal(&self, sn: &SplitNum, pre_word: &str) -> Result<String> {
let d = &sn.digits;
let l = d.len();
let t = sn.total_triplets_to_read;
let o = sn.total_digits_outside_triplets;
let z = sn.order_of_last_zero_digit;
let mut wrd = String::new();
if z >= l {
return Ok(format!("{}{}", pre_word, wrd));
}
if t == 1 {
if o == 2 {
if z == 1 {
wrd += cardinal_tens(d[0]);
return Ok(format!("{}{}", pre_word, wrd));
}
if z == 0 {
wrd += cardinal_tens(d[0]);
wrd += cardinal_ones(d[1]);
}
return Ok(format!("{}{}", pre_word, wrd));
}
if o == 1 && z == 0 {
wrd += cardinal_ones(d[0]);
if d[0] == b'0' {
return Ok(ZERO.to_string());
}
return Ok(format!("{}{}", pre_word, wrd));
}
if o == 0 {
if z == 2 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
return Ok(format!("{}{}", pre_word, wrd));
}
if z == 1 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
return Ok(format!("{}{}", pre_word, wrd));
}
if z == 0 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
wrd += cardinal_ones(d[2]);
return Ok(format!("{}{}", pre_word, wrd));
}
}
}
if t >= 2 {
if o == 2 {
if z == l - 1 {
wrd += cardinal_tens(d[0]);
wrd += cardinal_triplet(t - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
if z == l - 2 {
wrd += cardinal_tens(d[0]);
wrd += cardinal_ones(d[1]);
wrd += cardinal_triplet(t - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
if z < l - 2 {
wrd += cardinal_tens(d[0]);
wrd += cardinal_ones(d[1]);
wrd += cardinal_triplet(t - 1)?;
}
}
if o == 1 {
if z == l - 1 {
if !(t == 2 && d[0] == b'1') {
wrd += cardinal_ones(d[0]);
}
wrd += cardinal_triplet(t - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
if z < l - 1 {
if !(t == 2 && d[0] == b'1') {
wrd += cardinal_ones(d[0]);
}
wrd += cardinal_triplet(t - 1)?;
}
}
if o == 0 {
if z == l - 1 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_triplet(t - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
if z == l - 2 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
wrd += cardinal_triplet(t - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
if z == l - 3 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
wrd += cardinal_ones(d[2]);
wrd += cardinal_triplet(t - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
if z < l - 3 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
wrd += cardinal_ones(d[2]);
wrd += cardinal_triplet(t - 1)?;
}
}
for i in (1..=t - 1).rev() {
let reading_triplet_order = t - i;
let lrdo = if o == 0 {
reading_triplet_order * 3
} else {
(reading_triplet_order - 1) * 3 + o
};
if &d[lrdo..lrdo + 3] == b"000" {
continue;
}
if d[lrdo] != b'0' {
wrd += hundreds(d[lrdo]);
if z == l - lrdo - 1 {
if i == 1 {
wrd += CARDINAL_HUNDRED;
return Ok(format!("{}{}", pre_word, wrd));
} else {
wrd += CARDINAL_HUNDRED;
wrd += cardinal_triplet(i - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
} else {
wrd += CARDINAL_HUNDRED;
}
}
if d[lrdo + 1] != b'0' {
if z == l - lrdo - 2 {
if i == 1 {
wrd += cardinal_tens(d[lrdo + 1]);
return Ok(format!("{}{}", pre_word, wrd));
} else {
wrd += cardinal_tens(d[lrdo + 1]);
wrd += cardinal_triplet(i - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
} else {
wrd += cardinal_tens(d[lrdo + 1]);
}
}
if d[lrdo + 2] != b'0' {
if z == l - lrdo - 3 {
if i == 1 {
wrd += cardinal_ones(d[lrdo + 2]);
return Ok(format!("{}{}", pre_word, wrd));
}
if i == 2 {
if &d[lrdo..lrdo + 2] != b"00" || d[lrdo + 2] != b'1' {
wrd += cardinal_ones(d[lrdo + 2]);
}
wrd += cardinal_triplet(i - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
}
wrd += cardinal_ones(d[lrdo + 2]);
wrd += cardinal_triplet(i - 1)?;
return Ok(format!("{}{}", pre_word, wrd));
} else if &d[lrdo..lrdo + 2] != b"00" {
wrd += cardinal_ones(d[lrdo + 2]);
} else if i == 2 && d[lrdo + 2] != b'1' {
wrd += cardinal_ones(d[lrdo + 2]);
}
}
wrd += cardinal_triplet(i - 1)?;
}
}
Ok(format!("{}{}", pre_word, wrd))
}
fn to_ordinal_impl(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let sn = SplitNum::new(value.to_string().into_bytes());
let d = &sn.digits;
let l = d.len();
let t = sn.total_triplets_to_read;
let o = sn.total_digits_outside_triplets;
let z = sn.order_of_last_zero_digit;
let mut wrd = String::new();
if z >= l {
return Ok(wrd);
}
if t == 1 {
if o == 2 {
if z == 1 {
wrd += ordinal_tens(d[0]);
return Ok(wrd);
}
if z == 0 {
wrd += cardinal_tens(d[0]);
wrd += ordinal_ones(d[1]);
return Ok(wrd);
}
}
if o == 1 && z == 0 {
wrd += ordinal_ones(d[0]);
if d[0] == b'0' {
return Ok(ZEROTH.to_string());
}
return Ok(wrd);
}
if o == 0 {
if z == 2 {
wrd += hundreds(d[0]);
wrd += ORDINAL_HUNDRED;
return Ok(wrd);
}
if z == 1 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += ordinal_tens(d[1]);
return Ok(wrd);
}
if z == 0 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
if d[2] != b'0' {
wrd += ordinal_ones(d[2]);
}
return Ok(wrd);
}
}
}
if t >= 2 {
if o == 2 {
if z == l - 1 {
wrd += cardinal_tens(d[0]);
wrd += ordinal_triplet(t - 1)?;
return Ok(wrd);
}
if z == l - 2 {
wrd += cardinal_tens(d[0]);
wrd += cardinal_ones(d[1]);
wrd += ordinal_triplet(t - 1)?;
return Ok(wrd);
}
if z < l - 2 {
wrd += cardinal_tens(d[0]);
wrd += cardinal_ones(d[1]);
wrd += cardinal_triplet(t - 1)?;
}
}
if o == 1 {
if z == l - 1 {
if !(t == 2 && d[0] == b'1') {
wrd += cardinal_ones(d[0]);
}
wrd += ordinal_triplet(t - 1)?;
return Ok(wrd);
}
if z < l - 1 {
if !(t == 2 && d[0] == b'1') {
wrd += cardinal_ones(d[0]);
}
wrd += cardinal_triplet(t - 1)?;
}
}
if o == 0 {
if z == l - 1 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += ordinal_triplet(t - 1)?;
return Ok(wrd);
}
if z == l - 2 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
wrd += ordinal_triplet(t - 1)?;
return Ok(wrd);
}
if z == l - 3 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
wrd += cardinal_ones(d[2]);
wrd += ordinal_triplet(t - 1)?;
return Ok(wrd);
}
if z < l - 3 {
wrd += hundreds(d[0]);
wrd += CARDINAL_HUNDRED;
wrd += cardinal_tens(d[1]);
if !(t == 2 && d[2] == b'1') {
wrd += cardinal_ones(d[2]);
}
wrd += cardinal_triplet(t - 1)?;
}
}
for i in (1..=t - 1).rev() {
let reading_triplet_order = t - i;
let lrdo = if o == 0 {
reading_triplet_order * 3
} else {
(reading_triplet_order - 1) * 3 + o
};
if &d[lrdo..lrdo + 3] == b"000" {
continue;
}
if d[lrdo] != b'0' {
if d[lrdo] != b'1' {
wrd += cardinal_ones(d[lrdo]);
}
if z == l - lrdo - 1 {
if i == 1 {
wrd += ORDINAL_HUNDRED;
return Ok(wrd);
} else {
wrd += CARDINAL_HUNDRED;
wrd += ordinal_triplet(i - 1)?;
return Ok(wrd);
}
} else {
wrd += CARDINAL_HUNDRED;
}
}
if d[lrdo + 1] != b'0' {
if z == l - lrdo - 2 {
if i == 1 {
wrd += ordinal_tens(d[lrdo + 1]);
return Ok(wrd);
} else {
wrd += cardinal_tens(d[lrdo + 1]);
wrd += ordinal_triplet(i - 1)?;
return Ok(wrd);
}
} else {
wrd += cardinal_tens(d[lrdo + 1]);
}
}
if d[lrdo + 2] != b'0' {
if z == l - lrdo - 3 {
if i == 1 {
wrd += ordinal_ones(d[lrdo + 2]);
return Ok(wrd);
}
if i == 2 {
if &d[lrdo..lrdo + 2] != b"00" || d[lrdo + 2] != b'1' {
wrd += cardinal_ones(d[lrdo + 2]);
}
wrd += ordinal_triplet(i - 1)?;
return Ok(wrd);
}
wrd += cardinal_ones(d[lrdo + 2]);
wrd += ordinal_triplet(i - 1)?;
return Ok(wrd);
} else if &d[lrdo..lrdo + 2] != b"00" {
wrd += cardinal_ones(d[lrdo + 2]);
} else if d[lrdo + 2] != b'1' {
wrd += cardinal_ones(d[lrdo + 2]);
}
}
wrd += cardinal_triplet(i - 1)?;
}
}
Ok(wrd)
}
fn verify_ordinal_float(&self, value: &FloatValue) -> Result<()> {
let numerically_negative = match value {
FloatValue::Float { value: f, .. } => *f < 0.0,
FloatValue::Decimal { value: d, .. } => d.is_negative(),
};
if numerically_negative {
return Err(N2WError::Type(ERRMSG_NONNUM.to_string()));
}
if self.float_exceeds_maxval(value) {
return Err(N2WError::Overflow(errmsg_toobig_repr(
&float_value_repr(value),
&self.maxval,
)));
}
Ok(())
}
fn float_exceeds_maxval(&self, value: &FloatValue) -> bool {
if let Some(i) = value.as_whole_int() {
return i.abs() >= self.maxval;
}
match value {
FloatValue::Float { .. } => false,
FloatValue::Decimal { value: d, .. } => {
d.abs() >= BigDecimal::from(self.maxval.clone())
}
}
}
fn cardinal_float_impl(
&self,
value: &FloatValue,
precision_override: Option<u32>,
pointword: &str,
) -> Result<String> {
let self_precision: u32 = precision_override.unwrap_or(2);
let natural: u32 = value.precision();
let prec: usize = if self_precision == 2 && natural != 0 && natural < 2 {
natural as usize
} else {
self_precision.max(1) as usize
};
let is_negative = value.is_negative();
let (int_part, frac_part) = match value {
FloatValue::Float { value: f, .. } => {
let s = format!("{:.*}", prec, f.abs());
match s.split_once('.') {
Some((i, fr)) => (i.to_string(), fr.to_string()),
None => (s, "0".repeat(prec)),
}
}
FloatValue::Decimal { value: d, .. } => {
let ten_pow = BigInt::from(10).pow(prec as u32);
let scaled = (d.abs() * BigDecimal::from(ten_pow.clone()))
.with_scale_round(0, RoundingMode::HalfEven)
.as_bigint_and_exponent()
.0;
let int_val = &scaled / &ten_pow;
let frac_val = &scaled % &ten_pow;
let frac_str = frac_val.to_string();
let frac_part = format!(
"{}{}",
"0".repeat(prec.saturating_sub(frac_str.len())),
frac_str
);
(int_val.to_string(), frac_part)
}
};
let mut wrd = String::from(pointword);
if prec == 2 {
let fb = frac_part.as_bytes();
if fb[0] == b'0' && fb[1] != b'0' {
wrd.push_str(ZERO);
wrd.push_str(cardinal_ones(fb[1]));
} else {
wrd.push_str(cardinal_tens(fb[0]));
wrd.push_str(cardinal_ones(fb[1]));
}
} else {
let stripped = frac_part.trim_start_matches('0');
let leading_zeros = frac_part.len() - stripped.len();
for _ in 0..leading_zeros {
wrd.push_str(ZERO);
}
if !stripped.is_empty() {
let n: BigInt = stripped.parse().unwrap();
wrd.push_str(&self.to_cardinal_impl(&n)?);
}
}
if int_part == "0" {
wrd = format!("{}{}", ZERO, wrd);
} else {
let n: BigInt = int_part.parse().unwrap();
wrd = format!("{}{}", self.to_cardinal_impl(&n)?, wrd);
}
if is_negative {
wrd = format!("{} {}", NEGWORD, wrd);
}
Ok(wrd)
}
}
fn errmsg_toobig_repr(value_repr: &str, maxval: &BigInt) -> String {
format!(
"abs({}) sayı yazıya çevirmek için çok büyük. \
Yazıya çevrilebilecek en büyük rakam {}.",
value_repr, maxval
)
}
fn float_value_repr(value: &FloatValue) -> String {
match value {
FloatValue::Float { value: f, .. } => {
let s = format!("{:e}", f);
match s.split_once('e') {
Some((m, e)) if !e.starts_with('-') => format!("{}e+{}", m, e),
_ => s,
}
}
FloatValue::Decimal { value: d, .. } => python_decimal_str(d),
}
}
fn kw_truthy(v: &KwVal) -> bool {
match v {
KwVal::Bool(b) => *b,
KwVal::Int(i) => *i != 0,
KwVal::Str(s) => !s.is_empty(),
KwVal::List(l) => !l.is_empty(),
KwVal::None => false,
}
}
fn tr_decimal_word(kw: &Kwargs) -> Result<Option<&str>> {
match kw.get("decimal_word") {
None | Some(KwVal::None) => Ok(None),
Some(KwVal::Str(s)) => Ok(Some(s.as_str())),
Some(_) => Err(N2WError::Fallback("kwargs".into())),
}
}
fn insert_spaces(text: &str, pointword: &str) -> String {
let mut tokens: Vec<&str> = vec![
"bin",
"milyon",
"milyar",
"trilyon",
"katrilyon",
"kentilyon",
"yüz",
"on",
"yirmi",
"otuz",
"kırk",
"elli",
"altmış",
"yetmiş",
"seksen",
"doksan",
"bir",
"iki",
"üç",
"dört",
"beş",
"altı",
"yedi",
"sekiz",
"dokuz",
"sıfır",
];
tokens.push(pointword);
tokens.retain(|t| !t.is_empty());
tokens.sort_by_key(|t| std::cmp::Reverse(t.chars().count()));
let mut out: Vec<&str> = Vec::new();
let mut i = 0usize;
while i < text.len() {
let ch = text[i..].chars().next().unwrap();
if ch.is_whitespace() {
if matches!(out.last(), Some(last) if *last != " ") {
out.push(" ");
}
i += ch.len_utf8();
continue;
}
if let Some(tok) = tokens.iter().copied().find(|t| text[i..].starts_with(*t)) {
if matches!(out.last(), Some(last) if *last != " ") {
out.push(" ");
}
out.push(tok);
i += tok.len();
} else {
out.push(&text[i..i + ch.len_utf8()]);
i += ch.len_utf8();
}
}
out.concat().trim().to_string()
}
impl Lang for LangTr {
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
","
}
fn maxval(&self) -> &BigInt {
&self.maxval
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"virgül"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.to_cardinal_impl(value)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.to_ordinal_impl(value)
}
fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
let cardinal = self.to_cardinal_impl(value)?;
let ordinal = self.to_ordinal_impl(value)?;
let suffix: String = if ordinal.starts_with(&cardinal) {
ordinal[cardinal.len()..].to_string()
} else {
let chars: Vec<char> = ordinal.chars().collect();
chars[chars.len().saturating_sub(4)..].iter().collect()
};
Ok(format!("{}'{}", value, suffix))
}
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> {
self.cardinal_float_impl(value, precision_override, self.pointword())
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
if self.float_exceeds_maxval(value) {
return Err(N2WError::Overflow(errmsg_toobig_repr(
&float_value_repr(value),
&self.maxval,
)));
}
match value.as_whole_int() {
Some(i) => self.to_cardinal_impl(&i),
None => self.to_cardinal_float(value, precision_override),
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
self.verify_ordinal_float(value)?;
match value.as_whole_int() {
Some(i) => self.to_ordinal_impl(&i),
None => Ok(String::new()),
}
}
fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
self.verify_ordinal_float(value)?;
let (cardinal, ordinal) = match value.as_whole_int() {
Some(i) => {
let c = self.to_cardinal_impl(&i)?;
let o = self.to_ordinal_impl(&i)?;
(c, o)
}
None => (self.to_cardinal_float(value, None)?, String::new()),
};
let suffix: String = if ordinal.starts_with(&cardinal) {
ordinal[cardinal.len()..].to_string()
} else {
let chars: Vec<char> = ordinal.chars().collect();
chars[chars.len().saturating_sub(4)..].iter().collect()
};
Ok(format!("{}'{}", repr_str, suffix))
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
python_decimal_parse(s)
}
fn nan_result(&self, to: &str) -> Result<String> {
match to {
"cardinal" | "year" => Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "[<class 'decimal.InvalidOperation'>]".into(),
}),
_ => Err(N2WError::Type(ERRMSG_NONNUM.to_string())),
}
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["spaced", "decimal_word"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let decimal_word = tr_decimal_word(kw)?;
let wrd = self.to_cardinal_impl(value)?;
if kw.get("spaced").map(kw_truthy).unwrap_or(false) {
Ok(insert_spaces(
&wrd,
decimal_word.unwrap_or(self.pointword()),
))
} else {
Ok(wrd)
}
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if !kw.only(&["spaced", "decimal_word", "precision"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let precision_override = match kw.get("precision") {
None | Some(KwVal::None) => precision_override,
Some(KwVal::Int(p)) if *p >= 0 => Some(*p as u32),
Some(KwVal::Bool(b)) => Some(u32::from(*b)),
Some(_) => return Err(N2WError::Fallback("kwargs".into())),
};
let pointword = tr_decimal_word(kw)?.unwrap_or(self.pointword());
if self.float_exceeds_maxval(value) {
return Err(N2WError::Overflow(errmsg_toobig_repr(
&float_value_repr(value),
&self.maxval,
)));
}
let wrd = match value.as_whole_int() {
Some(i) => self.to_cardinal_impl(&i)?,
None => self.cardinal_float_impl(value, precision_override, pointword)?,
};
if kw.get("spaced").map(kw_truthy).unwrap_or(false) {
Ok(insert_spaces(&wrd, pointword))
} else {
Ok(wrd)
}
}
fn to_year_kw(&self, value: &BigInt, _kw: &Kwargs) -> Result<String> {
self.to_cardinal(value)
}
fn lang_name(&self) -> &str {
"Num2Word_TR"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, _n: &BigInt, forms: &[String]) -> Result<String> {
if forms.len() >= 2 {
return Ok(forms[0].clone());
}
Ok(format!(
"({})",
forms
.iter()
.map(|f| format!("'{}',", f))
.collect::<String>()
))
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
if let CurrencyValue::Int(v) = val {
let minus_str = if v.is_negative() { NEGWORD } else { "" };
let money_str = self.to_cardinal_impl(&v.abs())?;
return Ok(format!("{}{}{}", minus_str, money_str, CURRENCY_UNIT));
}
crate::currency::default_to_currency(
self,
val,
currency,
cents,
separator.unwrap_or(self.default_separator()),
adjective,
)
}
}
#[cfg(test)]
mod float_tests {
use super::*;
use bigdecimal::BigDecimal;
use std::str::FromStr;
fn frac_prec(f: f64) -> u32 {
let s = format!("{}", f);
match s.split_once('.') {
Some((_, fr)) if !fr.contains('e') => fr.len() as u32,
_ => 0,
}
}
fn cf(f: f64) -> String {
let v = FloatValue::Float {
value: f,
precision: frac_prec(f),
};
LangTr::new().to_cardinal_float(&v, None).unwrap()
}
fn cd(s: &str) -> String {
let bd = BigDecimal::from_str(s).unwrap();
let prec = s
.split_once('.')
.map(|(_, fr)| fr.len() as u32)
.unwrap_or(0);
let v = FloatValue::Decimal {
value: bd,
precision: prec,
};
LangTr::new().to_cardinal_float(&v, None).unwrap()
}
#[test]
fn corpus_float() {
assert_eq!(cf(0.5), "sıfırvirgülbeş");
assert_eq!(cf(1.5), "birvirgülbeş");
assert_eq!(cf(2.25), "ikivirgülyirmibeş");
assert_eq!(cf(3.14), "üçvirgülondört");
assert_eq!(cf(0.01), "sıfırvirgülsıfırbir");
assert_eq!(cf(0.1), "sıfırvirgülbir");
assert_eq!(cf(0.99), "sıfırvirgüldoksandokuz");
assert_eq!(cf(1.01), "birvirgülsıfırbir");
assert_eq!(cf(12.34), "onikivirgülotuzdört");
assert_eq!(cf(99.99), "doksandokuzvirgüldoksandokuz");
assert_eq!(cf(100.5), "yüzvirgülbeş");
assert_eq!(cf(1234.56), "binikiyüzotuzdörtvirgülellialtı");
assert_eq!(cf(-0.5), "eksi sıfırvirgülbeş");
assert_eq!(cf(-1.5), "eksi birvirgülbeş");
assert_eq!(cf(-12.34), "eksi onikivirgülotuzdört");
assert_eq!(cf(1.005), "birvirgül");
assert_eq!(cf(2.675), "ikivirgülaltmışyedi");
}
#[test]
fn corpus_decimal() {
assert_eq!(cd("0.01"), "sıfırvirgülsıfırbir");
assert_eq!(cd("1.10"), "birvirgülon");
assert_eq!(cd("12.345"), "onikivirgülotuzdört");
assert_eq!(cd("98746251323029.99"),
"doksansekiztrilyonyediyüzkırkaltımilyarikiyüzellibirmilyonüçyüzyirmiüçbinyirmidokuzvirgüldoksandokuz");
assert_eq!(cd("0.001"), "sıfırvirgül");
assert_eq!(cd("2.675"), "ikivirgülaltmışsekiz");
assert_eq!(cd("2.5"), "ikivirgülbeş");
}
}
#[cfg(test)]
mod entry_and_kwargs_tests {
use super::*;
use bigdecimal::BigDecimal;
use std::str::FromStr;
fn fl(f: f64) -> FloatValue {
let s = format!("{}", f);
let prec = match s.split_once('.') {
Some((_, fr)) if !fr.contains('e') => fr.len() as u32,
_ => 0,
};
FloatValue::Float {
value: f,
precision: prec,
}
}
fn dc(s: &str) -> FloatValue {
let bd = BigDecimal::from_str(s).unwrap();
let prec = s
.split_once('.')
.map(|(_, fr)| fr.len() as u32)
.unwrap_or(0);
FloatValue::Decimal {
value: bd,
precision: prec,
}
}
fn kw(items: &[(&str, KwVal)]) -> Kwargs {
Kwargs(
items
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect(),
)
}
fn l() -> LangTr {
LangTr::new()
}
#[test]
fn ordinal_float_corpus() {
assert_eq!(l().ordinal_float_entry(&fl(0.0)).unwrap(), "sıfırıncı");
assert_eq!(l().ordinal_float_entry(&fl(-0.0)).unwrap(), "sıfırıncı");
assert_eq!(l().ordinal_float_entry(&fl(1.0)).unwrap(), "birinci");
assert_eq!(l().ordinal_float_entry(&fl(21.0)).unwrap(), "yirmibirinci");
assert_eq!(
l().ordinal_float_entry(&fl(1234.0)).unwrap(),
"binikiyüzotuzdördüncü"
);
assert_eq!(
l().ordinal_float_entry(&fl(1e16)).unwrap(),
"onkatrilyonuncu"
);
assert_eq!(l().ordinal_float_entry(&fl(1e20)).unwrap(), "yüzkentilyon");
assert_eq!(l().ordinal_float_entry(&fl(0.5)).unwrap(), "");
assert_eq!(l().ordinal_float_entry(&fl(3.25)).unwrap(), "");
assert!(matches!(
l().ordinal_float_entry(&fl(-2.0)),
Err(N2WError::Type(m)) if m == ERRMSG_NONNUM
));
assert!(matches!(
l().ordinal_float_entry(&fl(-1.5)),
Err(N2WError::Type(_))
));
assert!(matches!(
l().ordinal_float_entry(&dc("-3.0")),
Err(N2WError::Type(_))
));
assert_eq!(l().ordinal_float_entry(&dc("5.00")).unwrap(), "beşinci");
assert_eq!(l().ordinal_float_entry(&dc("1E+2")).unwrap(), "yüzüncü");
assert_eq!(
l().ordinal_float_entry(&dc("12345.000")).unwrap(),
"onikibinüçyüzkırkbeşinci"
);
assert_eq!(
l().ordinal_float_entry(&dc("1E+20")).unwrap(),
"yüzkentilyon"
);
assert!(matches!(
l().ordinal_float_entry(&dc("1000000000000000000000.5")),
Err(N2WError::Overflow(_))
));
assert!(matches!(
l().ordinal_float_entry(&fl(1e21)),
Err(N2WError::Overflow(_))
));
}
#[test]
fn ordinal_num_float_corpus() {
let t = |v: FloatValue, r: &str| l().ordinal_num_float_entry(&v, r).unwrap();
assert_eq!(t(fl(0.0), "0.0"), "0.0'ıncı");
assert_eq!(t(fl(-0.0), "-0.0"), "-0.0'ıncı");
assert_eq!(t(fl(1.0), "1.0"), "1.0'inci");
assert_eq!(t(fl(2.0), "2.0"), "2.0'nci");
assert_eq!(t(fl(3.0), "3.0"), "3.0'üncü");
assert_eq!(t(fl(10.0), "10.0"), "10.0'uncu");
assert_eq!(t(fl(1234.0), "1234.0"), "1234.0'üncü");
assert_eq!(t(fl(1e16), "1e+16"), "1e+16'uncu");
assert_eq!(t(fl(1e20), "1e+20"), "1e+20'");
assert_eq!(t(fl(0.5), "0.5"), "0.5'");
assert_eq!(t(fl(3.25), "3.25"), "3.25'");
assert_eq!(t(dc("5.00"), "5.00"), "5.00'inci");
assert_eq!(t(dc("1E+2"), "1E+2"), "1E+2'üncü");
assert_eq!(t(dc("1E+20"), "1E+20"), "1E+20'");
assert!(matches!(
l().ordinal_num_float_entry(&fl(-1.5), "-1.5"),
Err(N2WError::Type(_))
));
assert!(matches!(
l().ordinal_num_float_entry(&fl(-1000000.0), "-1000000.0"),
Err(N2WError::Type(_))
));
}
#[test]
fn cardinal_float_entry_maxval() {
assert!(matches!(
l().cardinal_float_entry(&dc("1000000000000000000000.5"), None),
Err(N2WError::Overflow(_))
));
assert_eq!(
l().cardinal_float_entry(&fl(1e20), None).unwrap(),
"yüzkentilyon"
);
assert_eq!(l().cardinal_float_entry(&fl(-0.0), None).unwrap(), "sıfır");
assert_eq!(
l().cardinal_float_entry(&fl(1.5), Some(3)).unwrap(),
"birvirgülbeşyüz"
);
}
#[test]
fn str_nan_falls_back() {
assert!(l().str_to_number("NaN").is_ok());
assert!(matches!(
l().str_to_number("Infinity"),
Ok(ParsedNumber::Inf { negative: false })
));
assert!(matches!(l().str_to_number("5"), Ok(ParsedNumber::Dec(_))));
}
#[test]
fn kwargs_corpus() {
let sp = kw(&[("spaced", KwVal::Bool(true))]);
let unsp = kw(&[("spaced", KwVal::Bool(false))]);
let c = |n: i64, k: &Kwargs| l().to_cardinal_kw(&BigInt::from(n), k).unwrap();
assert_eq!(c(0, &sp), "sıfır");
assert_eq!(c(11, &sp), "on bir");
assert_eq!(c(21, &sp), "yirmi bir");
assert_eq!(c(100, &sp), "yüz");
assert_eq!(c(1234, &sp), "bin iki yüz otuz dört");
assert_eq!(c(-5, &sp), "eksi beş");
assert_eq!(c(1234, &unsp), "binikiyüzotuzdört");
assert_eq!(c(-5, &unsp), "eksibeş");
let nokta = kw(&[("decimal_word", KwVal::Str("nokta".into()))]);
let f = |v: FloatValue, k: &Kwargs| l().to_cardinal_float_kw(&v, None, k).unwrap();
assert_eq!(f(fl(1.5), &nokta), "birnoktabeş");
assert_eq!(f(fl(2.05), &nokta), "ikinoktasıfırbeş");
assert_eq!(f(fl(1.5), &sp), "bir virgül beş");
let both = kw(&[
("spaced", KwVal::Bool(true)),
("decimal_word", KwVal::Str("nokta".into())),
]);
assert_eq!(f(fl(3.14), &both), "üç nokta on dört");
assert_eq!(f(fl(-5.0), &sp), "eksi beş");
assert!(matches!(
l().to_cardinal_kw(&BigInt::from(5), &kw(&[("case", KwVal::Str("x".into()))])),
Err(N2WError::Fallback(_))
));
assert_eq!(l().to_year_kw(&BigInt::from(5), &sp).unwrap(), "beş");
}
}