mirror of
https://github.com/python/cpython.git
synced 2025-07-29 06:05:00 +00:00
Issue #1996: float.as_integer_ratio() should return fraction in lowest terms.
This commit is contained in:
parent
a51f61b555
commit
04c96d52a4
2 changed files with 26 additions and 85 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
#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)) {
|
||||||
|
float_part *= 2.0;
|
||||||
|
exponent--;
|
||||||
|
}
|
||||||
|
/* Now, self == float_part * 2**exponent exactly and float_part is integral */
|
||||||
|
|
||||||
/* Suck up chunk_size bits at a time; 28 is enough so that we
|
numerator = PyLong_FromDouble(float_part);
|
||||||
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;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add in the sign bit. */
|
|
||||||
if (is_negative) {
|
|
||||||
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)
|
||||||
#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:
|
||||||
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 *
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue