summaryrefslogtreecommitdiffstats
path: root/Objects
diff options
context:
space:
mode:
authorMark Dickinson <dickinsm@gmail.com>2009-01-28 21:25:58 (GMT)
committerMark Dickinson <dickinsm@gmail.com>2009-01-28 21:25:58 (GMT)
commit1124e71368c73b592020b4896fb1c5d371efbcef (patch)
tree3b6b7514894a95a88cfcd3a232c6e074c23bc61e /Objects
parent9de29afa7c083a24a5eabfbbc9bb3515e7026745 (diff)
downloadcpython-1124e71368c73b592020b4896fb1c5d371efbcef.zip
cpython-1124e71368c73b592020b4896fb1c5d371efbcef.tar.gz
cpython-1124e71368c73b592020b4896fb1c5d371efbcef.tar.bz2
Issue #4707: round(x, n) now returns an integer when x is an integer.
Previously it returned a float.
Diffstat (limited to 'Objects')
-rw-r--r--Objects/longobject.c148
1 files changed, 128 insertions, 20 deletions
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 76986fd..5476e19 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -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;
+
+ /* 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 (ndigits == UNDEF_NDIGITS)
+ */
+ 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,