cmlenz@1: # -*- coding: utf-8 -*- cmlenz@1: # jruigrok@530: # Copyright (C) 2007-2011 Edgewall Software cmlenz@1: # All rights reserved. cmlenz@1: # cmlenz@245: # This software is licensed as described in the file COPYING, which cmlenz@1: # you should have received as part of this distribution. The terms cmlenz@1: # are also available at http://babel.edgewall.org/wiki/License. cmlenz@1: # cmlenz@1: # This software consists of voluntary contributions made by many cmlenz@1: # individuals. For the exact contribution history, see the revision cmlenz@1: # history and logs, available at http://babel.edgewall.org/log/. cmlenz@1: cmlenz@1: """Locale dependent formatting and parsing of numeric data. cmlenz@1: cmlenz@1: The default locale for the functions in this module is determined by the cmlenz@1: following environment variables, in that order: cmlenz@1: cmlenz@1: * ``LC_NUMERIC``, cmlenz@1: * ``LC_ALL``, and cmlenz@1: * ``LANG`` cmlenz@1: """ jonas@246: # TODO: jonas@246: # Padding and rounding increments in pattern: jonas@246: # - http://www.unicode.org/reports/tr35/ (Appendix G.6) fschwarz@556: from decimal import Decimal jonas@243: import math cmlenz@1: import re cmlenz@1: cmlenz@72: from babel.core import default_locale, Locale cmlenz@1: cmlenz@1: __all__ = ['format_number', 'format_decimal', 'format_currency', cmlenz@1: 'format_percent', 'format_scientific', 'parse_number', cmlenz@32: 'parse_decimal', 'NumberFormatError'] cmlenz@1: __docformat__ = 'restructuredtext en' cmlenz@1: cmlenz@72: LC_NUMERIC = default_locale('LC_NUMERIC') cmlenz@1: cmlenz@385: def get_currency_name(currency, locale=LC_NUMERIC): cmlenz@385: """Return the name used by the locale for the specified currency. cmlenz@385: cmlenz@385: >>> get_currency_name('USD', 'en_US') cmlenz@385: u'US Dollar' cmlenz@385: cmlenz@385: :param currency: the currency code cmlenz@385: :param locale: the `Locale` object or locale identifier cmlenz@385: :return: the currency symbol cmlenz@385: :rtype: `unicode` cmlenz@385: :since: version 0.9.4 cmlenz@385: """ cmlenz@385: return Locale.parse(locale).currencies.get(currency, currency) cmlenz@385: cmlenz@125: def get_currency_symbol(currency, locale=LC_NUMERIC): cmlenz@125: """Return the symbol used by the locale for the specified currency. cmlenz@125: cmlenz@125: >>> get_currency_symbol('USD', 'en_US') cmlenz@125: u'$' cmlenz@125: cmlenz@125: :param currency: the currency code cmlenz@125: :param locale: the `Locale` object or locale identifier cmlenz@125: :return: the currency symbol cmlenz@125: :rtype: `unicode` cmlenz@125: """ cmlenz@125: return Locale.parse(locale).currency_symbols.get(currency, currency) cmlenz@125: cmlenz@1: def get_decimal_symbol(locale=LC_NUMERIC): cmlenz@1: """Return the symbol used by the locale to separate decimal fractions. cmlenz@1: cmlenz@1: >>> get_decimal_symbol('en_US') cmlenz@1: u'.' cmlenz@1: cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the decimal symbol cmlenz@1: :rtype: `unicode` cmlenz@1: """ cmlenz@1: return Locale.parse(locale).number_symbols.get('decimal', u'.') cmlenz@1: jonas@244: def get_plus_sign_symbol(locale=LC_NUMERIC): jonas@244: """Return the plus sign symbol used by the current locale. jonas@244: jonas@244: >>> get_plus_sign_symbol('en_US') jonas@244: u'+' jonas@244: jonas@244: :param locale: the `Locale` object or locale identifier jonas@244: :return: the plus sign symbol jonas@244: :rtype: `unicode` jonas@244: """ jonas@244: return Locale.parse(locale).number_symbols.get('plusSign', u'+') jonas@244: jonas@244: def get_minus_sign_symbol(locale=LC_NUMERIC): jonas@244: """Return the plus sign symbol used by the current locale. jonas@244: jonas@244: >>> get_minus_sign_symbol('en_US') jonas@244: u'-' jonas@244: jonas@244: :param locale: the `Locale` object or locale identifier jonas@244: :return: the plus sign symbol jonas@244: :rtype: `unicode` jonas@244: """ jonas@244: return Locale.parse(locale).number_symbols.get('minusSign', u'-') jonas@244: jonas@243: def get_exponential_symbol(locale=LC_NUMERIC): jonas@243: """Return the symbol used by the locale to separate mantissa and exponent. jonas@243: jonas@243: >>> get_exponential_symbol('en_US') jonas@243: u'E' jonas@243: jonas@243: :param locale: the `Locale` object or locale identifier jonas@243: :return: the exponential symbol jonas@243: :rtype: `unicode` jonas@243: """ jonas@243: return Locale.parse(locale).number_symbols.get('exponential', u'E') jonas@243: cmlenz@1: def get_group_symbol(locale=LC_NUMERIC): cmlenz@1: """Return the symbol used by the locale to separate groups of thousands. cmlenz@1: cmlenz@1: >>> get_group_symbol('en_US') cmlenz@1: u',' cmlenz@1: cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the group symbol cmlenz@1: :rtype: `unicode` cmlenz@1: """ jonas@9: return Locale.parse(locale).number_symbols.get('group', u',') cmlenz@1: cmlenz@1: def format_number(number, locale=LC_NUMERIC): jruigrok@435: u"""Return the given number formatted for a specific locale. cmlenz@1: cmlenz@1: >>> format_number(1099, locale='en_US') cmlenz@1: u'1,099' jruigrok@435: >>> format_number(1099, locale='de_DE') jruigrok@437: u'1.099' jruigrok@435: cmlenz@1: cmlenz@1: :param number: the number to format cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the formatted number cmlenz@1: :rtype: `unicode` cmlenz@1: """ jonas@9: # Do we really need this one? jonas@9: return format_decimal(number, locale=locale) cmlenz@1: jonas@9: def format_decimal(number, format=None, locale=LC_NUMERIC): jruigrok@434: u"""Return the given decimal number formatted for a specific locale. cmlenz@1: jonas@9: >>> format_decimal(1.2345, locale='en_US') jonas@9: u'1.234' jonas@50: >>> format_decimal(1.2346, locale='en_US') jonas@50: u'1.235' jonas@50: >>> format_decimal(-1.2346, locale='en_US') jonas@50: u'-1.235' jonas@9: >>> format_decimal(1.2345, locale='sv_SE') jonas@9: u'1,234' jruigrok@437: >>> format_decimal(1.2345, locale='de') jruigrok@437: u'1,234' jonas@9: cmlenz@1: The appropriate thousands grouping and the decimal separator are used for cmlenz@1: each locale: cmlenz@1: cmlenz@125: >>> format_decimal(12345.5, locale='en_US') cmlenz@125: u'12,345.5' jonas@9: cmlenz@1: :param number: the number to format jonas@9: :param format: cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the formatted decimal number cmlenz@1: :rtype: `unicode` cmlenz@1: """ cmlenz@1: locale = Locale.parse(locale) cmlenz@125: if not format: cmlenz@125: format = locale.decimal_formats.get(format) cmlenz@125: pattern = parse_pattern(format) jonas@9: return pattern.apply(number, locale) cmlenz@1: cmlenz@125: def format_currency(number, currency, format=None, locale=LC_NUMERIC): cmlenz@133: u"""Return formatted currency value. cmlenz@1: cmlenz@32: >>> format_currency(1099.98, 'USD', locale='en_US') cmlenz@125: u'$1,099.98' cmlenz@125: >>> format_currency(1099.98, 'USD', locale='es_CO') cmlenz@375: u'US$\\xa01.099,98' cmlenz@125: >>> format_currency(1099.98, 'EUR', locale='de_DE') jruigrok@437: u'1.099,98\\xa0\\u20ac' cmlenz@125: cmlenz@125: The pattern can also be specified explicitly: cmlenz@125: cmlenz@125: >>> format_currency(1099.98, 'EUR', u'\xa4\xa4 #,##0.00', locale='en_US') cmlenz@125: u'EUR 1,099.98' cmlenz@1: cmlenz@26: :param number: the number to format cmlenz@32: :param currency: the currency code cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the formatted currency value cmlenz@1: :rtype: `unicode` cmlenz@1: """ cmlenz@125: locale = Locale.parse(locale) cmlenz@125: if not format: cmlenz@125: format = locale.currency_formats.get(format) cmlenz@125: pattern = parse_pattern(format) cmlenz@125: return pattern.apply(number, locale, currency=currency) cmlenz@1: cmlenz@26: def format_percent(number, format=None, locale=LC_NUMERIC): cmlenz@101: """Return formatted percent value for a specific locale. jonas@22: jonas@22: >>> format_percent(0.34, locale='en_US') jonas@22: u'34%' jonas@22: >>> format_percent(25.1234, locale='en_US') jonas@22: u'2,512%' jonas@22: >>> format_percent(25.1234, locale='sv_SE') cmlenz@233: u'2\\xa0512\\xa0%' jonas@22: cmlenz@126: The format pattern can also be specified explicitly: cmlenz@126: cmlenz@126: >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US') cmlenz@126: u'25,123\u2030' cmlenz@126: jonas@22: :param number: the percent number to format jonas@22: :param format: jonas@22: :param locale: the `Locale` object or locale identifier jonas@22: :return: the formatted percent number jonas@22: :rtype: `unicode` jonas@22: """ jonas@22: locale = Locale.parse(locale) cmlenz@125: if not format: cmlenz@125: format = locale.percent_formats.get(format) cmlenz@125: pattern = parse_pattern(format) cmlenz@26: return pattern.apply(number, locale) cmlenz@1: jonas@243: def format_scientific(number, format=None, locale=LC_NUMERIC): jonas@243: """Return value formatted in scientific notation for a specific locale. jonas@243: jonas@243: >>> format_scientific(10000, locale='en_US') jonas@243: u'1E4' jonas@243: jonas@243: The format pattern can also be specified explicitly: jonas@243: jonas@243: >>> format_scientific(1234567, u'##0E00', locale='en_US') jonas@243: u'1.23E06' jonas@243: jonas@243: :param number: the number to format jonas@243: :param format: jonas@243: :param locale: the `Locale` object or locale identifier jonas@243: :return: value formatted in scientific notation. jonas@243: :rtype: `unicode` jonas@243: """ jonas@243: locale = Locale.parse(locale) jonas@243: if not format: jonas@243: format = locale.scientific_formats.get(format) jonas@243: pattern = parse_pattern(format) jonas@243: return pattern.apply(number, locale) cmlenz@1: cmlenz@32: cmlenz@32: class NumberFormatError(ValueError): cmlenz@32: """Exception raised when a string cannot be parsed into a number.""" cmlenz@32: cmlenz@32: cmlenz@1: def parse_number(string, locale=LC_NUMERIC): cmlenz@1: """Parse localized number string into a long integer. cmlenz@1: cmlenz@1: >>> parse_number('1,099', locale='en_US') cmlenz@1: 1099L cmlenz@1: >>> parse_number('1.099', locale='de_DE') cmlenz@1: 1099L cmlenz@1: cmlenz@32: When the given string cannot be parsed, an exception is raised: cmlenz@32: cmlenz@32: >>> parse_number('1.099,98', locale='de') cmlenz@32: Traceback (most recent call last): cmlenz@32: ... cmlenz@32: NumberFormatError: '1.099,98' is not a valid number cmlenz@32: cmlenz@1: :param string: the string to parse cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the parsed number cmlenz@1: :rtype: `long` cmlenz@32: :raise `NumberFormatError`: if the string can not be converted to a number cmlenz@1: """ cmlenz@32: try: cmlenz@32: return long(string.replace(get_group_symbol(locale), '')) cmlenz@32: except ValueError: cmlenz@32: raise NumberFormatError('%r is not a valid number' % string) cmlenz@1: cmlenz@1: def parse_decimal(string, locale=LC_NUMERIC): cmlenz@1: """Parse localized decimal string into a float. cmlenz@1: cmlenz@1: >>> parse_decimal('1,099.98', locale='en_US') cmlenz@1: 1099.98 cmlenz@32: >>> parse_decimal('1.099,98', locale='de') cmlenz@1: 1099.98 cmlenz@1: cmlenz@32: When the given string cannot be parsed, an exception is raised: cmlenz@32: cmlenz@32: >>> parse_decimal('2,109,998', locale='de') cmlenz@32: Traceback (most recent call last): cmlenz@32: ... cmlenz@32: NumberFormatError: '2,109,998' is not a valid decimal number cmlenz@32: cmlenz@1: :param string: the string to parse cmlenz@1: :param locale: the `Locale` object or locale identifier cmlenz@1: :return: the parsed decimal number cmlenz@1: :rtype: `float` cmlenz@32: :raise `NumberFormatError`: if the string can not be converted to a cmlenz@32: decimal number cmlenz@1: """ cmlenz@1: locale = Locale.parse(locale) cmlenz@32: try: cmlenz@32: return float(string.replace(get_group_symbol(locale), '') cmlenz@32: .replace(get_decimal_symbol(locale), '.')) cmlenz@32: except ValueError: cmlenz@32: raise NumberFormatError('%r is not a valid decimal number' % string) jonas@9: jonas@9: jonas@9: PREFIX_END = r'[^0-9@#.,]' jonas@243: NUMBER_TOKEN = r'[0-9@#.\-,E+]' jonas@9: jonas@9: PREFIX_PATTERN = r"(?P(?:'[^']*'|%s)*)" % PREFIX_END jonas@9: NUMBER_PATTERN = r"(?P%s+)" % NUMBER_TOKEN jonas@9: SUFFIX_PATTERN = r"(?P.*)" jonas@9: cmlenz@127: number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN, jonas@9: SUFFIX_PATTERN)) jonas@9: jonas@218: def split_number(value): jonas@218: """Convert a number into a (intasstring, fractionasstring) tuple""" fschwarz@556: if isinstance(value, Decimal): fschwarz@580: # NB can't just do text = str(value) as str repr of Decimal may be fschwarz@580: # in scientific notation, e.g. for small numbers. fschwarz@580: fschwarz@580: sign, digits, exp = value.as_tuple() fschwarz@580: # build list of digits in reverse order, then reverse+join fschwarz@580: # as per http://docs.python.org/library/decimal.html#recipes fschwarz@580: int_part = [] fschwarz@580: frac_part = [] fschwarz@580: fschwarz@580: digits = map(str, digits) fschwarz@580: fschwarz@580: # get figures after decimal point fschwarz@580: for i in range(-exp): fschwarz@580: # add digit if available, else 0 fschwarz@580: frac_part.append(digits.pop() if digits else '0') fschwarz@580: fschwarz@580: # add in some zeroes... fschwarz@580: for i in range(exp): fschwarz@580: int_part.append('0') fschwarz@580: fschwarz@580: # and the rest fschwarz@580: while digits: fschwarz@580: int_part.append(digits.pop()) fschwarz@580: fschwarz@580: # if < 1, int_part must be set to '0' fschwarz@580: if len(int_part) == 0: fschwarz@580: int_part = '0', fschwarz@580: fschwarz@580: if sign: fschwarz@580: int_part.append('-') fschwarz@580: fschwarz@580: return ''.join(reversed(int_part)), ''.join(reversed(frac_part)) fschwarz@580: text = ('%.9f' % value).rstrip('0') jonas@218: if '.' in text: jonas@218: a, b = text.split('.', 1) jonas@218: if b == '0': jonas@218: b = '' jonas@218: else: jonas@218: a, b = text, '' jonas@218: return a, b jonas@218: jonas@212: def bankersround(value, ndigits=0): jonas@212: """Round a number to a given precision. jonas@212: jonas@212: Works like round() except that the round-half-even (banker's rounding) jonas@212: algorithm is used instead of round-half-up. jonas@212: jonas@212: >>> bankersround(5.5, 0) jonas@212: 6.0 jonas@212: >>> bankersround(6.5, 0) jonas@212: 6.0 jonas@212: >>> bankersround(-6.5, 0) jonas@212: -6.0 jonas@218: >>> bankersround(1234.0, -2) jonas@212: 1200.0 jonas@212: """ jonas@212: sign = int(value < 0) and -1 or 1 jonas@212: value = abs(value) jonas@218: a, b = split_number(value) jonas@218: digits = a + b jonas@212: add = 0 jonas@212: i = len(a) + ndigits jonas@212: if i < 0 or i >= len(digits): jonas@212: pass jonas@212: elif digits[i] > '5': jonas@212: add = 1 jonas@212: elif digits[i] == '5' and digits[i-1] in '13579': jonas@212: add = 1 fschwarz@557: elif digits[i] == '5': # previous digit is even fschwarz@557: # We round up unless all following digits are zero. fschwarz@557: for j in xrange(i + 1, len(digits)): fschwarz@557: if digits[j] != '0': fschwarz@557: add = 1 fschwarz@557: break fschwarz@557: jonas@218: scale = 10**ndigits fschwarz@556: if isinstance(value, Decimal): jonas@218: return Decimal(int(value * scale + add)) / scale * sign jonas@218: else: jonas@218: return float(int(value * scale + add)) / scale * sign jonas@212: jonas@9: def parse_pattern(pattern): jonas@9: """Parse number format patterns""" jonas@9: if isinstance(pattern, NumberPattern): jonas@9: return pattern jonas@9: jonas@9: # Do we have a negative subpattern? jonas@9: if ';' in pattern: jonas@9: pattern, neg_pattern = pattern.split(';', 1) jonas@9: pos_prefix, number, pos_suffix = number_re.search(pattern).groups() jonas@9: neg_prefix, _, neg_suffix = number_re.search(neg_pattern).groups() jonas@9: else: jonas@9: pos_prefix, number, pos_suffix = number_re.search(pattern).groups() jonas@9: neg_prefix = '-' + pos_prefix jonas@9: neg_suffix = pos_suffix jonas@243: if 'E' in number: jonas@243: number, exp = number.split('E', 1) jonas@243: else: jonas@243: exp = None jonas@211: if '@' in number: jonas@211: if '.' in number and '0' in number: jonas@211: raise ValueError('Significant digit patterns can not contain ' jonas@211: '"@" or "0"') jonas@22: if '.' in number: jruigrok@520: integer, fraction = number.rsplit('.', 1) jonas@22: else: jonas@22: integer = number jonas@22: fraction = '' jonas@9: jonas@9: def parse_precision(p): jonas@9: """Calculate the min and max allowed digits""" jonas@9: min = max = 0 jonas@9: for c in p: jonas@211: if c in '@0': jonas@9: min += 1 jonas@9: max += 1 jonas@9: elif c == '#': jonas@9: max += 1 jonas@211: elif c == ',': jonas@211: continue jonas@9: else: jonas@9: break jonas@9: return min, max jonas@9: jonas@9: def parse_grouping(p): jonas@9: """Parse primary and secondary digit grouping jonas@9: jonas@9: >>> parse_grouping('##') jonas@9: 0, 0 jonas@9: >>> parse_grouping('#,###') jonas@9: 3, 3 jonas@9: >>> parse_grouping('#,####,###') jonas@9: 3, 4 jonas@9: """ jonas@9: width = len(p) jonas@9: g1 = p.rfind(',') jonas@9: if g1 == -1: jonas@9: return 1000, 1000 jonas@9: g1 = width - g1 - 1 jonas@9: g2 = p[:-g1 - 1].rfind(',') jonas@9: if g2 == -1: jonas@9: return g1, g1 jonas@9: g2 = width - g1 - g2 - 2 jonas@9: return g1, g2 jonas@9: jonas@244: int_prec = parse_precision(integer) jonas@244: frac_prec = parse_precision(fraction) jonas@243: if exp: jonas@244: frac_prec = parse_precision(integer+fraction) jonas@243: exp_plus = exp.startswith('+') jonas@243: exp = exp.lstrip('+') jonas@244: exp_prec = parse_precision(exp) jonas@243: else: jonas@243: exp_plus = None jonas@244: exp_prec = None jonas@9: grouping = parse_grouping(integer) jonas@9: return NumberPattern(pattern, (pos_prefix, neg_prefix), jonas@9: (pos_suffix, neg_suffix), grouping, jonas@244: int_prec, frac_prec, jonas@244: exp_prec, exp_plus) jonas@9: jonas@9: jonas@9: class NumberPattern(object): jonas@22: jonas@9: def __init__(self, pattern, prefix, suffix, grouping, jonas@244: int_prec, frac_prec, exp_prec, exp_plus): jonas@9: self.pattern = pattern jonas@9: self.prefix = prefix jonas@9: self.suffix = suffix jonas@9: self.grouping = grouping jonas@244: self.int_prec = int_prec jonas@244: self.frac_prec = frac_prec jonas@244: self.exp_prec = exp_prec jonas@243: self.exp_plus = exp_plus jonas@22: if '%' in ''.join(self.prefix + self.suffix): jonas@218: self.scale = 100 jonas@22: elif u'‰' in ''.join(self.prefix + self.suffix): jonas@218: self.scale = 1000 jonas@22: else: jonas@218: self.scale = 1 jonas@9: jonas@9: def __repr__(self): jonas@9: return '<%s %r>' % (type(self).__name__, self.pattern) jonas@9: cmlenz@125: def apply(self, value, locale, currency=None): fschwarz@576: if isinstance(value, float): fschwarz@576: value = Decimal(str(value)) jonas@22: value *= self.scale jonas@244: is_negative = int(value < 0) jonas@244: if self.exp_prec: # Scientific notation jonas@243: value = abs(value) aronacher@357: if value: aronacher@357: exp = int(math.floor(math.log(value, 10))) aronacher@357: else: aronacher@357: exp = 0 jonas@243: # Minimum number of integer digits jonas@244: if self.int_prec[0] == self.int_prec[1]: jonas@244: exp -= self.int_prec[0] - 1 jonas@243: # Exponent grouping jonas@244: elif self.int_prec[1]: jonas@244: exp = int(exp) / self.int_prec[1] * self.int_prec[1] fschwarz@556: if not isinstance(value, Decimal): jonas@244: value = float(value) jonas@244: if exp < 0: jonas@244: value = value * 10**(-exp) jonas@244: else: jonas@244: value = value / 10**exp jonas@244: exp_sign = '' jonas@244: if exp < 0: jonas@244: exp_sign = get_minus_sign_symbol(locale) jonas@244: elif self.exp_plus: jonas@244: exp_sign = get_plus_sign_symbol(locale) jonas@243: exp = abs(exp) jonas@244: number = u'%s%s%s%s' % \ jonas@244: (self._format_sigdig(value, self.frac_prec[0], jonas@244: self.frac_prec[1]), jonas@244: get_exponential_symbol(locale), exp_sign, jonas@244: self._format_int(str(exp), self.exp_prec[0], jonas@244: self.exp_prec[1], locale)) jonas@243: elif '@' in self.pattern: # Is it a siginificant digits pattern? jonas@211: text = self._format_sigdig(abs(value), jonas@244: self.int_prec[0], jonas@244: self.int_prec[1]) jonas@211: if '.' in text: jonas@211: a, b = text.split('.') jonas@211: a = self._format_int(a, 0, 1000, locale) jonas@211: if b: jonas@211: b = get_decimal_symbol(locale) + b jonas@244: number = a + b jonas@211: else: jonas@244: number = self._format_int(text, 0, 1000, locale) jonas@211: else: # A normal number pattern jonas@218: a, b = split_number(bankersround(abs(value), jonas@244: self.frac_prec[1])) jonas@218: b = b or '0' jonas@244: a = self._format_int(a, self.int_prec[0], jonas@244: self.int_prec[1], locale) jonas@211: b = self._format_frac(b, locale) jonas@244: number = a + b jonas@244: retval = u'%s%s%s' % (self.prefix[is_negative], number, jonas@244: self.suffix[is_negative]) cmlenz@125: if u'¤' in retval: cmlenz@125: retval = retval.replace(u'¤¤', currency.upper()) cmlenz@125: retval = retval.replace(u'¤', get_currency_symbol(currency, locale)) cmlenz@125: return retval jonas@9: jonas@211: def _format_sigdig(self, value, min, max): jonas@211: """Convert value to a string. jonas@211: jonas@211: The resulting string will contain between (min, max) number of jonas@211: significant digits. jonas@211: """ jonas@218: a, b = split_number(value) jonas@211: ndecimals = len(a) jonas@218: if a == '0' and b != '': jonas@211: ndecimals = 0 jonas@211: while b.startswith('0'): jonas@211: b = b[1:] jonas@211: ndecimals -= 1 jonas@218: a, b = split_number(bankersround(value, max - ndecimals)) jonas@218: digits = len((a + b).lstrip('0')) jonas@218: if not digits: jonas@211: digits = 1 jonas@211: # Figure out if we need to add any trailing '0':s jonas@211: if len(a) >= max and a != '0': jonas@211: return a jonas@211: if digits < min: jonas@211: b += ('0' * (min - digits)) jonas@211: if b: jonas@211: return '%s.%s' % (a, b) jonas@211: return a jonas@211: jonas@211: def _format_int(self, value, min, max, locale): jonas@9: width = len(value) jonas@9: if width < min: jonas@243: value = '0' * (min - width) + value jonas@9: gsize = self.grouping[0] jonas@9: ret = '' jonas@9: symbol = get_group_symbol(locale) jonas@9: while len(value) > gsize: jonas@9: ret = symbol + value[-gsize:] + ret jonas@9: value = value[:-gsize] jonas@9: gsize = self.grouping[1] jonas@9: return value + ret jonas@9: jonas@9: def _format_frac(self, value, locale): jonas@244: min, max = self.frac_prec jonas@218: if len(value) < min: jonas@218: value += ('0' * (min - len(value))) jonas@22: if max == 0 or (min == 0 and int(value) == 0): jonas@9: return '' jonas@9: width = len(value) jonas@50: while len(value) > min and value[-1] == '0': jonas@50: value = value[:-1] jonas@9: return get_decimal_symbol(locale) + value