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