diff options
author | Mark Dickinson <dickinsm@gmail.com> | 2010-01-16 17:57:49 (GMT) |
---|---|---|
committer | Mark Dickinson <dickinsm@gmail.com> | 2010-01-16 17:57:49 (GMT) |
commit | 811ff822f8cd87cf29867118cb3a74c5035a1a94 (patch) | |
tree | 037219175275142c50e95012e821cddf33bccbd8 | |
parent | db983a7c38ba3d98649c916afa12340bd8b6bed2 (diff) | |
download | cpython-811ff822f8cd87cf29867118cb3a74c5035a1a94.zip cpython-811ff822f8cd87cf29867118cb3a74c5035a1a94.tar.gz cpython-811ff822f8cd87cf29867118cb3a74c5035a1a94.tar.bz2 |
Issue #7632: Fix one more case of incorrect rounding for str -> float
conversion (see bug 5 in the issue tracker).
-rw-r--r-- | Lib/test/test_strtod.py | 9 | ||||
-rw-r--r-- | Python/dtoa.c | 79 |
2 files changed, 55 insertions, 33 deletions
diff --git a/Lib/test/test_strtod.py b/Lib/test/test_strtod.py index 0c6f59d..1883820 100644 --- a/Lib/test/test_strtod.py +++ b/Lib/test/test_strtod.py @@ -123,10 +123,6 @@ class StrtodTests(unittest.TestCase): digits = m * 5**-e exponent = e s = '{}e{}'.format(digits, exponent) - - # for the moment, ignore errors from trailing zeros - if digits % 10 == 0: - continue self.check_strtod(s) # get expected answer via struct, to triple check @@ -175,7 +171,8 @@ class StrtodTests(unittest.TestCase): self.check_strtod(s) def test_parsing(self): - digits = tuple(map(str, xrange(10))) + # make '0' more likely to be chosen than other digits + digits = '000000123456789' signs = ('+', '-', '') # put together random short valid strings @@ -257,7 +254,7 @@ class StrtodTests(unittest.TestCase): '247032822920623295e-341', # issue 7632 bug 5: the following 2 strings convert differently '1000000000000000000000000000000000000000e-16', - #'10000000000000000000000000000000000000000e-17', + '10000000000000000000000000000000000000000e-17', # issue 7632 bug 8: the following produced 10.0 '10.900000000000000012345678912345678912345', ] diff --git a/Python/dtoa.c b/Python/dtoa.c index 77117b9..aa577f2 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -1381,12 +1381,8 @@ _Py_dg_strtod(const char *s00, char **se) goto ret; } s0 = s; - y = z = 0; for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) - if (nd < 9) - y = 10*y + c - '0'; - else if (nd < 16) - z = 10*z + c - '0'; + ; nd0 = nd; if (c == '.') { c = *++s; @@ -1406,15 +1402,7 @@ _Py_dg_strtod(const char *s00, char **se) nz++; if (c -= '0') { nf += nz; - for(i = 1; i < nz; i++) - if (nd++ < 9) - y *= 10; - else if (nd <= DBL_DIG + 1) - z *= 10; - if (nd++ < 9) - y = 10*y + c; - else if (nd <= DBL_DIG + 1) - z = 10*z + c; + nd += nz; nz = 0; } } @@ -1465,32 +1453,59 @@ _Py_dg_strtod(const char *s00, char **se) } goto ret; } - bc.e0 = e1 = e -= nf; + e -= nf; + if (!nd0) + nd0 = nd; + + /* strip trailing zeros */ + for (i = nd; i > 0; ) { + /* scan back until we hit a nonzero digit. significant digit 'i' + is s0[i] if i < nd0, s0[i+1] if i >= nd0. */ + --i; + if (s0[i < nd0 ? i : i+1] != '0') { + ++i; + break; + } + } + e += nd - i; + nd = i; + if (nd0 > nd) + nd0 = nd; /* Now we have nd0 digits, starting at s0, followed by a * decimal point, followed by nd-nd0 digits. The number we're * after is the integer represented by those digits times * 10**e */ - if (!nd0) - nd0 = nd; + bc.e0 = e1 = e; /* Summary of parsing results. The parsing stage gives values - * s0, nd0, nd, e, y and z such that: + * s0, nd0, nd, e, sign, where: * - * - nd >= nd0 >= 1 + * - s0 points to the first significant digit of the input string s00; * - * - the nd significant digits are in s0[0:nd0] and s0[nd0+1:nd+1] - * (using the usual Python half-open slice notation) + * - nd is the total number of significant digits (here, and + * below, 'significant digits' means the set of digits of the + * significand of the input that remain after ignoring leading + * and trailing zeros. * - * - the absolute value of the number represented by the original input - * string is n * 10**e, where n is the integer represented by the - * concatenation of s0[0:nd0] and s0[nd0+1:nd+1] + * - nd0 indicates the position of the decimal point (if + * present): so the nd significant digits are in s0[0:nd0] and + * s0[nd0+1:nd+1] using the usual Python half-open slice + * notation. (If nd0 < nd, then s0[nd0] necessarily contains + * a '.' character; if nd0 == nd, then it could be anything.) * - * - the first significant digit is nonzero + * - e is the adjusted exponent: the absolute value of the number + * represented by the original input string is n * 10**e, where + * n is the integer represented by the concatenation of + * s0[0:nd0] and s0[nd0+1:nd+1] * - * - the last significant digit may or may not be nonzero; (some code - * currently assumes that it's nonzero; this is a bug) + * - sign gives the sign of the input: 1 for negative, 0 for positive + * + * - the first and last significant digits are nonzero + */ + + /* put first DBL_DIG+1 digits into integer y and z. * * - y contains the value represented by the first min(9, nd) * significant digits @@ -1500,6 +1515,16 @@ _Py_dg_strtod(const char *s00, char **se) * gives the value represented by the first min(16, nd) sig. digits. */ + y = z = 0; + for (i = 0; i < nd; i++) { + if (i < 9) + y = 10*y + s0[i < nd0 ? i : i+1] - '0'; + else if (i < DBL_DIG+1) + z = 10*z + s0[i < nd0 ? i : i+1] - '0'; + else + break; + } + k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; dval(&rv) = y; if (k > 9) { |