]> granicus.if.org Git - python/commitdiff
SF bug 705231: Assertion failed, python aborts.
authorTim Peters <tim.peters@gmail.com>
Sat, 24 May 2003 20:18:24 +0000 (20:18 +0000)
committerTim Peters <tim.peters@gmail.com>
Sat, 24 May 2003 20:18:24 +0000 (20:18 +0000)
float_pow():  Don't let the platform pow() raise -1.0 to an integer power
anymore; at least glibc gets it wrong in some cases.  Note that
math.pow() will continue to deliver wrong (but platform-native) results
in such cases.

Lib/test/test_pow.py
Misc/NEWS
Objects/floatobject.c

index 2c86b09667c86eb13b7b3fd82a84efd5b3aeb841..c6ab218eb8d0ad3f138e8a97a715d154d196666e 100644 (file)
@@ -101,6 +101,23 @@ class PowTest(unittest.TestCase):
                 return None
         None ** TestRpow() # Won't fail when __rpow__ invoked.  SF bug #643260.
 
+    def test_bug705231(self):
+        # -1.0 raised to an integer should never blow up.  It did if the
+        # platform pow() was buggy, and Python didn't worm around it.
+        eq = self.assertEquals
+        a = -1.0
+        eq(pow(a, 1.23e167), 1.0)
+        eq(pow(a, -1.23e167), 1.0)
+        for b in range(-10, 11):
+            eq(pow(a, float(b)), b & 1 and -1.0 or 1.0)
+        for n in range(0, 100):
+            fiveto = float(5 ** n)
+            # For small n, fiveto will be odd.  Eventually we run out of
+            # mantissa bits, though, and thereafer fiveto will be even.
+            expected = fiveto % 2.0 and -1.0 or 1.0
+            eq(pow(a, fiveto), expected)
+            eq(pow(a, -fiveto), expected)
+        eq(expected, 1.0)   # else we didn't push fiveto to evenness
 
 def test_main():
     test.test_support.run_unittest(PowTest)
index b7b444aabed298411693dbdf5fc925f4d7771a5d..a63f0227611958cec85cdad8ab36cab2e47ae1cd 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,12 @@ What's New in Python 2.3 beta 2?
 Core and builtins
 -----------------
 
+- SF bug 705231:  builtin pow() no longer lets the platform C pow()
+  raise -1.0 to integer powers, because (at least) glibc gets it wrong
+  in some cases.  The result should be -1.0 if the power is odd and 1.0
+  if the power is even, and any float with a sufficiently large exponent
+  is (mathematically) an exact even integer.
+
 - The encoding attribute has been added for file objects, and set to
   the terminal encoding on Unix and Windows.
 
index f7601ba9e7d46daecb1a1b78a5706651ea8027b4..f36479f9789351eb1bda1262e535f7002acc69f2 100644 (file)
@@ -572,10 +572,39 @@ float_pow(PyObject *v, PyObject *w, PyObject *z)
                }
                return PyFloat_FromDouble(0.0);
        }
-       if (iv < 0.0 && iw != floor(iw)) {
-               PyErr_SetString(PyExc_ValueError,
-                               "negative number cannot be raised to a fractional power");
-               return NULL;
+       if (iv < 0.0) {
+               /* Whether this is an error is a mess, and bumps into libm
+                * bugs so we have to figure it out ourselves.
+                */
+               if (iw != floor(iw)) {
+                       PyErr_SetString(PyExc_ValueError, "negative number "
+                               "cannot be raised to a fractional power");
+                       return NULL;
+               }
+               /* iw is an exact integer, albeit perhaps a very large one.
+                * -1 raised to an exact integer should never be exceptional.
+                * Alas, some libms (chiefly glibc as of early 2003) return
+                * NaN and set EDOM on pow(-1, large_int) if the int doesn't
+                * happen to be representable in a *C* integer.  That's a
+                * bug; we let that slide in math.pow() (which currently
+                * reflects all platform accidents), but not for Python's **.
+                */
+                if (iv == -1.0 && !Py_IS_INFINITY(iw) && iw == iw) {
+                       /* XXX the "iw == iw" was to weed out NaNs.  This
+                        * XXX doesn't actually work on all platforms.
+                        */
+                       /* Return 1 if iw is even, -1 if iw is odd; there's
+                        * no guarantee that any C integral type is big
+                        * enough to hold iw, so we have to check this
+                        * indirectly.
+                        */
+                       ix = floor(iw * 0.5) * 2.0;
+                       return PyFloat_FromDouble(ix == iw ? 1.0 : -1.0);
+               }
+               /* Else iv != -1.0, and overflow or underflow are possible.
+                * Unless we're to write pow() ourselves, we have to trust
+                * the platform to do this correctly.
+                */
        }
        errno = 0;
        PyFPE_START_PROTECT("pow", return NULL)
@@ -583,8 +612,11 @@ float_pow(PyObject *v, PyObject *w, PyObject *z)
        PyFPE_END_PROTECT(ix)
        Py_ADJUST_ERANGE1(ix);
        if (errno != 0) {
-               assert(errno == ERANGE);
-               PyErr_SetFromErrno(PyExc_OverflowError);
+               /* We don't expect any errno value other than ERANGE, but
+                * the range of libm bugs appears unbounded.
+                */
+               PyErr_SetFromErrno(errno == ERANGE ? PyExc_OverflowError :
+                                                    PyExc_ValueError);
                return NULL;
        }
        return PyFloat_FromDouble(ix);