diff options
author | Mark Dickinson <dickinsm@gmail.com> | 2009-04-18 11:48:33 (GMT) |
---|---|---|
committer | Mark Dickinson <dickinsm@gmail.com> | 2009-04-18 11:48:33 (GMT) |
commit | e6a076d86c51c9d72fee088dd229a7662ccc6c19 (patch) | |
tree | 86dc10e5fbdf54b653ef87d7a6a8ae4425345ce9 /Objects | |
parent | 60fd0999cce17ec8603edf4219f686c37a260d7b (diff) | |
download | cpython-e6a076d86c51c9d72fee088dd229a7662ccc6c19.zip cpython-e6a076d86c51c9d72fee088dd229a7662ccc6c19.tar.gz cpython-e6a076d86c51c9d72fee088dd229a7662ccc6c19.tar.bz2 |
Issue #1869 (and 4707, 5118, 5473, 1456775): use the new
string <-> float conversion routines to make round(x, n) correctly
rounded for floats x, so that it always agrees with format(x, '.<n>f').
Also fix some other round nuisances, like round(123.456, 1-2**31) giving
an integer rather than a float.
Diffstat (limited to 'Objects')
-rw-r--r-- | Objects/floatobject.c | 166 |
1 files changed, 142 insertions, 24 deletions
diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 2fbe810..b7b5220 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -899,43 +899,161 @@ float_trunc(PyObject *v) 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). Returns a Python float, or sets a Python error and + returns NULL on failure (OverflowError and memory errors are possible). */ + +#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); + /* 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); - if (ndigits != UNDEF_NDIGITS) { - f = pow(10.0, ndigits); - x *= f; + /* 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 + result = 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 pow1, pow2, y, z; + if (ndigits >= 0) { + if (ndigits > 22) { + /* pow1 and pow2 are each safe from overflow, but + pow1*pow2 ~= pow(10.0, ndigits) might overflow */ + pow1 = pow(10.0, (double)(ndigits-22)); + pow2 = 1e22; + } + else { + pow1 = pow(10.0, (double)ndigits); + pow2 = 1.0; + } + y = (x*pow1)*pow2; + /* if y overflows, then rounded value is exactly x */ + if (!Py_IS_FINITE(y)) + return PyFloat_FromDouble(x); + } + else { + pow1 = pow(10.0, (double)-ndigits); + pow2 = 1.0; /* unused; silences a gcc compiler warning */ + y = x / pow1; } - flr = floor(x); - cil = ceil(x); + z = round(y); + if (fabs(y-z) == 0.5) + /* halfway between two integers; use round-half-even */ + z = 2.0*round(y/2.0); - if (x-flr > 0.5) - rounded = cil; - else if (x-flr == 0.5) - rounded = fmod(flr, 2) == 0 ? flr : cil; + if (ndigits >= 0) + z = (z / pow2) / pow1; else - rounded = flr; + z *= pow1; - if (ndigits != UNDEF_NDIGITS) { - rounded /= f; - return PyFloat_FromDouble(rounded); + /* if computation resulted in overflow, raise OverflowError */ + if (!Py_IS_FINITE(z)) { + PyErr_SetString(PyExc_OverflowError, + "overflow occurred during round"); + return NULL; } - return PyLong_FromDouble(rounded); -#undef UNDEF_NDIGITS + return PyFloat_FromDouble(z); +} + +#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); + } + + /* 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); + else if (ndigits < NDIGITS_MIN) + /* return 0.0, but with sign of x */ + return PyFloat_FromDouble(0.0*x); + else + /* finite x, and ndigits is not unreasonably large */ + return double_round(x, (int)ndigits); +#undef NDIGITS_MAX +#undef NDIGITS_MIN } static PyObject * |