Skip to content

Commit 735aa41

Browse files
author
mark.dickinson
committed
Merged revisions 69068 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/branches/py3k ........ r69068 | mark.dickinson | 2009-01-28 21:25:58 +0000 (Wed, 28 Jan 2009) | 3 lines Issue #4707: round(x, n) now returns an integer when x is an integer. Previously it returned a float. ........ git-svn-id: http://svn.python.org/projects/python/branches/release30-maint@69069 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 1a203c7 commit 735aa41

5 files changed

Lines changed: 218 additions & 33 deletions

File tree

Lib/test/test_builtin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,9 @@ def test_round(self):
10681068
self.assertEqual(round(8), 8)
10691069
self.assertEqual(round(-8), -8)
10701070
self.assertEqual(type(round(0)), int)
1071-
self.assertEqual(type(round(-8, -1)), float)
1072-
self.assertEqual(type(round(-8, 0)), float)
1073-
self.assertEqual(type(round(-8, 1)), float)
1071+
self.assertEqual(type(round(-8, -1)), int)
1072+
self.assertEqual(type(round(-8, 0)), int)
1073+
self.assertEqual(type(round(-8, 1)), int)
10741074

10751075
# test new kwargs
10761076
self.assertEqual(round(number=-8.0, ndigits=-1), -10.0)

Lib/test/test_long.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,82 @@ def test_small_ints(self):
861861
self.assertTrue(i - i is 0)
862862
self.assertTrue(0 * i is 0)
863863

864+
def test_round(self):
865+
# check round-half-even algorithm. For round to nearest ten;
866+
# rounding map is invariant under adding multiples of 20
867+
test_dict = {0:0, 1:0, 2:0, 3:0, 4:0, 5:0,
868+
6:10, 7:10, 8:10, 9:10, 10:10, 11:10, 12:10, 13:10, 14:10,
869+
15:20, 16:20, 17:20, 18:20, 19:20}
870+
for offset in range(-520, 520, 20):
871+
for k, v in test_dict.items():
872+
got = round(k+offset, -1)
873+
expected = v+offset
874+
self.assertEqual(got, expected)
875+
self.assert_(type(got) is int)
876+
877+
# larger second argument
878+
self.assertEqual(round(-150, -2), -200)
879+
self.assertEqual(round(-149, -2), -100)
880+
self.assertEqual(round(-51, -2), -100)
881+
self.assertEqual(round(-50, -2), 0)
882+
self.assertEqual(round(-49, -2), 0)
883+
self.assertEqual(round(-1, -2), 0)
884+
self.assertEqual(round(0, -2), 0)
885+
self.assertEqual(round(1, -2), 0)
886+
self.assertEqual(round(49, -2), 0)
887+
self.assertEqual(round(50, -2), 0)
888+
self.assertEqual(round(51, -2), 100)
889+
self.assertEqual(round(149, -2), 100)
890+
self.assertEqual(round(150, -2), 200)
891+
self.assertEqual(round(250, -2), 200)
892+
self.assertEqual(round(251, -2), 300)
893+
self.assertEqual(round(172500, -3), 172000)
894+
self.assertEqual(round(173500, -3), 174000)
895+
self.assertEqual(round(31415926535, -1), 31415926540)
896+
self.assertEqual(round(31415926535, -2), 31415926500)
897+
self.assertEqual(round(31415926535, -3), 31415927000)
898+
self.assertEqual(round(31415926535, -4), 31415930000)
899+
self.assertEqual(round(31415926535, -5), 31415900000)
900+
self.assertEqual(round(31415926535, -6), 31416000000)
901+
self.assertEqual(round(31415926535, -7), 31420000000)
902+
self.assertEqual(round(31415926535, -8), 31400000000)
903+
self.assertEqual(round(31415926535, -9), 31000000000)
904+
self.assertEqual(round(31415926535, -10), 30000000000)
905+
self.assertEqual(round(31415926535, -11), 0)
906+
self.assertEqual(round(31415926535, -12), 0)
907+
self.assertEqual(round(31415926535, -999), 0)
908+
909+
# should get correct results even for huge inputs
910+
for k in range(10, 100):
911+
got = round(10**k + 324678, -3)
912+
expect = 10**k + 325000
913+
self.assertEqual(got, expect)
914+
self.assert_(type(got) is int)
915+
916+
# nonnegative second argument: round(x, n) should just return x
917+
for n in range(5):
918+
for i in range(100):
919+
x = random.randrange(-10000, 10000)
920+
got = round(x, n)
921+
self.assertEqual(got, x)
922+
self.assert_(type(got) is int)
923+
for huge_n in 2**31-1, 2**31, 2**63-1, 2**63, 2**100, 10**100:
924+
self.assertEqual(round(8979323, huge_n), 8979323)
925+
926+
# omitted second argument
927+
for i in range(100):
928+
x = random.randrange(-10000, 10000)
929+
got = round(x)
930+
self.assertEqual(got, x)
931+
self.assert_(type(got) is int)
932+
933+
# bad second argument
934+
bad_exponents = ('brian', 2.0, 0j, None)
935+
for e in bad_exponents:
936+
self.assertRaises(TypeError, round, 3, e)
937+
938+
939+
864940
def test_main():
865941
support.run_unittest(LongTest)
866942

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ What's New in Python 3.0.1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #4707: round(x, n) now returns an integer if x is an integer.
16+
Previously it returned a float.
17+
1518
- Issue #4874: Most builtin decoders now reject unicode input.
1619

