summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMark Dickinson <dickinsm@gmail.com>2010-01-12 23:09:26 (GMT)
committerMark Dickinson <dickinsm@gmail.com>2010-01-12 23:09:26 (GMT)
commit466725136d7aee2c0e09834e5ed802e69860e035 (patch)
tree7aad00b8f517f3bf77231d6cfd060ea2b3a84d03 /Lib
parentd7966fca123941b32fe22b4c13f3bd22332cf6c5 (diff)
downloadcpython-466725136d7aee2c0e09834e5ed802e69860e035.zip
cpython-466725136d7aee2c0e09834e5ed802e69860e035.tar.gz
cpython-466725136d7aee2c0e09834e5ed802e69860e035.tar.bz2
Merged revisions 77452 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/branches/py3k ................ r77452 | mark.dickinson | 2010-01-12 23:04:19 +0000 (Tue, 12 Jan 2010) | 23 lines Merged revisions 77410,77421,77450-77451 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r77410 | mark.dickinson | 2010-01-10 13:06:31 +0000 (Sun, 10 Jan 2010) | 1 line Remove unused BCinfo fields and an unused macro. ........ r77421 | mark.dickinson | 2010-01-11 17:15:13 +0000 (Mon, 11 Jan 2010) | 1 line Change a variable type to avoid signed overflow; replace repeated '19999' constant by a define. ........ r77450 | mark.dickinson | 2010-01-12 22:23:56 +0000 (Tue, 12 Jan 2010) | 4 lines Issue #7632: Fix a problem with _Py_dg_strtod that could lead to crashes in debug builds, for certain long numeric strings corresponding to subnormal values. ........ r77451 | mark.dickinson | 2010-01-12 22:55:51 +0000 (Tue, 12 Jan 2010) | 2 lines Issue #7632: Fix a bug in dtoa.c that could lead to incorrectly-rounded results. ........ ................
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/floating_points.txt7
-rw-r--r--Lib/test/test_float.py102
2 files changed, 109 insertions, 0 deletions
diff --git a/Lib/test/floating_points.txt b/Lib/test/floating_points.txt
index 70d21ea..539073d 100644
--- a/Lib/test/floating_points.txt
+++ b/Lib/test/floating_points.txt
@@ -1019,3 +1019,10 @@
+43723334984997307E-26
+10182419849537963E-24
-93501703572661982E-26
+
+# A value that caused a crash in debug builds for Python >= 2.7, 3.1
+# See http://bugs.python.org/issue7632
+2183167012312112312312.23538020374420446192e-370
+
+# Another value designed to test a corner case of Python's strtod code.
+0.99999999999999999999999999999999999999999e+23
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index ebb666b..fa6fde3 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -7,6 +7,7 @@ import math
from math import isinf, isnan, copysign, ldexp
import operator
import random, fractions
+import re
INF = float("inf")
NAN = float("nan")
@@ -15,6 +16,74 @@ NAN = float("nan")
test_dir = os.path.dirname(__file__) or os.curdir
format_testfile = os.path.join(test_dir, 'formatfloat_testcases.txt')
+finite_decimal_parser = re.compile(r""" # A numeric string consists of:
+ (?P<sign>[-+])? # an optional sign, followed by
+ (?=\d|\.\d) # a number with at least one digit
+ (?P<int>\d*) # having a (possibly empty) integer part
+ (?:\.(?P<frac>\d*))? # followed by an optional fractional part
+ (?:E(?P<exp>[-+]?\d+))? # and an optional exponent
+ \Z
+""", re.VERBOSE | re.IGNORECASE | re.UNICODE).match
+
+# Pure Python version of correctly rounded string->float conversion.
+# Avoids any use of floating-point by returning the result as a hex string.
+def strtod(s, mant_dig=53, min_exp = -1021, max_exp = 1024):
+ """Convert a finite decimal string to a hex string representing an
+ IEEE 754 binary64 float. Return 'inf' or '-inf' on overflow.
+ This function makes no use of floating-point arithmetic at any
+ stage."""
+
+ # parse string into a pair of integers 'a' and 'b' such that
+ # abs(decimal value) = a/b, and a boolean 'negative'.
+ m = finite_decimal_parser(s)
+ if m is None:
+ raise ValueError('invalid numeric string')
+ fraction = m.group('frac') or ''
+ intpart = int(m.group('int') + fraction)
+ exp = int(m.group('exp') or '0') - len(fraction)
+ negative = m.group('sign') == '-'
+ a, b = intpart*10**max(exp, 0), 10**max(0, -exp)
+
+ # quick return for zeros
+ if not a:
+ return '-0x0.0p+0' if negative else '0x0.0p+0'
+
+ # compute exponent e for result; may be one too small in the case
+ # that the rounded value of a/b lies in a different binade from a/b
+ d = a.bit_length() - b.bit_length()
+ d += (a >> d if d >= 0 else a << -d) >= b
+ e = max(d, min_exp) - mant_dig
+
+ # approximate a/b by number of the form q * 2**e; adjust e if necessary
+ a, b = a << max(-e, 0), b << max(e, 0)
+ q, r = divmod(a, b)
+ if 2*r > b or 2*r == b and q & 1:
+ q += 1
+ if q.bit_length() == mant_dig+1:
+ q //= 2
+ e += 1
+
+ # double check that (q, e) has the right form
+ assert q.bit_length() <= mant_dig and e >= min_exp - mant_dig
+ assert q.bit_length() == mant_dig or e == min_exp - mant_dig
+
+ # check for overflow and underflow
+ if e + q.bit_length() > max_exp:
+ return '-inf' if negative else 'inf'
+ if not q:
+ return '-0x0.0p+0' if negative else '0x0.0p+0'
+
+ # for hex representation, shift so # bits after point is a multiple of 4
+ hexdigs = 1 + (mant_dig-2)//4
+ shift = 3 - (mant_dig-2)%4
+ q, e = q << shift, e - shift
+ return '{}0x{:x}.{:0{}x}p{:+d}'.format(
+ '-' if negative else '',
+ q // 16**hexdigs,
+ q % 16**hexdigs,
+ hexdigs,
+ e + 4*hexdigs)
+
class GeneralFloatCases(unittest.TestCase):
def test_float(self):
@@ -1017,6 +1086,38 @@ class HexFloatTestCase(unittest.TestCase):
else:
self.identical(x, fromHex(toHex(x)))
+class StrtodTestCase(unittest.TestCase):
+ def check_string(self, s):
+ expected = strtod(s)
+ try:
+ fs = float(s)
+ except OverflowError:
+ got = '-inf' if s[0] == '-' else 'inf'
+ else:
+ got = fs.hex()
+ self.assertEqual(expected, got,
+ "Incorrectly rounded str->float conversion for "
+ "{}: expected {}, got {}".format(s, expected, got))
+
+ @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
+ "applies only when using short float repr style")
+ def test_bug7632(self):
+ # check a few particular values that gave incorrectly rounded
+ # results with previous versions of dtoa.c
+ test_strings = [
+ '94393431193180696942841837085033647913224148539854e-358',
+ '12579816049008305546974391768996369464963024663104e-357',
+ '17489628565202117263145367596028389348922981857013e-357',
+ '18487398785991994634182916638542680759613590482273e-357',
+ '32002864200581033134358724675198044527469366773928e-358',
+ '73608278998966969345824653500136787876436005957953e-358',
+ '64774478836417299491718435234611299336288082136054e-358',
+ '13704940134126574534878641876947980878824688451169e-357',
+ '46697445774047060960624497964425416610480524760471e-358',
+ ]
+ for s in test_strings:
+ self.check_string(s)
+
def test_main():
support.run_unittest(
@@ -1029,6 +1130,7 @@ def test_main():
RoundTestCase,
InfNanTest,
HexFloatTestCase,
+ StrtodTestCase,
)
if __name__ == '__main__':