1use crate::base::{Kwargs, KwVal, Lang, N2WError, Result};
49use crate::currency::{CurrencyForms, CurrencyValue};
50use crate::floatpath::FloatValue;
51use crate::strnum::{python_decimal_parse, ParsedNumber};
52use bigdecimal::BigDecimal;
53use num_bigint::BigInt;
54use num_integer::Integer;
55use num_traits::{FromPrimitive, Signed, ToPrimitive, Zero};
56use std::collections::HashMap;
57
58#[derive(Clone)]
64enum G {
65 S(String),
66 A(String, String),
67}
68
69#[derive(Clone)]
71enum C {
72 S(String),
73 G([G; 4]),
74}
75
76type Classifier = [C; 6];
78
79#[derive(Clone, Copy)]
85enum GenderArg<'a> {
86 Idx(usize),
88 Bad(&'a KwVal),
91}
92
93#[derive(Clone, Copy)]
103struct Opts<'a> {
104 case: usize,
106 plural: bool,
107 gender: GenderArg<'a>,
108 animate: bool,
109}
110
111impl Default for Opts<'_> {
114 fn default() -> Self {
115 Opts { case: 0, plural: false, gender: GenderArg::Idx(0), animate: true }
116 }
117}
118
119const CASE_N: usize = 0;
120const CASE_G: usize = 1;
121const GENDER_M: usize = 0;
122const GENDER_F: usize = 1;
123const GENDER_NEUT: usize = 2;
124
125fn case_classifier_element(cl: &Classifier, o: Opts<'_>) -> Result<String> {
127 Ok(match &cl[o.case] {
128 C::S(text) => text.clone(),
129 C::G(genders) => {
130 let idx = if o.plural {
133 3
134 } else {
135 match o.gender {
136 GenderArg::Idx(i) => i,
137 GenderArg::Bad(kv) => return Err(dict_lookup_error(kv)),
139 }
140 };
141 match &genders[idx] {
142 G::S(text) => text.clone(),
143 G::A(animate, inanimate) => {
144 if o.animate {
145 animate.clone()
146 } else {
147 inanimate.clone()
148 }
149 }
150 }
151 }
152 })
153}
154
155fn dict_lookup_error(kv: &KwVal) -> N2WError {
161 match kv {
162 KwVal::Str(s) => N2WError::Key(s.clone()),
163 KwVal::None => N2WError::Key("None".into()),
164 KwVal::Bool(b) => N2WError::Key(if *b { "True" } else { "False" }.into()),
165 KwVal::Int(i) => N2WError::Key(i.to_string()),
166 KwVal::List(_) => N2WError::Type("unhashable type: 'list'".into()),
167 }
168}
169
170struct Table(Vec<Option<Classifier>>);
172
173impl Table {
174 fn build(entries: Vec<(usize, Classifier)>) -> Table {
175 let max = entries.iter().map(|(k, _)| *k).max().unwrap_or(0);
176 let mut v: Vec<Option<Classifier>> = (0..=max).map(|_| None).collect();
177 for (k, c) in entries {
178 v[k] = Some(c);
179 }
180 Table(v)
181 }
182}
183
184fn get_num_element(t: &Table, num: usize, o: Opts<'_>) -> Result<String> {
189 match t.0.get(num).and_then(|x| x.as_ref()) {
190 Some(cl) => case_classifier_element(cl, o),
191 None => Err(N2WError::Key(num.to_string())),
192 }
193}
194
195fn case_index(s: &str) -> Option<usize> {
197 Some(match s {
198 "n" | "nominative" | "и" | "именительный" => 0,
199 "g" | "genitive" | "р" | "родительный" => 1,
200 "d" | "dative" | "д" | "дательный" => 2,
201 "a" | "accusative" | "в" | "винительный" => 3,
202 "i" | "instrumental" | "т" | "творительный" => 4,
203 "p" | "prepositional" | "п" | "предложный" => 5,
204 _ => return None,
205 })
206}
207
208fn gender_index(s: &str) -> Option<usize> {
211 Some(match s {
212 "m" | "masculine" | "м" | "мужской" => 0,
213 "f" | "feminine" | "ж" | "женский" => 1,
214 "n" | "neuter" | "с" | "средний" => 2,
215 "p" | "plural" => 3,
216 _ => return None,
217 })
218}
219
220fn kw_truthy(kv: Option<&KwVal>, default: bool) -> bool {
223 match kv {
224 None => default,
225 Some(KwVal::Bool(b)) => *b,
226 Some(KwVal::Int(i)) => *i != 0,
227 Some(KwVal::Str(s)) => !s.is_empty(),
228 Some(KwVal::List(l)) => !l.is_empty(),
229 Some(KwVal::None) => false,
230 }
231}
232
233fn all6(w: [&str; 6]) -> Classifier {
239 [
240 C::S(w[0].into()),
241 C::S(w[1].into()),
242 C::S(w[2].into()),
243 C::S(w[3].into()),
244 C::S(w[4].into()),
245 C::S(w[5].into()),
246 ]
247}
248
249fn g4(m: &str, f: &str, n: &str, p: &str) -> C {
251 C::G([G::S(m.into()), G::S(f.into()), G::S(n.into()), G::S(p.into())])
252}
253
254fn get_cases(prefix: &str, post_group: usize) -> Classifier {
263 let p = |suf: &str| format!("{}{}", prefix, suf);
264 let d = |a: &str, b: &str| if post_group == 0 { p(a) } else { p(b) };
266 [
267 C::G([G::S(d("ой", "ый")), G::S(p("ая")), G::S(p("ое")), G::S(p("ые"))]),
269 C::G([G::S(p("ого")), G::S(p("ой")), G::S(p("ого")), G::S(p("ых"))]),
271 C::G([G::S(p("ому")), G::S(p("ой")), G::S(p("ому")), G::S(p("ым"))]),
273 C::G([
275 G::A(p("ого"), d("ой", "ый")),
276 G::S(p("ую")),
277 G::S(p("ое")),
278 G::A(p("ых"), p("ые")),
279 ]),
280 C::G([G::S(p("ым")), G::S(p("ой")), G::S(p("ым")), G::S(p("ыми"))]),
282 C::G([G::S(p("ом")), G::S(p("ой")), G::S(p("ом")), G::S(p("ых"))]),
284 ]
285}
286
287fn ord_classifier(prefixes: &[(usize, &str, usize)]) -> Vec<(usize, Classifier)> {
289 prefixes.iter().map(|(n, p, pg)| (*n, get_cases(p, *pg))).collect()
290}
291
292const HUNDREDS_RAW: [(usize, [&str; 6]); 9] = [
299 (1, ["сто", "ста", "ста", "сто", "ста", "ста"]),
300 (2, ["двести", "двухсот", "двумстам", "двести", "двумястами", "двухстах"]),
301 (3, ["триста", "трёхсот", "трёмстам", "триста", "тремястами", "трёхстах"]),
302 (
303 4,
304 ["четыреста", "четырёхсот", "четырёмстам", "четыреста", "четырьмястами", "четырёхстах"],
305 ),
306 (5, ["пятьсот", "пятисот", "пятистам", "пятьсот", "пятьюстами", "пятистах"]),
307 (6, ["шестьсот", "шестисот", "шестистам", "шестьсот", "шестьюстами", "шестистах"]),
308 (7, ["семьсот", "семисот", "семистам", "семьсот", "семьюстами", "семистах"]),
309 (
310 8,
311 ["восемьсот", "восьмисот", "восьмистам", "восемьсот", "восемьюстами", "восьмистах"],
312 ),
313 (
314 9,
315 ["девятьсот", "девятисот", "девятистам", "девятьсот", "девятьюстами", "девятистах"],
316 ),
317];
318
319const TENS_PREFIXES: [(usize, &str); 9] = [
321 (1, "один"),
322 (2, "две"),
323 (3, "три"),
324 (4, "четыр"),
325 (5, "пят"),
326 (6, "шест"),
327 (7, "сем"),
328 (8, "восем"),
329 (9, "девят"),
330];
331
332const TENS_POSTFIXES: [&str; 6] =
333 ["надцать", "надцати", "надцати", "надцать", "надцатью", "надцати"];
334
335const THOUSANDS_PREFIXES: [(usize, &str); 9] = [
337 (2, "миллион"),
338 (3, "миллиард"),
339 (4, "триллион"),
340 (5, "квадриллион"),
341 (6, "квинтиллион"),
342 (7, "секстиллион"),
343 (8, "септиллион"),
344 (9, "октиллион"),
345 (10, "нониллион"),
346];
347
348const THOUSANDS_POSTFIXES: [[&str; 3]; 6] = [
350 ["", "а", "ов"],
351 ["а", "ов", "ов"],
352 ["у", "ам", "ам"],
353 ["", "а", "ов"],
354 ["ом", "ами", "ами"],
355 ["е", "ах", "ах"],
356];
357
358fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
374 const KOPEKS: [&str; 3] = ["копейка", "копейки", "копеек"];
375 const CENTS: [&str; 3] = ["цент", "цента", "центов"];
376
377 let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
378 m.insert("RUB", CurrencyForms::new(&["рубль", "рубля", "рублей"], &KOPEKS));
379 m.insert("EUR", CurrencyForms::new(&["евро", "евро", "евро"], &CENTS));
380 m.insert("USD", CurrencyForms::new(&["доллар", "доллара", "долларов"], &CENTS));
381 m.insert("UAH", CurrencyForms::new(&["гривна", "гривны", "гривен"], &KOPEKS));
382 m.insert(
383 "KZT",
384 CurrencyForms::new(&["тенге", "тенге", "тенге"], &["тиын", "тиына", "тиынов"]),
385 );
386 m.insert(
387 "BYN",
388 CurrencyForms::new(
389 &["белорусский рубль", "белорусских рубля", "белорусских рублей"],
390 &KOPEKS,
391 ),
392 );
393 m.insert(
394 "UZS",
395 CurrencyForms::new(&["сум", "сума", "сумов"], &["тийин", "тийина", "тийинов"]),
396 );
397 m.insert(
398 "PLN",
399 CurrencyForms::new(
400 &["польский злотый", "польских слотых", "польских злотых"],
403 &["грош", "гроша", "грошей"],
404 ),
405 );
406 m.insert(
407 "GBP",
408 CurrencyForms::new(
409 &["фунт стерлингов", "фунта стерлингов", "фунтов стерлингов"],
410 &["пенни", "пенни", "пенни"],
411 ),
412 );
413 m
414}
415
416pub struct LangRu {
421 ones: Table,
422 ones_ord: Table,
423 tens: Table,
424 tens_ord: Table,
425 twenties: Table,
426 twenties_ord: Table,
427 hundreds: Table,
428 hundreds_ord: Table,
429 thousands: Vec<Option<[[String; 3]; 6]>>,
432 thousands_ord: Table,
433 currency_forms: HashMap<&'static str, CurrencyForms>,
437}
438
439impl Default for LangRu {
440 fn default() -> Self {
441 Self::new()
442 }
443}
444
445impl LangRu {
446 pub fn new() -> Self {
447 let ones = Table::build(vec![
449 (0, all6(["ноль", "ноля", "нолю", "ноль", "нолём", "ноле"])),
450 (
451 1,
452 [
453 g4("один", "одна", "одно", "одни"),
454 g4("одного", "одной", "одного", "одних"),
455 g4("одному", "одной", "одному", "одним"),
456 C::G([
457 G::A("одного".into(), "один".into()),
458 G::S("одну".into()),
459 G::S("одно".into()),
460 G::A("одних".into(), "одни".into()),
461 ]),
462 g4("одним", "одной", "одним", "одними"),
463 g4("одном", "одной", "одном", "одних"),
464 ],
465 ),
466 (
467 2,
468 [
469 g4("два", "две", "два", "двое"),
470 g4("двух", "двух", "двух", "двоих"),
471 g4("двум", "двум", "двум", "двоим"),
472 C::G([
473 G::A("двух".into(), "два".into()),
474 G::A("двух".into(), "две".into()),
475 G::S("два".into()),
476 G::S("двоих".into()),
477 ]),
478 g4("двумя", "двумя", "двумя", "двоими"),
479 g4("двух", "двух", "двух", "двоих"),
480 ],
481 ),
482 (
483 3,
484 [
485 g4("три", "три", "три", "трое"),
486 g4("трёх", "трёх", "трёх", "троих"),
487 g4("трём", "трём", "трём", "троим"),
488 C::G([
489 G::A("трёх".into(), "три".into()),
490 G::A("трёх".into(), "три".into()),
491 G::S("три".into()),
492 G::S("троих".into()),
493 ]),
494 g4("тремя", "тремя", "тремя", "троими"),
495 g4("трёх", "трёх", "трёх", "троих"),
496 ],
497 ),
498 (
499 4,
500 [
501 g4("четыре", "четыре", "четыре", "четверо"),
502 g4("четырёх", "четырёх", "четырёх", "четверых"),
503 g4("четырём", "четырём", "четырём", "четверым"),
504 C::G([
505 G::A("четырёх".into(), "четыре".into()),
506 G::A("четырёх".into(), "четыре".into()),
507 G::S("четыре".into()),
508 G::S("четверых".into()),
509 ]),
510 g4("четырьмя", "четырьмя", "четырьмя", "четверыми"),
511 g4("четырёх", "четырёх", "четырёх", "четверых"),
512 ],
513 ),
514 (5, all6(["пять", "пяти", "пяти", "пять", "пятью", "пяти"])),
515 (6, all6(["шесть", "шести", "шести", "шесть", "шестью", "шести"])),
516 (7, all6(["семь", "семи", "семи", "семь", "семью", "семи"])),
517 (8, all6(["восемь", "восьми", "восьми", "восемь", "восемью", "восьми"])),
518 (9, all6(["девять", "девяти", "девяти", "девять", "девятью", "девяти"])),
519 ]);
520
521 let mut ones_ord_entries = vec![(
526 3,
527 [
528 g4("третий", "третья", "третье", "третьи"),
529 g4("третьего", "третьей", "третьего", "третьих"),
530 g4("третьему", "третьей", "третьему", "третьим"),
531 C::G([
532 G::A("третьего".into(), "третий".into()),
533 G::S("третью".into()),
534 G::S("третье".into()),
535 G::A("третьих".into(), "третьи".into()),
536 ]),
537 g4("третьим", "третьей", "третьим", "третьими"),
538 g4("третьем", "третьей", "третьем", "третьих"),
539 ],
540 )];
541 ones_ord_entries.extend(ord_classifier(&[
543 (0, "нулев", 0),
544 (1, "перв", 1),
545 (2, "втор", 0),
546 (4, "четвёрт", 1),
547 (5, "пят", 1),
548 (6, "шест", 0),
549 (7, "седьм", 0),
550 (8, "восьм", 0),
551 (9, "девят", 1),
552 ]));
553 let ones_ord = Table::build(ones_ord_entries);
554
555 let mut tens_entries =
557 vec![(0, all6(["десять", "десяти", "десяти", "десять", "десятью", "десяти"]))];
558 for (num, prefix) in TENS_PREFIXES {
559 let w: Vec<String> =
560 TENS_POSTFIXES.iter().map(|s| format!("{}{}", prefix, s)).collect();
561 tens_entries.push((
562 num,
563 all6([&w[0], &w[1], &w[2], &w[3], &w[4], &w[5]]),
564 ));
565 }
566 let tens = Table::build(tens_entries);
567
568 let mut tens_ord_prefixes: Vec<(usize, String, usize)> = vec![(0, "десят".into(), 1)];
570 tens_ord_prefixes
571 .extend(TENS_PREFIXES.iter().map(|(n, p)| (*n, format!("{}надцат", p), 1)));
572 let tens_ord = Table::build(
573 tens_ord_prefixes.iter().map(|(n, p, pg)| (*n, get_cases(p, *pg))).collect(),
574 );
575
576 let twenties = Table::build(vec![
578 (2, all6(["двадцать", "двадцати", "двадцати", "двадцать", "двадцатью", "двадцати"])),
579 (3, all6(["тридцать", "тридцати", "тридцати", "тридцать", "тридцатью", "тридцати"])),
580 (4, all6(["сорок", "сорока", "сорока", "сорок", "сорока", "сорока"])),
581 (
582 5,
583 all6([
584 "пятьдесят",
585 "пятидесяти",
586 "пятидесяти",
587 "пятьдесят",
588 "пятьюдесятью",
589 "пятидесяти",
590 ]),
591 ),
592 (
593 6,
594 all6([
595 "шестьдесят",
596 "шестидесяти",
597 "шестидесяти",
598 "шестьдесят",
599 "шестьюдесятью",
600 "шестидесяти",
601 ]),
602 ),
603 (
604 7,
605 all6([
606 "семьдесят",
607 "семидесяти",
608 "семидесяти",
609 "семьдесят",
610 "семьюдесятью",
611 "семидесяти",
612 ]),
613 ),
614 (
615 8,
616 all6([
617 "восемьдесят",
618 "восьмидесяти",
619 "восьмидесяти",
620 "восемьдесят",
621 "восемьюдесятью",
622 "восьмидесяти",
623 ]),
624 ),
625 (
626 9,
627 all6(["девяносто", "девяноста", "девяноста", "девяносто", "девяноста", "девяноста"]),
628 ),
629 ]);
630
631 let twenties_ord = Table::build(ord_classifier(&[
632 (2, "двадцат", 1),
633 (3, "тридцат", 1),
634 (4, "сороков", 0),
635 (5, "пятидесят", 1),
636 (6, "шестидесят", 1),
637 (7, "семидесят", 1),
638 (8, "восьмидесят", 1),
639 (9, "девяност", 1),
640 ]));
641
642 let hundreds =
644 Table::build(HUNDREDS_RAW.iter().map(|(n, w)| (*n, all6(*w))).collect());
645
646 let hundreds_ord = Table::build(
649 HUNDREDS_RAW
650 .iter()
651 .map(|(n, w)| {
652 let prefix = if *n != 1 { w[1] } else { "сот" };
653 (*n, get_cases(prefix, 1))
654 })
655 .collect(),
656 );
657
658 let mut thousands: Vec<Option<[[String; 3]; 6]>> = (0..=10).map(|_| None).collect();
660 thousands[1] = Some([
661 ["тысяча".into(), "тысячи".into(), "тысяч".into()],
662 ["тысячи".into(), "тысяч".into(), "тысяч".into()],
663 ["тысяче".into(), "тысячам".into(), "тысячам".into()],
664 ["тысячу".into(), "тысячи".into(), "тысяч".into()],
665 ["тысячей".into(), "тысячами".into(), "тысячами".into()],
666 ["тысяче".into(), "тысячах".into(), "тысячах".into()],
667 ]);
668 for (num, prefix) in THOUSANDS_PREFIXES {
669 let mk = |case: usize| -> [String; 3] {
670 let c = THOUSANDS_POSTFIXES[case];
671 [
672 format!("{}{}", prefix, c[0]),
673 format!("{}{}", prefix, c[1]),
674 format!("{}{}", prefix, c[2]),
675 ]
676 };
677 thousands[num] = Some([mk(0), mk(1), mk(2), mk(3), mk(4), mk(5)]);
678 }
679
680 let mut thousands_ord_prefixes: Vec<(usize, String, usize)> =
681 vec![(1, "тысячн".into(), 1)];
682 thousands_ord_prefixes
683 .extend(THOUSANDS_PREFIXES.iter().map(|(n, p)| (*n, format!("{}н", p), 1)));
684 let thousands_ord = Table::build(
685 thousands_ord_prefixes.iter().map(|(n, p, pg)| (*n, get_cases(p, *pg))).collect(),
686 );
687
688 LangRu {
689 ones,
690 ones_ord,
691 tens,
692 tens_ord,
693 twenties,
694 twenties_ord,
695 hundreds,
696 hundreds_ord,
697 thousands,
698 thousands_ord,
699 currency_forms: build_currency_forms(),
700 }
701 }
702
703 fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
706 if value.is_negative() {
707 return Err(N2WError::Type(format!(
708 "Cannot treat negative num {} as ordinal.",
709 value
710 )));
711 }
712 Ok(())
713 }
714
715 fn opts_from_kwargs<'a>(&self, kw: &'a Kwargs) -> Result<Opts<'a>> {
731 let case = match kw.get("case") {
732 None => 0,
733 Some(KwVal::Str(s)) => match case_index(s) {
734 Some(i) => i,
735 None => return Err(dict_lookup_error(kw.get("case").unwrap())),
736 },
737 Some(other) => return Err(dict_lookup_error(other)),
738 };
739 let gender = match kw.get("gender") {
740 None => GenderArg::Idx(0),
741 Some(KwVal::Str(s)) => match gender_index(s) {
742 Some(i) => GenderArg::Idx(i),
743 None => GenderArg::Bad(kw.get("gender").unwrap()),
744 },
745 Some(other) => GenderArg::Bad(other),
746 };
747 Ok(Opts {
748 case,
749 plural: kw_truthy(kw.get("plural"), false),
750 gender,
751 animate: kw_truthy(kw.get("animate"), true),
752 })
753 }
754
755 fn thousands_elements(&self, num: usize, case: usize) -> Result<&[String; 3]> {
763 match self.thousands.get(num).and_then(|x| x.as_ref()) {
764 Some(cases) => Ok(&cases[case]),
765 None => Err(N2WError::Key(num.to_string())),
766 }
767 }
768
769 fn pluralize(n: u32, forms: &[String; 3]) -> String {
772 if matches!(n % 100, 11 | 12 | 13 | 14) {
773 return forms[2].clone();
774 }
775 if n % 10 == 1 {
776 return forms[0].clone();
777 }
778 if matches!(n % 10, 2 | 3 | 4) {
779 return forms[1].clone();
780 }
781 forms[2].clone()
782 }
783
784 fn int2word(&self, n: &BigInt, cardinal: bool, o: Opts<'_>) -> Result<String> {
786 if n.is_negative() {
787 let inner = self.int2word(&n.abs(), cardinal, o)?;
790 return Ok(format!("{} {}", self.negword(), inner));
791 }
792
793 if n.is_zero() {
794 let t = if cardinal { &self.ones } else { &self.ones_ord };
795 return get_num_element(t, 0, o);
796 }
797
798 let digits = n.to_string();
799 let chunks = splitbyx(&digits, 3);
800 let ord_join = *chunks.last().unwrap() == 0;
802 let len = chunks.len();
803 let rightmost_nonzero = chunks.iter().rposition(|&e| e != 0).unwrap();
807 let rightest_nonzero_chunk_i = len - 1 - rightmost_nonzero;
808
809 let mut words: Vec<String> = Vec::new();
810 for (j, &x) in chunks.iter().enumerate() {
811 let i = len - 1 - j;
814 if x == 0 {
815 continue;
816 }
817
818 let (n1, n2, n3) = get_digits(x);
819 let mut chunk_words: Vec<String>;
820
821 if cardinal {
822 chunk_words = self.chunk_cardinal(n3, n2, n1, i, o)?;
823 if i > 0 {
824 let forms = self.thousands_elements(i, o.case)?;
825 chunk_words.push(Self::pluralize(x, forms));
826 }
827 } else if !(ord_join && rightest_nonzero_chunk_i == i) {
828 chunk_words = self.chunk_ordinal(n3, n2, n1, i, o)?;
830 if i > 0 {
831 let t_case = if rightest_nonzero_chunk_i == i { o.case } else { CASE_N };
832 let forms = self.thousands_elements(i, t_case)?;
833 chunk_words.push(Self::pluralize(x, forms));
834 }
835 } else {
836 chunk_words = self.chunk_ordinal_join(n3, n2, n1, i, o)?;
838 if i > 0 {
839 chunk_words.push(get_num_element(&self.thousands_ord, i, o)?);
840 }
841 chunk_words = vec![chunk_words.concat()];
842 }
843
844 words.extend(chunk_words);
845 }
846
847 Ok(words.join(" "))
848 }
849
850 fn chunk_cardinal(
852 &self,
853 hundreds: u32,
854 tens: u32,
855 ones: u32,
856 chunk_num: usize,
857 o: Opts<'_>,
858 ) -> Result<Vec<String>> {
859 let mut words = Vec::new();
860 if hundreds > 0 {
861 words.push(get_num_element(&self.hundreds, hundreds as usize, o)?);
862 }
863 if tens > 1 {
864 words.push(get_num_element(&self.twenties, tens as usize, o)?);
865 }
866 if tens == 1 {
867 words.push(get_num_element(&self.tens, ones as usize, o)?);
868 } else if ones > 0 {
869 let w_ones = if chunk_num == 1 {
870 let mut f = o;
872 f.gender = GenderArg::Idx(GENDER_F);
873 get_num_element(&self.ones, ones as usize, f)?
874 } else {
875 get_num_element(&self.ones, ones as usize, o)?
877 };
878 words.push(w_ones);
879 }
880 Ok(words)
881 }
882
883 fn chunk_ordinal(
889 &self,
890 hundreds: u32,
891 tens: u32,
892 ones: u32,
893 chunk_num: usize,
894 o: Opts<'_>,
895 ) -> Result<Vec<String>> {
896 let d = Opts::default();
897 let mut words = Vec::new();
898
899 if hundreds > 0 {
900 if tens == 0 && ones == 0 {
901 words.push(get_num_element(&self.hundreds_ord, hundreds as usize, o)?);
902 } else {
903 words.push(get_num_element(&self.hundreds, hundreds as usize, d)?);
904 }
905 }
906
907 if tens > 1 {
908 if ones == 0 {
909 words.push(get_num_element(&self.twenties_ord, tens as usize, o)?);
910 } else {
911 words.push(get_num_element(&self.twenties, tens as usize, d)?);
912 }
913 }
914
915 if tens == 1 {
916 words.push(get_num_element(&self.tens_ord, ones as usize, o)?);
920 } else if ones > 0 {
921 let w_ones: Option<String> = if chunk_num == 0 {
922 Some(get_num_element(&self.ones_ord, ones as usize, o)?)
923 } else if chunk_num > 0 && ones == 1 && hundreds == 0 && tens == 0 {
924 None
926 } else if chunk_num == 1 {
927 let f = Opts { gender: GenderArg::Idx(GENDER_F), ..Opts::default() };
929 Some(get_num_element(&self.ones, ones as usize, f)?)
930 } else {
931 Some(get_num_element(&self.ones, ones as usize, d)?)
932 };
933 if let Some(w) = w_ones {
935 if !w.is_empty() {
936 words.push(w);
937 }
938 }
939 }
940
941 Ok(words)
942 }
943
944 fn chunk_ordinal_join(
946 &self,
947 hundreds: u32,
948 tens: u32,
949 ones: u32,
950 chunk_num: usize,
951 o: Opts<'_>,
952 ) -> Result<Vec<String>> {
953 let d = Opts::default();
954 let g = Opts { case: CASE_G, ..Opts::default() };
955 let mut words = Vec::new();
956
957 if hundreds > 1 {
958 words.push(get_num_element(&self.hundreds, hundreds as usize, g)?);
959 } else if hundreds == 1 {
960 words.push(get_num_element(&self.hundreds, hundreds as usize, d)?);
962 }
963
964 if tens == 9 {
965 words.push(get_num_element(&self.twenties, tens as usize, d)?);
967 } else if tens > 1 {
968 words.push(get_num_element(&self.twenties, tens as usize, g)?);
969 }
970
971 if tens == 1 {
972 words.push(get_num_element(&self.tens, ones as usize, g)?);
973 } else if ones > 0 {
974 let w_ones: Option<String> = if chunk_num == 0 {
975 Some(get_num_element(&self.ones_ord, ones as usize, o)?)
976 } else if chunk_num > 0 && ones == 1 && tens != 1 {
977 if tens == 0 && hundreds == 0 {
981 None
982 } else {
983 let neut = Opts { gender: GenderArg::Idx(GENDER_NEUT), ..Opts::default() };
985 Some(get_num_element(&self.ones, 1, neut)?)
986 }
987 } else {
988 Some(get_num_element(&self.ones, ones as usize, g)?)
989 };
990 if let Some(w) = w_ones {
991 if !w.is_empty() {
992 words.push(w);
993 }
994 }
995 }
996
997 Ok(words)
998 }
999
1000 fn cardinal_from_str(&self, number: &str) -> Result<String> {
1030 let n = number.replace(',', ".");
1033 if let Some(_dot) = n.find('.') {
1034 let is_negative = n.starts_with('-');
1035 let abs_n: &str = if is_negative { &n[1..] } else { &n };
1036 let dotpos = abs_n.find('.').unwrap();
1038 let left = &abs_n[..dotpos];
1039 let right = &abs_n[dotpos + 1..];
1040
1041 let int_left = parse_int(left)?;
1042 let int_right = parse_int(right)?;
1043
1044 let fem = Opts { gender: GenderArg::Idx(GENDER_F), ..Opts::default() };
1047 let left_word = self.int2word(&int_left, true, fem)?;
1048
1049 let pointword: [String; 3] =
1053 ["целая".into(), "целых".into(), "целых".into()];
1054 let celaya = self.pluralize(&int_left, &pointword)?;
1055
1056 let decimal_part = self.int2word(&int_right, true, fem)?;
1057 let bitness = self.decimal_bitness(right)?;
1058
1059 let result =
1060 format!("{} {} {} {}", left_word, celaya, decimal_part, bitness);
1061 if is_negative {
1062 Ok(format!("{} {}", self.negword(), result))
1063 } else {
1064 Ok(result)
1065 }
1066 } else {
1067 if n.to_lowercase().contains('e') {
1071 let f: f64 = n.parse().map_err(|_| {
1073 N2WError::Value(format!(
1074 "could not convert string to float: '{}'",
1075 n
1076 ))
1077 })?;
1078 let bi = BigInt::from_f64(f.trunc()).ok_or_else(|| {
1079 N2WError::Value(format!(
1080 "cannot convert float {} to integer",
1081 f
1082 ))
1083 })?;
1084 self.int2word(&bi, true, Opts::default())
1085 } else {
1086 self.int2word(&parse_int(&n)?, true, Opts::default())
1088 }
1089 }
1090 }
1091
1092 fn decimal_bitness(&self, right: &str) -> Result<String> {
1101 let chars: Vec<char> = right.chars().collect();
1102 let last = *chars.last().ok_or_else(|| {
1105 N2WError::Index("string index out of range".into())
1106 })?;
1107 let last2: String = if chars.len() >= 2 {
1109 chars[chars.len() - 2..].iter().collect()
1110 } else {
1111 right.to_string()
1112 };
1113 let ten_pow = BigInt::from(10).pow(chars.len() as u32);
1116
1117 if last == '1' && last2 != "11" {
1118 self.int2word(&ten_pow, false, Opts { gender: GenderArg::Idx(GENDER_F), ..Opts::default() })
1119 } else {
1120 self.int2word(
1121 &ten_pow,
1122 false,
1123 Opts { case: CASE_G, plural: true, ..Opts::default() },
1124 )
1125 }
1126 }
1127}
1128
1129fn parse_int(s: &str) -> Result<BigInt> {
1132 BigInt::parse_bytes(s.as_bytes(), 10).ok_or_else(|| {
1133 N2WError::Value(format!("invalid literal for int() with base 10: '{}'", s))
1134 })
1135}
1136
1137fn shortest_digits(a: f64) -> (String, i32) {
1144 let sci = format!("{:e}", a);
1145 let (mant, exp) = sci
1146 .split_once('e')
1147 .expect("{:e} on a finite f64 always emits an exponent");
1148 let mut digits: Vec<u8> = mant.bytes().filter(|c| *c != b'.').collect();
1149 let mut decpt: i32 = exp.parse::<i32>().expect("{:e} exponent is an integer") + 1;
1150
1151 let bits = a.to_bits();
1153 let biased = ((bits >> 52) & 0x7ff) as i32;
1154 let frac = bits & ((1u64 << 52) - 1);
1155 let (mut m, mut e) = if biased == 0 {
1156 (frac, -1074i32) } else {
1158 (frac | (1u64 << 52), biased - 1075)
1159 };
1160 if m == 0 {
1161 return (String::from_utf8(digits).expect("ASCII digits"), decpt);
1163 }
1164 let z = m.trailing_zeros() as i32;
1165 m >>= z;
1166 e += z;
1167
1168 let q = digits.len() as i32 - decpt;
1169 let mut tie = e + q + 1 == 0;
1170 if tie && q < 0 {
1171 let r = -q as u32;
1172 tie = r <= 22 && m % 5u64.pow(r) == 0;
1173 }
1174 if !tie {
1175 return (String::from_utf8(digits).expect("ASCII digits"), decpt);
1176 }
1177
1178 let last = digits[digits.len() - 1] - b'0';
1179 if last % 2 == 1 {
1180 if m % 4 == 1 {
1181 *digits.last_mut().expect("non-empty") -= 1;
1183 } else {
1184 let mut i = digits.len();
1186 loop {
1187 if i == 0 {
1188 digits.insert(0, b'1');
1189 decpt += 1;
1190 break;
1191 }
1192 i -= 1;
1193 if digits[i] == b'9' {
1194 digits[i] = b'0';
1195 } else {
1196 digits[i] += 1;
1197 break;
1198 }
1199 }
1200 }
1201 while digits.len() > 1 && *digits.last().expect("non-empty") == b'0' {
1203 digits.pop();
1204 }
1205 }
1206 (String::from_utf8(digits).expect("ASCII digits"), decpt)
1207}
1208
1209fn py_float_repr(value: f64) -> String {
1214 if value.is_nan() {
1215 return "nan".to_string();
1216 }
1217 if value.is_infinite() {
1218 return if value > 0.0 { "inf" } else { "-inf" }.to_string();
1219 }
1220 let sign = if value.is_sign_negative() { "-" } else { "" };
1222 let (digits, decpt) = shortest_digits(value.abs());
1223 let ndigits = digits.len() as i32;
1224
1225 if decpt <= -4 || decpt > 16 {
1226 let exp = decpt - 1;
1227 let mut mant = String::from(&digits[..1]);
1228 if digits.len() > 1 {
1229 mant.push('.');
1230 mant.push_str(&digits[1..]);
1231 }
1232 format!(
1233 "{}{}e{}{:02}",
1234 sign,
1235 mant,
1236 if exp < 0 { '-' } else { '+' },
1237 exp.abs()
1238 )
1239 } else if decpt <= 0 {
1240 format!("{}0.{}{}", sign, "0".repeat(-decpt as usize), digits)
1241 } else if decpt >= ndigits {
1242 format!("{}{}{}.0", sign, digits, "0".repeat((decpt - ndigits) as usize))
1243 } else {
1244 let d = decpt as usize;
1245 format!("{}{}.{}", sign, &digits[..d], &digits[d..])
1246 }
1247}
1248
1249fn float_value_str(value: &FloatValue) -> String {
1252 match value {
1253 FloatValue::Float { value, .. } => py_float_repr(*value),
1254 FloatValue::Decimal { value, .. } => py_decimal_str(value),
1255 }
1256}
1257
1258fn py_decimal_str(value: &BigDecimal) -> String {
1273 let (int_val, scale) = value.as_bigint_and_exponent();
1274 let exp = -(scale as i128);
1275 let sign = if int_val.is_negative() { "-" } else { "" };
1276 let int_digits = int_val.abs().to_string();
1277 let len = int_digits.len() as i128;
1278
1279 let leftdigits = exp + len;
1280 let dotplace = if exp <= 0 && leftdigits > -6 { leftdigits } else { 1 };
1281
1282 let (intpart, fracpart) = if dotplace <= 0 {
1283 (
1284 "0".to_string(),
1285 format!(".{}{}", "0".repeat(-dotplace as usize), int_digits),
1286 )
1287 } else if dotplace >= len {
1288 (
1289 format!("{}{}", int_digits, "0".repeat((dotplace - len) as usize)),
1290 String::new(),
1291 )
1292 } else {
1293 let d = dotplace as usize;
1294 (int_digits[..d].to_string(), format!(".{}", &int_digits[d..]))
1295 };
1296
1297 let expstr = if leftdigits == dotplace {
1298 String::new()
1299 } else {
1300 let d = leftdigits - dotplace;
1301 format!("E{}{}", if d < 0 { '-' } else { '+' }, d.abs())
1302 };
1303
1304 format!("{}{}{}{}", sign, intpart, fracpart, expstr)
1305}
1306
1307fn splitbyx(s: &str, x: usize) -> Vec<u32> {
1312 let length = s.len();
1313 let mut out = Vec::new();
1314 if length > x {
1315 let start = length % x;
1316 if start > 0 {
1317 out.push(s[..start].parse().unwrap());
1318 }
1319 let mut i = start;
1320 while i < length {
1321 out.push(s[i..i + x].parse().unwrap());
1322 i += x;
1323 }
1324 } else {
1325 out.push(s.parse().unwrap());
1326 }
1327 out
1328}
1329
1330fn get_digits(x: u32) -> (u32, u32, u32) {
1334 (x % 10, (x / 10) % 10, (x / 100) % 10)
1335}
1336
1337impl Lang for LangRu {
1338 fn default_currency(&self) -> &str {
1341 "RUB"
1342 }
1343
1344 fn default_separator(&self) -> &str {
1349 ","
1350 }
1351
1352 fn negword(&self) -> &str {
1353 "минус"
1355 }
1356
1357 fn to_cardinal(&self, value: &BigInt) -> Result<String> {
1358 self.int2word(value, true, Opts::default())
1359 }
1360
1361 fn to_ordinal(&self, value: &BigInt) -> Result<String> {
1362 self.verify_ordinal(value)?;
1363 self.int2word(value, false, Opts::default())
1364 }
1365
1366 fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
1367 self.verify_ordinal(value)?;
1368 Ok(value.to_string())
1369 }
1370
1371 fn to_year(&self, value: &BigInt) -> Result<String> {
1374 self.to_cardinal(value)
1375 }
1376
1377 fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
1387 if !kw.only(&["case", "plural", "gender", "animate"]) {
1388 return Err(N2WError::Fallback("kwargs".into()));
1389 }
1390 let o = self.opts_from_kwargs(kw)?;
1391 self.int2word(value, true, o)
1392 }
1393
1394 fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
1395 if !kw.only(&["case", "plural", "gender", "animate"]) {
1396 return Err(N2WError::Fallback("kwargs".into()));
1397 }
1398 self.verify_ordinal(value)?;
1401 let o = self.opts_from_kwargs(kw)?;
1402 self.int2word(value, false, o)
1403 }
1404
1405 fn cardinal_float_entry(
1414 &self,
1415 value: &FloatValue,
1416 precision_override: Option<u32>,
1417 ) -> Result<String> {
1418 self.to_cardinal_float(value, precision_override)
1424 }
1425
1426 fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
1435 match value.as_whole_int() {
1436 None => Err(N2WError::Type(format!(
1437 "Cannot treat float {} as ordinal.",
1438 float_value_str(value)
1439 ))),
1440 Some(i) => {
1441 if i.is_negative() {
1442 Err(N2WError::Type(format!(
1443 "Cannot treat negative num {} as ordinal.",
1444 float_value_str(value)
1445 )))
1446 } else {
1447 self.int2word(&i, false, Opts::default())
1448 }
1449 }
1450 }
1451 }
1452
1453 fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
1457 match value.as_whole_int() {
1458 None => Err(N2WError::Type(format!(
1459 "Cannot treat float {} as ordinal.",
1460 repr_str
1461 ))),
1462 Some(i) => {
1463 if i.is_negative() {
1464 Err(N2WError::Type(format!(
1465 "Cannot treat negative num {} as ordinal.",
1466 repr_str
1467 )))
1468 } else {
1469 Ok(i.to_string())
1470 }
1471 }
1472 }
1473 }
1474
1475 fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
1479 self.cardinal_float_entry(value, None)
1480 }
1481
1482 fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
1491 match to {
1492 "cardinal" | "year" => Some(self.cardinal_from_str("-0.0")),
1493 _ => None,
1494 }
1495 }
1496
1497 fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
1510 match python_decimal_parse(s)? {
1511 ParsedNumber::Inf { negative } => Err(N2WError::Value(format!(
1512 "invalid literal for int() with base 10: '{}'",
1513 if negative { "-Infinity" } else { "Infinity" }
1514 ))),
1515 other => Ok(other),
1516 }
1517 }
1518
1519 fn to_cardinal_float(
1537 &self,
1538 value: &FloatValue,
1539 precision_override: Option<u32>,
1540 ) -> Result<String> {
1541 let _ = precision_override;
1542 let n = match value {
1543 FloatValue::Float { value: f, .. } => {
1544 if f.abs() < 0.01 {
1545 return self.int2word(&BigInt::zero(), true, Opts::default());
1547 }
1548 py_float_repr(*f)
1549 }
1550 FloatValue::Decimal { value: d, .. } => py_decimal_str(d),
1552 };
1553 self.cardinal_from_str(&n)
1554 }
1555
1556 fn lang_name(&self) -> &str {
1574 "Num2Word_RU"
1575 }
1576
1577 fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
1578 self.currency_forms.get(code)
1579 }
1580
1581 fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
1588 let m100 = n.mod_floor(&BigInt::from(100)).to_u32().unwrap_or(0);
1591 let m10 = n.mod_floor(&BigInt::from(10)).to_u32().unwrap_or(0);
1592 let i = if matches!(m100, 11..=14) {
1593 2
1594 } else if m10 == 1 {
1595 0
1596 } else if matches!(m10, 2..=4) {
1597 1
1598 } else {
1599 2
1600 };
1601 forms
1605 .get(i)
1606 .cloned()
1607 .ok_or_else(|| N2WError::Index("tuple index out of range".into()))
1608 }
1609
1610 fn money_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
1617 let gender = if currency == "UAH" { GENDER_F } else { GENDER_M };
1618 self.int2word(number, true, Opts { gender: GenderArg::Idx(gender), ..Opts::default() })
1619 }
1620
1621 fn cents_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
1629 let gender = if matches!(currency, "UAH" | "RUB" | "BYN") { GENDER_F } else { GENDER_M };
1630 self.int2word(number, true, Opts { gender: GenderArg::Idx(gender), ..Opts::default() })
1631 }
1632
1633 fn to_currency(
1648 &self,
1649 val: &CurrencyValue,
1650 currency: &str,
1651 cents: bool,
1652 separator: Option<&str>,
1653 adjective: bool,
1654 ) -> Result<String> {
1655 let val = match val {
1663 CurrencyValue::Decimal { value, .. } if value.is_integer() => {
1664 CurrencyValue::Int(value.with_scale(0).as_bigint_and_exponent().0)
1667 }
1668 other => other.clone(),
1669 };
1670
1671 if let CurrencyValue::Int(v) = &val {
1672 if let Some(forms) = self.currency_forms.get(currency) {
1680 let minus = if v.is_negative() { self.negword() } else { "" };
1684 let abs = v.abs();
1685 let money = self.to_cardinal(&abs)?;
1686 let unit = self.pluralize(&abs, &forms.unit)?;
1687 return Ok(format!("{} {} {}", minus, money, unit).trim().to_string());
1688 }
1689 }
1690
1691 crate::currency::default_to_currency(
1693 self,
1694 &val,
1695 currency,
1696 cents,
1697 separator.unwrap_or(self.default_separator()),
1698 adjective,
1699 )
1700 }
1701}
1702
1703#[cfg(test)]
1704mod tests {
1705 use super::*;
1706
1707 fn c(n: i64) -> String {
1708 LangRu::new().to_cardinal(&BigInt::from(n)).unwrap()
1709 }
1710 fn o(n: i64) -> String {
1711 LangRu::new().to_ordinal(&BigInt::from(n)).unwrap()
1712 }
1713
1714 #[test]
1715 fn cardinals() {
1716 assert_eq!(c(0), "ноль");
1717 assert_eq!(c(1), "один");
1718 assert_eq!(c(11), "одиннадцать");
1719 assert_eq!(c(20), "двадцать");
1720 assert_eq!(c(42), "сорок два");
1721 assert_eq!(c(100), "сто");
1722 assert_eq!(c(101), "сто один");
1723 assert_eq!(c(1000), "одна тысяча");
1724 assert_eq!(c(2000), "две тысячи");
1725 assert_eq!(c(1000000), "один миллион");
1726 assert_eq!(c(-42), "минус сорок два");
1727 assert_eq!(c(-1000), "минус одна тысяча");
1728 }
1729
1730 #[test]
1731 fn ordinals() {
1732 assert_eq!(o(0), "нулевой");
1733 assert_eq!(o(3), "третий");
1734 assert_eq!(o(100), "сотый");
1735 assert_eq!(o(200), "двухсотый");
1736 assert_eq!(o(1000), "тысячный");
1737 assert_eq!(o(1001), "тысяча первый");
1738 assert_eq!(o(2000), "двухтысячный");
1739 assert_eq!(o(10000), "десятитысячный");
1740 assert_eq!(o(100000), "стотысячный");
1741 assert_eq!(o(1000000), "миллионный");
1742 assert_eq!(o(10001), "десятый тысяч первый");
1744 assert_eq!(o(100001), "сотый тысяч первый");
1745 }
1746
1747 #[test]
1748 fn big() {
1749 let l = LangRu::new();
1750 let sextillion: BigInt = BigInt::from(10).pow(21);
1751 assert_eq!(l.to_cardinal(&sextillion).unwrap(), "один секстиллион");
1752 assert_eq!(l.to_ordinal(&sextillion).unwrap(), "секстиллионный");
1753 }
1754
1755 use bigdecimal::BigDecimal;
1758 use std::str::FromStr;
1759
1760 fn f(s: &str) -> CurrencyValue {
1763 CurrencyValue::Decimal {
1764 value: BigDecimal::from_str(s).unwrap(),
1765 has_decimal: true,
1766 is_float: true,
1767 }
1768 }
1769 fn i(n: i64) -> CurrencyValue {
1770 CurrencyValue::Int(BigInt::from(n))
1771 }
1772 fn cur(v: CurrencyValue, code: &str) -> String {
1773 LangRu::new().to_currency(&v, code, true, None, false).unwrap()
1774 }
1775
1776 #[test]
1777 fn currency_ints_skip_cents() {
1778 assert_eq!(cur(i(0), "EUR"), "ноль евро");
1779 assert_eq!(cur(i(1), "EUR"), "один евро");
1780 assert_eq!(cur(i(2), "EUR"), "два евро");
1781 assert_eq!(cur(i(100), "EUR"), "сто евро");
1782 assert_eq!(cur(i(1000000), "EUR"), "один миллион евро");
1783 assert_eq!(cur(i(0), "USD"), "ноль долларов");
1784 assert_eq!(cur(i(1), "USD"), "один доллар");
1785 assert_eq!(cur(i(2), "USD"), "два доллара");
1786 assert_eq!(cur(i(1), "GBP"), "один фунт стерлингов");
1787 assert_eq!(cur(i(-12), "USD"), "минус двенадцать долларов");
1788 }
1789
1790 #[test]
1793 fn currency_whole_float_demotes_to_int() {
1794 assert_eq!(cur(f("1.0"), "EUR"), "один евро");
1795 assert_eq!(cur(f("1.0"), "USD"), "один доллар");
1796 assert_eq!(cur(f("1.0"), "GBP"), "один фунт стерлингов");
1797 assert_eq!(cur(f("-1.0"), "USD"), "минус один доллар");
1798 assert_eq!(cur(f("1e+18"), "EUR"), "один квинтиллион евро");
1800 }
1801
1802 #[test]
1803 fn currency_floats_render_cents() {
1804 assert_eq!(cur(f("12.34"), "EUR"), "двенадцать евро, тридцать четыре цента");
1805 assert_eq!(cur(f("0.01"), "EUR"), "ноль евро, один цент");
1806 assert_eq!(cur(f("0.5"), "EUR"), "ноль евро, пятьдесят центов");
1807 assert_eq!(cur(f("-12.34"), "EUR"), "минус двенадцать евро, тридцать четыре цента");
1808 assert_eq!(
1809 cur(f("99.99"), "EUR"),
1810 "девяносто девять евро, девяносто девять центов"
1811 );
1812 assert_eq!(
1813 cur(f("1234.56"), "USD"),
1814 "одна тысяча двести тридцать четыре доллара, пятьдесят шесть центов"
1815 );
1816 assert_eq!(
1817 cur(f("1234.56"), "GBP"),
1818 "одна тысяча двести тридцать четыре фунта стерлингов, пятьдесят шесть пенни"
1819 );
1820 }
1821
1822 #[test]
1824 fn currency_cents_gender() {
1825 assert_eq!(cur(f("1.01"), "RUB"), "один рубль, одна копейка");
1826 assert_eq!(cur(f("1.01"), "BYN"), "один белорусский рубль, одна копейка");
1827 assert_eq!(cur(f("1.01"), "USD"), "один доллар, один цент");
1828 }
1829
1830 #[test]
1834 fn currency_uah_gender_bug() {
1835 assert_eq!(cur(i(1), "UAH"), "один гривна");
1836 assert_eq!(cur(f("21.0"), "UAH"), "двадцать один гривна");
1837 assert_eq!(cur(f("1.5"), "UAH"), "одна гривна, пятьдесят копеек");
1838 assert_eq!(cur(f("21.5"), "UAH"), "двадцать одна гривна, пятьдесят копеек");
1839 }
1840
1841 #[test]
1844 fn currency_unknown_code_not_implemented() {
1845 let l = LangRu::new();
1846 for code in ["JPY", "KWD", "BHD", "INR", "CNY", "CHF"] {
1847 for v in [i(1), f("12.34"), f("1.0")] {
1848 match l.to_currency(&v, code, true, None, false) {
1849 Err(N2WError::NotImplemented(m)) => assert_eq!(
1850 m,
1851 format!("Currency code \"{}\" not implemented for \"Num2Word_RU\"", code)
1852 ),
1853 other => panic!("{} {:?}: expected NotImplemented, got {:?}", code, v, other),
1854 }
1855 }
1856 }
1857 }
1858
1859 #[test]
1860 fn cheque() {
1861 let l = LangRu::new();
1862 let v = BigDecimal::from_str("1234.56").unwrap();
1863 assert_eq!(
1864 l.to_cheque(&v, "EUR").unwrap(),
1865 "ОДНА ТЫСЯЧА ДВЕСТИ ТРИДЦАТЬ ЧЕТЫРЕ AND 56/100 ЕВРО"
1866 );
1867 assert_eq!(
1868 l.to_cheque(&v, "USD").unwrap(),
1869 "ОДНА ТЫСЯЧА ДВЕСТИ ТРИДЦАТЬ ЧЕТЫРЕ AND 56/100 ДОЛЛАРОВ"
1870 );
1871 assert_eq!(
1872 l.to_cheque(&v, "GBP").unwrap(),
1873 "ОДНА ТЫСЯЧА ДВЕСТИ ТРИДЦАТЬ ЧЕТЫРЕ AND 56/100 ФУНТОВ СТЕРЛИНГОВ"
1874 );
1875 assert!(matches!(
1876 l.to_cheque(&v, "JPY"),
1877 Err(N2WError::NotImplemented(_))
1878 ));
1879 }
1880
1881 #[test]
1884 fn currency_terse_cents() {
1885 let l = LangRu::new();
1886 assert_eq!(
1887 l.to_currency(&f("12.34"), "EUR", false, None, false).unwrap(),
1888 "двенадцать евро, 34 цента"
1889 );
1890 assert_eq!(
1891 l.to_currency(&f("12.05"), "EUR", false, None, false).unwrap(),
1892 "двенадцать евро, 05 центов"
1893 );
1894 }
1895
1896 #[test]
1898 fn currency_pluralize_boundaries() {
1899 for (n, want) in [
1900 (1, "доллар"),
1901 (2, "доллара"),
1902 (4, "доллара"),
1903 (5, "долларов"),
1904 (11, "долларов"),
1905 (12, "долларов"),
1906 (14, "долларов"),
1907 (21, "доллар"),
1908 (22, "доллара"),
1909 (25, "долларов"),
1910 (100, "долларов"),
1911 (111, "долларов"),
1912 (121, "доллар"),
1913 ] {
1914 let got = cur(i(n), "USD");
1915 assert!(got.ends_with(want), "{}: {:?} should end with {:?}", n, got, want);
1916 }
1917 }
1918
1919 fn ff(v: f64) -> String {
1922 LangRu::new()
1923 .to_cardinal_float(&FloatValue::Float { value: v, precision: 0 }, None)
1924 .unwrap()
1925 }
1926 fn dd(s: &str, p: u32) -> String {
1927 LangRu::new()
1928 .to_cardinal_float(
1929 &FloatValue::Decimal { value: BigDecimal::from_str(s).unwrap(), precision: p },
1930 None,
1931 )
1932 .unwrap()
1933 }
1934
1935 #[test]
1936 fn cardinal_floats() {
1937 assert_eq!(ff(0.0), "ноль");
1939 assert_eq!(ff(0.5), "ноль целых пять десятых");
1940 assert_eq!(ff(1.0), "одна целая ноль десятых");
1941 assert_eq!(ff(1.5), "одна целая пять десятых");
1942 assert_eq!(ff(2.25), "две целых двадцать пять сотых");
1943 assert_eq!(ff(3.14), "три целых четырнадцать сотых");
1944 assert_eq!(ff(0.01), "ноль целых одна сотая");
1945 assert_eq!(ff(0.1), "ноль целых одна десятая");
1946 assert_eq!(ff(0.99), "ноль целых девяносто девять сотых");
1947 assert_eq!(ff(1.01), "одна целая одна сотая");
1948 assert_eq!(ff(12.34), "двенадцать целых тридцать четыре сотых");
1949 assert_eq!(ff(99.99), "девяносто девять целых девяносто девять сотых");
1950 assert_eq!(ff(100.5), "сто целых пять десятых");
1951 assert_eq!(
1952 ff(1234.56),
1953 "одна тысяча двести тридцать четыре целых пятьдесят шесть сотых"
1954 );
1955 assert_eq!(ff(-0.5), "минус ноль целых пять десятых");
1956 assert_eq!(ff(-1.5), "минус одна целая пять десятых");
1957 assert_eq!(ff(-12.34), "минус двенадцать целых тридцать четыре сотых");
1958 assert_eq!(ff(1.005), "одна целая пять тысячных");
1960 assert_eq!(ff(2.675), "две целых шестьсот семьдесят пять тысячных");
1961 }
1962
1963 #[test]
1964 fn cardinal_float_below_hundredth_is_zero() {
1965 assert_eq!(ff(0.001), "ноль");
1967 assert_eq!(ff(0.005), "ноль");
1968 assert_eq!(ff(-0.005), "ноль");
1969 }
1970
1971 #[test]
1972 fn cardinal_float_exponent_is_integer() {
1973 assert_eq!(ff(1e16), "десять квадриллионов");
1975 assert_eq!(ff(1e18), "один квинтиллион");
1976 assert_eq!(
1977 ff(123456789.5),
1978 "сто двадцать три миллиона четыреста пятьдесят шесть тысяч \
1979 семьсот восемьдесят девять целых пять десятых"
1980 );
1981 }
1982
1983 #[test]
1984 fn cardinal_decimals() {
1985 assert_eq!(dd("0.01", 2), "ноль целых одна сотая");
1987 assert_eq!(dd("1.10", 2), "одна целая десять сотых");
1988 assert_eq!(dd("12.345", 3), "двенадцать целых триста сорок пять тысячных");
1989 assert_eq!(dd("0.001", 3), "ноль целых одна тысячная");
1990 assert_eq!(
1991 dd("98746251323029.99", 2),
1992 "девяносто восемь триллионов семьсот сорок шесть миллиардов \
1993 двести пятьдесят одна миллион триста двадцать три тысячи \
1994 двадцать девять целых девяносто девять сотых"
1995 );
1996 assert_eq!(dd("5", 0), "пять");
1998 assert_eq!(dd("-5", 0), "минус пять");
1999 }
2000
2001 fn fentry(v: f64) -> String {
2004 LangRu::new()
2005 .cardinal_float_entry(&FloatValue::Float { value: v, precision: 1 }, None)
2006 .unwrap()
2007 }
2008
2009 #[test]
2012 fn cardinal_float_entry_keeps_decimal_tail() {
2013 assert_eq!(fentry(1.0), "одна целая ноль десятых");
2014 assert_eq!(fentry(2.0), "две целых ноль десятых");
2015 assert_eq!(fentry(-21.0), "минус двадцать одна целая ноль десятых");
2016 assert_eq!(fentry(1000000.0), "одна миллион целых ноль десятых");
2017 assert_eq!(
2021 LangRu::new()
2022 .cardinal_float_entry(&FloatValue::Float { value: -0.0, precision: 1 }, None)
2023 .unwrap(),
2024 "ноль"
2025 );
2026 let l = LangRu::new();
2027 let d = FloatValue::Decimal {
2029 value: BigDecimal::from_str("5.00").unwrap(),
2030 precision: 2,
2031 };
2032 assert_eq!(
2033 l.cardinal_float_entry(&d, None).unwrap(),
2034 "пять целых ноль сотых"
2035 );
2036 assert_eq!(l.year_float_entry(&d).unwrap(), "пять целых ноль сотых");
2037 }
2038
2039 #[test]
2042 fn ordinal_float_entry_verifies() {
2043 let l = LangRu::new();
2044 let f = |v: f64| FloatValue::Float { value: v, precision: 1 };
2045 assert_eq!(l.ordinal_float_entry(&f(5.0)).unwrap(), "пятый");
2046 assert_eq!(l.ordinal_float_entry(&f(-0.0)).unwrap(), "нулевой");
2047 assert_eq!(l.ordinal_float_entry(&f(1e16)).unwrap(), "десятиквадриллионный");
2048 match l.ordinal_float_entry(&f(2.5)) {
2049 Err(N2WError::Type(m)) => assert_eq!(m, "Cannot treat float 2.5 as ordinal."),
2050 other => panic!("expected TypeError, got {:?}", other),
2051 }
2052 match l.ordinal_float_entry(&f(-3.0)) {
2053 Err(N2WError::Type(m)) => {
2054 assert_eq!(m, "Cannot treat negative num -3.0 as ordinal.")
2055 }
2056 other => panic!("expected TypeError, got {:?}", other),
2057 }
2058 let d = |s: &str| FloatValue::Decimal {
2060 value: BigDecimal::from_str(s).unwrap(),
2061 precision: 2,
2062 };
2063 assert_eq!(l.ordinal_float_entry(&d("5.00")).unwrap(), "пятый");
2064 assert_eq!(l.ordinal_float_entry(&d("1E+2")).unwrap(), "сотый");
2065 assert!(matches!(l.ordinal_float_entry(&d("1.50")), Err(N2WError::Type(_))));
2066 }
2067
2068 #[test]
2070 fn ordinal_num_float_entry_verifies() {
2071 let l = LangRu::new();
2072 let f = |v: f64| FloatValue::Float { value: v, precision: 1 };
2073 assert_eq!(l.ordinal_num_float_entry(&f(5.0), "5.0").unwrap(), "5");
2074 assert_eq!(l.ordinal_num_float_entry(&f(-0.0), "-0.0").unwrap(), "0");
2075 assert_eq!(
2076 l.ordinal_num_float_entry(&f(1e20), "1e+20").unwrap(),
2077 "100000000000000000000"
2078 );
2079 assert!(matches!(
2080 l.ordinal_num_float_entry(&f(2.5), "2.5"),
2081 Err(N2WError::Type(_))
2082 ));
2083 assert!(matches!(
2084 l.ordinal_num_float_entry(&f(-1.0), "-1.0"),
2085 Err(N2WError::Type(_))
2086 ));
2087 }
2088
2089 #[test]
2092 fn infinity_string_is_value_error() {
2093 let l = LangRu::new();
2094 match l.str_to_number("Infinity") {
2095 Err(N2WError::Value(m)) => {
2096 assert_eq!(m, "invalid literal for int() with base 10: 'Infinity'")
2097 }
2098 other => panic!("expected ValueError, got {:?}", other),
2099 }
2100 assert!(matches!(l.str_to_number("-Infinity"), Err(N2WError::Value(_))));
2101 assert!(matches!(l.str_to_number("nan"), Ok(ParsedNumber::NaN)));
2103 assert!(matches!(l.str_to_number("5"), Ok(ParsedNumber::Dec(_))));
2104 }
2105
2106 use crate::base::{KwVal, Kwargs};
2109
2110 fn kw(items: &[(&str, KwVal)]) -> Kwargs {
2111 Kwargs(items.iter().map(|(k, v)| (k.to_string(), v.clone())).collect())
2112 }
2113 fn s(v: &str) -> KwVal {
2114 KwVal::Str(v.to_string())
2115 }
2116
2117 #[test]
2119 fn cardinal_kwargs() {
2120 let l = LangRu::new();
2121 let ck = |n: i64, items: &[(&str, KwVal)]| {
2122 l.to_cardinal_kw(&BigInt::from(n), &kw(items)).unwrap()
2123 };
2124 assert_eq!(ck(1234, &[("case", s("g"))]), "одной тысячи двухсот тридцати четырёх");
2125 assert_eq!(ck(1234, &[("case", s("a"))]), "одну тысячу двести тридцать четырёх");
2126 assert_eq!(
2127 ck(1234, &[("case", s("i"))]),
2128 "одной тысячей двумястами тридцатью четырьмя"
2129 );
2130 assert_eq!(ck(-5, &[("case", s("g"))]), "минус пяти");
2131 assert_eq!(ck(21, &[("case", s("родительный"))]), "двадцати одного");
2132 assert_eq!(ck(21, &[("case", s("n")), ("gender", s("f"))]), "двадцать одна");
2133 assert_eq!(ck(21, &[("case", s("n")), ("gender", s("ж"))]), "двадцать одна");
2134 assert_eq!(
2135 ck(2, &[("case", s("n")), ("plural", KwVal::Bool(true))]),
2136 "двое"
2137 );
2138 assert_eq!(
2139 ck(
2140 1,
2141 &[
2142 ("case", s("a")),
2143 ("plural", KwVal::Bool(false)),
2144 ("animate", KwVal::Bool(false))
2145 ]
2146 ),
2147 "один"
2148 );
2149 assert!(matches!(
2151 l.to_cardinal_kw(&BigInt::from(0), &kw(&[("case", s("xx"))])),
2152 Err(N2WError::Key(_))
2153 ));
2154 assert_eq!(ck(5, &[("case", s("n")), ("gender", s("xx"))]), "пять");
2156 assert!(matches!(
2157 l.to_cardinal_kw(&BigInt::from(1), &kw(&[("gender", s("xx"))])),
2158 Err(N2WError::Key(_))
2159 ));
2160 assert!(matches!(
2162 l.to_cardinal_kw(&BigInt::from(1), &kw(&[("foo", s("bar"))])),
2163 Err(N2WError::Fallback(_))
2164 ));
2165 }
2166
2167 #[test]
2169 fn ordinal_kwargs() {
2170 let l = LangRu::new();
2171 let ok = |n: i64, items: &[(&str, KwVal)]| {
2172 l.to_ordinal_kw(&BigInt::from(n), &kw(items)).unwrap()
2173 };
2174 assert_eq!(ok(1234, &[("case", s("g"))]), "тысяча двести тридцать четвёртого");
2175 assert_eq!(ok(21, &[("case", s("i"))]), "двадцать первым");
2176 assert_eq!(ok(3, &[("case", s("d"))]), "третьему");
2177 assert_eq!(ok(100, &[("case", s("p"))]), "сотом");
2178 assert_eq!(
2179 ok(1, &[("case", s("n")), ("plural", KwVal::Bool(true))]),
2180 "первые"
2181 );
2182 assert!(matches!(
2184 l.to_ordinal_kw(&BigInt::from(-5), &kw(&[("case", s("xx"))])),
2185 Err(N2WError::Type(_))
2186 ));
2187 assert!(matches!(
2188 l.to_ordinal_kw(&BigInt::from(5), &kw(&[("case", s("xx"))])),
2189 Err(N2WError::Key(_))
2190 ));
2191 }
2192
2193 #[test]
2194 fn negative_ordinal_is_type_error() {
2195 let l = LangRu::new();
2196 assert!(matches!(
2197 l.to_ordinal(&BigInt::from(-1)),
2198 Err(N2WError::Type(_))
2199 ));
2200 assert!(matches!(
2201 l.to_ordinal_num(&BigInt::from(-1)),
2202 Err(N2WError::Type(_))
2203 ));
2204 }
2205}