1720
- Issue #4842: Don't allow trailing 'L' when constructing an integer

Objects/longobject.c

Lines changed: 128 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3643,32 +3643,140 @@ long__format__(PyObject *self, PyObject *args)
36433643
PyUnicode_GET_SIZE(format_spec));
36443644
}
36453645

3646-
36473646
static PyObject *
36483647
long_round(PyObject *self, PyObject *args)
36493648
{
3650-
#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */
3651-
int ndigits = UNDEF_NDIGITS;
3652-
double x;
3653-
PyObject *res;
3654-
3655-
if (!PyArg_ParseTuple(args, "|i", &ndigits))
3656-
return NULL;
3649+
PyObject *o_ndigits=NULL, *temp;
3650+
PyLongObject *pow=NULL, *q=NULL, *r=NULL, *ndigits=NULL, *one;
3651+
int errcode;
3652+
digit q_mod_4;
3653+
3654+
/* Notes on the algorithm: to round to the nearest 10**n (n positive),
3655+
the straightforward method is:
3656+
3657+
(1) divide by 10**n
3658+
(2) round to nearest integer (round to even in case of tie)
3659+
(3) multiply result by 10**n.
3660+
3661+
But the rounding step involves examining the fractional part of the
3662+
quotient to see whether it's greater than 0.5 or not. Since we
3663+
want to do the whole calculation in integer arithmetic, it's
3664+
simpler to do:
3665+
3666+
(1) divide by (10**n)/2
3667+
(2) round to nearest multiple of 2 (multiple of 4 in case of tie)
3668+
(3) multiply result by (10**n)/2.
3669+
3670+
Then all we need to know about the fractional part of the quotient
3671+
arising in step (2) is whether it's zero or not.
3672+
3673+
Doing both a multiplication and division is wasteful, and is easily
3674+
avoided if we just figure out how much to adjust the original input
3675+
by to do the rounding.
3676+
3677+
Here's the whole algorithm expressed in Python.
3678+
3679+
def round(self, ndigits = None):
3680+
"""round(int, int) -> int"""
3681+
if ndigits is None or ndigits >= 0:
3682+
return self
3683+
pow = 10**-ndigits >> 1
3684+
q, r = divmod(self, pow)
3685+
self -= r
3686+
if (q & 1 != 0):
3687+
if (q & 2 == r == 0):
3688+
self -= pow
3689+
else:
3690+
self += pow
3691+
return self
36573692
3658-
if (ndigits == UNDEF_NDIGITS)
3693+
*/
3694+
if (!PyArg_ParseTuple(args, "|O", &o_ndigits))
3695+
return NULL;
3696+
if (o_ndigits == NULL)
36593697
return long_long(self);
36603698

3661-
/* If called with two args, defer to float.__round__(). */
3662-
x = PyLong_AsDouble(self);
3663-
if (x == -1.0 && PyErr_Occurred())
3699+
ndigits = (PyLongObject *)PyNumber_Index(o_ndigits);
3700+
if (ndigits == NULL)
36643701
return NULL;
3665-
self = PyFloat_FromDouble(x);
3666-
if (self == NULL)
3667-
return NULL;
3668-
res = PyObject_CallMethod(self, "__round__", "i", ndigits);
3702+
3703+
if (Py_SIZE(ndigits) >= 0) {
3704+
Py_DECREF(ndigits);
3705+
return long_long(self);
3706+
}
3707+
3708+
Py_INCREF(self); /* to keep refcounting simple */
3709+
/* we now own references to self, ndigits */
3710+
3711+
/* pow = 10 ** -ndigits >> 1 */
3712+
pow = (PyLongObject *)PyLong_FromLong(10L);
3713+
if (pow == NULL)
3714+
goto error;
3715+
temp = long_neg(ndigits);
3716+
Py_DECREF(ndigits);
3717+
ndigits = (PyLongObject *)temp;
3718+
if (ndigits == NULL)
3719+
goto error;
3720+
temp = long_pow((PyObject *)pow, (PyObject *)ndigits, Py_None);
3721+
Py_DECREF(pow);
3722+
pow = (PyLongObject *)temp;
3723+
if (pow == NULL)
3724+
goto error;
3725+
assert(PyLong_Check(pow)); /* check long_pow returned a long */
3726+
one = (PyLongObject *)PyLong_FromLong(1L);
3727+
if (one == NULL)
3728+
goto error;
3729+
temp = long_rshift(pow, one);
3730+
Py_DECREF(one);
3731+
Py_DECREF(pow);
3732+
pow = (PyLongObject *)temp;
3733+
if (pow == NULL)
3734+
goto error;
3735+
3736+
/* q, r = divmod(self, pow) */
3737+
errcode = l_divmod((PyLongObject *)self, pow, &q, &r);
3738+
if (errcode == -1)
3739+
goto error;
3740+
3741+
/* self -= r */
3742+
temp = long_sub((PyLongObject *)self, r);
36693743
Py_DECREF(self);
3670-
return res;
3671-
#undef UNDEF_NDIGITS
3744+
self = temp;
3745+
if (self == NULL)
3746+
goto error;
3747+
3748+
/* get value of quotient modulo 4 */
3749+
if (Py_SIZE(q) == 0)
3750+
q_mod_4 = 0;
3751+
else if (Py_SIZE(q) > 0)
3752+
q_mod_4 = q->ob_digit[0] & 3;
3753+
else
3754+
q_mod_4 = (PyLong_BASE-q->ob_digit[0]) & 3;
3755+
3756+
if ((q_mod_4 & 1) == 1) {
3757+
/* q is odd; round self up or down by adding or subtracting pow */
3758+
if (q_mod_4 == 1 && Py_SIZE(r) == 0)
3759+
temp = (PyObject *)long_sub((PyLongObject *)self, pow);
3760+
else
3761+
temp = (PyObject *)long_add((PyLongObject *)self, pow);
3762+
Py_DECREF(self);
3763+
self = temp;
3764+
if (self == NULL)
3765+
goto error;
3766+
}
3767+
Py_DECREF(q);
3768+
Py_DECREF(r);
3769+
Py_DECREF(pow);
3770+
Py_DECREF(ndigits);
3771+
return self;
3772+
3773+
error:
3774+
Py_XDECREF(q);
3775+
Py_XDECREF(r);
3776+
Py_XDECREF(pow);
3777+
Py_XDECREF(self);
3778+
Py_XDECREF(ndigits);
3779+
return NULL;
36723780
}
36733781

