# HG changeset patch # User jonas # Date 1184600134 0 # Node ID b566f483b763b4ff391151ec0c62b82644b3b02e # Parent 4de6f46048301eb84d3ff68e16b6473b5a038b0f Added Decimal support to the number formatter. diff --git a/babel/numbers.py b/babel/numbers.py --- a/babel/numbers.py +++ b/babel/numbers.py @@ -23,6 +23,11 @@ # TODO: percent and scientific formatting import re +try: + from decimal import Decimal + have_decimal = True +except ImportError: + have_decimal = False from babel.core import default_locale, Locale @@ -245,6 +250,20 @@ number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN, SUFFIX_PATTERN)) +def split_number(value): + """Convert a number into a (intasstring, fractionasstring) tuple""" + if have_decimal and isinstance(value, Decimal): + text = str(value) + else: + text = ('%.9f' % value).rstrip('0') + if '.' in text: + a, b = text.split('.', 1) + if b == '0': + b = '' + else: + a, b = text, '' + return a, b + def bankersround(value, ndigits=0): """Round a number to a given precision. @@ -257,13 +276,13 @@ 6.0 >>> bankersround(-6.5, 0) -6.0 - >>> bankersround(1234, -2) + >>> bankersround(1234.0, -2) 1200.0 """ sign = int(value < 0) and -1 or 1 value = abs(value) - a, b = str(float(value)).split('.', 1) - digits = str(float(value)).replace('.', '') + a, b = split_number(value) + digits = a + b add = 0 i = len(a) + ndigits if i < 0 or i >= len(digits): @@ -272,8 +291,11 @@ add = 1 elif digits[i] == '5' and digits[i-1] in '13579': add = 1 - scale = 10.**ndigits - return int(value * scale + add) / scale * sign + scale = 10**ndigits + if have_decimal and isinstance(value, Decimal): + return Decimal(int(value * scale + add)) / scale * sign + else: + return float(int(value * scale + add)) / scale * sign # TODO: # Filling @@ -343,8 +365,6 @@ int_precision = parse_precision(integer) frac_precision = parse_precision(fraction) grouping = parse_grouping(integer) - if not '@' in pattern: - int_precision = (int_precision[0], 1000) # Unlimited return NumberPattern(pattern, (pos_prefix, neg_prefix), (pos_suffix, neg_suffix), grouping, int_precision, frac_precision) @@ -361,11 +381,11 @@ self.int_precision = int_precision self.frac_precision = frac_precision if '%' in ''.join(self.prefix + self.suffix): - self.scale = 100.0 + self.scale = 100 elif u'‰' in ''.join(self.prefix + self.suffix): - self.scale = 1000.0 + self.scale = 1000 else: - self.scale = 1.0 + self.scale = 1 def __repr__(self): return '<%s %r>' % (type(self).__name__, self.pattern) @@ -385,8 +405,9 @@ else: a, b = self._format_int(text, 0, 1000, locale), '' else: # A normal number pattern - a, b = str(bankersround(abs(value), - self.frac_precision[1])).split('.', 1) + a, b = split_number(bankersround(abs(value), + self.frac_precision[1])) + b = b or '0' a = self._format_int(a, self.int_precision[0], self.int_precision[1], locale) b = self._format_frac(b, locale) @@ -403,21 +424,17 @@ The resulting string will contain between (min, max) number of significant digits. """ - a, b = str(float(value)).split('.', 1) + a, b = split_number(value) ndecimals = len(a) - if a == '0': + if a == '0' and b != '': ndecimals = 0 while b.startswith('0'): b = b[1:] ndecimals -= 1 - text = str(bankersround(value, max - ndecimals)) - if text == '0.0': - text = a = '0' - b = '' + a, b = split_number(bankersround(value, max - ndecimals)) + digits = len((a + b).lstrip('0')) + if not digits: digits = 1 - else: - a, b = text.split('.', 1) - digits = len(text.replace('.', '').lstrip('0')) # Figure out if we need to add any trailing '0':s if len(a) >= max and a != '0': return a @@ -442,6 +459,8 @@ def _format_frac(self, value, locale): min, max = self.frac_precision + if len(value) < min: + value += ('0' * (min - len(value))) if max == 0 or (min == 0 and int(value) == 0): return '' width = len(value) diff --git a/babel/tests/numbers.py b/babel/tests/numbers.py --- a/babel/tests/numbers.py +++ b/babel/tests/numbers.py @@ -11,6 +11,12 @@ # individuals. For the exact contribution history, see the revision # history and logs, available at http://babel.edgewall.org/log/. +try: + from decimal import Decimal + have_decimal = True +except ImportError: + have_decimal = False + import doctest import unittest @@ -19,6 +25,15 @@ class FormatDecimalTestCase(unittest.TestCase): + def test_patterns(self): + self.assertEqual(numbers.format_decimal(12345, '##0', + locale='en_US'), '12345') + self.assertEqual(numbers.format_decimal(6.5, '0.00', locale='sv'), + '6,50') + self.assertEqual(numbers.format_decimal(10.0**20, + '#.00', locale='en_US'), + '100000000000000000000.00') + def test_subpatterns(self): self.assertEqual(numbers.format_decimal(-12345, '#,##0.##;-#', locale='en_US'), '-12,345') @@ -33,6 +48,7 @@ """ self.assertEqual(numbers.format_decimal(5.5, '0', locale='sv'), '6') self.assertEqual(numbers.format_decimal(6.5, '0', locale='sv'), '6') + self.assertEqual(numbers.format_decimal(6.5, '0', locale='sv'), '6') self.assertEqual(numbers.format_decimal(1.2325, locale='sv'), '1,232') self.assertEqual(numbers.format_decimal(1.2335, locale='sv'), '1,234') @@ -72,6 +88,23 @@ '0.1') self.assertEqual(numbers.format_decimal(0.1, '@@',locale='en_US'), '0.10') + + if have_decimal: + def test_decimals(self): + """Test significant digits patterns""" + self.assertEqual(numbers.format_decimal(Decimal('1.2345'), + '#.00', locale='en_US'), + '1.23') + self.assertEqual(numbers.format_decimal(Decimal('1.2345000'), + '#.00', locale='en_US'), + '1.23') + self.assertEqual(numbers.format_decimal(Decimal('1.2345000'), + '@@', locale='en_US'), + '1.2') + self.assertEqual(numbers.format_decimal(Decimal('12345678901234567890.12345'), + '#.00', locale='en_US'), + '12345678901234567890.12') + def suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(numbers))