From bc1c03407649a37a8a3c26b8d0cb355ab2fc128e Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Sun, 23 Feb 2020 14:37:02 +0100 Subject: [PATCH] Fixed #28280 -- Prevented numberformat.format() from formatting large/tiny floats in scientific notation. --- django/utils/numberformat.py | 3 +++ tests/utils_tests/test_numberformat.py | 28 ++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index 961a60e37d..3bfdb2ea52 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -26,6 +26,9 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', return mark_safe(number) # sign sign = '' + # Treat potentially very large/small floats as Decimals. + if isinstance(number, float) and 'e' in str(number).lower(): + number = Decimal(str(number)) if isinstance(number, Decimal): if decimal_pos is not None: diff --git a/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py index 83e7d271b5..201e2cfe87 100644 --- a/tests/utils_tests/test_numberformat.py +++ b/tests/utils_tests/test_numberformat.py @@ -55,10 +55,30 @@ class TestNumberFormat(SimpleTestCase): self.assertEqual(nformat(-2 * int_max, '.'), most_max2.format('-')) def test_float_numbers(self): - # A float without a fractional part (3.) results in a ".0" when no - # decimal_pos is given. Contrast that with the Decimal('3.') case in - # test_decimal_numbers which doesn't return a fractional part. - self.assertEqual(nformat(3., '.'), '3.0') + tests = [ + (9e-10, 10, '0.0000000009'), + (9e-19, 2, '0.00'), + (.00000000000099, 0, '0'), + (.00000000000099, 13, '0.0000000000009'), + (1e16, None, '10000000000000000'), + (1e16, 2, '10000000000000000.00'), + # A float without a fractional part (3.) results in a ".0" when no + # decimal_pos is given. Contrast that with the Decimal('3.') case + # in test_decimal_numbers which doesn't return a fractional part. + (3., None, '3.0'), + ] + for value, decimal_pos, expected_value in tests: + with self.subTest(value=value, decimal_pos=decimal_pos): + self.assertEqual(nformat(value, '.', decimal_pos), expected_value) + # Thousand grouping behavior. + self.assertEqual( + nformat(1e16, '.', thousand_sep=',', grouping=3, force_grouping=True), + '10,000,000,000,000,000', + ) + self.assertEqual( + nformat(1e16, '.', decimal_pos=2, thousand_sep=',', grouping=3, force_grouping=True), + '10,000,000,000,000,000.00', + ) def test_decimal_numbers(self): self.assertEqual(nformat(Decimal('1234'), '.'), '1234')