changeset 245:d462423feeea

Added initial support for scientific notation patterns.
author jonas
date Sat, 11 Aug 2007 11:31:05 +0000
parents cfb4cb07cbc7
children f21b6e6a13a7
files babel/numbers.py babel/tests/numbers.py
diffstat 2 files changed, 115 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/babel/numbers.py
+++ b/babel/numbers.py
@@ -20,8 +20,9 @@
  * ``LC_ALL``, and
  * ``LANG``
 """
-# TODO: percent and scientific formatting
+# TODO: scientific formatting
 
+import math
 import re
 try:
     from decimal import Decimal
@@ -63,6 +64,18 @@
     """
     return Locale.parse(locale).number_symbols.get('decimal', u'.')
 
+def get_exponential_symbol(locale=LC_NUMERIC):
+    """Return the symbol used by the locale to separate mantissa and exponent.
+    
+    >>> get_exponential_symbol('en_US')
+    u'E'
+    
+    :param locale: the `Locale` object or locale identifier
+    :return: the exponential symbol
+    :rtype: `unicode`
+    """
+    return Locale.parse(locale).number_symbols.get('exponential', u'E')
+
 def get_group_symbol(locale=LC_NUMERIC):
     """Return the symbol used by the locale to separate groups of thousands.
     
@@ -175,9 +188,28 @@
     pattern = parse_pattern(format)
     return pattern.apply(number, locale)
 
-def format_scientific(number, locale=LC_NUMERIC):
-    # TODO: implement
-    raise NotImplementedError
+def format_scientific(number, format=None, locale=LC_NUMERIC):
+    """Return value formatted in scientific notation for a specific locale.
+    
+    >>> format_scientific(10000, locale='en_US')
+    u'1E4'
+
+    The format pattern can also be specified explicitly:
+    
+    >>> format_scientific(1234567, u'##0E00', locale='en_US')
+    u'1.23E06'
+
+    :param number: the number to format
+    :param format: 
+    :param locale: the `Locale` object or locale identifier
+    :return: value formatted in scientific notation.
+    :rtype: `unicode`
+    """
+    locale = Locale.parse(locale)
+    if not format:
+        format = locale.scientific_formats.get(format)
+    pattern = parse_pattern(format)
+    return pattern.apply(number, locale)
 
 
 class NumberFormatError(ValueError):
@@ -241,7 +273,7 @@
 
 
 PREFIX_END = r'[^0-9@#.,]'
-NUMBER_TOKEN = r'[0-9@#.\-,E]'
+NUMBER_TOKEN = r'[0-9@#.\-,E+]'
 
 PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END
 NUMBER_PATTERN = r"(?P<number>%s+)" % NUMBER_TOKEN
@@ -315,6 +347,10 @@
         pos_prefix, number, pos_suffix = number_re.search(pattern).groups()
         neg_prefix = '-' + pos_prefix
         neg_suffix = pos_suffix
+    if 'E' in number:
+        number, exp = number.split('E', 1)
+    else:
+        exp = None
     if '@' in number:
         if '.' in number and '0' in number:
             raise ValueError('Significant digit patterns can not contain '
@@ -364,22 +400,33 @@
 
     int_precision = parse_precision(integer)
     frac_precision = parse_precision(fraction)
+    if exp:
+        frac_precision = parse_precision(integer+fraction)
+        exp_plus = exp.startswith('+')
+        exp = exp.lstrip('+')
+        exp_precision = parse_precision(exp)
+    else:
+        exp_plus = None
+        exp_precision = None
     grouping = parse_grouping(integer)
     return NumberPattern(pattern, (pos_prefix, neg_prefix), 
                          (pos_suffix, neg_suffix), grouping,
-                         int_precision, frac_precision)
+                         int_precision, frac_precision, 
+                         exp_precision, exp_plus)
 
 
 class NumberPattern(object):
 
     def __init__(self, pattern, prefix, suffix, grouping,
-                 int_precision, frac_precision):
+                 int_precision, frac_precision, exp_precision, exp_plus):
         self.pattern = pattern
         self.prefix = prefix
         self.suffix = suffix
         self.grouping = grouping
         self.int_precision = int_precision
         self.frac_precision = frac_precision
+        self.exp_precision = exp_precision
+        self.exp_plus = exp_plus
         if '%' in ''.join(self.prefix + self.suffix):
             self.scale = 100
         elif u'‰' in ''.join(self.prefix + self.suffix):
@@ -393,7 +440,32 @@
     def apply(self, value, locale, currency=None):
         value *= self.scale
         negative = int(value < 0)
-        if '@' in self.pattern: # Is it a siginificant digits pattern?
+        if self.exp_precision:
+            value = abs(value)
+            exp = int(math.floor(math.log(value, 10)))
+            # Minimum number of integer digits
+            if self.int_precision[0] == self.int_precision[1]:
+                exp -= self.int_precision[0] - 1
+            # Exponent grouping
+            elif self.int_precision[1]:
+                exp = int(exp) / self.int_precision[1] * self.int_precision[1]
+            value = value / 10.0**exp
+            exp_negative = exp < 0
+            exp = abs(exp)
+            exp_sign = ['', '-']
+            if self.exp_plus:
+                exp_sign[0] = '+'
+            return u'%s%s%s%s%s%s' % \
+                (self.prefix[negative],
+                 self._format_sigdig(value, self.frac_precision[0], 
+                                     self.frac_precision[1]), 
+                 get_exponential_symbol(locale), 
+                 exp_sign[exp_negative],
+                 self._format_int(str(exp), self.exp_precision[0],
+                                  self.exp_precision[1], locale),
+                 self.suffix[negative])
+            
+        elif '@' in self.pattern: # Is it a siginificant digits pattern?
             text = self._format_sigdig(abs(value),
                                       self.int_precision[0],
                                       self.int_precision[1])
@@ -447,7 +519,7 @@
     def _format_int(self, value, min, max, locale):
         width = len(value)
         if width < min:
-            value += '0' * (min - width)
+            value = '0' * (min - width) + value
         gsize = self.grouping[0]
         ret = ''
         symbol = get_group_symbol(locale)
--- a/babel/tests/numbers.py
+++ b/babel/tests/numbers.py
@@ -86,7 +86,7 @@
                          '0.1')
         self.assertEqual(numbers.format_decimal(0.1, '@#',locale='en_US'), 
                          '0.1')
-        self.assertEqual(numbers.format_decimal(0.1, '@@',locale='en_US'), 
+        self.assertEqual(numbers.format_decimal(0.1, '@@', locale='en_US'), 
                          '0.10')
 
     if have_decimal:
@@ -105,6 +105,39 @@
                                                     '#.00', locale='en_US'), 
                              '12345678901234567890.12')
 
+    def test_scientific_notation(self):
+        fmt = numbers.format_scientific(0.1, '#E0', locale='en_US')
+        self.assertEqual(fmt, '1E-1')
+        fmt = numbers.format_scientific(0.01, '#E0', locale='en_US')
+        self.assertEqual(fmt, '1E-2')
+        fmt = numbers.format_scientific(10, '#E0', locale='en_US')
+        self.assertEqual(fmt, '1E1')
+        fmt = numbers.format_scientific(1234, '0.###E0', locale='en_US')
+        self.assertEqual(fmt, '1.234E3')
+        fmt = numbers.format_scientific(1234, '0.#E0', locale='en_US')
+        self.assertEqual(fmt, '1.2E3')
+        # Exponent grouping
+        fmt = numbers.format_scientific(12345, '##0.####E0', locale='en_US')
+        self.assertEqual(fmt, '12.345E3')
+        # Minimum number of int digits
+        fmt = numbers.format_scientific(12345, '00.###E0', locale='en_US')
+        self.assertEqual(fmt, '12.345E3')
+        fmt = numbers.format_scientific(-12345.6, '00.###E0', locale='en_US')
+        self.assertEqual(fmt, '-12.346E3')
+        fmt = numbers.format_scientific(-0.01234, '00.###E0', locale='en_US')
+        self.assertEqual(fmt, '-12.34E-3')
+        # Custom pattern suffic
+        fmt = numbers.format_scientific(123.45, '#.##E0 m/s', locale='en_US')
+        self.assertEqual(fmt, '1.23E2 m/s')
+        # Exponent patterns
+        fmt = numbers.format_scientific(123.45, '#.##E00 m/s', locale='en_US')
+        self.assertEqual(fmt, '1.23E02 m/s')
+        fmt = numbers.format_scientific(0.012345, '#.##E00 m/s', locale='en_US')
+        self.assertEqual(fmt, '1.23E-02 m/s')
+        fmt = numbers.format_scientific(12345, '#.##E+00 m/s', locale='en_US')
+        self.assertEqual(fmt, '1.23E+04 m/s')
+
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(doctest.DocTestSuite(numbers))
Copyright (C) 2012-2017 Edgewall Software