diff options
author | Mark Dickinson <dickinsm@gmail.com> | 2010-01-12 23:04:19 (GMT) |
---|---|---|
committer | Mark Dickinson <dickinsm@gmail.com> | 2010-01-12 23:04:19 (GMT) |
commit | 81612e877870c52bae7c590076eec642b9803138 (patch) | |
tree | d5d1a2032727107a1ee41d76d1de39864944ed58 /Lib/test/test_float.py | |
parent | f845302d06542c2a3a52a9356c09d54ecbf44610 (diff) | |
download | cpython-81612e877870c52bae7c590076eec642b9803138.zip cpython-81612e877870c52bae7c590076eec642b9803138.tar.gz cpython-81612e877870c52bae7c590076eec642b9803138.tar.bz2 |
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/test/test_float.py')
-rw-r--r-- | Lib/test/test_float.py | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index e2d1ea0..fd0f410 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") @@ -20,6 +21,74 @@ requires_IEEE_754 = unittest.skipUnless( 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): @@ -1263,6 +1332,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( @@ -1275,6 +1376,7 @@ def test_main(): RoundTestCase, InfNanTest, HexFloatTestCase, + StrtodTestCase, ) if __name__ == '__main__': |