[Python-checkins] r69068 - in python/branches/py3k: Lib/test/test_builtin.py Lib/test/test_long.py Misc/NEWS Objects/longobject.c Python/bltinmodule.c

mark.dickinson python-checkins at python.org
Wed Jan 28 22:25:58 CET 2009


Author: mark.dickinson
Date: Wed Jan 28 22:25:58 2009
New Revision: 69068

Log:
Issue #4707: round(x, n) now returns an integer when x is an integer.
Previously it returned a float.


Modified:
   python/branches/py3k/Lib/test/test_builtin.py
   python/branches/py3k/Lib/test/test_long.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Objects/longobject.c
   python/branches/py3k/Python/bltinmodule.c

Modified: python/branches/py3k/Lib/test/test_builtin.py
==============================================================================
--- python/branches/py3k/Lib/test/test_builtin.py	(original)
+++ python/branches/py3k/Lib/test/test_builtin.py	Wed Jan 28 22:25:58 2009
@@ -1068,9 +1068,9 @@
         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)

Modified: python/branches/py3k/Lib/test/test_long.py
==============================================================================
--- python/branches/py3k/Lib/test/test_long.py	(original)
+++ python/branches/py3k/Lib/test/test_long.py	Wed Jan 28 22:25:58 2009
@@ -896,6 +896,81 @@
             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)

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Wed Jan 28 22:25:58 2009
@@ -12,6 +12,9 @@
 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

Modified: python/branches/py3k/Objects/longobject.c
==============================================================================
--- python/branches/py3k/Objects/longobject.c	(original)
+++ python/branches/py3k/Objects/longobject.c	Wed Jan 28 22:25:58 2009
@@ -3643,32 +3643,140 @@
 				      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 @@
 	{"__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,

Modified: python/branches/py3k/Python/bltinmodule.c
==============================================================================
--- python/branches/py3k/Python/bltinmodule.c	(original)
+++ python/branches/py3k/Python/bltinmodule.c	Wed Jan 28 22:25:58 2009
@@ -1717,15 +1717,14 @@
 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 @@
 		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\


More information about the Python-checkins mailing list