numbers.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. # -*- coding: utf-8 -*-
  2. """
  3. babel.numbers
  4. ~~~~~~~~~~~~~
  5. Locale dependent formatting and parsing of numeric data.
  6. The default locale for the functions in this module is determined by the
  7. following environment variables, in that order:
  8. * ``LC_NUMERIC``,
  9. * ``LC_ALL``, and
  10. * ``LANG``
  11. :copyright: (c) 2013 by the Babel Team.
  12. :license: BSD, see LICENSE for more details.
  13. """
  14. # TODO:
  15. # Padding and rounding increments in pattern:
  16. # - http://www.unicode.org/reports/tr35/ (Appendix G.6)
  17. import re
  18. from datetime import date as date_, datetime as datetime_
  19. from babel.core import default_locale, Locale, get_global
  20. from babel._compat import decimal
  21. LC_NUMERIC = default_locale('LC_NUMERIC')
  22. def get_currency_name(currency, count=None, locale=LC_NUMERIC):
  23. """Return the name used by the locale for the specified currency.
  24. >>> get_currency_name('USD', locale='en_US')
  25. u'US Dollar'
  26. .. versionadded:: 0.9.4
  27. :param currency: the currency code
  28. :param count: the optional count. If provided the currency name
  29. will be pluralized to that number if possible.
  30. :param locale: the `Locale` object or locale identifier
  31. """
  32. loc = Locale.parse(locale)
  33. if count is not None:
  34. plural_form = loc.plural_form(count)
  35. plural_names = loc._data['currency_names_plural']
  36. if currency in plural_names:
  37. return plural_names[currency][plural_form]
  38. return loc.currencies.get(currency, currency)
  39. def get_currency_symbol(currency, locale=LC_NUMERIC):
  40. """Return the symbol used by the locale for the specified currency.
  41. >>> get_currency_symbol('USD', locale='en_US')
  42. u'$'
  43. :param currency: the currency code
  44. :param locale: the `Locale` object or locale identifier
  45. """
  46. return Locale.parse(locale).currency_symbols.get(currency, currency)
  47. def get_territory_currencies(territory, start_date=None, end_date=None,
  48. tender=True, non_tender=False,
  49. include_details=False):
  50. """Returns the list of currencies for the given territory that are valid for
  51. the given date range. In addition to that the currency database
  52. distinguishes between tender and non-tender currencies. By default only
  53. tender currencies are returned.
  54. The return value is a list of all currencies roughly ordered by the time
  55. of when the currency became active. The longer the currency is being in
  56. use the more to the left of the list it will be.
  57. The start date defaults to today. If no end date is given it will be the
  58. same as the start date. Otherwise a range can be defined. For instance
  59. this can be used to find the currencies in use in Austria between 1995 and
  60. 2011:
  61. >>> from datetime import date
  62. >>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1))
  63. ['ATS', 'EUR']
  64. Likewise it's also possible to find all the currencies in use on a
  65. single date:
  66. >>> get_territory_currencies('AT', date(1995, 1, 1))
  67. ['ATS']
  68. >>> get_territory_currencies('AT', date(2011, 1, 1))
  69. ['EUR']
  70. By default the return value only includes tender currencies. This
  71. however can be changed:
  72. >>> get_territory_currencies('US')
  73. ['USD']
  74. >>> get_territory_currencies('US', tender=False, non_tender=True,
  75. ... start_date=date(2014, 1, 1))
  76. ['USN', 'USS']
  77. .. versionadded:: 2.0
  78. :param territory: the name of the territory to find the currency fo
  79. :param start_date: the start date. If not given today is assumed.
  80. :param end_date: the end date. If not given the start date is assumed.
  81. :param tender: controls whether tender currencies should be included.
  82. :param non_tender: controls whether non-tender currencies should be
  83. included.
  84. :param include_details: if set to `True`, instead of returning currency
  85. codes the return value will be dictionaries
  86. with detail information. In that case each
  87. dictionary will have the keys ``'currency'``,
  88. ``'from'``, ``'to'``, and ``'tender'``.
  89. """
  90. currencies = get_global('territory_currencies')
  91. if start_date is None:
  92. start_date = date_.today()
  93. elif isinstance(start_date, datetime_):
  94. start_date = start_date.date()
  95. if end_date is None:
  96. end_date = start_date
  97. elif isinstance(end_date, datetime_):
  98. end_date = end_date.date()
  99. curs = currencies.get(territory.upper(), ())
  100. # TODO: validate that the territory exists
  101. def _is_active(start, end):
  102. return (start is None or start <= end_date) and \
  103. (end is None or end >= start_date)
  104. result = []
  105. for currency_code, start, end, is_tender in curs:
  106. if start:
  107. start = date_(*start)
  108. if end:
  109. end = date_(*end)
  110. if ((is_tender and tender) or
  111. (not is_tender and non_tender)) and _is_active(start, end):
  112. if include_details:
  113. result.append({
  114. 'currency': currency_code,
  115. 'from': start,
  116. 'to': end,
  117. 'tender': is_tender,
  118. })
  119. else:
  120. result.append(currency_code)
  121. return result
  122. def get_decimal_symbol(locale=LC_NUMERIC):
  123. """Return the symbol used by the locale to separate decimal fractions.
  124. >>> get_decimal_symbol('en_US')
  125. u'.'
  126. :param locale: the `Locale` object or locale identifier
  127. """
  128. return Locale.parse(locale).number_symbols.get('decimal', u'.')
  129. def get_plus_sign_symbol(locale=LC_NUMERIC):
  130. """Return the plus sign symbol used by the current locale.
  131. >>> get_plus_sign_symbol('en_US')
  132. u'+'
  133. :param locale: the `Locale` object or locale identifier
  134. """
  135. return Locale.parse(locale).number_symbols.get('plusSign', u'+')
  136. def get_minus_sign_symbol(locale=LC_NUMERIC):
  137. """Return the plus sign symbol used by the current locale.
  138. >>> get_minus_sign_symbol('en_US')
  139. u'-'
  140. :param locale: the `Locale` object or locale identifier
  141. """
  142. return Locale.parse(locale).number_symbols.get('minusSign', u'-')
  143. def get_exponential_symbol(locale=LC_NUMERIC):
  144. """Return the symbol used by the locale to separate mantissa and exponent.
  145. >>> get_exponential_symbol('en_US')
  146. u'E'
  147. :param locale: the `Locale` object or locale identifier
  148. """
  149. return Locale.parse(locale).number_symbols.get('exponential', u'E')
  150. def get_group_symbol(locale=LC_NUMERIC):
  151. """Return the symbol used by the locale to separate groups of thousands.
  152. >>> get_group_symbol('en_US')
  153. u','
  154. :param locale: the `Locale` object or locale identifier
  155. """
  156. return Locale.parse(locale).number_symbols.get('group', u',')
  157. def format_number(number, locale=LC_NUMERIC):
  158. u"""Return the given number formatted for a specific locale.
  159. >>> format_number(1099, locale='en_US')
  160. u'1,099'
  161. >>> format_number(1099, locale='de_DE')
  162. u'1.099'
  163. :param number: the number to format
  164. :param locale: the `Locale` object or locale identifier
  165. """
  166. # Do we really need this one?
  167. return format_decimal(number, locale=locale)
  168. def format_decimal(number, format=None, locale=LC_NUMERIC):
  169. u"""Return the given decimal number formatted for a specific locale.
  170. >>> format_decimal(1.2345, locale='en_US')
  171. u'1.234'
  172. >>> format_decimal(1.2346, locale='en_US')
  173. u'1.235'
  174. >>> format_decimal(-1.2346, locale='en_US')
  175. u'-1.235'
  176. >>> format_decimal(1.2345, locale='sv_SE')
  177. u'1,234'
  178. >>> format_decimal(1.2345, locale='de')
  179. u'1,234'
  180. The appropriate thousands grouping and the decimal separator are used for
  181. each locale:
  182. >>> format_decimal(12345.5, locale='en_US')
  183. u'12,345.5'
  184. :param number: the number to format
  185. :param format:
  186. :param locale: the `Locale` object or locale identifier
  187. """
  188. locale = Locale.parse(locale)
  189. if not format:
  190. format = locale.decimal_formats.get(format)
  191. pattern = parse_pattern(format)
  192. return pattern.apply(number, locale)
  193. class UnknownCurrencyFormatError(KeyError):
  194. """Exception raised when an unknown currency format is requested."""
  195. def format_currency(number, currency, format=None, locale=LC_NUMERIC,
  196. currency_digits=True, format_type='standard'):
  197. u"""Return formatted currency value.
  198. >>> format_currency(1099.98, 'USD', locale='en_US')
  199. u'$1,099.98'
  200. >>> format_currency(1099.98, 'USD', locale='es_CO')
  201. u'US$\\xa01.099,98'
  202. >>> format_currency(1099.98, 'EUR', locale='de_DE')
  203. u'1.099,98\\xa0\\u20ac'
  204. The format can also be specified explicitly. The currency is
  205. placed with the '¤' sign. As the sign gets repeated the format
  206. expands (¤ being the symbol, ¤¤ is the currency abbreviation and
  207. ¤¤¤ is the full name of the currency):
  208. >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US')
  209. u'EUR 1,099.98'
  210. >>> format_currency(1099.98, 'EUR', u'#,##0.00 \xa4\xa4\xa4', locale='en_US')
  211. u'1,099.98 euros'
  212. Currencies usually have a specific number of decimal digits. This function
  213. favours that information over the given format:
  214. >>> format_currency(1099.98, 'JPY', locale='en_US')
  215. u'\\xa51,100'
  216. >>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES')
  217. u'1.100'
  218. However, the number of decimal digits can be overriden from the currency
  219. information, by setting the last parameter to ``False``:
  220. >>> format_currency(1099.98, 'JPY', locale='en_US', currency_digits=False)
  221. u'\\xa51,099.98'
  222. >>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES', currency_digits=False)
  223. u'1.099,98'
  224. If a format is not specified the type of currency format to use
  225. from the locale can be specified:
  226. >>> format_currency(1099.98, 'EUR', locale='en_US', format_type='standard')
  227. u'\\u20ac1,099.98'
  228. When the given currency format type is not available, an exception is
  229. raised:
  230. >>> format_currency('1099.98', 'EUR', locale='root', format_type='unknown')
  231. Traceback (most recent call last):
  232. ...
  233. UnknownCurrencyFormatError: "'unknown' is not a known currency format type"
  234. :param number: the number to format
  235. :param currency: the currency code
  236. :param format: the format string to use
  237. :param locale: the `Locale` object or locale identifier
  238. :param currency_digits: use the currency's number of decimal digits
  239. :param format_type: the currency format type to use
  240. """
  241. locale = Locale.parse(locale)
  242. if format:
  243. pattern = parse_pattern(format)
  244. else:
  245. try:
  246. pattern = locale.currency_formats[format_type]
  247. except KeyError:
  248. raise UnknownCurrencyFormatError("%r is not a known currency format"
  249. " type" % format_type)
  250. if currency_digits:
  251. fractions = get_global('currency_fractions')
  252. try:
  253. digits = fractions[currency][0]
  254. except KeyError:
  255. digits = fractions['DEFAULT'][0]
  256. frac = (digits, digits)
  257. else:
  258. frac = None
  259. return pattern.apply(number, locale, currency=currency, force_frac=frac)
  260. def format_percent(number, format=None, locale=LC_NUMERIC):
  261. """Return formatted percent value for a specific locale.
  262. >>> format_percent(0.34, locale='en_US')
  263. u'34%'
  264. >>> format_percent(25.1234, locale='en_US')
  265. u'2,512%'
  266. >>> format_percent(25.1234, locale='sv_SE')
  267. u'2\\xa0512\\xa0%'
  268. The format pattern can also be specified explicitly:
  269. >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US')
  270. u'25,123\u2030'
  271. :param number: the percent number to format
  272. :param format:
  273. :param locale: the `Locale` object or locale identifier
  274. """
  275. locale = Locale.parse(locale)
  276. if not format:
  277. format = locale.percent_formats.get(format)
  278. pattern = parse_pattern(format)
  279. return pattern.apply(number, locale)
  280. def format_scientific(number, format=None, locale=LC_NUMERIC):
  281. """Return value formatted in scientific notation for a specific locale.
  282. >>> format_scientific(10000, locale='en_US')
  283. u'1E4'
  284. The format pattern can also be specified explicitly:
  285. >>> format_scientific(1234567, u'##0E00', locale='en_US')
  286. u'1.23E06'
  287. :param number: the number to format
  288. :param format:
  289. :param locale: the `Locale` object or locale identifier
  290. """
  291. locale = Locale.parse(locale)
  292. if not format:
  293. format = locale.scientific_formats.get(format)
  294. pattern = parse_pattern(format)
  295. return pattern.apply(number, locale)
  296. class NumberFormatError(ValueError):
  297. """Exception raised when a string cannot be parsed into a number."""
  298. def parse_number(string, locale=LC_NUMERIC):
  299. """Parse localized number string into an integer.
  300. >>> parse_number('1,099', locale='en_US')
  301. 1099
  302. >>> parse_number('1.099', locale='de_DE')
  303. 1099
  304. When the given string cannot be parsed, an exception is raised:
  305. >>> parse_number('1.099,98', locale='de')
  306. Traceback (most recent call last):
  307. ...
  308. NumberFormatError: '1.099,98' is not a valid number
  309. :param string: the string to parse
  310. :param locale: the `Locale` object or locale identifier
  311. :return: the parsed number
  312. :raise `NumberFormatError`: if the string can not be converted to a number
  313. """
  314. try:
  315. return int(string.replace(get_group_symbol(locale), ''))
  316. except ValueError:
  317. raise NumberFormatError('%r is not a valid number' % string)
  318. def parse_decimal(string, locale=LC_NUMERIC):
  319. """Parse localized decimal string into a decimal.
  320. >>> parse_decimal('1,099.98', locale='en_US')
  321. Decimal('1099.98')
  322. >>> parse_decimal('1.099,98', locale='de')
  323. Decimal('1099.98')
  324. When the given string cannot be parsed, an exception is raised:
  325. >>> parse_decimal('2,109,998', locale='de')
  326. Traceback (most recent call last):
  327. ...
  328. NumberFormatError: '2,109,998' is not a valid decimal number
  329. :param string: the string to parse
  330. :param locale: the `Locale` object or locale identifier
  331. :raise NumberFormatError: if the string can not be converted to a
  332. decimal number
  333. """
  334. locale = Locale.parse(locale)
  335. try:
  336. return decimal.Decimal(string.replace(get_group_symbol(locale), '')
  337. .replace(get_decimal_symbol(locale), '.'))
  338. except decimal.InvalidOperation:
  339. raise NumberFormatError('%r is not a valid decimal number' % string)
  340. PREFIX_END = r'[^0-9@#.,]'
  341. NUMBER_TOKEN = r'[0-9@#.,E+]'
  342. PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END
  343. NUMBER_PATTERN = r"(?P<number>%s+)" % NUMBER_TOKEN
  344. SUFFIX_PATTERN = r"(?P<suffix>.*)"
  345. number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN,
  346. SUFFIX_PATTERN))
  347. def parse_grouping(p):
  348. """Parse primary and secondary digit grouping
  349. >>> parse_grouping('##')
  350. (1000, 1000)
  351. >>> parse_grouping('#,###')
  352. (3, 3)
  353. >>> parse_grouping('#,####,###')
  354. (3, 4)
  355. """
  356. width = len(p)
  357. g1 = p.rfind(',')
  358. if g1 == -1:
  359. return 1000, 1000
  360. g1 = width - g1 - 1
  361. g2 = p[:-g1 - 1].rfind(',')
  362. if g2 == -1:
  363. return g1, g1
  364. g2 = width - g1 - g2 - 2
  365. return g1, g2
  366. def parse_pattern(pattern):
  367. """Parse number format patterns"""
  368. if isinstance(pattern, NumberPattern):
  369. return pattern
  370. def _match_number(pattern):
  371. rv = number_re.search(pattern)
  372. if rv is None:
  373. raise ValueError('Invalid number pattern %r' % pattern)
  374. return rv.groups()
  375. pos_pattern = pattern
  376. # Do we have a negative subpattern?
  377. if ';' in pattern:
  378. pos_pattern, neg_pattern = pattern.split(';', 1)
  379. pos_prefix, number, pos_suffix = _match_number(pos_pattern)
  380. neg_prefix, _, neg_suffix = _match_number(neg_pattern)
  381. else:
  382. pos_prefix, number, pos_suffix = _match_number(pos_pattern)
  383. neg_prefix = '-' + pos_prefix
  384. neg_suffix = pos_suffix
  385. if 'E' in number:
  386. number, exp = number.split('E', 1)
  387. else:
  388. exp = None
  389. if '@' in number:
  390. if '.' in number and '0' in number:
  391. raise ValueError('Significant digit patterns can not contain '
  392. '"@" or "0"')
  393. if '.' in number:
  394. integer, fraction = number.rsplit('.', 1)
  395. else:
  396. integer = number
  397. fraction = ''
  398. def parse_precision(p):
  399. """Calculate the min and max allowed digits"""
  400. min = max = 0
  401. for c in p:
  402. if c in '@0':
  403. min += 1
  404. max += 1
  405. elif c == '#':
  406. max += 1
  407. elif c == ',':
  408. continue
  409. else:
  410. break
  411. return min, max
  412. int_prec = parse_precision(integer)
  413. frac_prec = parse_precision(fraction)
  414. if exp:
  415. frac_prec = parse_precision(integer + fraction)
  416. exp_plus = exp.startswith('+')
  417. exp = exp.lstrip('+')
  418. exp_prec = parse_precision(exp)
  419. else:
  420. exp_plus = None
  421. exp_prec = None
  422. grouping = parse_grouping(integer)
  423. return NumberPattern(pattern, (pos_prefix, neg_prefix),
  424. (pos_suffix, neg_suffix), grouping,
  425. int_prec, frac_prec,
  426. exp_prec, exp_plus)
  427. class NumberPattern(object):
  428. def __init__(self, pattern, prefix, suffix, grouping,
  429. int_prec, frac_prec, exp_prec, exp_plus):
  430. self.pattern = pattern
  431. self.prefix = prefix
  432. self.suffix = suffix
  433. self.grouping = grouping
  434. self.int_prec = int_prec
  435. self.frac_prec = frac_prec
  436. self.exp_prec = exp_prec
  437. self.exp_plus = exp_plus
  438. if '%' in ''.join(self.prefix + self.suffix):
  439. self.scale = 2
  440. elif u'‰' in ''.join(self.prefix + self.suffix):
  441. self.scale = 3
  442. else:
  443. self.scale = 0
  444. def __repr__(self):
  445. return '<%s %r>' % (type(self).__name__, self.pattern)
  446. def apply(self, value, locale, currency=None, force_frac=None):
  447. frac_prec = force_frac or self.frac_prec
  448. if not isinstance(value, decimal.Decimal):
  449. value = decimal.Decimal(str(value))
  450. value = value.scaleb(self.scale)
  451. is_negative = int(value.is_signed())
  452. if self.exp_prec: # Scientific notation
  453. exp = value.adjusted()
  454. value = abs(value)
  455. # Minimum number of integer digits
  456. if self.int_prec[0] == self.int_prec[1]:
  457. exp -= self.int_prec[0] - 1
  458. # Exponent grouping
  459. elif self.int_prec[1]:
  460. exp = int(exp / self.int_prec[1]) * self.int_prec[1]
  461. if exp < 0:
  462. value = value * 10**(-exp)
  463. else:
  464. value = value / 10**exp
  465. exp_sign = ''
  466. if exp < 0:
  467. exp_sign = get_minus_sign_symbol(locale)
  468. elif self.exp_plus:
  469. exp_sign = get_plus_sign_symbol(locale)
  470. exp = abs(exp)
  471. number = u'%s%s%s%s' % \
  472. (self._format_significant(value, frac_prec[0], frac_prec[1]),
  473. get_exponential_symbol(locale), exp_sign,
  474. self._format_int(str(exp), self.exp_prec[0],
  475. self.exp_prec[1], locale))
  476. elif '@' in self.pattern: # Is it a siginificant digits pattern?
  477. text = self._format_significant(abs(value),
  478. self.int_prec[0],
  479. self.int_prec[1])
  480. a, sep, b = text.partition(".")
  481. number = self._format_int(a, 0, 1000, locale)
  482. if sep:
  483. number += get_decimal_symbol(locale) + b
  484. else: # A normal number pattern
  485. precision = decimal.Decimal('1.' + '1' * frac_prec[1])
  486. rounded = value.quantize(precision)
  487. a, sep, b = str(abs(rounded)).partition(".")
  488. number = (self._format_int(a, self.int_prec[0],
  489. self.int_prec[1], locale) +
  490. self._format_frac(b or '0', locale, force_frac))
  491. retval = u'%s%s%s' % (self.prefix[is_negative], number,
  492. self.suffix[is_negative])
  493. if u'¤' in retval:
  494. retval = retval.replace(u'¤¤¤',
  495. get_currency_name(currency, value, locale))
  496. retval = retval.replace(u'¤¤', currency.upper())
  497. retval = retval.replace(u'¤', get_currency_symbol(currency, locale))
  498. return retval
  499. #
  500. # This is one tricky piece of code. The idea is to rely as much as possible
  501. # on the decimal module to minimize the amount of code.
  502. #
  503. # Conceptually, the implementation of this method can be summarized in the
  504. # following steps:
  505. #
  506. # - Move or shift the decimal point (i.e. the exponent) so the maximum
  507. # amount of significant digits fall into the integer part (i.e. to the
  508. # left of the decimal point)
  509. #
  510. # - Round the number to the nearest integer, discarding all the fractional
  511. # part which contained extra digits to be eliminated
  512. #
  513. # - Convert the rounded integer to a string, that will contain the final
  514. # sequence of significant digits already trimmed to the maximum
  515. #
  516. # - Restore the original position of the decimal point, potentially
  517. # padding with zeroes on either side
  518. #
  519. def _format_significant(self, value, minimum, maximum):
  520. exp = value.adjusted()
  521. scale = maximum - 1 - exp
  522. digits = str(value.scaleb(scale).quantize(decimal.Decimal(1)))
  523. if scale <= 0:
  524. result = digits + '0' * -scale
  525. else:
  526. intpart = digits[:-scale]
  527. i = len(intpart)
  528. j = i + max(minimum - i, 0)
  529. result = "{intpart}.{pad:0<{fill}}{fracpart}{fracextra}".format(
  530. intpart=intpart or '0',
  531. pad='',
  532. fill=-min(exp + 1, 0),
  533. fracpart=digits[i:j],
  534. fracextra=digits[j:].rstrip('0'),
  535. ).rstrip('.')
  536. return result
  537. def _format_int(self, value, min, max, locale):
  538. width = len(value)
  539. if width < min:
  540. value = '0' * (min - width) + value
  541. gsize = self.grouping[0]
  542. ret = ''
  543. symbol = get_group_symbol(locale)
  544. while len(value) > gsize:
  545. ret = symbol + value[-gsize:] + ret
  546. value = value[:-gsize]
  547. gsize = self.grouping[1]
  548. return value + ret
  549. def _format_frac(self, value, locale, force_frac=None):
  550. min, max = force_frac or self.frac_prec
  551. if len(value) < min:
  552. value += ('0' * (min - len(value)))
  553. if max == 0 or (min == 0 and int(value) == 0):
  554. return ''
  555. while len(value) > min and value[-1] == '0':
  556. value = value[:-1]
  557. return get_decimal_symbol(locale) + value