Issue #10925: Add equivalent pure Python code for the builtin int-to-float conversion to test_long.

This commit is contained in:
Mark Dickinson 2011-10-23 20:07:13 +01:00
parent 36645681c8
commit 30970e9e44
1 changed files with 80 additions and 0 deletions

View File

@ -43,6 +43,53 @@ DBL_MIN_EXP = sys.float_info.min_exp
DBL_MANT_DIG = sys.float_info.mant_dig
DBL_MIN_OVERFLOW = 2**DBL_MAX_EXP - 2**(DBL_MAX_EXP - DBL_MANT_DIG - 1)
# Pure Python version of correctly-rounded integer-to-float conversion.
def int_to_float(n):
"""
Correctly-rounded integer-to-float conversion.
"""
# Constants, depending only on the floating-point format in use.
# We use an extra 2 bits of precision for rounding purposes.
PRECISION = sys.float_info.mant_dig + 2
SHIFT_MAX = sys.float_info.max_exp - PRECISION
Q_MAX = 1 << PRECISION
ROUND_HALF_TO_EVEN_CORRECTION = [0, -1, -2, 1, 0, -1, 2, 1]
# Reduce to the case where n is positive.
if n == 0:
return 0.0
elif n < 0:
return -int_to_float(-n)
# Convert n to a 'floating-point' number q * 2**shift, where q is an
# integer with 'PRECISION' significant bits. When shifting n to create q,
# the least significant bit of q is treated as 'sticky'. That is, the
# least significant bit of q is set if either the corresponding bit of n
# was already set, or any one of the bits of n lost in the shift was set.
shift = n.bit_length() - PRECISION
q = n << -shift if shift < 0 else (n >> shift) | bool(n & ~(-1 << shift))
# Round half to even (actually rounds to the nearest multiple of 4,
# rounding ties to a multiple of 8).
q += ROUND_HALF_TO_EVEN_CORRECTION[q & 7]
# Detect overflow.
if shift + (q == Q_MAX) > SHIFT_MAX:
raise OverflowError("integer too large to convert to float")
# Checks: q is exactly representable, and q**2**shift doesn't overflow.
assert q % 4 == 0 and q // 4 <= 2**(sys.float_info.mant_dig)
assert q * 2**shift <= sys.float_info.max
# Some circularity here, since float(q) is doing an int-to-float
# conversion. But here q is of bounded size, and is exactly representable
# as a float. In a low-level C-like language, this operation would be a
# simple cast (e.g., from unsigned long long to double).
return math.ldexp(float(q), shift)
# pure Python version of correctly-rounded true division
def truediv(a, b):
"""Correctly-rounded true division for integers."""
@ -367,6 +414,23 @@ class LongTest(unittest.TestCase):
return 1729
self.assertEqual(int(LongTrunc()), 1729)
def check_float_conversion(self, n):
# Check that int -> float conversion behaviour matches
# that of the pure Python version above.
try:
actual = float(n)
except OverflowError:
actual = 'overflow'
try:
expected = int_to_float(n)
except OverflowError:
expected = 'overflow'
msg = ("Error in conversion of integer {} to float. "
"Got {}, expected {}.".format(n, actual, expected))
self.assertEqual(actual, expected, msg)
@support.requires_IEEE_754
def test_float_conversion(self):
@ -421,6 +485,22 @@ class LongTest(unittest.TestCase):
y = 2**p * 2**53
self.assertEqual(int(float(x)), y)
# Compare builtin float conversion with pure Python int_to_float
# function above.
test_values = [
int_dbl_max-1, int_dbl_max, int_dbl_max+1,
halfway-1, halfway, halfway + 1,
top_power-1, top_power, top_power+1,
2*top_power-1, 2*top_power, top_power*top_power,
]
test_values.extend(exact_values)
for p in range(-4, 8):
for x in range(-128, 128):
test_values.append(2**(p+53) + x)
for value in test_values:
self.check_float_conversion(value)
self.check_float_conversion(-value)
def test_float_overflow(self):
for x in -2.0, -1.0, 0.0, 1.0, 2.0:
self.assertEqual(float(int(x)), x)