diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index f824b6a9839..4530adf7c5b 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -3,7 +3,7 @@ import re import random as random_module import unicodedata -from decimal import Decimal, InvalidOperation, ROUND_HALF_UP +from decimal import Decimal, InvalidOperation, Context, ROUND_HALF_UP from functools import wraps from pprint import pformat @@ -166,9 +166,15 @@ def floatformat(text, arg=-1): else: exp = Decimal(u'1.0') / (Decimal(10) ** abs(p)) try: + # Set the precision high enough to avoid an exception, see #15789. + tupl = d.as_tuple() + units = len(tupl[1]) - tupl[2] + prec = abs(arg) + units + 1 + # Avoid conversion to scientific notation by accessing `sign`, `digits` # and `exponent` from `Decimal.as_tuple()` directly. - sign, digits, exponent = d.quantize(exp, ROUND_HALF_UP).as_tuple() + sign, digits, exponent = d.quantize(exp, ROUND_HALF_UP, + Context(prec=prec)).as_tuple() digits = [unicode(digit) for digit in reversed(digits)] while len(digits) <= abs(exponent): digits.append(u'0') diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 6da4f75a67d..5288e501775 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -2,6 +2,7 @@ from __future__ import with_statement import datetime +import decimal from django.template.defaultfilters import * from django.test import TestCase @@ -26,6 +27,11 @@ class DefaultFiltersTests(TestCase): self.assertEqual(floatformat(11.0000, -2), u'11') self.assertEqual(floatformat(11.000001, -2), u'11.00') self.assertEqual(floatformat(8.2798, 3), u'8.280') + self.assertEqual(floatformat(5555.555, 2), u'5555.56') + self.assertEqual(floatformat(001.3000, 2), u'1.30') + self.assertEqual(floatformat(0.12345, 2), u'0.12') + self.assertEqual(floatformat(decimal.Decimal('555.555'), 2), u'555.56') + self.assertEqual(floatformat(decimal.Decimal('09.000')), u'9') self.assertEqual(floatformat(u'foo'), u'') self.assertEqual(floatformat(13.1031, u'bar'), u'13.1031') self.assertEqual(floatformat(18.125, 2), u'18.13') @@ -57,6 +63,18 @@ class DefaultFiltersTests(TestCase): self.assertEqual(floatformat(FloatWrapper(11.000001), -2), u'11.00') + # Regression for #15789 + decimal_ctx = decimal.getcontext() + old_prec, decimal_ctx.prec = decimal_ctx.prec, 2 + try: + self.assertEqual(floatformat(1.2345, 2), u'1.23') + self.assertEqual(floatformat(15.2042, -3), u'15.204') + self.assertEqual(floatformat(decimal.Decimal('1.2345'), 2), u'1.23') + self.assertEqual(floatformat(decimal.Decimal('15.2042'), -3), u'15.204') + finally: + decimal_ctx.prec = old_prec + + # This fails because of Python's float handling. Floats with many zeroes # after the decimal point should be passed in as another type such as # unicode or Decimal.