36743782
static PyObject *
@@ -3702,8 +3810,8 @@ static PyMethodDef long_methods[] = {
37023810
{"__ceil__", (PyCFunction)long_long, METH_NOARGS,
37033811
"Ceiling of an Integral returns itself."},
37043812
{"__round__", (PyCFunction)long_round, METH_VARARGS,
3705-
"Rounding an Integral returns itself.\n"
3706-
"Rounding with an ndigits arguments defers to float.__round__."},
3813+
"Rounding an Integral returns itself.\n"
3814+
"Rounding with an ndigits argument also returns an integer."},
37073815
{"__getnewargs__", (PyCFunction)long_getnewargs, METH_NOARGS},
37083816
{"__format__", (PyCFunction)long__format__, METH_VARARGS},
37093817
{"__sizeof__", (PyCFunction)long_sizeof, METH_NOARGS,

Python/bltinmodule.c

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,15 +1718,14 @@ For most object types, eval(repr(object)) == object.");
17181718
static PyObject *
17191719
builtin_round(PyObject *self, PyObject *args, PyObject *kwds)
17201720
{
1721-
#define UNDEF_NDIGITS (-0x7fffffff) /* Unlikely ndigits value */
17221721
static PyObject *round_str = NULL;
1723-
int ndigits = UNDEF_NDIGITS;
1722+
PyObject *ndigits = NULL;
17241723
static char *kwlist[] = {"number", "ndigits", 0};
17251724
PyObject *number, *round;
17261725

1727-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i:round",
1728-
kwlist, &number, &ndigits))
1729-
return NULL;
1726+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:round",
1727+
kwlist, &number, &ndigits))
1728+
return NULL;
17301729

17311730
if (Py_TYPE(number)->tp_dict == NULL) {
17321731
if (PyType_Ready(Py_TYPE(number)) < 0)
@@ -1747,15 +1746,14 @@ builtin_round(PyObject *self, PyObject *args, PyObject *kwds)
17471746
return NULL;
17481747
}
17491748

1750-
if (ndigits == UNDEF_NDIGITS)
1751-
return PyObject_CallFunction(round, "O", number);
1749+
if (ndigits == NULL)
1750+
return PyObject_CallFunction(round, "O", number);
17521751
else
1753-
return PyObject_CallFunction(round, "Oi", number, ndigits);
1754-
#undef UNDEF_NDIGITS
1752+
return PyObject_CallFunction(round, "OO", number, ndigits);
17551753
}
17561754

17571755
PyDoc_STRVAR(round_doc,
1758-
"round(number[, ndigits]) -> floating point number\n\
1756+
"round(number[, ndigits]) -> number\n\
17591757
\n\
17601758
Round a number to a given precision in decimal digits (default 0 digits).\n\
17611759
This returns an int when called with one argument, otherwise the\n\

0 commit comments

Comments
 (0)