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 63 2007-01-02 09:22:16Z the.pythy $"
22 __url__ = "$URL: https://pythy.googlecode.com/svn/trunk/pytils/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
138
140 """
141 Choose proper case depending on amount
142
143 @param amount: amount of objects
144 @type amount: C{int} or C{long}
145
146 @param variants: variants (forms) of object in such form:
147 (1 object, 2 objects, 5 objects).
148 @type variants: 3-element C{sequence} of C{unicode}
149 or C{unicode} (three variants with delimeter ',')
150
151 @return: proper variant
152 @rtype: C{unicode}
153
154 @raise TypeError: amount isn't C{int}, variants isn't C{sequence}
155 @raise ValueError: amount is negative
156 @raise ValueError: variants' length lesser than 3
157 """
158 utils.check_type('amount', (int, long))
159 utils.check_positive('amount')
160 utils.check_type('variants', (list, tuple, unicode))
161
162 if isinstance(variants, unicode):
163 variants = [v.strip() for v in variants.split(',')]
164 if amount % 10 == 1 and amount % 100 != 11:
165 variant = 0
166 elif amount % 10 >= 2 and amount % 10 <= 4 and \
167 (amount % 100 < 10 or amount % 100 >= 20):
168 variant = 1
169 else:
170 variant = 2
171
172 utils.check_length('variants', 3)
173 return variants[variant]
174
175
176 -def rubles(amount, zero_for_kopeck=False):
177 """
178 Get string for money
179
180 @param amount: amount of money
181 @type amount: C{int}, C{long} or C{float}
182
183 @param zero_for_kopeck: If false, then zero kopecks ignored
184 @type zero_for_kopeck: C{bool}
185
186 @return: in-words representation of money's amount
187 @rtype: C{unicode}
188
189 @raise TypeError: amount neither C{int}, no C{float}
190 @raise ValueError: amount is negative
191 """
192 utils.check_type('amount', (int, long, float))
193 utils.check_positive('amount')
194
195 pts = []
196 amount = round(amount, 2)
197 pts.append(sum_string(int(amount), 1, (u"рубль", u"рубля", u"рублей")))
198 remainder = _get_float_remainder(amount, 2)
199 iremainder = int(remainder)
200
201 if iremainder != 0 or zero_for_kopeck:
202
203 if iremainder < 10 and len(remainder) == 1:
204 iremainder *= 10
205 pts.append(sum_string(iremainder, 2,
206 (u"копейка", u"копейки", u"копеек")))
207
208 return u" ".join(pts)
209
210
212 """
213 Integer in words
214
215 @param amount: numeral
216 @type amount: C{int} or C{long}
217
218 @param gender: gender (MALE, FEMALE or NEUTER)
219 @type gender: C{int}
220
221 @return: in-words reprsentation of numeral
222 @rtype: C{unicode}
223
224 @raise TypeError: when amount is not C{int}
225 @raise ValueError: amount is negative
226 """
227 utils.check_type('amount', (int, long))
228 utils.check_positive('amount')
229
230 return sum_string(amount, gender)
231
232
234 """
235 Float in words
236
237 @param amount: float numeral
238 @type amount: C{float}
239
240 @return: in-words reprsentation of float numeral
241 @rtype: C{unicode}
242
243 @raise TypeError: when amount is not C{float}
244 @raise ValueError: when ammount is negative
245 """
246 utils.check_type('amount', float)
247 utils.check_positive('amount')
248
249 pts = []
250
251 pts.append(sum_string(int(amount), 2,
252 (u"целая", u"целых", u"целых")))
253
254 remainder = _get_float_remainder(amount)
255 signs = len(str(remainder)) - 1
256 pts.append(sum_string(int(remainder), 2, FRACTIONS[signs]))
257
258 return u" ".join(pts)
259
260
262 """
263 Numeral in words
264
265 @param amount: numeral
266 @type amount: C{int}, C{long} or C{float}
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 not C{int} or C{float}
275 raise ValueError: when amount is negative
276 raise TypeError: when gender is not C{int} (and not None)
277 raise ValueError: if gender isn't in (MALE, FEMALE, NEUTER)
278 """
279 utils.check_positive('amount')
280 gender is not None and utils.check_type('gender', int)
281 if not (gender is None or gender in (MALE, FEMALE, NEUTER)):
282 raise ValueError("Gender must be MALE, FEMALE or NEUTER, " + \
283 "not %d" % gender)
284 if gender is None:
285 args = (amount,)
286 else:
287 args = (amount, gender)
288
289 if isinstance(amount, (int, long)):
290 return in_words_int(*args)
291
292 elif isinstance(amount, float):
293 return in_words_float(*args)
294
295 else:
296 raise TypeError("Amount must be float or int, not %s" % \
297 type(amount))
298
299
301 """
302 Get sum in words
303
304 @param amount: amount of objects
305 @type amount: C{int} or C{long}
306
307 @param gender: gender of object (MALE, FEMALE or NEUTER)
308 @type gender: C{int}
309
310 @param items: variants of object in three forms:
311 for one object, for two objects and for five objects
312 @type items: 3-element C{sequence} of C{unicode} or
313 just C{unicode} (three variants with delimeter ',')
314
315 @return: in-words representation objects' amount
316 @rtype: C{unicode}
317
318 @raise TypeError: input parameters' check failed
319 @raise ValueError: items isn't 3-element C{sequence}
320 @raise ValueError: amount bigger than 10**11
321 @raise ValueError: amount is negative
322 """
323 if isinstance(items, unicode):
324 items = [i.strip() for i in items.split(',')]
325 if items is None:
326 items = (u"", u"", u"")
327
328 utils.check_type('items', (list, tuple))
329
330 try:
331 one_item, two_items, five_items = items
332 except ValueError:
333 raise ValueError("Items must be 3-element sequence")
334
335 utils.check_type('amount', (int, long))
336 utils.check_type('gender', int)
337 utils.check_type('one_item', unicode)
338 utils.check_type('two_items', unicode)
339 utils.check_type('five_items', unicode)
340 utils.check_positive('amount')
341
342 if amount == 0:
343 return u"ноль %s" % five_items
344
345 into = u''
346 tmp_val = amount
347
348
349 into, tmp_val = _sum_string_fn(into, tmp_val, gender, items)
350
351 into, tmp_val = _sum_string_fn(into, tmp_val, FEMALE,
352 (u"тысяча", u"тысячи", u"тысяч"))
353
354 into, tmp_val = _sum_string_fn(into, tmp_val, MALE,
355 (u"миллион", u"миллиона", u"миллионов"))
356
357 into, tmp_val = _sum_string_fn(into, tmp_val, MALE,
358 (u"миллиард", u"миллиарда", u"миллиардов"))
359 if tmp_val == 0:
360 return into
361 else:
362 raise ValueError("Cannot operand with numbers bigger than 10**11")
363
364
366 """
367 Make in-words representation of single order
368
369 @param into: in-words representation of lower orders
370 @type into: C{unicode}
371
372 @param tmp_val: temporary value without lower orders
373 @type tmp_val: C{int} or C{long}
374
375 @param gender: gender (MALE, FEMALE or NEUTER)
376 @type gender: C{int}
377
378 @param items: variants of objects
379 @type items: 3-element C{sequence} of C{unicode}
380
381 @return: new into and tmp_val
382 @rtype: C{tuple}
383
384 @raise TypeError: input parameters' check failed
385 @raise ValueError: tmp_val is negative
386 """
387 if items is None:
388 items = (u"", u"", u"")
389 one_item, two_items, five_items = items
390 utils.check_type('into', unicode)
391 utils.check_type('tmp_val', (int, long))
392 utils.check_type('gender', int)
393 utils.check_type('one_item', unicode)
394 utils.check_type('two_items', unicode)
395 utils.check_type('five_items', unicode)
396 utils.check_positive('tmp_val')
397
398 if tmp_val == 0:
399 return into, tmp_val
400
401 rest = rest1 = end_word = None
402 words = []
403
404 rest = tmp_val % 1000
405 tmp_val = tmp_val / 1000
406 if rest == 0:
407
408 if into == u"":
409 into = u"%s " % five_items
410 return into, tmp_val
411
412
413 end_word = five_items
414
415
416 words.append(HUNDREDS[rest / 100])
417
418
419 rest = rest % 100
420 rest1 = rest / 10
421
422 tens = rest1 == 1 and TENS[rest] or TENS[rest1]
423 words.append(tens)
424
425
426 if rest1 < 1 or rest1 > 1:
427 amount = rest % 10
428 end_word = choose_plural(amount, items)
429 words.append(ONES[amount][gender-1])
430 words.append(end_word)
431
432
433 words.append(into)
434
435
436 words = filter(lambda x: len(x) > 0, words)
437
438
439 return u" ".join(words).strip(), tmp_val
440