Index: Objects/floatobject.c =================================================================== --- Objects/floatobject.c (revision 71669) +++ Objects/floatobject.c (working copy) @@ -899,43 +899,127 @@ return PyLong_FromDouble(wholepart); } +/* double_round: rounds a finite double to the closest multiple of + 10**-ndigits; here ndigits is within reasonable bounds + (typically, -308 <= ndigits <= 323). Sets a Python error and + returns NULL on failure. */ + +#ifndef PY_NO_SHORT_FLOAT_REPR +/* version of double_round that uses the correctly-rounded string<->double + conversions from Python/dtoa.c */ + static PyObject * -float_round(PyObject *v, PyObject *args) -{ -#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */ - double x; - double f = 1.0; - double flr, cil; +double_round(double x, int ndigits) { + double rounded; - int ndigits = UNDEF_NDIGITS; + Py_ssize_t buflen, mybuflen=100; + char *buf, *buf_end, shortbuf[100], *mybuf=shortbuf; + int decpt, sign; + PyObject *result = NULL; - if (!PyArg_ParseTuple(args, "|i", &ndigits)) + /* round to a decimal string */ + buf = _Py_dg_dtoa(x, 3, ndigits, &decpt, &sign, &buf_end); + if (buf == NULL) { + PyErr_NoMemory(); return NULL; + } - x = PyFloat_AsDouble(v); - - if (ndigits != UNDEF_NDIGITS) { - f = pow(10.0, ndigits); - x *= f; + /* Get new buffer if shortbuf is too small. Space needed <= buf_end - + buf + 8: (1 extra for '0', 1 for sign, 5 for exp, 1 for '\0'). */ + buflen = buf_end - buf; + if (buflen + 8 > mybuflen) { + mybuflen = buflen+8; + mybuf = (char *)PyMem_Malloc(mybuflen); + if (mybuf == NULL) { + PyErr_NoMemory(); + goto exit; + } } + /* copy buf to mybuf, adding exponent, sign and leading 0 */ + PyOS_snprintf(mybuf, mybuflen, "%s0%se%d", (sign ? "-" : ""), + buf, decpt - (int)buflen); - flr = floor(x); - cil = ceil(x); - - if (x-flr > 0.5) - rounded = cil; - else if (x-flr == 0.5) - rounded = fmod(flr, 2) == 0 ? flr : cil; + /* and convert the resulting string back to a double */ + errno = 0; + rounded = _Py_dg_strtod(mybuf, NULL); + if (errno == ERANGE && fabs(rounded) >= 1.) + PyErr_SetString(PyExc_OverflowError, + "rounded value too large to represent"); else - rounded = flr; + result = PyFloat_FromDouble(rounded); - if (ndigits != UNDEF_NDIGITS) { - rounded /= f; - return PyFloat_FromDouble(rounded); + /* done computing value; now clean up */ + if (mybuf != shortbuf) + PyMem_Free(mybuf); + exit: + _Py_dg_freedtoa(buf); + return result; +} + +#else /* PY_NO_SHORT_FLOAT_REPR */ + +/* fallback version, to be used when correctly rounded binary<->decimal + conversions aren't available */ + +static PyObject * +double_round(double x, int ndigits) { + double f; + f = pow(10.0, ndigits); + x *= f; + rounded = round(x); + if (fabs(x-rounded) == 0.5) + rounded = 2.0*round(x/2.0); + return PyFloat_FromDouble(rounded / f); +} + +#endif /* PY_NO_SHORT_FLOAT_REPR */ + +/* round a Python float v to the closest multiple of 10**-ndigits */ + +static PyObject * +float_round(PyObject *v, PyObject *args) +{ + double x, rounded; + PyObject *o_ndigits = NULL; + Py_ssize_t ndigits; + + x = PyFloat_AsDouble(v); + if (!PyArg_ParseTuple(args, "|O", &o_ndigits)) + return NULL; + if (o_ndigits == NULL) { + /* single-argument round: round to nearest integer */ + rounded = round(x); + if (fabs(x-rounded) == 0.5) + /* halfway case: round to even */ + rounded = 2.0*round(x/2.0); + return PyLong_FromDouble(rounded); } - return PyLong_FromDouble(rounded); -#undef UNDEF_NDIGITS + /* interpret second argument as a Py_ssize_t; clips on overflow */ + ndigits = PyNumber_AsSsize_t(o_ndigits, NULL); + if (ndigits == -1 && PyErr_Occurred()) + return NULL; + + /* nans and infinities round to themselves */ + if (!Py_IS_FINITE(x)) + return PyFloat_FromDouble(x); + + /* Deal with extreme values for ndigits. For ndigits > NDIGITS_MAX, x + always rounds to itself. For ndigits < NDIGITS_MIN, x always + rounds to +-0.0. Here 0.30103 is an upper bound for log10(2). */ +#define NDIGITS_MAX ((int)((DBL_MANT_DIG-DBL_MIN_EXP) * 0.30103)) +#define NDIGITS_MIN (-(int)((DBL_MAX_EXP + 1) * 0.30103)) + if (ndigits > NDIGITS_MAX) + /* return x */ + return PyFloat_FromDouble(x); + if (ndigits < NDIGITS_MIN) + /* return 0.0, but with sign of x */ + return PyFloat_FromDouble(0.0*x); +#undef NDIGITS_MAX +#undef NDIGITS_MIN + + /* Now we've got a finite x, and ndigits is not unreasonably large */ + return double_round(x, (int)ndigits); } static PyObject *