Issue #11144: Fix corner cases where float-to-int conversion unnecessarily returned a long.

This commit is contained in:
Mark Dickinson 2011-03-26 12:18:00 +00:00
parent d3cb2f6e2c
commit 874d59ee91
3 changed files with 56 additions and 7 deletions

View File

@ -52,6 +52,48 @@ class GeneralFloatCases(unittest.TestCase):
float('.' + '1'*1000) float('.' + '1'*1000)
float(unicode('.' + '1'*1000)) float(unicode('.' + '1'*1000))
def check_conversion_to_int(self, x):
"""Check that int(x) has the correct value and type, for a float x."""
n = int(x)
if x >= 0.0:
# x >= 0 and n = int(x) ==> n <= x < n + 1
self.assertLessEqual(n, x)
self.assertLess(x, n + 1)
else:
# x < 0 and n = int(x) ==> n >= x > n - 1
self.assertGreaterEqual(n, x)
self.assertGreater(x, n - 1)
# Result should be an int if within range, else a long.
if -sys.maxint-1 <= n <= sys.maxint:
self.assertEqual(type(n), int)
else:
self.assertEqual(type(n), long)
# Double check.
self.assertEqual(type(int(n)), type(n))
def test_conversion_to_int(self):
# Check that floats within the range of an int convert to type
# int, not long. (issue #11144.)
boundary = float(sys.maxint + 1)
epsilon = 2**-sys.float_info.mant_dig * boundary
# These 2 floats are either side of the positive int/long boundary on
# both 32-bit and 64-bit systems.
self.check_conversion_to_int(boundary - epsilon)
self.check_conversion_to_int(boundary)
# These floats are either side of the negative long/int boundary on
# 64-bit systems...
self.check_conversion_to_int(-boundary - 2*epsilon)
self.check_conversion_to_int(-boundary)
# ... and these ones are either side of the negative long/int
# boundary on 32-bit systems.
self.check_conversion_to_int(-boundary - 1.0)
self.check_conversion_to_int(-boundary - 1.0 + 2*epsilon)
@test_support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE') @test_support.run_with_locale('LC_NUMERIC', 'fr_FR', 'de_DE')
def test_float_with_comma(self): def test_float_with_comma(self):
# set locale to something that doesn't use '.' for the decimal point # set locale to something that doesn't use '.' for the decimal point

View File

@ -9,6 +9,10 @@ What's New in Python 2.7.2?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #11144: Ensure that int(a_float) returns an int whenever possible.
Previously, there were some corner cases where a long was returned even
though the result was within the range of an int.
- Issue #11675: multiprocessing.[Raw]Array objects created from an integer size - Issue #11675: multiprocessing.[Raw]Array objects created from an integer size
are now zeroed on creation. This matches the behaviour specified by the are now zeroed on creation. This matches the behaviour specified by the
documentation. documentation.

View File

@ -1035,14 +1035,17 @@ float_trunc(PyObject *v)
* happens if the double is too big to fit in a long. Some rare * happens if the double is too big to fit in a long. Some rare
* systems raise an exception then (RISCOS was mentioned as one, * systems raise an exception then (RISCOS was mentioned as one,
* and someone using a non-default option on Sun also bumped into * and someone using a non-default option on Sun also bumped into
* that). Note that checking for >= and <= LONG_{MIN,MAX} would * that). Note that checking for <= LONG_MAX is unsafe: if a long
* still be vulnerable: if a long has more bits of precision than * has more bits of precision than a double, casting LONG_MAX to
* a double, casting MIN/MAX to double may yield an approximation, * double may yield an approximation, and if that's rounded up,
* and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would * then, e.g., wholepart=LONG_MAX+1 would yield true from the C
* yield true from the C expression wholepart<=LONG_MAX, despite * expression wholepart<=LONG_MAX, despite that wholepart is
* that wholepart is actually greater than LONG_MAX. * actually greater than LONG_MAX. However, assuming a two's complement
* machine with no trap representation, LONG_MIN will be a power of 2 (and
* hence exactly representable as a double), and LONG_MAX = -1-LONG_MIN, so
* the comparisons with (double)LONG_MIN below should be safe.
*/ */
if (LONG_MIN < wholepart && wholepart < LONG_MAX) { if ((double)LONG_MIN <= wholepart && wholepart < -(double)LONG_MIN) {
const long aslong = (long)wholepart; const long aslong = (long)wholepart;
return PyInt_FromLong(aslong); return PyInt_FromLong(aslong);
} }