use crate::base::{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;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{One, Signed, Zero};
use std::collections::HashMap;
const ZERO: &str = "нуль";
const NEGWORD: &str = "мінус";
const POINTWORD: &str = "кома";
static ONES_FEMININE: [[&str; 6]; 10] = [
["", "", "", "", "", ""], ["одна", "однієї", "одній", "одну", "однією", "одній"],
["дві", "двох", "двом", "дві", "двома", "двох"],
["три", "трьох", "трьом", "три", "трьома", "трьох"],
[
"чотири",
"чотирьох",
"чотирьом",
"чотири",
"чотирма",
"чотирьох",
],
["п'ять", "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"],
["шість", "шести", "шести", "шість", "шістьма", "шести"],
["сім", "семи", "семи", "сім", "сьома", "семи"],
["вісім", "восьми", "восьми", "вісім", "вісьма", "восьми"],
[
"дев'ять",
"дев'яти",
"дев'яти",
"дев'ять",
"дев'ятьма",
"дев'яти",
],
];
static ONES: [[&str; 6]; 10] = [
["", "", "", "", "", ""], ["один", "одного", "одному", "один", "одним", "одному"],
["два", "двох", "двом", "два", "двома", "двох"],
["три", "трьох", "трьом", "три", "трьома", "трьох"],
[
"чотири",
"чотирьох",
"чотирьом",
"чотири",
"чотирма",
"чотирьох",
],
["п'ять", "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"],
["шість", "шести", "шести", "шість", "шістьма", "шести"],
["сім", "семи", "семи", "сім", "сьома", "семи"],
["вісім", "восьми", "восьми", "вісім", "вісьма", "восьми"],
[
"дев'ять",
"дев'яти",
"дев'яти",
"дев'ять",
"дев'ятьма",
"дев'яти",
],
];
static ONES_ORDINALS: [[&str; 2]; 20] = [
["", ""], ["перший", "одно"],
["другий", "двох"],
["третій", "трьох"],
["четвертий", "чотирьох"],
["п'ятий", "п'яти"],
["шостий", "шести"],
["сьомий", "семи"],
["восьмий", "восьми"],
["дев'ятий", "дев'яти"],
["десятий", "десяти"],
["одинадцятий", "одинадцяти"],
["дванадцятий", "дванадцяти"],
["тринадцятий", "тринадцяти"],
["чотирнадцятий", "чотирнадцяти"],
["п'ятнадцятий", "п'ятнадцяти"],
["шістнадцятий", "шістнадцяти"],
["сімнадцятий", "сімнадцяти"],
["вісімнадцятий", "вісімнадцяти"],
["дев'ятнадцятий", "дев'ятнадцяти"],
];
static TENS: [[&str; 6]; 10] = [
["десять", "десяти", "десяти", "десять", "десятьма", "десяти"],
[
"одинадцять",
"одинадцяти",
"одинадцяти",
"одинадцять",
"одинадцятьма",
"одинадцяти",
],
[
"дванадцять",
"дванадцяти",
"дванадцяти",
"дванадцять",
"дванадцятьма",
"дванадцяти",
],
[
"тринадцять",
"тринадцяти",
"тринадцяти",
"тринадцять",
"тринадцятьма",
"тринадцяти",
],
[
"чотирнадцять",
"чотирнадцяти",
"чотирнадцяти",
"чотирнадцять",
"чотирнадцятьма",
"чотирнадцяти",
],
[
"п'ятнадцять",
"п'ятнадцяти",
"п'ятнадцяти",
"п'ятнадцять",
"п'ятнадцятьма",
"п'ятнадцяти",
],
[
"шістнадцять",
"шістнадцяти",
"шістнадцяти",
"шістнадцять",
"шістнадцятьма",
"шістнадцяти",
],
[
"сімнадцять",
"сімнадцяти",
"сімнадцяти",
"сімнадцять",
"сімнадцятьма",
"сімнадцяти",
],
[
"вісімнадцять",
"вісімнадцяти",
"вісімнадцяти",
"вісімнадцять",
"вісімнадцятьма",
"вісімнадцяти",
],
[
"дев'ятнадцять",
"дев'ятнадцяти",
"дев'ятнадцяти",
"дев'ятнадцять",
"дев'ятнадцятьма",
"дев'ятнадцяти",
],
];
static TWENTIES: [[&str; 6]; 10] = [
["", "", "", "", "", ""], ["", "", "", "", "", ""], [
"двадцять",
"двадцяти",
"двадцяти",
"двадцять",
"двадцятьма",
"двадцяти",
],
[
"тридцять",
"тридцяти",
"тридцяти",
"тридцять",
"тридцятьма",
"тридцяти",
],
["сорок", "сорока", "сорока", "сорок", "сорока", "сорока"],
[
"п'ятдесят",
"п'ятдесяти",
"п'ятдесяти",
"п'ятдесят",
"п'ятдесятьма",
"п'ятдесяти",
],
[
"шістдесят",
"шістдесяти",
"шістдесяти",
"шістдесят",
"шістдесятьма",
"шістдесяти",
],
[
"сімдесят",
"сімдесяти",
"сімдесяти",
"сімдесят",
"сімдесятьма",
"сімдесяти",
],
[
"вісімдесят",
"вісімдесяти",
"вісімдесяти",
"вісімдесят",
"вісімдесятьма",
"вісімдесяти",
],
[
"дев'яносто",
"дев'яноста",
"дев'яноста",
"дев'яносто",
"дев'яностами",
"дев'яноста",
],
];
static TWENTIES_ORDINALS: [[&str; 2]; 10] = [
["", ""], ["", ""], ["двадцятий", "двадцяти"],
["тридцятий", "тридцяти"],
["сороковий", "сорока"],
["п'ятдесятий", "п'ятдесяти"],
["шістдесятий", "шістдесяти"],
["сімдесятий", "сімдесяти"],
["вісімдесятий", "вісімдесяти"],
["дев'яностий", "дев'яности"], ];
static HUNDREDS: [[&str; 6]; 10] = [
["", "", "", "", "", ""], ["сто", "ста", "ста", "сто", "стами", "стах"],
[
"двісті",
"двохста",
"двомстам",
"двісті",
"двомастами",
"двохстах",
],
[
"триста",
"трьохста",
"трьомстам",
"триста",
"трьомастами",
"трьохстах",
],
[
"чотириста",
"чотирьохста",
"чотирьомстам",
"чотириста",
"чотирмастами",
"чотирьохстах",
],
[
"п'ятсот",
"п'ятиста",
"п'ятистам",
"п'ятсот",
"п'ятьмастами",
"п'ятистах",
],
[
"шістсот",
"шестиста",
"шестистам",
"шістсот",
"шістьмастами",
"шестистах",
],
[
"сімсот",
"семиста",
"семистам",
"сімсот",
"сьомастами",
"семистах",
],
[
"вісімсот",
"восьмиста",
"восьмистам",
"вісімсот",
"восьмастами",
"восьмистах",
],
[
"дев'ятсот",
"дев'ятиста",
"дев'ятистам",
"дев'ятсот",
"дев'ятьмастами",
"дев'ятистах",
],
];
static HUNDREDS_ORDINALS: [[&str; 2]; 10] = [
["", ""], ["сотий", "сто"],
["двохсотий", "двохсот"],
["трьохсотий", "трьохсот"],
["чотирьохсотий", "чотирьохсот"],
["п'ятисотий", "п'ятсот"],
["шестисотий", "шістсот"],
["семисотий", "сімсот"],
["восьмисотий", "вісімсот"],
["дев'ятисотий", "дев'ятсот"],
];
static THOUSANDS: [[[&str; 3]; 6]; 11] = [
[
["", "", ""],
["", "", ""],
["", "", ""],
["", "", ""],
["", "", ""],
["", "", ""],
],
[
["тисяча", "тисячі", "тисяч"],
["тисячи", "тисяч", "тисяч"], ["тисячі", "тисячам", "тисячам"],
["тисячу", "тисячі", "тисяч"],
["тисячею", "тисячами", "тисячами"],
["тисячі", "тисячах", "тисячах"],
],
[
["мільйон", "мільйони", "мільйонів"],
["мільйона", "мільйонів", "мільйонів"],
["мільйону", "мільйонам", "мільйонам"],
["мільйон", "мільйони", "мільйонів"],
["мільйоном", "мільйонами", "мільйонів"],
["мільйоні", "мільйонах", "мільйонах"],
],
[
["мільярд", "мільярди", "мільярдів"],
["мільярда", "мільярдів", "мільярдів"],
["мільярду", "мільярдам", "мільярдам"],
["мільярд", "мільярди", "мільярдів"],
["мільярдом", "мільярдами", "мільярдів"],
["мільярді", "мільярдах", "мільярдах"],
],
[
["трильйон", "трильйони", "трильйонів"],
["трильйона", "трильйонів", "трильйонів"],
["трильйону", "трильйонам", "трильйонам"],
["трильйон", "трильйони", "трильйонів"],
["трильйоном", "трильйонами", "трильйонів"],
["трильйоні", "трильйонах", "трильйонах"],
],
[
["квадрильйон", "квадрильйони", "квадрильйонів"],
["квадрильйона", "квадрильйонів", "квадрильйонів"],
["квадрильйону", "квадрильйонам", "квадрильйонам"],
["квадрильйон", "квадрильйони", "квадрильйонів"],
["квадрильйоном", "квадрильйонами", "квадрильйонів"],
["квадрильйоні", "квадрильйонах", "квадрильйонах"],
],
[
["квінтильйон", "квінтильйони", "квінтильйонів"],
["квінтильйона", "квінтильйонів", "квінтильйонів"],
["квінтильйону", "квінтильйонам", "квінтильйонам"],
["квінтильйон", "квінтильйони", "квінтильйонів"],
["квінтильйоном", "квінтильйонами", "квінтильйонів"],
["квінтильйоні", "квінтильйонах", "квінтильйонах"],
],
[
["секстильйон", "секстильйони", "секстильйонів"],
["секстильйона", "секстильйонів", "секстильйонів"],
["секстильйону", "секстильйонам", "секстильйонам"],
["секстильйон", "секстильйони", "секстильйонів"],
["секстильйоном", "секстильйонами", "секстильйонів"],
["секстильйоні", "секстильйонах", "секстильйонах"],
],
[
["септильйон", "септильйони", "септильйонів"],
["септильйона", "септильйонів", "септильйонів"],
["септильйону", "септильйонам", "септильйонам"],
["септильйон", "септильйони", "септильйонів"],
["септильйоном", "септильйонами", "септильйонів"],
["септильйоні", "септильйонах", "септильйонах"],
],
[
["октильйон", "октильйони", "октильйонів"],
["октильйона", "октильйонів", "октильйонів"],
["октильйону", "октильйонам", "октильйонам"],
["октильйон", "октильйони", "октильйонів"],
["октильйоном", "октильйонами", "октильйонів"],
["октильйоні", "октильйонах", "октильйонах"],
],
[
["нонільйон", "нонільйони", "нонільйонів"],
["нонільйона", "нонільйонів", "нонільйонів"],
["нонільйону", "нонільйонам", "нонільйонам"],
["нонільйон", "нонільйони", "нонільйонів"],
["нонільйоном", "нонільйонами", "нонільйонів"],
["нонільйоні", "нонільйонах", "нонільйонах"],
],
];
static PREFIXES_ORDINAL: [&str; 11] = [
"", "тисячний",
"мільйонний",
"мільярдний",
"трильйонний",
"квадрильйонний",
"квінтильйонний",
"секстильйонний",
"септильйонний",
"октильйонний",
"нонільйонний",
];
const GENERIC_DOLLARS: &[&str] = &["долар", "долари", "доларів"];
const GENERIC_CENTS: &[&str] = &["цент", "центи", "центів"];
static FEMININE_MONEY: [&str; 33] = [
"AOA", "BAM", "BDT", "BWP", "CZK", "DKK", "ERN", "HNL", "HRK", "IDR", "INR", "ISK", "JPY",
"KPW", "KRW", "LKR", "MOP", "MRU", "MUR", "MVR", "MWK", "NGN", "NIO", "NOK", "NPR", "PKR",
"SCR", "SEK", "STN", "TRY", "WST", "UAH", "ZMW",
];
static FEMININE_CENTS: [&str; 15] = [
"ALL", "BDT", "BGN", "BYN", "GHS", "HRK", "ILS", "INR", "NPR", "OMR", "OMR", "PKR", "RSD",
"RUB", "UAH",
];
static CURRENCY_FORMS_DATA: &[(&str, &[&str], &[&str])] = &[
(
"AED",
&["дирхам", "дирхами", "дирхамів"],
&["філс", "філси", "філсів"],
),
(
"AFN",
&["афгані", "афгані", "афгані"],
&["пул", "пули", "пулів"],
),
(
"ALL",
&["лек", "леки", "леків"],
&["кіндарка", "кіндарки", "кіндарок"],
),
("AMD", &["драм", "драми", "драмів"], &["лум", "лум", "лум"]),
("ANG", &["гульден", "гульдени", "гульденів"], GENERIC_CENTS),
(
"AOA",
&["кванза", "кванзи", "кванз"],
&["сентимо", "сентимо", "сентимо"],
),
(
"ARS",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
("AUD", GENERIC_DOLLARS, GENERIC_CENTS),
("AWG", &["флорин", "флорини", "флоринів"], GENERIC_CENTS),
(
"AZN",
&["манат", "манати", "манатів"],
&["гяпік", "гяпіки", "гяпіків"],
),
(
"BAM",
&["марка", "марки", "марок"],
&["фенінг", "фенінги", "фенінгів"],
),
("BBD", GENERIC_DOLLARS, GENERIC_CENTS),
("BDT", &["така", "таки", "так"], &["пойша", "пойші", "пойш"]),
(
"BGN",
&["лев", "леви", "левів"],
&["стотинка", "стотинки", "стотинок"],
),
(
"BHD",
&["динар", "динари", "динарів"],
&["філс", "філси", "філсів"],
),
(
"BIF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
("BMD", GENERIC_DOLLARS, GENERIC_CENTS),
("BND", GENERIC_DOLLARS, GENERIC_CENTS),
(
"BOB",
&["болівіано", "болівіано", "болівіано"],
&["сентаво", "сентаво", "сентаво"],
),
(
"BRL",
&["реал", "реали", "реалів"],
&["сентаво", "сентаво", "сентаво"],
),
("BSD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"BTN",
&["нгултрум", "нгултруми", "нгултрумів"],
&["четрум", "четруми", "четрумів"],
),
(
"BWP",
&["пула", "пули", "пул"],
&["тхебе", "тхебе", "тхебе"],
),
(
"BYN",
&["рубель", "рублі", "рублів"],
&["копійка", "копійки", "копійок"],
),
("BZD", GENERIC_DOLLARS, GENERIC_CENTS),
("CAD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"CDF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
(
"CHF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
(
"CLP",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"CNY",
&["юань", "юані", "юанів"],
&["финь", "фині", "финів"],
),
(
"COP",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"CRC",
&["колон", "колони", "колонів"],
&["сентімо", "сентімо", "сентімо"],
),
(
"CUC",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"CUP",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"CVE",
&["ескудо", "ескудо", "ескудо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"CZK",
&["крона", "крони", "крон"],
&["гелер", "гелери", "гелерів"],
),
(
"DJF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
("DKK", &["крона", "крони", "крон"], &["ере", "ере", "ере"]),
(
"DOP",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"DZD",
&["динар", "динари", "динарів"],
&["сантим", "сантими", "сантимів"],
),
(
"EGP",
&["фунт", "фунти", "фунтів"],
&["піастр", "піастри", "піастрів"],
),
("ERN", &["накфа", "накфи", "накф"], GENERIC_CENTS),
("ETB", &["бир", "бири", "бирів"], GENERIC_CENTS),
("EUR", &["євро", "євро", "євро"], GENERIC_CENTS),
("FJD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"FKP",
&["фунт", "фунти", "фунтів"],
&["пенс", "пенси", "пенсів"],
),
(
"GBP",
&["фунт", "фунти", "фунтів"],
&["пенс", "пенси", "пенсів"],
),
(
"GEL",
&["ларі", "ларі", "ларі"],
&["тетрі", "тетрі", "тетрі"],
),
(
"GHS",
&["седі", "седі", "седі"],
&["песева", "песеви", "песев"],
),
(
"GIP",
&["фунт", "фунти", "фунтів"],
&["пенс", "пенси", "пенсів"],
),
(
"GMD",
&["даласі", "даласі", "даласі"],
&["бутут", "бутути", "бутутів"],
),
(
"GNF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
(
"GTQ",
&["кетсаль", "кетсалі", "кетсалів"],
&["сентаво", "сентаво", "сентаво"],
),
("GYD", GENERIC_DOLLARS, GENERIC_CENTS),
("HKD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"HNL",
&["лемпіра", "лемпіри", "лемпір"],
&["сентаво", "сентаво", "сентаво"],
),
("HRK", &["куна", "куни", "кун"], &["ліпа", "ліпи", "ліп"]),
(
"HTG",
&["гурд", "гурди", "гурдів"],
&["сантим", "сантими", "сантимів"],
),
(
"HUF",
&["форинт", "форинти", "форинтів"],
&["філлер", "філлери", "філлерів"],
),
("IDR", &["рупія", "рупії", "рупій"], GENERIC_CENTS),
(
"ILS",
&["шекель", "шекелі", "шекелів"],
&["агора", "агори", "агор"],
),
(
"INR",
&["рупія", "рупії", "рупій"],
&["пайса", "пайси", "пайс"],
),
(
"IQD",
&["динар", "динари", "динарів"],
&["філс", "філси", "філсів"],
),
(
"IRR",
&["ріал", "ріали", "ріалів"],
&["динар", "динари", "динарів"],
),
(
"ISK",
&["крона", "крони", "крон"],
&["ейре", "ейре", "ейре"],
),
("JMD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"JOD",
&["динар", "динари", "динарів"],
&["філс", "філси", "філсів"],
),
("JPY", &["єна", "єни", "єн"], &["сен", "сен", "сен"]),
("KES", &["шилінг", "шилінги", "шилінгів"], GENERIC_CENTS),
(
"KGS",
&["сом", "соми", "сомів"],
&["тиїн", "тиїни", "тиїнів"],
),
("KHR", &["рієль", "рієлі", "рієлів"], &["су", "су", "су"]),
(
"KMF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
("KPW", &["вона", "вони", "вон"], &["чон", "чони", "чонів"]),
(
"KRW",
&["вона", "вони", "вон"],
&["джеон", "джеони", "джеонів"],
),
(
"KWD",
&["динар", "динари", "динарів"],
&["філс", "філси", "філсів"],
),
("KYD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"KZT",
&["теньге", "теньге", "теньге"],
&["тиїн", "тиїни", "тиїнів"],
),
("LAK", &["кіп", "кіпи", "кіпів"], &["ат", "ати", "атів"]),
(
"LBP",
&["фунт", "фунти", "фунтів"],
&["піастр", "піастри", "піастрів"],
),
("LKR", &["рупія", "рупії", "рупій"], GENERIC_CENTS),
("LRD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"LSL",
&["лоті", "малоті", "малоті"],
&["сенте", "лісенте", "лісенте"],
),
(
"LYD",
&["динар", "динари", "динарів"],
&["дирхам", "дирхами", "дирхамів"],
),
(
"MAD",
&["дирхам", "дирхами", "дирхамів"],
&["сантим", "сантими", "сантимів"],
),
("MDL", &["лей", "леї", "леї"], &["бан", "бані", "бані"]),
(
"MGA",
&["аріарі", "аріарі", "аріарі"],
&["іраймбіланья", "іраймбіланья", "іраймбіланья"],
),
(
"MKD",
&["денар", "денари", "денарів"],
&["дені", "дені", "дені"],
),
("MMK", &["к'ят", "к'ят", "к'ят"], &["п'я", "п'я", "п'я"]),
(
"MNT",
&["тугрик", "тугрики", "тугриків"],
&["мунгу", "мунгу", "мунгу"],
),
(
"MOP",
&["патака", "патакі", "патак"],
&["аво", "аво", "аво"],
),
("MRU", &["угія", "угії", "угій"], &["хумс", "хумс", "хумс"]),
("MUR", &["рупія", "рупії", "рупій"], GENERIC_CENTS),
(
"MVR",
&["руфія", "руфії", "руфій"],
&["ларі", "ларі", "ларі"],
),
(
"MWK",
&["квача", "квачі", "квач"],
&["тамбала", "тамбала", "тамбала"],
),
(
"MXN",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
("MYR", &["рингіт", "рингіти", "рингітів"], GENERIC_CENTS),
(
"MZN",
&["метікал", "метікали", "метікалів"],
&["сентаво", "сентаво", "сентаво"],
),
("NAD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"NGN",
&["найра", "найри", "найр"],
&["кобо", "кобо", "кобо"],
),
(
"NIO",
&["кордоба", "кордоби", "кордоб"],
&["сентаво", "сентаво", "сентаво"],
),
("NOK", &["крона", "крони", "крон"], &["ере", "ере", "ере"]),
(
"NPR",
&["рупія", "рупії", "рупій"],
&["пайса", "пайси", "пайс"],
),
("NZD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"OMR",
&["ріал", "ріали", "ріалів"],
&["байза", "байзи", "байз"],
),
(
"PAB",
&["бальбоа", "бальбоа", "бальбоа"],
&["сентесімо", "сентесімо", "сентесімо"],
),
(
"PEN",
&["соль", "соль", "соль"],
&["сентімо", "сентімо", "сентімо"],
),
("PGK", &["кіна", "кіна", "кіна"], &["тойя", "тойя", "тойя"]),
(
"PHP",
&["песо", "песо", "песо"],
&["сентаво", "сентаво", "сентаво"],
),
(
"PKR",
&["рупія", "рупії", "рупій"],
&["пайса", "пайси", "пайс"],
),
(
"PLN",
&["злотий", "злоті", "злотих"],
&["грош", "гроші", "грошів"],
),
(
"PYG",
&["гуарані", "гуарані", "гуарані"],
&["сентімо", "сентімо", "сентімо"],
),
(
"QAR",
&["ріал", "ріали", "ріалів"],
&["дирхам", "дирхами", "дирхамів"],
),
("RON", &["лей", "леї", "леї"], &["бан", "бані", "бані"]),
(
"RSD",
&["динар", "динари", "динарів"],
&["пара", "пари", "пар"],
),
(
"RUB",
&["рубль", "рублі", "рублів"],
&["копійка", "копійки", "копійок"],
),
(
"RWF",
&["франк", "франки", "франків"],
&["сантим", "сантими", "сантимів"],
),
(
"SAR",
&["ріал", "ріали", "ріалів"],
&["халал", "халали", "халалів"],
),
("SBD", GENERIC_DOLLARS, GENERIC_CENTS),
("SCR", &["рупія", "рупії", "рупій"], GENERIC_CENTS),
(
"SDG",
&["фунт", "фунти", "фунтів"],
&["піастр", "піастри", "піастрів"],
),
("SEK", &["крона", "крони", "крон"], &["ере", "ере", "ере"]),
("SGD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"SHP",
&["фунт", "фунти", "фунтів"],
&["пенс", "пенси", "пенсів"],
),
("SLL", &["леоне", "леоне", "леоне"], GENERIC_CENTS),
("SOS", &["шилінг", "шилінги", "шилінгів"], GENERIC_CENTS),
("SRD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"SSP",
&["фунт", "фунти", "фунтів"],
&["піастр", "піастри", "піастрів"],
),
(
"STN",
&["добра", "добри", "добр"],
&["сентімо", "сентімо", "сентімо"],
),
(
"SYP",
&["фунт", "фунти", "фунтів"],
&["піастр", "піастри", "піастрів"],
),
(
"SZL",
&["ліланґені", "ліланґені", "ліланґені"],
GENERIC_CENTS,
),
(
"THB",
&["бат", "бати", "батів"],
&["сатанг", "сатанги", "сатангів"],
),
(
"TJS",
&["сомоні", "сомоні", "сомоні"],
&["дірам", "дірами", "дірамів"],
),
(
"TMT",
&["манат", "манати", "манатів"],
&["тенге", "тенге", "тенге"],
),
(
"TND",
&["динар", "динари", "динарів"],
&["міллім", "мілліми", "міллімів"],
),
(
"TOP",
&["паанга", "паанга", "паанга"],
&["сеніті", "сеніті", "сеніті"],
),
(
"TRY",
&["ліра", "ліри", "лір"],
&["куруш", "куруші", "курушів"],
),
("TTD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"TWD",
&["новий долар", "нові долари", "нових доларів"],
GENERIC_CENTS,
),
("TZS", &["шилінг", "шилінги", "шилінгів"], GENERIC_CENTS),
(
"UAH",
&["гривня", "гривні", "гривень"],
&["копійка", "копійки", "копійок"],
),
("UGX", &["шилінг", "шилінги", "шилінгів"], GENERIC_CENTS),
("USD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"UYU",
&["песо", "песо", "песо"],
&["сентесімо", "сентесімо", "сентесімо"],
),
(
"UZS",
&["сум", "суми", "сумів"],
&["тиїн", "тиїни", "тиїнів"],
),
("VND", &["донг", "донги", "донгів"], &["су", "су", "су"]),
("WST", &["тала", "тали", "тал"], &["сене", "сене", "сене"]),
("XCD", GENERIC_DOLLARS, GENERIC_CENTS),
(
"YER",
&["ріал", "ріали", "ріалів"],
&["філс", "філси", "філсів"],
),
("ZAR", &["ранд", "ранди", "рандів"], GENERIC_CENTS),
(
"ZMW",
&["квача", "квачі", "квач"],
&["нгве", "нгве", "нгве"],
),
];
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
CURRENCY_FORMS_DATA
.iter()
.map(|(code, unit, sub)| (*code, CurrencyForms::new(unit, sub)))
.collect()
}
fn is_feminine_money(currency: &str) -> bool {
FEMININE_MONEY.iter().any(|c| *c == currency)
}
fn is_feminine_cents(currency: &str) -> bool {
FEMININE_CENTS.iter().any(|c| *c == currency)
}
fn index_error(msg: &str) -> N2WError {
N2WError::Index(msg.to_string())
}
fn key_error(key: String) -> N2WError {
N2WError::Key(key)
}
fn value_error(msg: String) -> N2WError {
N2WError::Value(msg)
}
fn parse_chunk(s: &str) -> Result<u32> {
if !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()) {
Ok(s.parse::<u32>().unwrap_or(0))
} else {
Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
s
)))
}
}
fn parse_int(s: &str) -> Result<BigInt> {
if !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()) {
s.parse::<BigInt>()
.map_err(|_| value_error(format!("invalid literal for int() with base 10: '{}'", s)))
} else {
Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
s
)))
}
}
fn float_repr_has_point(v: f64) -> bool {
v == 0.0 || (v.abs() >= 1e-4 && v.abs() < 1e16)
}
fn parse_signed_int(s: &str) -> Option<BigInt> {
let t = s.strip_prefix('-').unwrap_or(s);
if t.is_empty() || !t.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
s.parse::<BigInt>().ok()
}
fn py_value_str(value: &FloatValue) -> String {
match value {
FloatValue::Float { value, .. } => format!("{}", value),
FloatValue::Decimal { value, .. } => python_decimal_str(value),
}
}
fn splitbyx(n: &str, x: usize) -> Result<Vec<u32>> {
let chars: Vec<char> = n.chars().collect();
let length = chars.len();
let slice = |i: usize, j: usize| -> String { chars[i..j.min(length)].iter().collect() };
let mut out: Vec<u32> = Vec::new();
if length > x {
let start = length % x;
if start > 0 {
out.push(parse_chunk(&slice(0, start))?);
}
let mut i = start;
while i < length {
out.push(parse_chunk(&slice(i, i + x))?);
i += x;
}
} else {
out.push(parse_chunk(n)?);
}
Ok(out)
}
fn get_digits(n: u32) -> (usize, usize, usize) {
(
(n % 10) as usize,
((n / 10) % 10) as usize,
((n / 100) % 10) as usize,
)
}
fn thousands_at(i: usize, mcase: usize) -> Result<&'static [&'static str; 3]> {
if i == 0 || i >= THOUSANDS.len() {
return Err(key_error(i.to_string()));
}
THOUSANDS[i]
.get(mcase)
.ok_or_else(|| index_error("tuple index out of range"))
}
fn prefixes_ordinal_at(level: usize) -> Result<&'static str> {
if level == 0 || level >= PREFIXES_ORDINAL.len() {
return Err(key_error(level.to_string()));
}
Ok(PREFIXES_ORDINAL[level])
}
fn pluralize(n: u32, forms: &'static [&'static str; 3]) -> &'static str {
let form = if n % 100 < 10 || n % 100 > 20 {
if n % 10 == 1 {
0
} else if n % 10 > 1 && n % 10 < 5 {
1
} else {
2
}
} else {
2
};
forms[form]
}
fn last_fragment_to_ordinal(last: u32, words: &mut Vec<String>, level: usize) -> Result<()> {
let (n1, n2, n3) = get_digits(last);
let last_two = n2 * 10 + n1;
if last_two == 0 {
words.push(HUNDREDS_ORDINALS[n3][level].to_string());
} else if level == 1 && last == 1 {
return Ok(());
} else if last_two < 20 {
if level == 0 {
if n3 > 0 {
words.push(HUNDREDS[n3][0].to_string());
}
words.push(ONES_ORDINALS[last_two][0].to_string());
} else {
let mut s = String::new();
if n3 > 0 {
s.push_str(HUNDREDS_ORDINALS[n3][1]);
}
s.push_str(ONES_ORDINALS[last_two][1]);
words.push(s);
}
} else if last_two % 10 == 0 {
if level == 0 {
if n3 > 0 {
words.push(HUNDREDS[n3][0].to_string());
}
words.push(TWENTIES_ORDINALS[n2][0].to_string());
} else {
let mut s = String::new();
if n3 > 0 {
s.push_str(HUNDREDS_ORDINALS[n3][1]);
}
s.push_str(TWENTIES_ORDINALS[n2][1]);
words.push(s);
}
} else {
if level == 0 {
if n3 > 0 {
words.push(HUNDREDS[n3][0].to_string());
}
words.push(TWENTIES[n2][0].to_string());
words.push(ONES_ORDINALS[n1][0].to_string());
} else {
let mut s = String::new();
if n3 > 0 {
s.push_str(HUNDREDS_ORDINALS[n3][1]);
}
s.push_str(TWENTIES_ORDINALS[n2][1]);
s.push_str(ONES_ORDINALS[n1][1]);
words.push(s);
}
}
Ok(())
}
pub struct LangUk {
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangUk {
fn default() -> Self {
Self::new()
}
}
impl LangUk {
pub fn new() -> Self {
LangUk {
currency_forms: build_currency_forms(),
}
}
fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
if value.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
value
)));
}
Ok(())
}
fn int2word(&self, n: &BigInt, feminine: bool, mcase: usize) -> Result<String> {
if n.is_negative() {
let n_value = self.int2word(&n.abs(), feminine, mcase)?;
return Ok(format!("{} {}", NEGWORD, n_value));
}
if n.is_zero() {
return Ok(ZERO.to_string());
}
let mut words: Vec<String> = Vec::new();
let chunks = splitbyx(&n.to_string(), 3)?;
let mut i = chunks.len();
for x in chunks.iter().copied() {
i -= 1;
if x == 0 {
continue;
}
let (n1, n2, n3) = get_digits(x);
if n3 > 0 {
words.push(HUNDREDS[n3][mcase].to_string());
}
if n2 > 1 {
words.push(TWENTIES[n2][mcase].to_string());
}
if n2 == 1 {
words.push(TENS[n1][mcase].to_string());
} else if n1 > 0 {
let ones = if i == 1 || (feminine && i == 0) {
&ONES_FEMININE
} else {
&ONES
};
words.push(ones[n1][mcase].to_string());
}
if i > 0 {
let thousands_val = thousands_at(i, mcase)?;
words.push(pluralize(x, thousands_val).to_string());
}
}
Ok(words.join(" "))
}
}
impl Lang for LangUk {
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
","
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"кома"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.int2word(value, false, 0)
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let (is_negative, left_int, right): (bool, BigInt, String) = match value {
FloatValue::Float { value, precision } => {
let precision = *precision as usize;
let s = format!("{value:.precision$}");
let is_negative = s.starts_with('-');
let abs_s = s.strip_prefix('-').unwrap_or(&s);
match abs_s.split_once('.') {
Some((l, r)) => (is_negative, parse_int(l)?, r.to_string()),
None => (is_negative, parse_int(abs_s)?, String::new()),
}
}
FloatValue::Decimal { value, precision } => {
let precision = *precision;
let is_negative = value.is_negative();
let abs_v = value.abs();
let divisor = BigInt::from(10).pow(precision);
let mantissa = (&abs_v * BigDecimal::from(divisor.clone()))
.with_scale(0)
.as_bigint_and_exponent()
.0;
let left_int = &mantissa / &divisor;
let right_int = &mantissa % &divisor;
let right = if precision == 0 {
String::new()
} else {
let digits = right_int.to_string();
let pad = (precision as usize).saturating_sub(digits.len());
format!("{}{}", "0".repeat(pad), digits)
};
(is_negative, left_int, right)
}
};
if right.is_empty() {
let n = if is_negative { -left_int } else { left_int };
return self.int2word(&n, false, 0);
}
let leading_zero_count = right.len() - right.trim_start_matches('0').len();
let right_int = parse_int(&right)?;
let left_word = self.int2word(&left_int, false, 0)?;
let right_side = self.int2word(&right_int, false, 0)?;
let decimal_part = format!(
"{}{}",
format!("{} ", ZERO).repeat(leading_zero_count),
right_side
);
let mut result = format!("{} {} {}", left_word, POINTWORD, decimal_part);
if is_negative {
result = format!("{} {}", NEGWORD, result);
}
Ok(result)
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
let has_point = match value {
FloatValue::Float { value, .. } => float_repr_has_point(*value),
FloatValue::Decimal { value, .. } => python_decimal_str(value).contains('.'),
};
if has_point {
return self.to_cardinal_float(value, precision_override);
}
match value {
FloatValue::Float { value, .. } => Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
value
))),
FloatValue::Decimal { value, .. } => {
let s = python_decimal_str(value);
match parse_signed_int(&s) {
Some(n) => self.int2word(&n, false, 0),
None => Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
s
))),
}
}
}
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let whole = match value.as_whole_int() {
Some(i) => i,
None => {
return Err(N2WError::Type(format!(
"Cannot treat float {} as ordinal.",
py_value_str(value)
)))
}
};
if whole.is_negative() {
return Err(N2WError::Type(format!(
"Cannot treat negative num {} as ordinal.",
py_value_str(value)
)));
}
match value {
FloatValue::Float { value, .. } => Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
value
))),
FloatValue::Decimal { value, .. } => {
let s = python_decimal_str(value);
if parse_signed_int(&s).is_some() {
self.to_ordinal(&whole)
} else {
Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
s
)))
}
}
}
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
match python_decimal_parse(s)? {
ParsedNumber::Inf { negative } => Err(value_error(format!(
"invalid literal for int() with base 10: '{}Infinity'",
if negative { "-" } else { "" }
))),
other => Ok(other),
}
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.verify_ordinal(value)?;
if value.is_zero() {
return Ok("нульовий".to_string());
}
let mut words: Vec<String> = Vec::new();
let mut fragments = splitbyx(&value.to_string(), 3)?;
let mut level: usize = 0;
let mut last = *fragments
.last()
.ok_or_else(|| index_error("list index out of range"))?;
while last == 0 {
level += 1;
fragments.pop();
last = *fragments
.last()
.ok_or_else(|| index_error("list index out of range"))?;
}
if fragments.len() > 1 {
let pow = BigInt::from(1000u32).pow(level as u32);
let pre_part = self.int2word(&(value - BigInt::from(last) * pow), false, 0)?;
words.push(pre_part);
}
last_fragment_to_ordinal(last, &mut words, if level == 0 { 0 } else { 1 })?;
let mut output = words.join(" ");
if last == 1 && level > 0 && !output.is_empty() {
output.push(' ');
}
if level > 0 {
output.push_str(prefixes_ordinal_at(level)?);
}
Ok(output)
}
fn lang_name(&self) -> &str {
"Num2Word_UK"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let ten = BigInt::from(10);
let m100 = n.mod_floor(&BigInt::from(100));
let m10 = n.mod_floor(&ten);
let form = if m100 < ten || m100 > BigInt::from(20) {
if m10.is_one() {
0
} else if m10 > BigInt::one() && m10 < BigInt::from(5) {
1
} else {
2
}
} else {
2
};
forms
.get(form)
.cloned()
.ok_or_else(|| index_error("tuple index out of range"))
}
fn money_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
self.int2word(number, is_feminine_money(currency), 0)
}
fn cents_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
self.int2word(number, is_feminine_cents(currency), 0)
}
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());
if let CurrencyValue::Int(v) = val {
if let Some(forms) = self.currency_forms.get(currency) {
let cr1 = &forms.unit;
let minus_str = if v.is_negative() { NEGWORD } else { "" };
let abs_val = v.abs();
let money_str = self.to_cardinal(&abs_val)?;
let currency_str = if abs_val.is_one() {
&cr1[0]
} else if cr1.len() > 1 {
&cr1[1]
} else {
&cr1[0]
};
return Ok(format!("{} {} {}", minus_str, money_str, currency_str)
.trim()
.to_string());
}
}
crate::currency::default_to_currency(self, val, currency, cents, separator, adjective)
}
}
#[cfg(test)]
mod float_tests {
use super::*;
use std::str::FromStr;
fn card_float(f: f64, precision: u32) -> String {
let l = LangUk::new();
l.to_cardinal_float(
&FloatValue::Float {
value: f,
precision,
},
None,
)
.unwrap()
}
fn card_dec(s: &str) -> String {
let l = LangUk::new();
let value = BigDecimal::from_str(s).unwrap();
let precision = s.split_once('.').map(|(_, f)| f.len() as u32).unwrap_or(0);
l.to_cardinal_float(&FloatValue::Decimal { value, precision }, None)
.unwrap()
}
#[test]
fn corpus_floats() {
assert_eq!(card_float(0.0, 1), "нуль кома нуль нуль");
assert_eq!(card_float(0.5, 1), "нуль кома п'ять");
assert_eq!(card_float(1.0, 1), "один кома нуль нуль");
assert_eq!(card_float(1.5, 1), "один кома п'ять");
assert_eq!(card_float(2.25, 2), "два кома двадцять п'ять");
assert_eq!(card_float(3.14, 2), "три кома чотирнадцять");
assert_eq!(card_float(0.01, 2), "нуль кома нуль один");
assert_eq!(card_float(0.1, 1), "нуль кома один");
assert_eq!(card_float(0.99, 2), "нуль кома дев'яносто дев'ять");
assert_eq!(card_float(1.01, 2), "один кома нуль один");
assert_eq!(card_float(12.34, 2), "дванадцять кома тридцять чотири");
assert_eq!(
card_float(99.99, 2),
"дев'яносто дев'ять кома дев'яносто дев'ять"
);
assert_eq!(card_float(100.5, 1), "сто кома п'ять");
assert_eq!(
card_float(1234.56, 2),
"одна тисяча двісті тридцять чотири кома п'ятдесят шість"
);
assert_eq!(card_float(-0.5, 1), "мінус нуль кома п'ять");
assert_eq!(card_float(-1.5, 1), "мінус один кома п'ять");
assert_eq!(
card_float(-12.34, 2),
"мінус дванадцять кома тридцять чотири"
);
assert_eq!(card_float(1.005, 3), "один кома нуль нуль п'ять");
assert_eq!(card_float(2.675, 3), "два кома шістсот сімдесят п'ять");
}
#[test]
fn corpus_decimals() {
assert_eq!(card_dec("0.01"), "нуль кома нуль один");
assert_eq!(card_dec("1.10"), "один кома десять"); assert_eq!(card_dec("12.345"), "дванадцять кома триста сорок п'ять");
assert_eq!(
card_dec("98746251323029.99"),
"дев'яносто вісім трильйонів сімсот сорок шість мільярдів двісті п'ятдесят один \
мільйон триста двадцять три тисячі двадцять дев'ять кома дев'яносто дев'ять"
);
assert_eq!(card_dec("0.001"), "нуль кома нуль нуль один");
}
}