[Python-checkins] cpython (2.7): Issue #11144: Fix corner cases where float-to-int conversion unnecessarily

mark.dickinson python-checkins at python.org
Sat Mar 26 13:30:17 CET 2011


http://hg.python.org/cpython/rev/e1ebec2446cd
changeset:   68965:e1ebec2446cd
branch:      2.7
parent:      68961:2af0c2c106ea
user:        Mark Dickinson <mdickinson at enthought.com>
date:        Sat Mar 26 12:18:00 2011 +0000
summary:
  Issue #11144: Fix corner cases where float-to-int conversion unnecessarily returned a long.

files:
  Lib/test/test_float.py |  42 ++++++++++++++++++++++++++++++
  Misc/NEWS              |   4 ++
  Objects/floatobject.c  |  17 +++++++-----
  3 files changed, 56 insertions(+), 7 deletions(-)


diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -52,6 +52,48 @@
         float('.' + '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')
     def test_float_with_comma(self):
         # set locale to something that doesn't use '.' for the decimal point
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -9,6 +9,10 @@
 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
   are now zeroed on creation.  This matches the behaviour specified by the
   documentation.
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -1035,14 +1035,17 @@
      * happens if the double is too big to fit in a long.  Some rare
      * systems raise an exception then (RISCOS was mentioned as one,
      * and someone using a non-default option on Sun also bumped into
-     * that).  Note that checking for >= and <= LONG_{MIN,MAX} would
-     * still be vulnerable:  if a long has more bits of precision than
-     * a double, casting MIN/MAX to double may yield an approximation,
-     * and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would
-     * yield true from the C expression wholepart<=LONG_MAX, despite
-     * that wholepart is actually greater than LONG_MAX.
+     * that).  Note that checking for <= LONG_MAX is unsafe: if a long
+     * has more bits of precision than a double, casting LONG_MAX to
+     * double may yield an approximation, and if that's rounded up,
+     * then, e.g., wholepart=LONG_MAX+1 would yield true from the C
+     * expression wholepart<=LONG_MAX, despite that wholepart is
+     * 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;
         return PyInt_FromLong(aslong);
     }

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list