summaryrefslogtreecommitdiffstats
path: root/Objects
diff options
context:
space:
mode:
authorMark Dickinson <dickinsm@gmail.com>2009-04-18 11:48:33 (GMT)
committerMark Dickinson <dickinsm@gmail.com>2009-04-18 11:48:33 (GMT)
commite6a076d86c51c9d72fee088dd229a7662ccc6c19 (patch)
tree86dc10e5fbdf54b653ef87d7a6a8ae4425345ce9 /Objects
parent60fd0999cce17ec8603edf4219f686c37a260d7b (diff)
downloadcpython-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.c166
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 *