num2words2 0.1.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. num2words2/README.md +69 -0
  2. num2words2/__init__.py +196 -0
  3. num2words2/_version.py +3 -0
  4. num2words2/base.py +377 -0
  5. num2words2/compat.py +29 -0
  6. num2words2/currency.py +60 -0
  7. num2words2/lang_AF.py +202 -0
  8. num2words2/lang_AM.py +193 -0
  9. num2words2/lang_AR.py +435 -0
  10. num2words2/lang_AS.py +119 -0
  11. num2words2/lang_AZ.py +208 -0
  12. num2words2/lang_BA.py +119 -0
  13. num2words2/lang_BE.py +356 -0
  14. num2words2/lang_BG.py +393 -0
  15. num2words2/lang_BN.py +242 -0
  16. num2words2/lang_BO.py +122 -0
  17. num2words2/lang_BR.py +135 -0
  18. num2words2/lang_BS.py +189 -0
  19. num2words2/lang_CA.py +476 -0
  20. num2words2/lang_CE.py +546 -0
  21. num2words2/lang_CS.py +260 -0
  22. num2words2/lang_CY.py +574 -0
  23. num2words2/lang_DA.py +170 -0
  24. num2words2/lang_DE.py +180 -0
  25. num2words2/lang_EL.py +338 -0
  26. num2words2/lang_EN.py +118 -0
  27. num2words2/lang_EN_IN.py +26 -0
  28. num2words2/lang_EN_NG.py +45 -0
  29. num2words2/lang_EO.py +130 -0
  30. num2words2/lang_ES.py +399 -0
  31. num2words2/lang_ES_CO.py +37 -0
  32. num2words2/lang_ES_CR.py +38 -0
  33. num2words2/lang_ES_GT.py +37 -0
  34. num2words2/lang_ES_NI.py +35 -0
  35. num2words2/lang_ES_VE.py +43 -0
  36. num2words2/lang_ET.py +393 -0
  37. num2words2/lang_EU.py +158 -0
  38. num2words2/lang_EUR.py +104 -0
  39. num2words2/lang_FA.py +187 -0
  40. num2words2/lang_FI.py +759 -0
  41. num2words2/lang_FO.py +136 -0
  42. num2words2/lang_FR.py +133 -0
  43. num2words2/lang_FR_BE.py +49 -0
  44. num2words2/lang_FR_CH.py +48 -0
  45. num2words2/lang_FR_DZ.py +35 -0
  46. num2words2/lang_GL.py +135 -0
  47. num2words2/lang_GU.py +172 -0
  48. num2words2/lang_HA.py +293 -0
  49. num2words2/lang_HAW.py +144 -0
  50. num2words2/lang_HE.py +314 -0
  51. num2words2/lang_HI.py +243 -0
  52. num2words2/lang_HR.py +304 -0
  53. num2words2/lang_HT.py +136 -0
  54. num2words2/lang_HU.py +189 -0
  55. num2words2/lang_HY.py +364 -0
  56. num2words2/lang_ID.py +242 -0
  57. num2words2/lang_IS.py +208 -0
  58. num2words2/lang_IT.py +265 -0
  59. num2words2/lang_JA.py +615 -0
  60. num2words2/lang_JW.py +136 -0
  61. num2words2/lang_KA.py +169 -0
  62. num2words2/lang_KK.py +136 -0
  63. num2words2/lang_KM.py +173 -0
  64. num2words2/lang_KN.py +182 -0
  65. num2words2/lang_KO.py +175 -0
  66. num2words2/lang_KZ.py +161 -0
  67. num2words2/lang_LA.py +135 -0
  68. num2words2/lang_LB.py +135 -0
  69. num2words2/lang_LN.py +136 -0
  70. num2words2/lang_LO.py +149 -0
  71. num2words2/lang_LT.py +283 -0
  72. num2words2/lang_LV.py +266 -0
  73. num2words2/lang_MG.py +143 -0
  74. num2words2/lang_MI.py +151 -0
  75. num2words2/lang_MK.py +136 -0
  76. num2words2/lang_ML.py +159 -0
  77. num2words2/lang_MN.py +319 -0
  78. num2words2/lang_MR.py +187 -0
  79. num2words2/lang_MS.py +293 -0
  80. num2words2/lang_MT.py +159 -0
  81. num2words2/lang_MY.py +152 -0
  82. num2words2/lang_NE.py +157 -0
  83. num2words2/lang_NL.py +204 -0
  84. num2words2/lang_NN.py +136 -0
  85. num2words2/lang_NO.py +143 -0
  86. num2words2/lang_OC.py +135 -0
  87. num2words2/lang_PA.py +159 -0
  88. num2words2/lang_PL.py +336 -0
  89. num2words2/lang_PS.py +136 -0
  90. num2words2/lang_PT.py +280 -0
  91. num2words2/lang_PT_BR.py +224 -0
  92. num2words2/lang_RO.py +183 -0
  93. num2words2/lang_RU.py +570 -0
  94. num2words2/lang_SA.py +136 -0
  95. num2words2/lang_SD.py +136 -0
  96. num2words2/lang_SI.py +152 -0
  97. num2words2/lang_SK.py +250 -0
  98. num2words2/lang_SL.py +306 -0
  99. num2words2/lang_SN.py +447 -0
  100. num2words2/lang_SO.py +136 -0
  101. num2words2/lang_SQ.py +252 -0
  102. num2words2/lang_SR.py +254 -0
  103. num2words2/lang_SU.py +136 -0
  104. num2words2/lang_SV.py +169 -0
  105. num2words2/lang_SW.py +219 -0
  106. num2words2/lang_TA.py +303 -0
  107. num2words2/lang_TE.py +169 -0
  108. num2words2/lang_TET.py +285 -0
  109. num2words2/lang_TG.py +149 -0
  110. num2words2/lang_TH.py +214 -0
  111. num2words2/lang_TK.py +136 -0
  112. num2words2/lang_TL.py +145 -0
  113. num2words2/lang_TR.py +882 -0
  114. num2words2/lang_TT.py +136 -0
  115. num2words2/lang_UK.py +1101 -0
  116. num2words2/lang_UR.py +168 -0
  117. num2words2/lang_UZ.py +136 -0
  118. num2words2/lang_VI.py +143 -0
  119. num2words2/lang_WO.py +136 -0
  120. num2words2/lang_YI.py +135 -0
  121. num2words2/lang_YO.py +136 -0
  122. num2words2/lang_ZH.py +296 -0
  123. num2words2/lang_ZH_CN.py +85 -0
  124. num2words2/lang_ZH_HK.py +48 -0
  125. num2words2/lang_ZH_TW.py +198 -0
  126. num2words2/utils.py +35 -0
  127. num2words2-0.1.0.dev1.data/scripts/num2words2 +98 -0
  128. num2words2-0.1.0.dev1.dist-info/METADATA +244 -0
  129. num2words2-0.1.0.dev1.dist-info/RECORD +133 -0
  130. num2words2-0.1.0.dev1.dist-info/WHEEL +5 -0
  131. num2words2-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  132. num2words2-0.1.0.dev1.dist-info/licenses/COPYING +145 -0
  133. num2words2-0.1.0.dev1.dist-info/top_level.txt +1 -0
