Issue #4707: round(x, n) now returns an integer when x is an integer.

Previously it returned a float.
This commit is contained in:
Mark Dickinson 2009-01-28 21:25:58 +00:00
parent 9de29afa7c
commit 1124e71368
5 changed files with 217 additions and 33 deletions

View File

@ -1068,9 +1068,9 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(round(8), 8)
self.assertEqual(round(-8), -8)
self.assertEqual(type(round(0)), int)
self.assertEqual(type(round(-8, -1)), float)
self.assertEqual(type(round(-8, 0)), float)
self.assertEqual(type(round(-8, 1)), float)
self.assertEqual(type(round(-8, -1)), int)
self.assertEqual(type(round(-8, 0)), int)
self.assertEqual(type(round(-8, 1)), int)
# test new kwargs
self.assertEqual(round(number=-8.0, ndigits=-1), -10.0)

View File

@ -896,6 +896,81 @@ class LongTest(unittest.TestCase):
self.assertEqual((a+1).bit_length(), i+1)
self.assertEqual((-a-1).bit_length(), i+1)
def test_round(self):
# check round-half-even algorithm. For round to nearest ten;
# rounding map is invariant under adding multiples of 20
test_dict = {0:0, 1:0, 2:0, 3:0, 4:0, 5:0,
6:10, 7:10, 8:10, 9:10, 10:10, 11:10, 12:10, 13:10, 14:10,
15:20, 16:20, 17:20, 18:20, 19:20}
for offset in range(-520, 520, 20):
for k, v in test_dict.items():
got = round(k+offset, -1)
expected = v+offset
self.assertEqual(got, expected)
self.assert_(type(got) is int)
# larger second argument
self.assertEqual(round(-150, -2), -200)
self.assertEqual(round(-149, -2), -100)
self.assertEqual(round(-51, -2), -100)
self.assertEqual(round(-50, -2), 0)
self.assertEqual(round(-49, -2), 0)
self.assertEqual(round(-1, -2), 0)
self.assertEqual(round(0, -2), 0)
self.assertEqual(round(1, -2), 0)
self.assertEqual(round(49, -2), 0)
self.assertEqual(round(50, -2), 0)
self.assertEqual(round(51, -2), 100)
self.assertEqual(round(149, -2), 100)
self.assertEqual(round(150, -2), 200)
self.assertEqual(round(250, -2), 200)
self.assertEqual(round(251, -2), 300)
self.assertEqual(round(172500, -3), 172000)
self.assertEqual(round(173500, -3), 174000)
self.assertEqual(round(31415926535, -1), 31415926540)
self.assertEqual(round(31415926535, -2), 31415926500)
self.assertEqual(round(31415926535, -3), 31415927000)
self.assertEqual(round(31415926535, -4), 31415930000)
self.assertEqual(round(31415926535, -5), 31415900000)
self.assertEqual(round(31415926535, -6), 31416000000)
self.assertEqual(round(31415926535, -7), 31420000000)
self.assertEqual(round(31415926535, -8), 31400000000)
self.assertEqual(round(31415926535, -9), 31000000000)
self.assertEqual(round(31415926535, -10), 30000000000)
self.assertEqual(round(31415926535, -11), 0)
self.assertEqual(round(31415926535, -12), 0)
self.assertEqual(round(31415926535, -999), 0)
# should get correct results even for huge inputs
for k in range(10, 100):
got = round(10**k + 324678, -3)
expect = 10**k + 325000
self.assertEqual(got, expect)
self.assert_(type(got) is int)
# nonnegative second argument: round(x, n) should just return x
for n in range(5):
for i in range(100):
x = random.randrange(-10000, 10000)
got = round(x, n)
self.assertEqual(got, x)
self.assert_(type(got) is int)
for huge_n in 2**31-1, 2**31, 2**63-1, 2**63, 2**100, 10**100:
self.assertEqual(round(8979323, huge_n), 8979323)
# omitted second argument
for i in range(100):
x = random.randrange(-10000, 10000)
got = round(x)
self.assertEqual(got, x)
self.assert_(type(got) is int)
# bad second argument
bad_exponents = ('brian', 2.0, 0j, None)
for e in bad_exponents:
self.assertRaises(TypeError, round, 3, e)
def test_main():
support.run_unittest(LongTest)

