493
|
1 # -*- coding: utf-8 -*-
|
|
2 #
|
|
3 # Copyright (C) 2007 Edgewall Software
|
|
4 # All rights reserved.
|
|
5 #
|
|
6 # This software is licensed as described in the file COPYING, which
|
|
7 # you should have received as part of this distribution. The terms
|
|
8 # are also available at http://babel.edgewall.org/wiki/License.
|
|
9 #
|
|
10 # This software consists of voluntary contributions made by many
|
|
11 # individuals. For the exact contribution history, see the revision
|
|
12 # history and logs, available at http://babel.edgewall.org/log/.
|
|
13
|
|
14 """Locale dependent formatting and parsing of numeric data.
|
|
15
|
|
16 The default locale for the functions in this module is determined by the
|
|
17 following environment variables, in that order:
|
|
18
|
|
19 * ``LC_NUMERIC``,
|
|
20 * ``LC_ALL``, and
|
|
21 * ``LANG``
|
|
22 """
|
|
23 # TODO:
|
|
24 # Padding and rounding increments in pattern:
|
|
25 # - http://www.unicode.org/reports/tr35/ (Appendix G.6)
|
|
26 import math
|
|
27 import re
|
|
28 try:
|
|
29 from decimal import Decimal
|
|
30 have_decimal = True
|
|
31 except ImportError:
|
|
32 have_decimal = False
|
|
33
|
|
34 from babel.core import default_locale, Locale
|
|
35
|
|
36 __all__ = ['format_number', 'format_decimal', 'format_currency',
|
|
37 'format_percent', 'format_scientific', 'parse_number',
|
|
38 'parse_decimal', 'NumberFormatError']
|
|
39 __docformat__ = 'restructuredtext en'
|
|
40
|
|
41 LC_NUMERIC = default_locale('LC_NUMERIC')
|
|
42
|
|
43 def get_currency_name(currency, locale=LC_NUMERIC):
|
|
44 """Return the name used by the locale for the specified currency.
|
|
45
|
|
46 >>> get_currency_name('USD', 'en_US')
|
|
47 u'US Dollar'
|
|
48
|
|
49 :param currency: the currency code
|
|
50 :param locale: the `Locale` object or locale identifier
|
|
51 :return: the currency symbol
|
|
52 :rtype: `unicode`
|
|
53 :since: version 0.9.4
|
|
54 """
|
|
55 return Locale.parse(locale).currencies.get(currency, currency)
|
|
56
|
|
57 def get_currency_symbol(currency, locale=LC_NUMERIC):
|
|
58 """Return the symbol used by the locale for the specified currency.
|
|
59
|
|
60 >>> get_currency_symbol('USD', 'en_US')
|
|
61 u'$'
|
|
62
|
|
63 :param currency: the currency code
|
|
64 :param locale: the `Locale` object or locale identifier
|
|
65 :return: the currency symbol
|
|
66 :rtype: `unicode`
|
|
67 """
|
|
68 return Locale.parse(locale).currency_symbols.get(currency, currency)
|
|
69
|
|
70 def get_decimal_symbol(locale=LC_NUMERIC):
|
|
71 """Return the symbol used by the locale to separate decimal fractions.
|
|
72
|
|
73 >>> get_decimal_symbol('en_US')
|
|
74 u'.'
|
|
75
|
|
76 :param locale: the `Locale` object or locale identifier
|
|
77 :return: the decimal symbol
|
|
78 :rtype: `unicode`
|
|
79 """
|
|
80 return Locale.parse(locale).number_symbols.get('decimal', u'.')
|
|
81
|
|
82 def get_plus_sign_symbol(locale=LC_NUMERIC):
|
|
83 """Return the plus sign symbol used by the current locale.
|
|
84
|
|
85 >>> get_plus_sign_symbol('en_US')
|
|
86 u'+'
|
|
87
|
|
88 :param locale: the `Locale` object or locale identifier
|
|
89 :return: the plus sign symbol
|
|
90 :rtype: `unicode`
|
|
91 """
|
|
92 return Locale.parse(locale).number_symbols.get('plusSign', u'+')
|
|
93
|
|
94 def get_minus_sign_symbol(locale=LC_NUMERIC):
|
|
95 """Return the plus sign symbol used by the current locale.
|
|
96
|
|
97 >>> get_minus_sign_symbol('en_US')
|
|
98 u'-'
|
|
99
|
|
100 :param locale: the `Locale` object or locale identifier
|
|
101 :return: the plus sign symbol
|
|
102 :rtype: `unicode`
|
|
103 """
|
|
104 return Locale.parse(locale).number_symbols.get('minusSign', u'-')
|
|
105
|
|
106 def get_exponential_symbol(locale=LC_NUMERIC):
|
|
107 """Return the symbol used by the locale to separate mantissa and exponent.
|
|
108
|
|
109 >>> get_exponential_symbol('en_US')
|
|
110 u'E'
|
|
111
|
|
112 :param locale: the `Locale` object or locale identifier
|
|
113 :return: the exponential symbol
|
|
114 :rtype: `unicode`
|
|
115 """
|
|
116 return Locale.parse(locale).number_symbols.get('exponential', u'E')
|
|
117
|
|
118 def get_group_symbol(locale=LC_NUMERIC):
|
|
119 """Return the symbol used by the locale to separate groups of thousands.
|
|
120
|
|
121 >>> get_group_symbol('en_US')
|
|
122 u','
|
|
123
|
|
124 :param locale: the `Locale` object or locale identifier
|
|
125 :return: the group symbol
|
|
126 :rtype: `unicode`
|
|
127 """
|
|
128 return Locale.parse(locale).number_symbols.get('group', u',')
|
|
129
|
|
130 def format_number(number, locale=LC_NUMERIC):
|
|
131 u"""Return the given number formatted for a specific locale.
|
|
132
|
|
133 >>> format_number(1099, locale='en_US')
|
|
134 u'1,099'
|
|
135 >>> format_number(1099, locale='de_DE')
|
|
136 u'1.099'
|
|
137
|
|
138
|
|
139 :param number: the number to format
|
|
140 :param locale: the `Locale` object or locale identifier
|
|
141 :return: the formatted number
|
|
142 :rtype: `unicode`
|
|
143 """
|
|
144 # Do we really need this one?
|
|
145 return format_decimal(number, locale=locale)
|
|
146
|
|
147 def format_decimal(number, format=None, locale=LC_NUMERIC):
|
|
148 u"""Return the given decimal number formatted for a specific locale.
|
|
149
|
|
150 >>> format_decimal(1.2345, locale='en_US')
|
|
151 u'1.234'
|
|
152 >>> format_decimal(1.2346, locale='en_US')
|
|
153 u'1.235'
|
|
154 >>> format_decimal(-1.2346, locale='en_US')
|
|
155 u'-1.235'
|
|
156 >>> format_decimal(1.2345, locale='sv_SE')
|
|
157 u'1,234'
|
|
158 >>> format_decimal(1.2345, locale='de')
|
|
159 u'1,234'
|
|
160
|
|
161 The appropriate thousands grouping and the decimal separator are used for
|
|
162 each locale:
|
|
163
|
|
164 >>> format_decimal(12345.5, locale='en_US')
|
|
165 u'12,345.5'
|
|
166
|
|
167 :param number: the number to format
|
|
168 :param format:
|
|
169 :param locale: the `Locale` object or locale identifier
|
|
170 :return: the formatted decimal number
|
|
171 :rtype: `unicode`
|
|
172 """
|
|
173 locale = Locale.parse(locale)
|
|
174 if not format:
|
|
175 format = locale.decimal_formats.get(format)
|
|
176 pattern = parse_pattern(format)
|
|
177 return pattern.apply(number, locale)
|
|
178
|
|
179 def format_currency(number, currency, format=None, locale=LC_NUMERIC):
|
|
180 u"""Return formatted currency value.
|
|
181
|
|
182 >>> format_currency(1099.98, 'USD', locale='en_US')
|
|
183 u'$1,099.98'
|
|
184 >>> format_currency(1099.98, 'USD', locale='es_CO')
|
|
185 u'US$\\xa01.099,98'
|
|
186 >>> format_currency(1099.98, 'EUR', locale='de_DE')
|
|
187 u'1.099,98\\xa0\\u20ac'
|
|
188
|
|
189 The pattern can also be specified explicitly:
|
|
190
|
|
191 >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US')
|
|
192 u'EUR 1,099.98'
|
|
193
|
|
194 :param number: the number to format
|
|
195 :param currency: the currency code
|
|
196 :param locale: the `Locale` object or locale identifier
|
|
197 :return: the formatted currency value
|
|
198 :rtype: `unicode`
|
|
199 """
|
|
200 locale = Locale.parse(locale)
|
|
201 if not format:
|
|
202 format = locale.currency_formats.get(format)
|
|
203 pattern = parse_pattern(format)
|
|
204 return pattern.apply(number, locale, currency=currency)
|
|
205
|
|
206 def format_percent(number, format=None, locale=LC_NUMERIC):
|
|
207 """Return formatted percent value for a specific locale.
|
|
208
|
|
209 >>> format_percent(0.34, locale='en_US')
|
|
210 u'34%'
|
|
211 >>> format_percent(25.1234, locale='en_US')
|
|
212 u'2,512%'
|
|
213 >>> format_percent(25.1234, locale='sv_SE')
|
|
214 u'2\\xa0512\\xa0%'
|
|
215
|
|
216 The format pattern can also be specified explicitly:
|
|
217
|
|
218 >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US')
|
|
219 u'25,123\u2030'
|
|
220
|
|
221 :param number: the percent number to format
|
|
222 :param format:
|
|
223 :param locale: the `Locale` object or locale identifier
|
|
224 :return: the formatted percent number
|
|
225 :rtype: `unicode`
|
|
226 """
|
|
227 locale = Locale.parse(locale)
|
|
228 if not format:
|
|
229 format = locale.percent_formats.get(format)
|
|
230 pattern = parse_pattern(format)
|
|
231 return pattern.apply(number, locale)
|
|
232
|
|
233 def format_scientific(number, format=None, locale=LC_NUMERIC):
|
|
234 """Return value formatted in scientific notation for a specific locale.
|
|
235
|
|
236 >>> format_scientific(10000, locale='en_US')
|
|
237 u'1E4'
|
|
238
|
|
239 The format pattern can also be specified explicitly:
|
|
240
|
|
241 >>> format_scientific(1234567, u'##0E00', locale='en_US')
|
|
242 u'1.23E06'
|
|
243
|
|
244 :param number: the number to format
|
|
245 :param format:
|
|
246 :param locale: the `Locale` object or locale identifier
|
|
247 :return: value formatted in scientific notation.
|
|
248 :rtype: `unicode`
|
|
249 """
|
|
250 locale = Locale.parse(locale)
|
|
251 if not format:
|
|
252 format = locale.scientific_formats.get(format)
|
|
253 pattern = parse_pattern(format)
|
|
254 return pattern.apply(number, locale)
|
|
255
|
|
256
|
|
257 class NumberFormatError(ValueError):
|
|
258 """Exception raised when a string cannot be parsed into a number."""
|
|
259
|
|
260
|
|
261 def parse_number(string, locale=LC_NUMERIC):
|
|
262 """Parse localized number string into a long integer.
|
|
263
|
|
264 >>> parse_number('1,099', locale='en_US')
|
|
265 1099L
|
|
266 >>> parse_number('1.099', locale='de_DE')
|
|
267 1099L
|
|
268
|
|
269 When the given string cannot be parsed, an exception is raised:
|
|
270
|
|
271 >>> parse_number('1.099,98', locale='de')
|
|
272 Traceback (most recent call last):
|
|
273 ...
|
|
274 NumberFormatError: '1.099,98' is not a valid number
|
|
275
|
|
276 :param string: the string to parse
|
|
277 :param locale: the `Locale` object or locale identifier
|
|
278 :return: the parsed number
|
|
279 :rtype: `long`
|
|
280 :raise `NumberFormatError`: if the string can not be converted to a number
|
|
281 """
|
|
282 try:
|
|
283 return long(string.replace(get_group_symbol(locale), ''))
|
|
284 except ValueError:
|
|
285 raise NumberFormatError('%r is not a valid number' % string)
|
|
286
|
|
287 def parse_decimal(string, locale=LC_NUMERIC):
|
|
288 """Parse localized decimal string into a float.
|
|
289
|
|
290 >>> parse_decimal('1,099.98', locale='en_US')
|
|
291 1099.98
|
|
292 >>> parse_decimal('1.099,98', locale='de')
|
|
293 1099.98
|
|
294
|
|
295 When the given string cannot be parsed, an exception is raised:
|
|
296
|
|
297 >>> parse_decimal('2,109,998', locale='de')
|
|
298 Traceback (most recent call last):
|
|
299 ...
|
|
300 NumberFormatError: '2,109,998' is not a valid decimal number
|
|
301
|
|
302 :param string: the string to parse
|
|
303 :param locale: the `Locale` object or locale identifier
|
|
304 :return: the parsed decimal number
|
|
305 :rtype: `float`
|
|
306 :raise `NumberFormatError`: if the string can not be converted to a
|
|
307 decimal number
|
|
308 """
|
|
309 locale = Locale.parse(locale)
|
|
310 try:
|
|
311 return float(string.replace(get_group_symbol(locale), '')
|
|
312 .replace(get_decimal_symbol(locale), '.'))
|
|
313 except ValueError:
|
|
314 raise NumberFormatError('%r is not a valid decimal number' % string)
|
|
315
|
|
316
|
|
317 PREFIX_END = r'[^0-9@#.,]'
|
|
318 NUMBER_TOKEN = r'[0-9@#.\-,E+]'
|
|
319
|
|
320 PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END
|
|
321 NUMBER_PATTERN = r"(?P<number>%s+)" % NUMBER_TOKEN
|
|
322 SUFFIX_PATTERN = r"(?P<suffix>.*)"
|
|
323
|
|
324 number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN,
|
|
325 SUFFIX_PATTERN))
|
|
326
|
|
327 def split_number(value):
|
|
328 """Convert a number into a (intasstring, fractionasstring) tuple"""
|
|
329 if have_decimal and isinstance(value, Decimal):
|
|
330 text = str(value)
|
|
331 else:
|
|
332 text = ('%.9f' % value).rstrip('0')
|
|
333 if '.' in text:
|
|
334 a, b = text.split('.', 1)
|
|
335 if b == '0':
|
|
336 b = ''
|
|
337 else:
|
|
338 a, b = text, ''
|
|
339 return a, b
|
|
340
|
|
341 def bankersround(value, ndigits=0):
|
|
342 """Round a number to a given precision.
|
|
343
|
|
344 Works like round() except that the round-half-even (banker's rounding)
|
|
345 algorithm is used instead of round-half-up.
|
|
346
|
|
347 >>> bankersround(5.5, 0)
|
|
348 6.0
|
|
349 >>> bankersround(6.5, 0)
|
|
350 6.0
|
|
351 >>> bankersround(-6.5, 0)
|
|
352 -6.0
|
|
353 >>> bankersround(1234.0, -2)
|
|
354 1200.0
|
|
355 """
|
|
356 sign = int(value < 0) and -1 or 1
|
|
357 value = abs(value)
|
|
358 a, b = split_number(value)
|
|
359 digits = a + b
|
|
360 add = 0
|
|
361 i = len(a) + ndigits
|
|
362 if i < 0 or i >= len(digits):
|
|
363 pass
|
|
364 elif digits[i] > '5':
|
|
365 add = 1
|
|
366 elif digits[i] == '5' and digits[i-1] in '13579':
|
|
367 add = 1
|
|
368 scale = 10**ndigits
|
|
369 if have_decimal and isinstance(value, Decimal):
|
|
370 return Decimal(int(value * scale + add)) / scale * sign
|
|
371 else:
|
|
372 return float(int(value * scale + add)) / scale * sign
|
|
373
|
|
374 def parse_pattern(pattern):
|
|
375 """Parse number format patterns"""
|
|
376 if isinstance(pattern, NumberPattern):
|
|
377 return pattern
|
|
378
|
|
379 # Do we have a negative subpattern?
|
|
380 if ';' in pattern:
|
|
381 pattern, neg_pattern = pattern.split(';', 1)
|
|
382 pos_prefix, number, pos_suffix = number_re.search(pattern).groups()
|
|
383 neg_prefix, _, neg_suffix = number_re.search(neg_pattern).groups()
|
|
384 else:
|
|
385 pos_prefix, number, pos_suffix = number_re.search(pattern).groups()
|
|
386 neg_prefix = '-' + pos_prefix
|
|
387 neg_suffix = pos_suffix
|
|
388 if 'E' in number:
|
|
389 number, exp = number.split('E', 1)
|
|
390 else:
|
|
391 exp = None
|
|
392 if '@' in number:
|
|
393 if '.' in number and '0' in number:
|
|
394 raise ValueError('Significant digit patterns can not contain '
|
|
395 '"@" or "0"')
|
|
396 if '.' in number:
|
|
397 #integer, fraction = number.rsplit('.', 1)
|
|
398 # 2.3 compat: this is rsplit
|
|
399 parts = number.split('.')
|
|
400 integer, fraction = '.'.join(parts[:-1]), parts[-1]
|
|
401 else:
|
|
402 integer = number
|
|
403 fraction = ''
|
|
404 min_frac = max_frac = 0
|
|
405
|
|
406 def parse_precision(p):
|
|
407 """Calculate the min and max allowed digits"""
|
|
408 min = max = 0
|
|
409 for c in p:
|
|
410 if c in '@0':
|
|
411 min += 1
|
|
412 max += 1
|
|
413 elif c == '#':
|
|
414 max += 1
|
|
415 elif c == ',':
|
|
416 continue
|
|
417 else:
|
|
418 break
|
|
419 return min, max
|
|
420
|
|
421 def parse_grouping(p):
|
|
422 """Parse primary and secondary digit grouping
|
|
423
|
|
424 >>> parse_grouping('##')
|
|
425 0, 0
|
|
426 >>> parse_grouping('#,###')
|
|
427 3, 3
|
|
428 >>> parse_grouping('#,####,###')
|
|
429 3, 4
|
|
430 """
|
|
431 width = len(p)
|
|
432 g1 = p.rfind(',')
|
|
433 if g1 == -1:
|
|
434 return 1000, 1000
|
|
435 g1 = width - g1 - 1
|
|
436 g2 = p[:-g1 - 1].rfind(',')
|
|
437 if g2 == -1:
|
|
438 return g1, g1
|
|
439 g2 = width - g1 - g2 - 2
|
|
440 return g1, g2
|
|
441
|
|
442 int_prec = parse_precision(integer)
|
|
443 frac_prec = parse_precision(fraction)
|
|
444 if exp:
|
|
445 frac_prec = parse_precision(integer+fraction)
|
|
446 exp_plus = exp.startswith('+')
|
|
447 exp = exp.lstrip('+')
|
|
448 exp_prec = parse_precision(exp)
|
|
449 else:
|
|
450 exp_plus = None
|
|
451 exp_prec = None
|
|
452 grouping = parse_grouping(integer)
|
|
453 return NumberPattern(pattern, (pos_prefix, neg_prefix),
|
|
454 (pos_suffix, neg_suffix), grouping,
|
|
455 int_prec, frac_prec,
|
|
456 exp_prec, exp_plus)
|
|
457
|
|
458
|
|
459 class NumberPattern(object):
|
|
460
|
|
461 def __init__(self, pattern, prefix, suffix, grouping,
|
|
462 int_prec, frac_prec, exp_prec, exp_plus):
|
|
463 self.pattern = pattern
|
|
464 self.prefix = prefix
|
|
465 self.suffix = suffix
|
|
466 self.grouping = grouping
|
|
467 self.int_prec = int_prec
|
|
468 self.frac_prec = frac_prec
|
|
469 self.exp_prec = exp_prec
|
|
470 self.exp_plus = exp_plus
|
|
471 if '%' in ''.join(self.prefix + self.suffix):
|
|
472 self.scale = 100
|
|
473 elif u'‰' in ''.join(self.prefix + self.suffix):
|
|
474 self.scale = 1000
|
|
475 else:
|
|
476 self.scale = 1
|
|
477
|
|
478 def __repr__(self):
|
|
479 return '<%s %r>' % (type(self).__name__, self.pattern)
|
|
480
|
|
481 def apply(self, value, locale, currency=None):
|
|
482 value *= self.scale
|
|
483 is_negative = int(value < 0)
|
|
484 if self.exp_prec: # Scientific notation
|
|
485 value = abs(value)
|
|
486 if value:
|
|
487 exp = int(math.floor(math.log(value, 10)))
|
|
488 else:
|
|
489 exp = 0
|
|
490 # Minimum number of integer digits
|
|
491 if self.int_prec[0] == self.int_prec[1]:
|
|
492 exp -= self.int_prec[0] - 1
|
|
493 # Exponent grouping
|
|
494 elif self.int_prec[1]:
|
|
495 exp = int(exp) / self.int_prec[1] * self.int_prec[1]
|
|
496 if not have_decimal or not isinstance(value, Decimal):
|
|
497 value = float(value)
|
|
498 if exp < 0:
|
|
499 value = value * 10**(-exp)
|
|
500 else:
|
|
501 value = value / 10**exp
|
|
502 exp_sign = ''
|
|
503 if exp < 0:
|
|
504 exp_sign = get_minus_sign_symbol(locale)
|
|
505 elif self.exp_plus:
|
|
506 exp_sign = get_plus_sign_symbol(locale)
|
|
507 exp = abs(exp)
|
|
508 number = u'%s%s%s%s' % \
|
|
509 (self._format_sigdig(value, self.frac_prec[0],
|
|
510 self.frac_prec[1]),
|
|
511 get_exponential_symbol(locale), exp_sign,
|
|
512 self._format_int(str(exp), self.exp_prec[0],
|
|
513 self.exp_prec[1], locale))
|
|
514 elif '@' in self.pattern: # Is it a siginificant digits pattern?
|
|
515 text = self._format_sigdig(abs(value),
|
|
516 self.int_prec[0],
|
|
517 self.int_prec[1])
|
|
518 if '.' in text:
|
|
519 a, b = text.split('.')
|
|
520 a = self._format_int(a, 0, 1000, locale)
|
|
521 if b:
|
|
522 b = get_decimal_symbol(locale) + b
|
|
523 number = a + b
|
|
524 else:
|
|
525 number = self._format_int(text, 0, 1000, locale)
|
|
526 else: # A normal number pattern
|
|
527 a, b = split_number(bankersround(abs(value),
|
|
528 self.frac_prec[1]))
|
|
529 b = b or '0'
|
|
530 a = self._format_int(a, self.int_prec[0],
|
|
531 self.int_prec[1], locale)
|
|
532 b = self._format_frac(b, locale)
|
|
533 number = a + b
|
|
534 retval = u'%s%s%s' % (self.prefix[is_negative], number,
|
|
535 self.suffix[is_negative])
|
|
536 if u'¤' in retval:
|
|
537 retval = retval.replace(u'¤¤', currency.upper())
|
|
538 retval = retval.replace(u'¤', get_currency_symbol(currency, locale))
|
|
539 return retval
|
|
540
|
|
541 def _format_sigdig(self, value, min, max):
|
|
542 """Convert value to a string.
|
|
543
|
|
544 The resulting string will contain between (min, max) number of
|
|
545 significant digits.
|
|
546 """
|
|
547 a, b = split_number(value)
|
|
548 ndecimals = len(a)
|
|
549 if a == '0' and b != '':
|
|
550 ndecimals = 0
|
|
551 while b.startswith('0'):
|
|
552 b = b[1:]
|
|
553 ndecimals -= 1
|
|
554 a, b = split_number(bankersround(value, max - ndecimals))
|
|
555 digits = len((a + b).lstrip('0'))
|
|
556 if not digits:
|
|
557 digits = 1
|
|
558 # Figure out if we need to add any trailing '0':s
|
|
559 if len(a) >= max and a != '0':
|
|
560 return a
|
|
561 if digits < min:
|
|
562 b += ('0' * (min - digits))
|
|
563 if b:
|
|
564 return '%s.%s' % (a, b)
|
|
565 return a
|
|
566
|
|
567 def _format_int(self, value, min, max, locale):
|
|
568 width = len(value)
|
|
569 if width < min:
|
|
570 value = '0' * (min - width) + value
|
|
571 gsize = self.grouping[0]
|
|
572 ret = ''
|
|
573 symbol = get_group_symbol(locale)
|
|
574 while len(value) > gsize:
|
|
575 ret = symbol + value[-gsize:] + ret
|
|
576 value = value[:-gsize]
|
|
577 gsize = self.grouping[1]
|
|
578 return value + ret
|
|
579
|
|
580 def _format_frac(self, value, locale):
|
|
581 min, max = self.frac_prec
|
|
582 if len(value) < min:
|
|
583 value += ('0' * (min - len(value)))
|
|
584 if max == 0 or (min == 0 and int(value) == 0):
|
|
585 return ''
|
|
586 width = len(value)
|
|
587 while len(value) > min and value[-1] == '0':
|
|
588 value = value[:-1]
|
|
589 return get_decimal_symbol(locale) + value
|