changeset 220:ce3ad60145db

Added Decimal support to the number formatter.
author jonas
date Mon, 16 Jul 2007 15:35:34 +0000
parents f065fb523fd6
children 19eaa0f8fae5
files babel/numbers.py babel/tests/numbers.py
diffstat 2 files changed, 73 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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))
Copyright (C) 2012-2017 Edgewall Software