1
2
3
4
5 """
6 Plural forms and in-word representation for numerals.
7 """
8
9 __id__ = __revision__ = "$Id: numeral.py 29 2006-10-21 08:28:27Z the.pythy $"
10 __url__ = "$URL: https://pythy.googlecode.com/svn/trunk/pytils/pytils/numeral.py $"
11
12 from pytils import utils
13
14 FRACTIONS = (
15 (u"десятая", u"десятых", u"десятых"),
16 (u"сотая", u"сотых", u"сотых"),
17 (u"тысячная", u"тысячных", u"тысячных"),
18 (u"десятитысячная", u"десятитысячных", u"десятитысячных"),
19 (u"стотысячная", u"стотысячных", u"стотысячных"),
20 (u"миллионная", u"милллионных", u"милллионных"),
21 (u"десятимиллионная", u"десятимилллионных", u"десятимиллионных"),
22 (u"стомиллионная", u"стомилллионных", u"стомиллионных"),
23 (u"миллиардная", u"миллиардных", u"миллиардных"),
24 )
25
26 ONES = {
27 0: (u"", u"", u""),
28 1: (u"один", u"одна", u"одно"),
29 2: (u"два", u"две", u"два"),
30 3: (u"три", u"три", u"три"),
31 4: (u"четыре", u"четыре", u"четыре"),
32 5: (u"пять", u"пять", u"пять"),
33 6: (u"шесть", u"шесть", u"шесть"),
34 7: (u"семь", u"семь", u"семь"),
35 8: (u"восемь", u"восемь", u"восемь"),
36 9: (u"девять", u"девять", u"девять"),
37 }
38
39 TENS = {
40 0: u"",
41
42 10: u"десять",
43 11: u"одиннадцать",
44 12: u"двенадцать",
45 13: u"тринадцать",
46 14: u"четырнадцать",
47 15: u"пятнадцать",
48 16: u"шестнадцать",
49 17: u"семнадцать",
50 18: u"восемнадцать",
51 19: u"девятнадцать",
52 2: u"двадцать",
53 3: u"тридцать",
54 4: u"сорок",
55 5: u"пятьдесят",
56 6: u"шестьдесят",
57 7: u"семьдесят",
58 8: u"восемьдесят",
59 9: u"девяносто",
60 }
61
62 HUNDREDS = {
63 0: u"",
64 1: u"сто",
65 2: u"двести",
66 3: u"триста",
67 4: u"четыреста",
68 5: u"пятьсот",
69 6: u"шестьсот",
70 7: u"семьсот",
71 8: u"восемьсот",
72 9: u"девятьсот",
73 }
74
75 -def _get_float_remainder(fvalue, signs=9):
76 """
77 Get remainder of float, i.e. 2.05 -> '05'
78
79 @param fvalue: input value
80 @type fvalue: C{int} or C{float}
81
82 @param signs: maximum number of signs
83 @type signs: C{int}
84
85 @return: remainder
86 @rtype: C{str}
87
88 @raise TypeError: fvalue neither C{int}, no C{float}
89 @raise ValueError: fvalue is negative
90 @raise ValueError: signs overflow
91 """
92 utils.check_type('fvalue', (int, float))
93 utils.check_positive('fvalue')
94 if isinstance(fvalue, int):
95 return "0"
96
97 signs = min(signs, len(FRACTIONS))
98
99
100
101 remainder = str(fvalue).split('.')[1]
102 iremainder = int(remainder)
103 orig_remainder = remainder
104 factor = len(str(remainder)) - signs
105
106 if factor > 0:
107
108 iremainder = int(round(iremainder / (10.0**factor)))
109 format = "%%0%dd" % min(len(remainder), signs)
110
111 remainder = format % iremainder
112
113 if len(remainder) > signs:
114
115 raise ValueError("Signs overflow: I can't round only fractional part \
116 of %s to fit %s in %d signs" % \
117 (str(fvalue), orig_remainder, signs))
118
119 return remainder
120
121
123 """
124 Choose proper case depending on amount
125
126 @param amount: amount of objects
127 @type amount: C{int}
128
129 @param variants: variants (forms) of object in such form:
130 (1 object, 2 objects, 5 objects).
131 @type variants: 3-element C{sequence} of C{unicode}
132 or C{unicode} (three variants with delimeter ',')
133
134 @return: proper variant
135 @rtype: C{unicode}
136
137 @raise TypeError: amount isn't C{int}, variants isn't C{sequence}
138 @raise ValueError: amount is negative
139 @raise ValueError: variants' length lesser than 3
140 """
141 utils.check_type('amount', int)
142 utils.check_positive('amount')
143 utils.check_type('variants', (list, tuple, unicode))
144
145 if isinstance(variants, unicode):
146 variants = [v.strip() for v in variants.split(',')]
147 if amount % 10 == 1 and amount % 100 != 11:
148 variant = 0
149 elif amount % 10 >= 2 and amount % 10 <= 4 and \
150 (amount % 100 < 10 or amount % 100 >= 20):
151 variant = 1
152 else:
153 variant = 2
154
155 utils.check_length('variants', 3)
156 return variants[variant]
157
158 -def rubles(amount, zero_for_kopeck=False):
159 """
160 Get string for money
161
162 @param amount: amount of money
163 @type amount: C{int} or C{float}
164
165 @param zero_for_kopeck: If false, then zero kopecks ignored
166 @type zero_for_kopeck: C{bool}
167
168 @return: in-words representation of money's amount
169 @rtype: C{unicode}
170
171 @raise TypeError: amount neither C{int}, no C{float}
172 @raise ValueError: amount is negative
173 """
174 utils.check_type('amount', (int, float))
175 utils.check_positive('amount')
176
177 pts = []
178 amount = round(amount, 2)
179 pts.append(sum_string(int(amount), 1, (u"рубль", u"рубля", u"рублей")))
180 remainder = _get_float_remainder(amount, 2)
181 iremainder = int(remainder)
182
183 if iremainder != 0 or zero_for_kopeck:
184
185 if iremainder < 10 and len(remainder) == 1:
186 iremainder *= 10
187 pts.append(sum_string(iremainder, 2,
188 (u"копейка", u"копейки", u"копеек")))
189
190 return u" ".join(pts)
191
193 """
194 Integer in words
195
196 @param amount: numeral
197 @type amount: C{int}
198
199 @param gender: gender (male=1, female=2, neuter=3)
200 @type gender: C{int}
201
202 @return: in-words reprsentation of numeral
203 @rtype: C{unicode}
204
205 @raise TypeError: when amount is not C{int}
206 @raise ValueError: amount is negative
207 """
208 utils.check_type('amount', int)
209 utils.check_positive('amount')
210
211 return sum_string(amount, gender)
212
214 """
215 Float in words
216
217 @param amount: float numeral
218 @type amount: C{float}
219
220 @return: in-words reprsentation of float numeral
221 @rtype: C{unicode}
222
223 @raise TypeError: when amount is not C{float}
224 @raise ValueError: when ammount is negative
225 """
226 utils.check_type('amount', float)
227 utils.check_positive('amount')
228
229 pts = []
230
231 pts.append(sum_string(int(amount), 2,
232 (u"целая", u"целых", u"целых")))
233
234 remainder = _get_float_remainder(amount)
235 signs = len(str(remainder)) - 1
236 pts.append(sum_string(int(remainder), 2, FRACTIONS[signs]))
237
238 return u" ".join(pts)
239
241 """
242 Numeral in words
243
244 @param amount: numeral
245 @type amount: C{int} or C{float}
246
247 @param gender: gender (male=1, female=2, neuter=3)
248 @type gender: C{int}
249
250 @return: in-words reprsentation of numeral
251 @rtype: C{unicode}
252
253 raise TypeError: when amount not C{int} or C{float}
254 raise ValueError: when amount is negative
255 raise TypeError: when gender is not C{int} (and not None)
256 raise ValueError: if gender isn't in (1,2,3)
257 """
258 utils.check_positive('amount')
259 gender is not None and utils.check_type('gender', int)
260 if not (gender is None or 1 <= gender <= 3):
261 raise ValueError("Gender must be male (1), female (2), " + \
262 "neuter (3), not %d" % gender)
263 if gender is None:
264 args = (amount,)
265 else:
266 args = (amount, gender)
267
268 if isinstance(amount, int):
269 return in_words_int(*args)
270
271 elif isinstance(amount, float):
272 return in_words_float(*args)
273
274 else:
275 raise TypeError("Amount must be float or int, not %s" % \
276 type(amount))
277
279 """
280 Get sum in words
281
282 @param amount: amount of objects
283 @type amount: C{int}
284
285 @param gender: gender of object (male=1, female=2, neuter=3)
286 @type gender: C{int}
287
288 @param items: variants of object in three forms:
289 for one object, for two objects and for five objects
290 @type items: 3-element C{sequence} of C{unicode} or
291 just C{unicode} (three variants with delimeter ',')
292
293 @return: in-words representation objects' amount
294 @rtype: C{unicode}
295
296 @raise TypeError: input parameters' check failed
297 @raise ValueError: items isn't 3-element C{sequence}
298 @raise ValueError: amount bigger than 10**11
299 @raise ValueError: amount is negative
300 """
301 if isinstance(items, unicode):
302 items = [i.strip() for i in items.split(',')]
303 if items is None:
304 items = (u"", u"", u"")
305
306 utils.check_type('items', (list, tuple))
307
308 try:
309 one_item, two_items, five_items = items
310 except ValueError:
311 raise ValueError("Items must be 3-element sequence")
312
313 utils.check_type('amount', int)
314 utils.check_type('gender', int)
315 utils.check_type('one_item', unicode)
316 utils.check_type('two_items', unicode)
317 utils.check_type('five_items', unicode)
318 utils.check_positive('amount')
319
320 if amount == 0:
321 return u"ноль %s" % five_items
322
323 into = u''
324 tmp_val = amount
325
326
327 into, tmp_val = _sum_string_fn(into, tmp_val, gender, items)
328
329 into, tmp_val = _sum_string_fn(into, tmp_val, 2,
330 (u"тысяча", u"тысячи", u"тысяч"))
331
332 into, tmp_val = _sum_string_fn(into, tmp_val, 1,
333 (u"миллион", u"миллиона", u"миллионов"))
334
335 into, tmp_val = _sum_string_fn(into, tmp_val, 1,
336 (u"миллиард", u"миллиарда", u"миллиардов"))
337 if tmp_val == 0:
338 return into
339 else:
340 raise ValueError("Cannot operand with numbers bigger than 10**11")
341
343 """
344 Make in-words representation of single order
345
346 @param into: in-words representation of lower orders
347 @type into: C{unicode}
348
349 @param tmp_val: temporary value without lower orders
350 @type tmp_val: C{int}
351
352 @param gender: gender (male=1, female=2, neuter=3)
353 @type gender: C{int}
354
355 @param items: variants of objects
356 @type items: 3-element C{sequence} of C{unicode}
357
358 @return: new into and tmp_val
359 @rtype: C{tuple}
360
361 @raise TypeError: input parameters' check failed
362 @raise ValueError: tmp_val is negative
363 """
364 if items is None:
365 items = (u"", u"", u"")
366 one_item, two_items, five_items = items
367 utils.check_type('into', unicode)
368 utils.check_type('tmp_val', int)
369 utils.check_type('gender', int)
370 utils.check_type('one_item', unicode)
371 utils.check_type('two_items', unicode)
372 utils.check_type('five_items', unicode)
373 utils.check_positive('tmp_val')
374
375 if tmp_val == 0:
376 return into, tmp_val
377
378 rest = rest1 = end_word = None
379 words = []
380
381 rest = tmp_val % 1000
382 tmp_val = tmp_val / 1000
383 if rest == 0:
384
385 if into == u"":
386 into = u"%s " % five_items
387 return into, tmp_val
388
389
390 end_word = five_items
391
392
393 words.append(HUNDREDS[rest / 100])
394
395
396 rest = rest % 100
397 rest1 = rest / 10
398
399 tens = rest1 == 1 and TENS[rest] or TENS[rest1]
400 words.append(tens)
401
402
403 if rest1 < 1 or rest1 > 1:
404 amount = rest % 10
405 end_word = choose_plural(amount, items)
406 words.append(ONES[amount][gender-1])
407 words.append(end_word)
408
409
410 words.append(into)
411
412
413 words = filter(lambda x: len(x) > 0, words)
414
415
416 return u" ".join(words).strip(), tmp_val
417