Index: Objects/floatobject.c =================================================================== --- Objects/floatobject.c (revision 71669) +++ Objects/floatobject.c (working copy) @@ -899,46 +899,153 @@ 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); + /* 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) { + /* 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 f, rounded; + if (ndigits >= 0) { f = pow(10.0, ndigits); - x *= f; + y = x*f; + /* if y overflows, then rounded value is exactly x */ + if (!Py_IS_FINITE(y)) + return PyFloat_FromDouble(x); } + else { + f = pow(10.0, -ndigits); + y = x/f; + } + rounded = round(y); + if (fabs(y-rounded) == 0.5) + /* halfway between two integers; use round-half-even */ + rounded = 2.0*round(y/2.0); - 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; + if (ndigits >= 0) + rounded /= f; else - rounded = flr; + rounded *= f; - if (ndigits != UNDEF_NDIGITS) { - rounded /= f; - return PyFloat_FromDouble(rounded); + /* if computation resulted in overflow, raise OverflowError */ + if (!Py_IS_FINITE(rounded)) { + PyErr_SetString(PyExc_OverflowError, + "overflow occurred during round"); + return NULL; } - return PyLong_FromDouble(rounded); -#undef UNDEF_NDIGITS + return PyFloat_FromDouble(rounded); } +#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); + 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 * float_float(PyObject *v) { if (PyFloat_CheckExact(v)) Index: Lib/test/test_float.py =================================================================== --- Lib/test/test_float.py (revision 71691) +++ Lib/test/test_float.py (working copy) @@ -389,6 +389,72 @@ self.assertEqual(s, repr(float(s))) self.assertEqual(negs, repr(float(negs))) +class RoundTestCase(unittest.TestCase): + @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"), + "test requires IEEE 754 doubles") + def test_inf_nan(self): + self.assertRaises(OverflowError, round, INF) + self.assertRaises(OverflowError, round, -INF) + self.assertRaises(ValueError, round, NAN) + + @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"), + "test requires IEEE 754 doubles") + def test_large_n(self): + for n in [324, 325, 400, 2**31-1, 2**31, 2**32, 2**100]: + self.assertEqual(round(123.456, n), 123.456) + self.assertEqual(round(-123.456, n), -123.456) + self.assertEqual(round(1e300, n), 1e300) + self.assertEqual(round(1e-320, n), 1e-320) + self.assertEqual(round(1e150, 300), 1e150) + self.assertEqual(round(1e300, 307), 1e300) + self.assertEqual(round(-3.1415, 308), -3.1415) + self.assertEqual(round(1e150, 309), 1e150) + + @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"), + "test requires IEEE 754 doubles") + def test_small_n(self): + for n in [-308, -309, -400, 1-2**31, -2**31, -2**31-1, -2**100]: + self.assertEqual(round(123.456, n), 0.0) + self.assertEqual(round(-123.456, n), -0.0) + self.assertEqual(round(1e300, n), 0.0) + self.assertEqual(round(1e-320, n), 0.0) + + @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"), + "test requires IEEE 754 doubles") + def test_overflow(self): + self.assertRaises(OverflowError, round, 1.6e308, -308) + self.assertRaises(OverflowError, round, -1.7e308, -308) + + @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short', + "applies only when using short float repr style") + def test_previous_round_bugs(self): + # particular cases that have occurred in bug reports + self.assertEqual(round(562949953421312.5, 1), + 562949953421312.5) + self.assertEqual(round(56294995342131.5, 3), + 56294995342131.5) + # round-half-even + self.assertEqual(round(25.0, -1), 20.0) + self.assertEqual(round(35.0, -1), 40.0) + self.assertEqual(round(45.0, -1), 40.0) + self.assertEqual(round(55.0, -1), 60.0) + self.assertEqual(round(65.0, -1), 60.0) + self.assertEqual(round(75.0, -1), 80.0) + self.assertEqual(round(85.0, -1), 80.0) + self.assertEqual(round(95.0, -1), 100.0) + # note: the following tests are *not* instances of + # round-half-even, because the decimal values used + # (e.g. 2.545) aren't exactly representable in binary, + # so aren't exactly halfway between multiples of 0.01. + self.assertEqual(round(2.545, 2), 2.54) + self.assertEqual(round(2.555, 2), 2.56) + self.assertEqual(round(2.565, 2), 2.56) + self.assertEqual(round(2.575, 2), 2.58) + self.assertEqual(round(2.635, 2), 2.63) + self.assertEqual(round(2.645, 2), 2.65) + self.assertEqual(round(2.655, 2), 2.65) + self.assertEqual(round(2.665, 2), 2.67) + # Beginning with Python 2.6 float has cross platform compatible # ways to create and represent inf and nan class InfNanTest(unittest.TestCase): @@ -878,6 +944,7 @@ IEEEFormatTestCase, FormatTestCase, ReprTestCase, + RoundTestCase, InfNanTest, HexFloatTestCase, )