num2words2/lang_RU.py ADDED
@@ -0,0 +1,570 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
3
+ # Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
4
+
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16
+ # MA 02110-1301 USA
17
+
18
+ from __future__ import unicode_literals
19
+
20
+ from .base import Num2Word_Base
21
+ from .utils import get_digits, splitbyx
22
+
23
+ GENDER_PLURAL_INDEXES = {
24
+ 'm': 0, 'masculine': 0, 'м': 0, 'мужской': 0,
25
+ 'f': 1, 'feminine': 1, 'ж': 0, 'женский': 0,
26
+ 'n': 2, 'neuter': 2, 'с': 0, 'средний': 0,
27
+ 'p': 3, 'plural': 3
28
+ }
29
+ CASE_INDEXES = {
30
+ 'n': 0, 'nominative': 0, 'и': 0, 'именительный': 0,
31
+ 'g': 1, 'genitive': 1, 'р': 1, 'родительный': 1,
32
+ 'd': 2, 'dative': 2, 'д': 2, 'дательный': 2,
33
+ 'a': 3, 'accusative': 3, 'в': 3, 'винительный': 3,
34
+ 'i': 4, 'instrumental': 4, 'т': 4, 'творительный': 4,
35
+ 'p': 5, 'prepositional': 5, 'п': 5, 'предложный': 5
36
+ }
37
+ # Default values
38
+ D_CASE = 'n'
39
+ D_PLURAL = False
40
+ D_GENDER = 'm'
41
+ D_ANIMATE = True
42
+
43
+
44
+ def get_num_element(cases_dict, num, **kwargs):
45
+ return case_classifier_element(cases_dict[num], **kwargs)
46
+
47
+
48
+ def case_classifier_element(classifier, case=D_CASE, plural=D_PLURAL,
49
+ gender=D_GENDER, animate=D_ANIMATE):
50
+ case = classifier[CASE_INDEXES[case]]
51
+ if isinstance(case, str):
52
+ return case
53
+
54
+ if plural:
55
+ gender = case[GENDER_PLURAL_INDEXES['plural']]
56
+ else:
57
+ gender = case[GENDER_PLURAL_INDEXES[gender]]
58
+ if isinstance(gender, str):
59
+ return gender
60
+
61
+ if animate:
62
+ return gender[0]
63
+ return gender[1]
64
+
65
+
66
+ # format:
67
+ # {n : [case_1 .. case_5]}
68
+ # case: text or [gender_1 .. gender_3 plural_4]
69
+ # gender: text or [animate, inanimate]
70
+ ONES = {
71
+ 0: ['ноль', 'ноля', 'нолю', 'ноль', 'нолём', 'ноле'],
72
+ 1: [['один', 'одна', 'одно', 'одни'],
73
+ ['одного', 'одной', 'одного', 'одних'],
74
+ ['одному', 'одной', 'одному', 'одним'],
75
+ [['одного', 'один'], 'одну', 'одно', ['одних', 'одни']],
76
+ ['одним', 'одной', 'одним', 'одними'],
77
+ ['одном', 'одной', 'одном', 'одних']],
78
+ 2: [['два', 'две', 'два', 'двое'],
79
+ ['двух'] * 3 + ['двоих'],
80
+ ['двум'] * 3 + ['двоим'],
81
+ [['двух', 'два'], ['двух', 'две'], 'два', 'двоих'],
82
+ ['двумя'] * 3 + ['двоими'],
83
+ ['двух'] * 3 + ['двоих']],
84
+ 3: [['три'] * 3 + ['трое'],
85
+ ['трёх'] * 3 + ['троих'],
86
+ ['трём'] * 3 + ['троим'],
87
+ [['трёх', 'три'], ['трёх', 'три'], 'три', 'троих'],
88
+ ['тремя'] * 3 + ['троими'],
89
+ ['трёх'] * 3 + ['троих']],
90
+ 4: [['четыре'] * 3 + ['четверо'],
91
+ ['четырёх'] * 3 + ['четверых'],
92
+ ['четырём'] * 3 + ['четверым'],
93
+ [['четырёх', 'четыре'], ['четырёх', 'четыре'], 'четыре', 'четверых'],
94
+ ['четырьмя'] * 3 + ['четверыми'],
95
+ ['четырёх'] * 3 + ['четверых']],
96
+ 5: ['пять', 'пяти', 'пяти', 'пять', 'пятью', 'пяти'],
97
+ 6: ['шесть', 'шести', 'шести', 'шесть', 'шестью', 'шести'],
98
+ 7: ['семь', 'семи', 'семи', 'семь', 'семью', 'семи'],
99
+ 8: ['восемь', 'восьми', 'восьми', 'восемь', 'восемью', 'восьми'],
100
+ 9: ['девять', 'девяти', 'девяти', 'девять', 'девятью', 'девяти']
101
+ }
102
+
103
+ ONES_ORD_PREFIXES = {0: 'нулев', 1: 'перв', 2: 'втор', 4: 'четвёрт', 5: 'пят',
104
+ 6: 'шест', 7: 'седьм', 8: 'восьм', 9: 'девят'}
105
+ ONES_ORD_POSTFIXES_GROUPS = {0: 0, 1: 1, 2: 0, 4: 1, 5: 1, 6: 0, 7: 0, 8: 0,
106
+ 9: 1}
107
+ CASE_POSTFIXES = [[{0: 'ой', 1: 'ый'}, 'ая', 'ое', 'ые'],
108
+ ['ого', 'ой', 'ого', 'ых'],
109
+ ['ому', 'ой', 'ому', 'ым'],
110
+ [['ого', {0: 'ой', 1: 'ый'}], 'ую', 'ое', ['ых', 'ые']],
111
+ ['ым', 'ой', 'ым', 'ыми'],
112
+ ['ом', 'ой', 'ом', 'ых']]
113
+
114
+
115
+ def get_cases(prefix, post_group):
116
+ return [[
117
+ prefix + postfix if isinstance(postfix, str) else
118
+ [prefix + animate if isinstance(animate, str) else
119
+ prefix + animate[post_group]
120
+ for animate in postfix] if isinstance(postfix, list) else
121
+ prefix + postfix[post_group]
122
+ for postfix in case]
123
+ for case in CASE_POSTFIXES]
124
+
125
+
126
+ def get_ord_classifier(prefixes, post_groups):
127
+ if isinstance(post_groups, int):
128
+ post_groups = {n: post_groups for n, i in prefixes.items()}
129
+ return {
130
+ num: get_cases(prefix, post_groups[num])
131
+ for num, prefix in prefixes.items()
132
+ }
133
+
134
+
135
+ ONES_ORD = {
136
+ 3: [['третий', 'третья', 'третье', 'третьи'],
137
+ ['третьего', 'третьей', 'третьего', 'третьих'],
138
+ ['третьему', 'третьей', 'третьему', 'третьим'],
139
+ [['третьего', 'третий'], 'третью', 'третье', ['третьих', 'третьи']],
140
+ ['третьим', 'третьей', 'третьим', 'третьими'],
141
+ ['третьем', 'третьей', 'третьем', 'третьих']],
142
+ }
143
+ ONES_ORD.update(
144
+ get_ord_classifier(ONES_ORD_PREFIXES, ONES_ORD_POSTFIXES_GROUPS)
145
+ )
146
+
147
+ TENS_PREFIXES = {1: 'один', 2: 'две', 3: 'три', 4: 'четыр', 5: 'пят',
148
+ 6: 'шест', 7: 'сем', 8: 'восем', 9: 'девят'}
149
+ TENS_POSTFIXES = ['надцать', 'надцати', 'надцати', 'надцать', 'надцатью',
150
+ 'надцати']
151
+ TENS = {0: ['десять', 'десяти', 'десяти', 'десять', 'десятью', 'десяти']}
152
+ TENS.update({
153
+ num: [prefix + postfix for postfix in TENS_POSTFIXES]
154
+ for num, prefix in TENS_PREFIXES.items()
155
+ })
156
+
157
+ TENS_ORD_PREFIXES = {0: "десят"}
158
+ TENS_ORD_PREFIXES.update({
159
+ num: prefix + 'надцат' for num, prefix in TENS_PREFIXES.items()
160
+ })
161
+ TENS_ORD = get_ord_classifier(TENS_ORD_PREFIXES, 1)
162
+
163
+ TWENTIES = {
164
+ 2: ['двадцать', 'двадцати', 'двадцати', 'двадцать', 'двадцатью',
165
+ 'двадцати'],
166
+ 3: ['тридцать', 'тридцати', 'тридцати', 'тридцать', 'тридцатью',
167
+ 'тридцати'],
168
+ 4: ['сорок', 'сорока', 'сорока', 'сорок', 'сорока', 'сорока'],
169
+ 5: ['пятьдесят', 'пятидесяти', 'пятидесяти', 'пятьдесят', 'пятьюдесятью',
170
+ 'пятидесяти'],
171
+ 6: ['шестьдесят', 'шестидесяти', 'шестидесяти', 'шестьдесят',
172
+ 'шестьюдесятью', 'шестидесяти'],
173
+ 7: ['семьдесят', 'семидесяти', 'семидесяти', 'семьдесят', 'семьюдесятью',
174
+ 'семидесяти'],
175
+ 8: ['восемьдесят', 'восьмидесяти', 'восьмидесяти', 'восемьдесят',
176
+ 'восемьюдесятью', 'восьмидесяти'],
177
+ 9: ['девяносто', 'девяноста', 'девяноста', 'девяносто', 'девяноста',
178
+ 'девяноста'],
179
+ }
180
+
181
+ TWENTIES_ORD_PREFIXES = {2: 'двадцат', 3: 'тридцат', 4: 'сороков',
182
+ 5: 'пятидесят', 6: 'шестидесят', 7: 'семидесят',
183
+ 8: 'восьмидесят', 9: 'девяност'}
184
+ TWENTIES_ORD_POSTFIXES_GROUPS = {2: 1, 3: 1, 4: 0, 5: 1, 6: 1, 7: 1, 8: 1,
185
+ 9: 1}
186
+ TWENTIES_ORD = get_ord_classifier(TWENTIES_ORD_PREFIXES,
187
+ TWENTIES_ORD_POSTFIXES_GROUPS)
188
+
189
+ HUNDREDS = {
190
+ 1: ['сто', 'ста', 'ста', 'сто', 'ста', 'ста'],
191
+ 2: ['двести', 'двухсот', 'двумстам', 'двести', 'двумястами', 'двухстах'],
192
+ 3: ['триста', 'трёхсот', 'трёмстам', 'триста', 'тремястами', 'трёхстах'],
193
+ 4: ['четыреста', 'четырёхсот', 'четырёмстам', 'четыреста', 'четырьмястами',
194
+ 'четырёхстах'],
195
+ 5: ['пятьсот', 'пятисот', 'пятистам', 'пятьсот', 'пятьюстами', 'пятистах'],
196
+ 6: ['шестьсот', 'шестисот', 'шестистам', 'шестьсот', 'шестьюстами',
197
+ 'шестистах'],
198
+ 7: ['семьсот', 'семисот', 'семистам', 'семьсот', 'семьюстами', 'семистах'],
199
+ 8: ['восемьсот', 'восьмисот', 'восьмистам', 'восемьсот', 'восемьюстами',
200
+ 'восьмистах'],
201
+ 9: ['девятьсот', 'девятисот', 'девятистам', 'девятьсот', 'девятьюстами',
202
+ 'девятистах'],
203
+ }
204
+
205
+ HUNDREDS_ORD_PREFIXES = {
206
+ num: case[1] if num != 1 else 'сот' for num, case in HUNDREDS.items()
207
+ }
208
+ HUNDREDS_ORD = get_ord_classifier(HUNDREDS_ORD_PREFIXES, 1)
209
+
210
+
211
+ THOUSANDS_PREFIXES = {2: 'миллион', 3: 'миллиард', 4: 'триллион',
212
+ 5: 'квадриллион', 6: 'квинтиллион', 7: 'секстиллион',
213
+ 8: 'септиллион', 9: 'октиллион', 10: 'нониллион'}
214
+ THOUSANDS_POSTFIXES = [('', 'а', 'ов'),
215
+ ('а', 'ов', 'ов'),
216
+ ('у', 'ам', 'ам'),
217
+ ('', 'а', 'ов'),
218
+ ('ом', 'ами', 'ами'),
219
+ ('е', 'ах', 'ах')]
220
+ THOUSANDS = {
221
+ 1: [['тысяча', 'тысячи', 'тысяч'],
222
+ ['тысячи', 'тысяч', 'тысяч'],
223
+ ['тысяче', 'тысячам', 'тысячам'],
224
+ ['тысячу', 'тысячи', 'тысяч'],
225
+ ['тысячей', 'тысячами', 'тысячами'],
226
+ ['тысяче', 'тысячах', 'тысячах']]
227
+ }
228
+ THOUSANDS.update({
229
+ num: [
230
+ [prefix + postfix for postfix in case] for case in THOUSANDS_POSTFIXES
231
+ ] for num, prefix in THOUSANDS_PREFIXES.items()
232
+ })
233
+
234
+
235
+ def get_thousands_elements(num, case):
236
+ return THOUSANDS[num][CASE_INDEXES[case]]
237
+
238
+
239
+ THOUSANDS_ORD_PREFIXES = {1: 'тысячн'}
240
+ THOUSANDS_ORD_PREFIXES.update({
241
+ num: prefix + 'н' for num, prefix in THOUSANDS_PREFIXES.items()
242
+ })
243
+ THOUSANDS_ORD = get_ord_classifier(THOUSANDS_ORD_PREFIXES, 1)
244
+
245
+
246
+ class Num2Word_RU(Num2Word_Base):
247
+ CURRENCY_FORMS = {
248
+ 'RUB': (
249
+ ('рубль', 'рубля', 'рублей'), ('копейка', 'копейки', 'копеек')
250
+ ),
251
+ 'EUR': (
252
+ ('евро', 'евро', 'евро'), ('цент', 'цента', 'центов')
253
+ ),
254
+ 'USD': (
255
+ ('доллар', 'доллара', 'долларов'), ('цент', 'цента', 'центов')
256
+ ),
257
+ 'UAH': (
258
+ ('гривна', 'гривны', 'гривен'), ('копейка', 'копейки', 'копеек')
259
+ ),
260
+ 'KZT': (
261
+ ('тенге', 'тенге', 'тенге'), ('тиын', 'тиына', 'тиынов')
262
+ ),
263
+ 'BYN': (
264
+ ('белорусский рубль', 'белорусских рубля', 'белорусских рублей'),
265
+ ('копейка', 'копейки', 'копеек')
266
+ ),
267
+ 'UZS': (
268
+ ('сум', 'сума', 'сумов'), ('тийин', 'тийина', 'тийинов')
269
+ ),
270
+ 'PLN': (
271
+ ('польский злотый', 'польских слотых', 'польских злотых'),
272
+ ('грош', 'гроша', 'грошей')
273
+ ),
274
+ 'GBP': (
275
+ ('фунт стерлингов', 'фунта стерлингов', 'фунтов стерлингов'),
276
+ ('пенни', 'пенни', 'пенни')
277
+ ),
278
+ }
279
+
280
+ def setup(self):
281
+ self.negword = "минус"
282
+ self.pointword = ('целая', 'целых', 'целых')
283
+ self.pointword_ord = get_cases("цел", 1)
284
+
285
+ def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL,
286
+ gender=D_GENDER, animate=D_ANIMATE):
287
+ # Handle scientific notation
288
+ if isinstance(number, str) and ('e' in number.lower() or 'E' in number):
289
+ number = float(number)
290
+
291
+ # Convert float to proper format if needed
292
+ if isinstance(number, float):
293
+ # For very small numbers close to zero, just return zero
294
+ if abs(number) < 0.01:
295
+ return self._int2word(0, cardinal=True, case=case,
296
+ plural=plural, gender=gender,
297
+ animate=animate)
298
+ # Convert to string with proper precision
299
+ n = str(number)
300
+ else:
301
+ n = str(number)
302
+
303
+ n = n.replace(',', '.')
304
+ if '.' in n:
305
+ is_negative = n.startswith('-')
306
+ abs_n = n[1:] if is_negative else n
307
+ left, right = abs_n.split('.')
308
+ decimal_part = self._int2word(int(right), cardinal=True,
309
+ gender='f')
310
+ result = u'%s %s %s %s' % (
311
+ self._int2word(int(left), cardinal=True, gender='f'),
312
+ self.pluralize(int(left), self.pointword),
313
+ decimal_part,
314
+ self.__decimal_bitness(right)
315
+ )
316
+ if is_negative:
317
+ result = self.negword + ' ' + result
318
+ return result
319
+ else:
320
+ # Handle scientific notation in integer strings
321
+ if 'e' in n.lower() or 'E' in n:
322
+ n = str(int(float(n)))
323
+ return self._int2word(int(n), cardinal=True, case=case,
324
+ plural=plural, gender=gender,
325
+ animate=animate)
326
+
327
+ def __decimal_bitness(self, n):
328
+ if n[-1] == "1" and n[-2:] != "11":
329
+ return self._int2word(10 ** len(n), cardinal=False, gender='f')
330
+ return self._int2word(10 ** len(n), cardinal=False, case='g',
331
+ plural=True)
332
+
333
+ def pluralize(self, n, forms):
334
+ if n % 100 in (11, 12, 13, 14):
335
+ return forms[2]
336
+ if n % 10 == 1:
337
+ return forms[0]
338
+ if n % 10 in (2, 3, 4):
339
+ return forms[1]
340
+ return forms[2]
341
+
342
+ def to_currency(self, val, currency='EUR', cents=True, separator=',',
343
+ adjective=False):
344
+ # Handle scientific notation that results in whole numbers
345
+ # (e.g., 1e+18 becomes 1000000000000000000.0)
346
+ if isinstance(val, float) and val == int(val):
347
+ val = int(val)
348
+
349
+ # Handle integers specially - just add currency name without cents
350
+ if isinstance(val, int):
351
+ try:
352
+ cr1, cr2 = self.CURRENCY_FORMS[currency]
353
+ except (KeyError, AttributeError):
354
+ # Fallback to base implementation for unknown currency
355
+ return super(Num2Word_RU, self).to_currency(
356
+ val, currency=currency, cents=cents, separator=separator,
357
+ adjective=adjective)
358
+
359
+ minus_str = self.negword if val < 0 else ""
360
+ abs_val = abs(val)
361
+ money_str = self.to_cardinal(abs_val)
362
+
363
+ # Use the pluralize method for proper currency form selection
364
+ currency_str = self.pluralize(abs_val, cr1)
365
+
366
+ return (u'%s %s %s' % (minus_str, money_str, currency_str)).strip()
367
+
368
+ # For floats, use the parent class implementation
369
+ return super(Num2Word_RU, self).to_currency(
370
+ val, currency=currency, cents=cents, separator=separator,
371
+ adjective=adjective)
372
+
373
+ def to_ordinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER,
374
+ animate=D_ANIMATE):
375
+ self.verify_ordinal(number)
376
+ n = str(number).replace(',', '.')
377
+ return self._int2word(int(n), cardinal=False, case=case, plural=plural,
378
+ gender=gender, animate=animate)
379
+
380
+ def _money_verbose(self, number, currency):
381
+ if currency == 'UAH':
382
+ return self._int2word(number, gender='f')
383
+ return self._int2word(number, gender='m')
384
+
385
+ def _cents_verbose(self, number, currency):
386
+ # Handle scientific notation or very small decimals
387
+ if isinstance(number, str) and ('e' in number.lower() or 'E' in number):
388
+ number = float(number)
389
+ # Convert float to int if it represents a whole number
390
+ if isinstance(number, float):
391
+ # For very small numbers, round to avoid scientific notation issues
392
+ number = round(number * 100) / 100 # Round to 2 decimal places
393
+ if number == int(number):
394
+ number = int(number)
395
+
396
+ if currency in ('UAH', 'RUB', 'BYN'):
397
+ return self._int2word(int(number), gender='f')
398
+ return self._int2word(int(number), gender='m')
399
+
400
+ def _int2word(self, n, feminine=False, cardinal=True, case=D_CASE,
401
+ plural=D_PLURAL, gender=D_GENDER, animate=D_ANIMATE):
402
+ """
403
+ n: number
404
+ feminine: not used - for backward compatibility
405
+ cardinal:True - cardinal
406
+ False - ordinal
407
+ case: 'n' - nominative
408
+ 'g' - genitive
409
+ 'd' - dative
410
+ 'a' - accusative
411
+ 'i' - instrumental
412
+ 'p' - prepositional
413
+ plural: True - plural
414
+ False - singular
415
+ gender: 'f' - masculine
416
+ 'm' - feminine
417
+ 'n' - neuter
418
+ animate: True - animate
419
+ False - inanimate
420
+ """
421
+ # For backward compatibility
422
+ if feminine:
423
+ gender = 'f'
424
+
425
+ kwargs = {'case': case, 'plural': plural, 'gender': gender,
426
+ 'animate': animate}
427
+
428
+ if n < 0:
429
+ return ' '.join([self.negword, self._int2word(abs(n),
430
+ cardinal=cardinal,
431
+ **kwargs)])
432
+
433
+ if n == 0:
434
+ return get_num_element(ONES, 0, **kwargs) if cardinal else \
435
+ get_num_element(ONES_ORD, 0, **kwargs)
436
+
437
+ words = []
438
+ chunks = list(splitbyx(str(n), 3))
439
+ ord_join = chunks[-1] == 0 # join in one word if ending on 'тысячный'
440
+ i = len(chunks)
441
+ rightest_nonzero_chunk_i = i - 1 - max(
442
+ [i for i, e in enumerate(chunks) if e != 0])
443
+ for x in chunks:
444
+ chunk_words = []
445
+ i -= 1
446
+
447
+ if x == 0:
448
+ continue
449
+
450
+ n1, n2, n3 = get_digits(x)
451
+
452
+ if cardinal:
453
+ chunk_words.extend(
454
+ self.__chunk_cardianl(n3, n2, n1, i, **kwargs)
455
+ )
456
+ if i > 0:
457
+ chunk_words.append(
458
+ self.pluralize(x, get_thousands_elements(i, case)))
459
+ # ordinal, not joined like 'двухтысячный'
460
+ elif not (ord_join and rightest_nonzero_chunk_i == i):
461
+ chunk_words.extend(
462
+ self.__chunk_ordinal(n3, n2, n1, i, **kwargs)
463
+ )
464
+ if i > 0:
465
+ t_case = case if rightest_nonzero_chunk_i == i else 'n'
466
+ chunk_words.append(
467
+ self.pluralize(x, get_thousands_elements(i, t_case)))
468
+ # ordinal, joined
469
+ else:
470
+ chunk_words.extend(
471
+ self.__chunk_ordinal_join(n3, n2, n1, i, **kwargs)
472
+ )
473
+ if i > 0:
474
+ chunk_words.append(
475
+ get_num_element(THOUSANDS_ORD, i, **kwargs))
476
+
477
+ chunk_words = [''.join(chunk_words)]
478
+
479
+ words.extend(chunk_words)
480
+
481
+ return ' '.join(words)
482
+
483
+ def __chunk_cardianl(self, hundreds, tens, ones, chunk_num, **kwargs):
484
+ words = []
485
+ if hundreds > 0:
486
+ words.append(get_num_element(HUNDREDS, hundreds, **kwargs))
487
+
488
+ if tens > 1:
489
+ words.append(get_num_element(TWENTIES, tens, **kwargs))
490
+
491
+ if tens == 1:
492
+ words.append(get_num_element(TENS, ones, **kwargs))
493
+ elif ones > 0:
494
+ if chunk_num == 0:
495
+ w_ones = get_num_element(ONES, ones, **kwargs)
496
+ elif chunk_num == 1:
497
+ # Thousands are feminine
498
+ f_kwargs = kwargs.copy()
499
+ f_kwargs['gender'] = 'f'
500
+ w_ones = get_num_element(ONES, ones, **f_kwargs)
501
+ else:
502
+ w_ones = get_num_element(ONES, ones, **kwargs)
503
+
504
+ words.append(w_ones)
505
+ return words
506
+
507
+ def __chunk_ordinal(self, hundreds, tens, ones, chunk_num, **kwargs):
508
+ words = []
509
+ if hundreds > 0:
510
+ if tens == 0 and ones == 0:
511
+ words.append(get_num_element(HUNDREDS_ORD, hundreds, **kwargs))
512
+ else:
513
+ words.append(get_num_element(HUNDREDS, hundreds))
514
+
515
+ if tens > 1:
516
+ if ones == 0:
517
+ words.append(get_num_element(TWENTIES_ORD, tens, **kwargs))
518
+ else:
519
+ words.append(get_num_element(TWENTIES, tens))
520
+
521
+ if tens == 1:
522
+ words.append(get_num_element(TENS_ORD, ones, **kwargs))
523
+ elif ones > 0:
524
+ if chunk_num == 0:
525
+ w_ones = get_num_element(ONES_ORD, ones, **kwargs)
526
+ # тысячный, миллионнный и т.д.
527
+ elif chunk_num > 0 and ones == 1 and hundreds == 0 and tens == 0:
528
+ w_ones = None
529
+ elif chunk_num == 1:
530
+ # Thousands are feminine
531
+ w_ones = get_num_element(ONES, ones, gender='f')
532
+ else:
533
+ w_ones = get_num_element(ONES, ones)
534
+
535
+ if w_ones:
536
+ words.append(w_ones)
537
+
538
+ return words
539
+
540
+ def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs):
541
+ words = []
542
+
543
+ if hundreds > 1:
544
+ words.append(get_num_element(HUNDREDS, hundreds, case='g'))
545
+ elif hundreds == 1:
546
+ words.append(get_num_element(HUNDREDS, hundreds)) # стО, not стА
547
+
548
+ if tens == 9:
549
+ words.append(get_num_element(TWENTIES, tens)) # девяностО, not А
550
+ elif tens > 1:
551
+ words.append(get_num_element(TWENTIES, tens, case='g'))
552
+
553
+ if tens == 1:
554
+ words.append(get_num_element(TENS, ones, case='g'))
555
+ elif ones > 0:
556
+ if chunk_num == 0:
557
+ w_ones = get_num_element(ONES_ORD, ones, **kwargs)
558
+ # тысячный, миллионнный и т.д., двадцатиодномиллионный
559
+ elif chunk_num > 0 and ones == 1 and tens != 1:
560
+ if tens == 0 and hundreds == 0:
561
+ w_ones = None
562
+ else:
563
+ w_ones = get_num_element(ONES, 1, gender='n')
564
+ else:
565
+ w_ones = get_num_element(ONES, ones, case='g')
566
+
567
+ if w_ones:
568
+ words.append(w_ones)
569
+
570
+ return words