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"), "нуль кома нуль нуль один");
}
}