View File

@ -12,6 +12,9 @@ What's New in Python 3.1 alpha 0
Core and Builtins
-----------------
- Issue #4707: round(x, n) now returns an integer if x is an integer.
Previously it returned a float.
- Issue #4753: By enabling a configure option named '--with-computed-gotos'
on compilers that support it (notably: gcc, SunPro, icc), the bytecode
evaluation loop is compiled with a new dispatch mechanism which gives

View File

@ -3643,32 +3643,140 @@ long__format__(PyObject *self, PyObject *args)
PyUnicode_GET_SIZE(format_spec));
}
static PyObject *
long_round(PyObject *self, PyObject *args)
{
#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */
int ndigits = UNDEF_NDIGITS;
double x;
PyObject *res;
if (!PyArg_ParseTuple(args, "|i", &ndigits))
return NULL;
PyObject *o_ndigits=NULL, *temp;
PyLongObject *pow=NULL, *q=NULL, *r=NULL, *ndigits=NULL, *one;
int errcode;
digit q_mod_4;
if (ndigits == UNDEF_NDIGITS)
/* Notes on the algorithm: to round to the nearest 10**n (n positive),
the straightforward method is:
(1) divide by 10**n
(2) round to nearest integer (round to even in case of tie)
(3) multiply result by 10**n.
But the rounding step involves examining the fractional part of the
quotient to see whether it's greater than 0.5 or not. Since we
want to do the whole calculation in integer arithmetic, it's
simpler to do:
(1) divide by (10**n)/2
(2) round to nearest multiple of 2 (multiple of 4 in case of tie)
(3) multiply result by (10**n)/2.
Then all we need to know about the fractional part of the quotient
arising in step (2) is whether it's zero or not.
Doing both a multiplication and division is wasteful, and is easily
avoided if we just figure out how much to adjust the original input
by to do the rounding.
Here's the whole algorithm expressed in Python.
def round(self, ndigits = None):
"""round(int, int) -> int"""
if ndigits is None or ndigits >= 0:
return self
pow = 10**-ndigits >> 1
q, r = divmod(self, pow)
self -= r
if (q & 1 != 0):
if (q & 2 == r == 0):
self -= pow
else:
self += pow
return self
*/
if (!PyArg_ParseTuple(args, "|O", &o_ndigits))
return NULL;
if (o_ndigits == NULL)
return long_long(self);
/* If called with two args, defer to float.__round__(). */
x = PyLong_AsDouble(self);
if (x == -1.0 && PyErr_Occurred())
ndigits = (PyLongObject *)PyNumber_Index(o_ndigits);
if (ndigits == NULL)
return NULL;
self = PyFloat_FromDouble(x);
if (self == NULL)
return NULL;
res = PyObject_CallMethod(self, "__round__", "i", ndigits);
if (Py_SIZE(ndigits) >= 0) {
Py_DECREF(ndigits);
return long_long(self);
}
Py_INCREF(self); /* to keep refcounting simple */
/* we now own references to self, ndigits */
/* pow = 10 ** -ndigits >> 1 */
pow = (PyLongObject *)PyLong_FromLong(10L);
if (pow == NULL)
goto error;
temp = long_neg(ndigits);
Py_DECREF(ndigits);
ndigits = (PyLongObject *)temp;
if (ndigits == NULL)
goto error;
temp = long_pow((PyObject *)pow, (PyObject *)ndigits, Py_None);
Py_DECREF(pow);
pow = (PyLongObject *)temp;
if (pow == NULL)
goto error;
assert(PyLong_Check(pow)); /* check long_pow returned a long */
one = (PyLongObject *)PyLong_FromLong(1L);
if (one == NULL)
goto error;
temp = long_rshift(pow, one);
Py_DECREF(one);
Py_DECREF(pow);
pow = (PyLongObject *)temp;
if (pow == NULL)
goto error;
/* q, r = divmod(self, pow) */
errcode = l_divmod((PyLongObject *)self, pow, &q, &r);
if (errcode == -1)
goto error;
/* self -= r */
temp = long_sub((PyLongObject *)self, r);
Py_DECREF(self);
return res;
#undef UNDEF_NDIGITS
self = temp;
if (self == NULL)
goto error;
/* get value of quotient modulo 4 */
if (Py_SIZE(q) == 0)
q_mod_4 = 0;
else if (Py_SIZE(q) > 0)
q_mod_4 = q->ob_digit[0] & 3;
else
q_mod_4 = (PyLong_BASE-q->ob_digit[0]) & 3;
if ((q_mod_4 & 1) == 1) {
/* q is odd; round self up or down by adding or subtracting pow */
if (q_mod_4 == 1 && Py_SIZE(r) == 0)
temp = (PyObject *)long_sub((PyLongObject *)self, pow);
else
temp = (PyObject *)long_add((PyLongObject *)self, pow);
Py_DECREF(self);
self = temp;
if (self == NULL)
goto error;
}
Py_DECREF(q);
Py_DECREF(r);
Py_DECREF(pow);
Py_DECREF(ndigits);
return self;
error:
Py_XDECREF(q);
Py_XDECREF(r);
Py_XDECREF(pow);
Py_XDECREF(self);
Py_XDECREF(ndigits);
return NULL;
}
static PyObject *
@ -3773,8 +3881,8 @@ static PyMethodDef long_methods[] = {
{"__ceil__", (PyCFunction)long_long, METH_NOARGS,
"Ceiling of an Integral returns itself."},
{"__round__", (PyCFunction)long_round, METH_VARARGS,
"Rounding an Integral returns itself.\n"
"Rounding with an ndigits arguments defers to float.__round__."},
"Rounding an Integral returns itself.\n"
"Rounding with an ndigits argument also returns an integer."},
{"__getnewargs__", (PyCFunction)long_getnewargs, METH_NOARGS},
{"__format__", (PyCFunction)long__format__, METH_VARARGS},
{"__sizeof__", (PyCFunction)long_sizeof, METH_NOARGS,

View File

@ -1717,15 +1717,14 @@ For most object types, eval(repr(object)) == object.");
static PyObject *
builtin_round(PyObject *self, PyObject *args, PyObject *kwds)
{
#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */
static PyObject *round_str = NULL;
int ndigits = UNDEF_NDIGITS;
PyObject *ndigits = NULL;
static char *kwlist[] = {"number", "ndigits", 0};
PyObject *number, *round;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i:round",
kwlist, &number, &ndigits))
return NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:round",
kwlist, &number, &ndigits))
return NULL;
if (Py_TYPE(number)->tp_dict == NULL) {
if (PyType_Ready(Py_TYPE(number)) < 0)
@ -1746,15 +1745,14 @@ builtin_round(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
if (ndigits == UNDEF_NDIGITS)
return PyObject_CallFunction(round, "O", number);
if (ndigits == NULL)
return PyObject_CallFunction(round, "O", number);
else
return PyObject_CallFunction(round, "Oi", number, ndigits);
#undef UNDEF_NDIGITS
return PyObject_CallFunction(round, "OO", number, ndigits);
}
PyDoc_STRVAR(round_doc,
"round(number[, ndigits]) -> floating point number\n\
"round(number[, ndigits]) -> number\n\
\n\
Round a number to a given precision in decimal digits (default 0 digits).\n\
This returns an int when called with one argument, otherwise the\n\