From b6ffd2e58da312b31df8a68814013f8a47804cc2 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Tue, 26 May 2020 17:26:37 +0100 Subject: [PATCH 1/5] Fix failure of _Py_dg_dtoa to remove trailing zeros --- Python/dtoa.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Python/dtoa.c b/Python/dtoa.c index 822adc612962a9..6dcf4c03d66be5 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -2563,6 +2563,13 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, } ++*s++; } + /* This branch was missing from the original dtoa.c, leading + to surplus trailing zeros in some cases. + See bugs.python.org/issue40780. */ + else { + while(*--s == '0'); + s++; + } break; } } From 589da50d7ffe71c7ffadfdd2b0a4b1c8be16fb12 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Tue, 26 May 2020 17:44:22 +0100 Subject: [PATCH 2/5] Add regression test and news entry --- Lib/test/test_format.py | 11 +++++++++++ .../2020-05-26-17-43-58.bpo-40780.3Ckdgm.rst | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-05-26-17-43-58.bpo-40780.3Ckdgm.rst diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 4559cd5623efe9..b13f46faebfbf0 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -484,6 +484,17 @@ def test_precision_c_limits(self): with self.assertRaises(ValueError) as cm: format(c, ".%sf" % (INT_MAX + 1)) + def test_g_format_has_no_trailing_zeros(self): + # regression test for bugs.python.org/issue40780 + self.assertEqual("%.3g" % 1505, "1.5e+03") + self.assertEqual("%#.3g" % 1505, "1.50e+03") + + self.assertEqual(format(1505, ".3g"), "1.5e+03") + self.assertEqual(format(1505, "#.3g"), "1.50e+03") + + self.assertEqual(format(12300050, ".6g"), "1.23e+07") + self.assertEqual(format(12300050, "#.6g"), "1.23000e+07") + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-05-26-17-43-58.bpo-40780.3Ckdgm.rst b/Misc/NEWS.d/next/Core and Builtins/2020-05-26-17-43-58.bpo-40780.3Ckdgm.rst new file mode 100644 index 00000000000000..ed6020c2e23550 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-05-26-17-43-58.bpo-40780.3Ckdgm.rst @@ -0,0 +1,2 @@ +Fix a corner case where g-style string formatting of a float failed to +remove trailing zeros. From e9a13a1d4c6f384ab2e4f7e069e77b8fecd38f9c Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Tue, 26 May 2020 18:21:38 +0100 Subject: [PATCH 3/5] Add explanation about why it's safe to strip trailing zeros --- Python/dtoa.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Python/dtoa.c b/Python/dtoa.c index 6dcf4c03d66be5..1403dd042ac26d 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -2567,6 +2567,11 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, to surplus trailing zeros in some cases. See bugs.python.org/issue40780. */ else { + /* At the beginning of the for loop we have 10**k <= d; it + follows that on the first iteration, ds <= dval(&u), and + so the first digit written to s is nonzero and it's safe + to strip trailing zeros. */ + assert(k_check == 0); while(*--s == '0'); s++; } From 5773e56d03c7d964fc974c836334b8d6bdf3b0b8 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Wed, 27 May 2020 14:42:19 +0100 Subject: [PATCH 4/5] Make code safer, clean up comments, add change note at top of file --- Python/dtoa.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Python/dtoa.c b/Python/dtoa.c index 1403dd042ac26d..e629b296426f31 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -64,6 +64,9 @@ * 7. _Py_dg_strtod has been modified so that it doesn't accept strings with * leading whitespace. * + * 8. A corner case where _Py_dg_dtoa didn't strip trailing zeros has been + * fixed. (bugs.python.org/issue40780) + * ***************************************************************/ /* Please send bug reports for the original dtoa.c code to David M. Gay (dmg @@ -2563,17 +2566,13 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, } ++*s++; } - /* This branch was missing from the original dtoa.c, leading - to surplus trailing zeros in some cases. - See bugs.python.org/issue40780. */ else { - /* At the beginning of the for loop we have 10**k <= d; it - follows that on the first iteration, ds <= dval(&u), and - so the first digit written to s is nonzero and it's safe - to strip trailing zeros. */ - assert(k_check == 0); - while(*--s == '0'); - s++; + /* Strip trailing zeros. This branch was missing from the + original dtoa.c, leading to surplus trailing zeros in + some cases. See bugs.python.org/issue40780. */ + while (s > s0 && s[-1] == '0') { + --s; + } } break; } From 556f7dd89b21a3dca868aed4148c17db3093152e Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Wed, 27 May 2020 14:45:31 +0100 Subject: [PATCH 5/5] Nitpick: avoid implicit int-to-float conversion in tests --- Lib/test/test_format.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index b13f46faebfbf0..e9e5bb9cf00a62 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -486,14 +486,14 @@ def test_precision_c_limits(self): def test_g_format_has_no_trailing_zeros(self): # regression test for bugs.python.org/issue40780 - self.assertEqual("%.3g" % 1505, "1.5e+03") - self.assertEqual("%#.3g" % 1505, "1.50e+03") + self.assertEqual("%.3g" % 1505.0, "1.5e+03") + self.assertEqual("%#.3g" % 1505.0, "1.50e+03") - self.assertEqual(format(1505, ".3g"), "1.5e+03") - self.assertEqual(format(1505, "#.3g"), "1.50e+03") + self.assertEqual(format(1505.0, ".3g"), "1.5e+03") + self.assertEqual(format(1505.0, "#.3g"), "1.50e+03") - self.assertEqual(format(12300050, ".6g"), "1.23e+07") - self.assertEqual(format(12300050, "#.6g"), "1.23000e+07") + self.assertEqual(format(12300050.0, ".6g"), "1.23e+07") + self.assertEqual(format(12300050.0, "#.6g"), "1.23000e+07") if __name__ == "__main__":