Commit ba284896 authored by Raymond Hettinger's avatar Raymond Hettinger

Issue #1996: float.as_integer_ratio() should return fraction in lowest terms.

parent fc45e1d0
...@@ -689,6 +689,14 @@ class BuiltinTest(unittest.TestCase): ...@@ -689,6 +689,14 @@ class BuiltinTest(unittest.TestCase):
self.assertRaises(TypeError, float, Foo4(42)) self.assertRaises(TypeError, float, Foo4(42))
def test_floatasratio(self): 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 R = rational.Rational
self.assertEqual(R(0, 1), self.assertEqual(R(0, 1),
R(*float(0.0).as_integer_ratio())) R(*float(0.0).as_integer_ratio()))
......
...@@ -1154,20 +1154,18 @@ float_float(PyObject *v) ...@@ -1154,20 +1154,18 @@ float_float(PyObject *v)
} }
static PyObject * static PyObject *
float_as_integer_ratio(PyObject *v) float_as_integer_ratio(PyObject *v, PyObject *unused)
{ {
double self; double self;
double float_part; double float_part;
int exponent; int exponent;
int is_negative;
const int chunk_size = 28;
PyObject *prev; PyObject *prev;
PyObject *py_chunk = NULL;
PyObject *py_exponent = NULL; PyObject *py_exponent = NULL;
PyObject *numerator = NULL; PyObject *numerator = NULL;
PyObject *denominator = NULL; PyObject *denominator = NULL;
PyObject *result_pair = NULL; PyObject *result_pair = NULL;
PyNumberMethods *long_methods; PyNumberMethods *long_methods = PyLong_Type.tp_as_number;
#define INPLACE_UPDATE(obj, call) \ #define INPLACE_UPDATE(obj, call) \
prev = obj; \ prev = obj; \
...@@ -1189,85 +1187,22 @@ float_as_integer_ratio(PyObject *v) ...@@ -1189,85 +1187,22 @@ float_as_integer_ratio(PyObject *v)
} }
#endif #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); PyFPE_START_PROTECT("as_integer_ratio", goto error);
float_part = frexp(self, &exponent); float_part = frexp(self, &exponent); /* self == float_part * 2**exponent exactly */
is_negative = 0;
if (float_part < 0) {
float_part = -float_part;
is_negative = 1;
/* 0.5 <= float_part < 1.0 */
}
PyFPE_END_PROTECT(float_part); PyFPE_END_PROTECT(float_part);
/* abs(self) == float_part * 2**exponent exactly */
while (float_part != floor(float_part)) {
/* Suck up chunk_size bits at a time; 28 is enough so that we float_part *= 2.0;
suck up all bits in 2 iterations for all known binary exponent--;
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;
} }
/* Now, self == float_part * 2**exponent exactly and float_part is integral */
/* Add in the sign bit. */ numerator = PyLong_FromDouble(float_part);
if (is_negative) { if (numerator == NULL) goto error;
INPLACE_UPDATE(numerator,
long_methods->nb_negative(numerator));
if (numerator == NULL) goto error;
}
/* now self = numerator * 2**exponent exactly; fold in 2**exponent */ /* now self = numerator * 2**exponent exactly; fold in 2**exponent */
denominator = PyLong_FromLong(1); denominator = PyInt_FromLong(1);
py_exponent = PyLong_FromLong(labs(exponent)); py_exponent = PyInt_FromLong(labs(exponent));
if (py_exponent == NULL) goto error; if (py_exponent == NULL) goto error;
INPLACE_UPDATE(py_exponent, INPLACE_UPDATE(py_exponent,
long_methods->nb_lshift(denominator, py_exponent)); long_methods->nb_lshift(denominator, py_exponent));
...@@ -1289,7 +1224,6 @@ float_as_integer_ratio(PyObject *v) ...@@ -1289,7 +1224,6 @@ float_as_integer_ratio(PyObject *v)
#undef INPLACE_UPDATE #undef INPLACE_UPDATE
error: error:
Py_XDECREF(py_exponent); Py_XDECREF(py_exponent);
Py_XDECREF(py_chunk);
Py_XDECREF(denominator); Py_XDECREF(denominator);
Py_XDECREF(numerator); Py_XDECREF(numerator);
return result_pair; return result_pair;
...@@ -1298,17 +1232,16 @@ error: ...@@ -1298,17 +1232,16 @@ error:
PyDoc_STRVAR(float_as_integer_ratio_doc, PyDoc_STRVAR(float_as_integer_ratio_doc,
"float.as_integer_ratio() -> (int, int)\n" "float.as_integer_ratio() -> (int, int)\n"
"\n" "\n"
"Returns a pair of integers, not necessarily in lowest terms, whose\n" "Returns a pair of integers, whose ratio is exactly equal to the original\n"
"ratio is exactly equal to the original float. This method raises an\n" "float and with a positive denominator.\n"
"OverflowError on infinities and a ValueError on nans. The resulting\n" "Raises OverflowError on infinities and a ValueError on nans.\n"
"denominator will be positive.\n"
"\n" "\n"
">>> (10.0).as_integer_ratio()\n" ">>> (10.0).as_integer_ratio()\n"
"(167772160L, 16777216L)\n" "(10, 1)\n"
">>> (0.0).as_integer_ratio()\n" ">>> (0.0).as_integer_ratio()\n"
"(0, 1)\n" "(0, 1)\n"
">>> (-.25).as_integer_ratio()\n" ">>> (-.25).as_integer_ratio()\n"
"(-134217728L, 536870912L)"); "(-1, 4)");
static PyObject * static PyObject *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment