mirror of https://github.com/python/cpython
Issue #4707: round(x, n) now returns an integer when x is an integer.
Previously it returned a float.
This commit is contained in:
parent
9de29afa7c
commit
1124e71368
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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\
|
||||
|
|
Loading…
Reference in New Issue