Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix for #14316 (number formatting issues)

  • Loading branch information...
commit fda8ffc71d69eeee1721e5f595fc48e8abaab84c 1 parent 5999eb4
@rvarshney rvarshney authored
Showing with 43 additions and 30 deletions.
  1. +30 −24 django/utils/numberformat.py
  2. +13 −6 tests/regressiontests/i18n/tests.py
View
54 django/utils/numberformat.py
@@ -3,8 +3,7 @@
from django.utils import six
-def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='',
- force_grouping=False):
+def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', force_grouping=False):
"""
Gets a number (as a number or string), and returns it as a string,
using formats defined as arguments:
@@ -17,35 +16,42 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='',
use_grouping = settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR
use_grouping = use_grouping or force_grouping
use_grouping = use_grouping and grouping > 0
+ use_grouping = use_grouping and thousand_sep
+
# Make the common case fast
if isinstance(number, int) and not use_grouping and not decimal_pos:
return mark_safe(six.text_type(number))
- # sign
- if float(number) < 0:
+
+ float_number = float(number)
+ str_number = six.text_type(number)
+
+ if decimal_pos is not None:
+ # Use the %f format string. This gives us rounding and the
+ # right number of decimal positions automatically. This also
+ # removes any 'e' if the number is really small or really large
+ str_number = six.text_type(('%.' + str(decimal_pos) + 'f') % float_number)
+
+ if not use_grouping:
+ return str_number.replace('.', decimal_sep)
+
+ # For grouping, we need to separate the sign, int_part and decimal part
+ if float_number < 0:
sign = '-'
+ str_number = str_number[1:]
else:
sign = ''
- str_number = six.text_type(number)
- if str_number[0] == '-':
- str_number = str_number[1:]
- # decimal part
- if '.' in str_number:
+
+ if decimal_pos is not None or '.' in str_number:
int_part, dec_part = str_number.split('.')
- if decimal_pos is not None:
- dec_part = dec_part[:decimal_pos]
+ dec_part = decimal_sep + dec_part
else:
int_part, dec_part = str_number, ''
- if decimal_pos is not None:
- dec_part = dec_part + ('0' * (decimal_pos - len(dec_part)))
- if dec_part:
- dec_part = decimal_sep + dec_part
- # grouping
- if use_grouping:
- int_part_gd = ''
- for cnt, digit in enumerate(int_part[::-1]):
- if cnt and not cnt % grouping:
- int_part_gd += thousand_sep
- int_part_gd += digit
- int_part = int_part_gd[::-1]
- return sign + int_part + dec_part
+ first_part_len = len(int_part) % grouping
+ full_parts = int_part[first_part_len:]
+ groups = [full_parts[i:i+grouping] for i in range(0, len(full_parts), grouping)]
+
+ if first_part_len > 0:
+ groups.insert(0, int_part[0:first_part_len])
+
+ return sign + thousand_sep.join(groups) + dec_part
View
19 tests/regressiontests/i18n/tests.py
@@ -334,14 +334,21 @@ def test_locale_independent(self):
Localization of numbers
"""
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False):
- self.assertEqual('66666.66', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
- self.assertEqual('66666A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
- self.assertEqual('66666', nformat(self.n, decimal_sep='X', decimal_pos=0, grouping=1, thousand_sep='Y'))
+ self.assertEqual(u'66666.67', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
+ self.assertEqual(u'66666A7', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
+ self.assertEqual('66667', nformat(self.n, decimal_sep='X', decimal_pos=0, grouping=1, thousand_sep='Y'))
+
+ # ticket #14317
+ self.assertEqual(u'0,00', nformat(0.000000001, decimal_sep=',', decimal_pos=2))
+ self.assertEqual(u'0,00', nformat(0.00000000000099, decimal_sep=',', decimal_pos=2))
+ self.assertEqual(u'0,00', nformat(decimal.Decimal(1e-37), decimal_sep=',', decimal_pos=2))
+ self.assertEqual(u'0,67', nformat(0.666, decimal_sep=',', decimal_pos=2))
+ self.assertEqual(u'0,67', nformat('0.666', decimal_sep=',', decimal_pos=2))
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
- self.assertEqual('66,666.66', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
- self.assertEqual('6B6B6B6B6A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
- self.assertEqual('-66666.6', nformat(-66666.666, decimal_sep='.', decimal_pos=1))
+ self.assertEqual('66,666.67', nformat(self.n, decimal_sep='.', decimal_pos=2, grouping=3, thousand_sep=','))
+ self.assertEqual('6B6B6B6B6A7', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B'))
+ self.assertEqual('-66666.7', nformat(-66666.666, decimal_sep='.', decimal_pos=1))
self.assertEqual('-66666.0', nformat(int('-66666'), decimal_sep='.', decimal_pos=1))
self.assertEqual('10000.0', nformat(self.l, decimal_sep='.', decimal_pos=1))
# This unusual grouping/force_grouping combination may be triggered by the intcomma filter (#17414)
Please sign in to comment.
Something went wrong with that request. Please try again.