[Python-checkins] r60511 - in python/trunk: Lib/test/test_builtin.py Objects/floatobject.c

raymond.hettinger python-checkins at python.org
Fri Feb 1 22:30:24 CET 2008


Author: raymond.hettinger
Date: Fri Feb  1 22:30:23 2008
New Revision: 60511

Modified:
   python/trunk/Lib/test/test_builtin.py
   python/trunk/Objects/floatobject.c
Log:
Issue #1996: float.as_integer_ratio() should return fraction in lowest terms.

Modified: python/trunk/Lib/test/test_builtin.py
==============================================================================
--- python/trunk/Lib/test/test_builtin.py	(original)
+++ python/trunk/Lib/test/test_builtin.py	Fri Feb  1 22:30:23 2008
@@ -689,6 +689,14 @@
         self.assertRaises(TypeError, float, Foo4(42))
 
     def test_floatasratio(self):
+        for f, ratio in [
+                (0.875, (7, 8)),
+                (-0.875, (-7, 8)),
+                (0.0, (0, 1)),
+                (11.5, (23, 2)),
+            ]:
+            self.assertEqual(f.as_integer_ratio(), ratio)
+
         R = rational.Rational
         self.assertEqual(R(0, 1),
                          R(*float(0.0).as_integer_ratio()))

Modified: python/trunk/Objects/floatobject.c
==============================================================================
--- python/trunk/Objects/floatobject.c	(original)
+++ python/trunk/Objects/floatobject.c	Fri Feb  1 22:30:23 2008
@@ -1154,20 +1154,18 @@
 }
 
 static PyObject *
-float_as_integer_ratio(PyObject *v)
+float_as_integer_ratio(PyObject *v, PyObject *unused)
 {
 	double self;
 	double float_part;
 	int exponent;
-	int is_negative;
-	const int chunk_size = 28;
+
 	PyObject *prev;
-	PyObject *py_chunk = NULL;
 	PyObject *py_exponent = NULL;
 	PyObject *numerator = NULL;
 	PyObject *denominator = NULL;
 	PyObject *result_pair = NULL;
-	PyNumberMethods *long_methods;
+	PyNumberMethods *long_methods = PyLong_Type.tp_as_number;
 
 #define INPLACE_UPDATE(obj, call) \
 	prev = obj; \
@@ -1189,85 +1187,22 @@
 	}
 #endif
 
-	if (self == 0) {
-		numerator = PyInt_FromLong(0);
-		if (numerator == NULL) goto error;
-		denominator = PyInt_FromLong(1);
-		if (denominator == NULL) goto error;
-		result_pair = PyTuple_Pack(2, numerator, denominator);
-		/* Hand ownership over to the tuple. If the tuple
-		   wasn't created successfully, we want to delete the
-		   ints anyway. */
-		Py_DECREF(numerator);
-		Py_DECREF(denominator);
-		return result_pair;
-	}
-
-	/* XXX: Could perhaps handle FLT_RADIX!=2 by using ilogb and
-	   scalbn, but those may not be in C89. */
 	PyFPE_START_PROTECT("as_integer_ratio", goto error);
-	float_part = frexp(self, &exponent);
-	is_negative = 0;
-	if (float_part < 0) {
-		float_part = -float_part;
-		is_negative = 1;
-		/* 0.5 <= float_part < 1.0 */
-	}
+	float_part = frexp(self, &exponent);  	/* self == float_part * 2**exponent exactly */
 	PyFPE_END_PROTECT(float_part);
-	/* abs(self) == float_part * 2**exponent exactly */
-
-	/* Suck up chunk_size bits at a time; 28 is enough so that we
-	   suck up all bits in 2 iterations for all known binary
-	   double-precision formats, and small enough to fit in a
-	   long. */
-	numerator = PyLong_FromLong(0);
-	if (numerator == NULL) goto error;
-
-	long_methods = PyLong_Type.tp_as_number;
-
-	py_chunk = PyLong_FromLong(chunk_size);
-	if (py_chunk == NULL) goto error;
-
-	while (float_part != 0) {
-		/* invariant: abs(self) ==
-		   (numerator + float_part) * 2**exponent exactly */
-		long digit;
-		PyObject *py_digit;
-
-		PyFPE_START_PROTECT("as_integer_ratio", goto error);
-		/* Pull chunk_size bits out of float_part, into digits. */
-		float_part = ldexp(float_part, chunk_size);
-		digit = (long)float_part;
-		float_part -= digit;
-                /* 0 <= float_part < 1 */
-		exponent -= chunk_size;
-		PyFPE_END_PROTECT(float_part);
-
-		/* Shift digits into numerator. */
-		// numerator <<= chunk_size
-		INPLACE_UPDATE(numerator,
-			       long_methods->nb_lshift(numerator, py_chunk));
-		if (numerator == NULL) goto error;
-
-		// numerator |= digit
-		py_digit = PyLong_FromLong(digit);
-		if (py_digit == NULL) goto error;
-		INPLACE_UPDATE(numerator,
-			       long_methods->nb_or(numerator, py_digit));
-		Py_DECREF(py_digit);
-		if (numerator == NULL) goto error;
+	
+	while (float_part != floor(float_part)) {
+		float_part *= 2.0;
+		exponent--;
 	}
+	/* Now, self == float_part * 2**exponent exactly and float_part is integral */
 
-	/* Add in the sign bit. */
-	if (is_negative) {
-		INPLACE_UPDATE(numerator,
-			       long_methods->nb_negative(numerator));
-		if (numerator == NULL) goto error;
-	}
+	numerator = PyLong_FromDouble(float_part);
+	if (numerator == NULL) goto error;
 
 	/* now self = numerator * 2**exponent exactly; fold in 2**exponent */
-	denominator = PyLong_FromLong(1);
-	py_exponent = PyLong_FromLong(labs(exponent));
+	denominator = PyInt_FromLong(1);
+	py_exponent = PyInt_FromLong(labs(exponent));
 	if (py_exponent == NULL) goto error;
 	INPLACE_UPDATE(py_exponent,
 		       long_methods->nb_lshift(denominator, py_exponent));
@@ -1289,7 +1224,6 @@
 #undef INPLACE_UPDATE
 error:
 	Py_XDECREF(py_exponent);
-	Py_XDECREF(py_chunk);
 	Py_XDECREF(denominator);
 	Py_XDECREF(numerator);
 	return result_pair;
@@ -1298,17 +1232,16 @@
 PyDoc_STRVAR(float_as_integer_ratio_doc,
 "float.as_integer_ratio() -> (int, int)\n"
 "\n"
-"Returns a pair of integers, not necessarily in lowest terms, whose\n"
-"ratio is exactly equal to the original float. This method raises an\n"
-"OverflowError on infinities and a ValueError on nans. The resulting\n"
-"denominator will be positive.\n"
+"Returns a pair of integers, whose ratio is exactly equal to the original\n"
+"float and with a positive denominator.\n"
+"Raises OverflowError on infinities and a ValueError on nans.\n"
 "\n"
 ">>> (10.0).as_integer_ratio()\n"
-"(167772160L, 16777216L)\n"
+"(10, 1)\n"
 ">>> (0.0).as_integer_ratio()\n"
 "(0, 1)\n"
 ">>> (-.25).as_integer_ratio()\n"
-"(-134217728L, 536870912L)");
+"(-1, 4)");
 
 
 static PyObject *


More information about the Python-checkins mailing list