diff --git a/Lib/test/test_pow.py b/Lib/test/test_pow.py index 2c86b09667c..c6ab218eb8d 100644 --- a/Lib/test/test_pow.py +++ b/Lib/test/test_pow.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index b7b444aabed..a63f0227611 100644 --- 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. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index f7601ba9e7d..f36479f9789 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -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);