mirror of https://github.com/python/cpython
Moved Rational._binary_float_to_ratio() to float.as_integer_ratio() because
it's useful outside of rational numbers. This is my first C code that had to do anything significant. Please be more careful when looking over it.
This commit is contained in:
parent
56eadd9d0d
commit
3ea7b41b58
|
@ -25,60 +25,6 @@ def gcd(a, b):
|
||||||
return a
|
return a
|
||||||
|
|
||||||
|
|
||||||
def _binary_float_to_ratio(x):
|
|
||||||
"""x -> (top, bot), a pair of ints s.t. x = top/bot.
|
|
||||||
|
|
||||||
The conversion is done exactly, without rounding.
|
|
||||||
bot > 0 guaranteed.
|
|
||||||
Some form of binary fp is assumed.
|
|
||||||
Pass NaNs or infinities at your own risk.
|
|
||||||
|
|
||||||
>>> _binary_float_to_ratio(10.0)
|
|
||||||
(10, 1)
|
|
||||||
>>> _binary_float_to_ratio(0.0)
|
|
||||||
(0, 1)
|
|
||||||
>>> _binary_float_to_ratio(-.25)
|
|
||||||
(-1, 4)
|
|
||||||
"""
|
|
||||||
# XXX Move this to floatobject.c with a name like
|
|
||||||
# float.as_integer_ratio()
|
|
||||||
|
|
||||||
if x == 0:
|
|
||||||
return 0, 1
|
|
||||||
f, e = math.frexp(x)
|
|
||||||
signbit = 1
|
|
||||||
if f < 0:
|
|
||||||
f = -f
|
|
||||||
signbit = -1
|
|
||||||
assert 0.5 <= f < 1.0
|
|
||||||
# x = signbit * f * 2**e exactly
|
|
||||||
|
|
||||||
# Suck up CHUNK 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 an int.
|
|
||||||
CHUNK = 28
|
|
||||||
top = 0
|
|
||||||
# invariant: x = signbit * (top + f) * 2**e exactly
|
|
||||||
while f:
|
|
||||||
f = math.ldexp(f, CHUNK)
|
|
||||||
digit = trunc(f)
|
|
||||||
assert digit >> CHUNK == 0
|
|
||||||
top = (top << CHUNK) | digit
|
|
||||||
f = f - digit
|
|
||||||
assert 0.0 <= f < 1.0
|
|
||||||
e = e - CHUNK
|
|
||||||
assert top
|
|
||||||
|
|
||||||
# Add in the sign bit.
|
|
||||||
top = signbit * top
|
|
||||||
|
|
||||||
# now x = top * 2**e exactly; fold in 2**e
|
|
||||||
if e>0:
|
|
||||||
return (top * 2**e, 1)
|
|
||||||
else:
|
|
||||||
return (top, 2 ** -e)
|
|
||||||
|
|
||||||
|
|
||||||
_RATIONAL_FORMAT = re.compile(
|
_RATIONAL_FORMAT = re.compile(
|
||||||
r'^\s*(?P<sign>[-+]?)(?P<num>\d+)'
|
r'^\s*(?P<sign>[-+]?)(?P<num>\d+)'
|
||||||
r'(?:/(?P<denom>\d+)|\.(?P<decimal>\d+))?\s*$')
|
r'(?:/(?P<denom>\d+)|\.(?P<decimal>\d+))?\s*$')
|
||||||
|
@ -163,7 +109,7 @@ class Rational(RationalAbc):
|
||||||
(cls.__name__, f, type(f).__name__))
|
(cls.__name__, f, type(f).__name__))
|
||||||
if math.isnan(f) or math.isinf(f):
|
if math.isnan(f) or math.isinf(f):
|
||||||
raise TypeError("Cannot convert %r to %s." % (f, cls.__name__))
|
raise TypeError("Cannot convert %r to %s." % (f, cls.__name__))
|
||||||
return cls(*_binary_float_to_ratio(f))
|
return cls(*f.as_integer_ratio())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_decimal(cls, dec):
|
def from_decimal(cls, dec):
|
||||||
|
|
|
@ -5,7 +5,7 @@ from test.test_support import fcmp, have_unicode, TESTFN, unlink, \
|
||||||
run_unittest, run_with_locale
|
run_unittest, run_with_locale
|
||||||
from operator import neg
|
from operator import neg
|
||||||
|
|
||||||
import sys, warnings, cStringIO, random, UserDict
|
import sys, warnings, cStringIO, random, rational, UserDict
|
||||||
warnings.filterwarnings("ignore", "hex../oct.. of negative int",
|
warnings.filterwarnings("ignore", "hex../oct.. of negative int",
|
||||||
FutureWarning, __name__)
|
FutureWarning, __name__)
|
||||||
warnings.filterwarnings("ignore", "integer argument expected",
|
warnings.filterwarnings("ignore", "integer argument expected",
|
||||||
|
@ -688,6 +688,25 @@ class BuiltinTest(unittest.TestCase):
|
||||||
self.assertAlmostEqual(float(Foo3(21)), 42.)
|
self.assertAlmostEqual(float(Foo3(21)), 42.)
|
||||||
self.assertRaises(TypeError, float, Foo4(42))
|
self.assertRaises(TypeError, float, Foo4(42))
|
||||||
|
|
||||||
|
def test_floatasratio(self):
|
||||||
|
R = rational.Rational
|
||||||
|
self.assertEqual(R(0, 1),
|
||||||
|
R(*float(0.0).as_integer_ratio()))
|
||||||
|
self.assertEqual(R(5, 2),
|
||||||
|
R(*float(2.5).as_integer_ratio()))
|
||||||
|
self.assertEqual(R(1, 2),
|
||||||
|
R(*float(0.5).as_integer_ratio()))
|
||||||
|
self.assertEqual(R(4728779608739021, 2251799813685248),
|
||||||
|
R(*float(2.1).as_integer_ratio()))
|
||||||
|
self.assertEqual(R(-4728779608739021, 2251799813685248),
|
||||||
|
R(*float(-2.1).as_integer_ratio()))
|
||||||
|
self.assertEqual(R(-2100, 1),
|
||||||
|
R(*float(-2100.0).as_integer_ratio()))
|
||||||
|
|
||||||
|
self.assertRaises(OverflowError, float('inf').as_integer_ratio)
|
||||||
|
self.assertRaises(OverflowError, float('-inf').as_integer_ratio)
|
||||||
|
self.assertRaises(ValueError, float('nan').as_integer_ratio)
|
||||||
|
|
||||||
def test_getattr(self):
|
def test_getattr(self):
|
||||||
import sys
|
import sys
|
||||||
self.assert_(getattr(sys, 'stdout') is sys.stdout)
|
self.assert_(getattr(sys, 'stdout') is sys.stdout)
|
||||||
|
|
|
@ -1161,6 +1161,163 @@ float_float(PyObject *v)
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
float_as_integer_ratio(PyObject *v)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
#define INPLACE_UPDATE(obj, call) \
|
||||||
|
prev = obj; \
|
||||||
|
obj = call; \
|
||||||
|
Py_DECREF(prev); \
|
||||||
|
|
||||||
|
CONVERT_TO_DOUBLE(v, self);
|
||||||
|
|
||||||
|
if (Py_IS_INFINITY(self)) {
|
||||||
|
PyErr_SetString(PyExc_OverflowError,
|
||||||
|
"Cannot pass infinity to float.as_integer_ratio.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#ifdef Py_NAN
|
||||||
|
if (Py_IS_NAN(self)) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"Cannot pass nan to float.as_integer_ratio.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#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 */
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
|
denominator = PyLong_FromLong(1);
|
||||||
|
py_exponent = PyLong_FromLong(labs(exponent));
|
||||||
|
if (py_exponent == NULL) goto error;
|
||||||
|
INPLACE_UPDATE(py_exponent,
|
||||||
|
long_methods->nb_lshift(denominator, py_exponent));
|
||||||
|
if (py_exponent == NULL) goto error;
|
||||||
|
if (exponent > 0) {
|
||||||
|
INPLACE_UPDATE(numerator,
|
||||||
|
long_methods->nb_multiply(numerator,
|
||||||
|
py_exponent));
|
||||||
|
if (numerator == NULL) goto error;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Py_DECREF(denominator);
|
||||||
|
denominator = py_exponent;
|
||||||
|
py_exponent = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_pair = PyTuple_Pack(2, numerator, denominator);
|
||||||
|
|
||||||
|
#undef INPLACE_UPDATE
|
||||||
|
error:
|
||||||
|
Py_XDECREF(py_exponent);
|
||||||
|
Py_XDECREF(py_chunk);
|
||||||
|
Py_XDECREF(denominator);
|
||||||
|
Py_XDECREF(numerator);
|
||||||
|
return result_pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
"\n"
|
||||||
|
">>> (10.0).as_integer_ratio()\n"
|
||||||
|
"(167772160L, 16777216L)\n"
|
||||||
|
">>> (0.0).as_integer_ratio()\n"
|
||||||
|
"(0, 1)\n"
|
||||||
|
">>> (-.25).as_integer_ratio()\n"
|
||||||
|
"(-134217728L, 536870912L)");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
float_subtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
|
float_subtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
|
||||||
|
@ -1349,6 +1506,8 @@ static PyMethodDef float_methods[] = {
|
||||||
"Returns self, the complex conjugate of any float."},
|
"Returns self, the complex conjugate of any float."},
|
||||||
{"__trunc__", (PyCFunction)float_trunc, METH_NOARGS,
|
{"__trunc__", (PyCFunction)float_trunc, METH_NOARGS,
|
||||||
"Returns the Integral closest to x between 0 and x."},
|
"Returns the Integral closest to x between 0 and x."},
|
||||||
|
{"as_integer_ratio", (PyCFunction)float_as_integer_ratio, METH_NOARGS,
|
||||||
|
float_as_integer_ratio_doc},
|
||||||
{"__getnewargs__", (PyCFunction)float_getnewargs, METH_NOARGS},
|
{"__getnewargs__", (PyCFunction)float_getnewargs, METH_NOARGS},
|
||||||
{"__getformat__", (PyCFunction)float_getformat,
|
{"__getformat__", (PyCFunction)float_getformat,
|
||||||
METH_O|METH_CLASS, float_getformat_doc},
|
METH_O|METH_CLASS, float_getformat_doc},
|
||||||
|
|
Loading…
Reference in New Issue