From 9fffa3eea3a7e99b0179988e7a016a45bf63ab96 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Tue, 4 Sep 2001 05:14:19 +0000 Subject: [PATCH] Raise OverflowError when appropriate on long->float conversion. Most of the fiddling is simply due to that no caller of PyLong_AsDouble ever checked for failure (so that's fixing old bugs). PyLong_AsDouble is much faster for big inputs now too, but that's more of a happy consequence than a design goal. --- Lib/test/test_long.py | 37 +++++++++++++++++++++++++++++++++++++ Misc/NEWS | 12 ++++++++++++ Objects/complexobject.c | 2 ++ Objects/floatobject.c | 11 ++++++----- Objects/longobject.c | 37 +++++++++++++++++++------------------ 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index b0eaff7eb77..ac345a661f9 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -328,6 +328,42 @@ def test_auto_overflow(): raise TestFailed("pow%r should have raised " "TypeError" % ((longx, longy, long(z)))) +# ---------------------------------------- tests of long->float overflow + +def test_float_overflow(): + import math + + if verbose: + print "long->float overflow" + + for x in -2.0, -1.0, 0.0, 1.0, 2.0: + verify(float(long(x)) == x) + + huge = 1L << 30000 + mhuge = -huge + namespace = {'huge': huge, 'mhuge': mhuge, 'math': math} + for test in ["float(huge)", "float(mhuge)", + "complex(huge)", "complex(mhuge)", + "complex(huge, 1)", "complex(mhuge, 1)", + "complex(1, huge)", "complex(1, mhuge)", + "1. + huge", "huge + 1.", "1. + mhuge", "mhuge + 1.", + "1. - huge", "huge - 1.", "1. - mhuge", "mhuge - 1.", + "1. * huge", "huge * 1.", "1. * mhuge", "mhuge * 1.", + "1. // huge", "huge // 1.", "1. // mhuge", "mhuge // 1.", + "1. / huge", "huge / 1.", "1. / mhuge", "mhuge / 1.", + "1. ** huge", "huge ** 1.", "1. ** mhuge", "mhuge ** 1.", + "math.sin(huge)", "math.sin(mhuge)", + "math.log(huge)", "math.log(mhuge)", # should do better + "math.sqrt(huge)", "math.sqrt(mhuge)", # should do better + "math.log10(huge)", "math.log10(mhuge)", # should do better + "math.floor(huge)", "math.floor(mhuge)"]: + + try: + eval(test, namespace) + except OverflowError: + pass + else: + raise TestFailed("expected OverflowError from %s" % test) # ---------------------------------------------------------------- do it test_division() @@ -335,3 +371,4 @@ test_bitop_identities() test_format() test_misc() test_auto_overflow() +test_float_overflow() diff --git a/Misc/NEWS b/Misc/NEWS index 43eff32c262..a8f05c7e82d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -3,6 +3,9 @@ What's New in Python 2.2a3? Core +- Conversion of long to float now raises OverflowError if the long is too + big to represent as a C double. + - The 3-argument builtin pow() no longer allows a third non-None argument if either of the first two arguments is a float, or if both are of integer types and the second argument is negative (in which latter case @@ -95,6 +98,15 @@ Build API +- Note that PyLong_AsDouble can fail! This has always been true, but no + callers checked for it. It's more likely to fail now, because overflow + errors are properly detected now. The proper way to check: + + double x = PyLong_AsDouble(some_long_object); + if (x == -1.0 && PyErr_Occurred()) { + /* The conversion failed. */ + } + - The GC API has been changed. Extensions that use the old API will still compile but will not participate in GC. To upgrade an extension module: diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 236f4d52427..740499319a9 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -522,6 +522,8 @@ complex_coerce(PyObject **pv, PyObject **pw) } else if (PyLong_Check(*pw)) { cval.real = PyLong_AsDouble(*pw); + if (cval.real == -1.0 && PyErr_Occurred()) + return -1; *pw = PyComplex_FromCComplex(cval); Py_INCREF(*pv); return 0; diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 478e13174f2..8443aff4aea 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -271,18 +271,19 @@ PyFloat_AsStringEx(char *buf, PyFloatObject *v, int precision) return obj; static int -convert_to_double(PyObject **v, - double *dbl) +convert_to_double(PyObject **v, double *dbl) { register PyObject *obj = *v; - + if (PyInt_Check(obj)) { *dbl = (double)PyInt_AS_LONG(obj); } else if (PyLong_Check(obj)) { - PyFPE_START_PROTECT("convert_to_double", {*v=NULL;return -1;}) *dbl = PyLong_AsDouble(obj); - PyFPE_END_PROTECT(*dbl) + if (*dbl == -1.0 && PyErr_Occurred()) { + *v = NULL; + return -1; + } } else { Py_INCREF(Py_NotImplemented); diff --git a/Objects/longobject.c b/Objects/longobject.c index b511928dbd4..e97ebd59888 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -531,27 +531,28 @@ _PyLong_AsScaledDouble(PyObject *vv, int *exponent) double PyLong_AsDouble(PyObject *vv) { - register PyLongObject *v; + int e; double x; - double multiplier = (double) (1L << SHIFT); - int i, sign; - + if (vv == NULL || !PyLong_Check(vv)) { PyErr_BadInternalCall(); return -1; } - v = (PyLongObject *)vv; - i = v->ob_size; - sign = 1; - x = 0.0; - if (i < 0) { - sign = -1; - i = -(i); - } - while (--i >= 0) { - x = x*multiplier + (double)v->ob_digit[i]; - } - return x * sign; + x = _PyLong_AsScaledDouble(vv, &e); + if (x == -1.0 && PyErr_Occurred()) + return -1.0; + if (e > INT_MAX / SHIFT) + goto overflow; + errno = 0; + x = ldexp(x, e * SHIFT); + if (errno == ERANGE) + goto overflow; + return x; + +overflow: + PyErr_SetString(PyExc_OverflowError, + "long int too large to convert to float"); + return -1.0; } /* Create a new long (or int) object from a C pointer */ @@ -2098,9 +2099,9 @@ static PyObject * long_float(PyObject *v) { double result; - PyFPE_START_PROTECT("long_float", return 0) result = PyLong_AsDouble(v); - PyFPE_END_PROTECT(result) + if (result == -1.0 && PyErr_Occurred()) + return NULL; return PyFloat_FromDouble(result); }