cpython: f3b09c269af0 (original) (raw)
Mercurial > cpython
changeset 99700:f3b09c269af0
Issue #25928: Add Decimal.as_integer_ratio(). Python parts and docs by Mark Dickinson. [#25928]
Stefan Krah skrah@bytereef.org | |
---|---|
date | Mon, 28 Dec 2015 23:02:02 +0100 |
parents | 1472c08d9c23 |
children | 7f44e0ff6d72 |
files | Doc/library/decimal.rst Lib/_pydecimal.py Lib/test/test_decimal.py Misc/NEWS Modules/_decimal/_decimal.c Modules/_decimal/docstrings.h Modules/_decimal/tests/deccheck.py |
diffstat | 7 files changed, 213 insertions(+), 3 deletions(-)[+] [-] Doc/library/decimal.rst 13 Lib/_pydecimal.py 52 Lib/test/test_decimal.py 33 Misc/NEWS 2 Modules/_decimal/_decimal.c 101 Modules/_decimal/docstrings.h 9 Modules/_decimal/tests/deccheck.py 6 |
line wrap: on
line diff
--- a/Doc/library/decimal.rst
+++ b/Doc/library/decimal.rst
@@ -448,6 +448,19 @@ Decimal objects
Decimal('321e+5').adjusted()
returns seven. Used for determining the
position of the most significant digit with respect to the decimal point.
- .. method:: as_integer_ratio() +
Return a pair ``(n, d)`` of integers that represent the given[](#l1.9)
:class:`Decimal` instance as a fraction, in lowest terms and[](#l1.10)
with a positive denominator::[](#l1.11)
>>> Decimal('-3.14').as_integer_ratio()[](#l1.13)
(-157, 50)[](#l1.14)
The conversion is exact. Raise OverflowError on infinities and ValueError[](#l1.16)
on NaNs.[](#l1.17)
--- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -1010,6 +1010,58 @@ class Decimal(object): """ return DecimalTuple(self._sign, tuple(map(int, self._int)), self._exp)
Returns a pair (n, d) of integers. When called on an infinity[](#l2.10)
or NaN, raises OverflowError or ValueError respectively.[](#l2.11)
>>> Decimal('3.14').as_integer_ratio()[](#l2.13)
(157, 50)[](#l2.14)
>>> Decimal('-123e5').as_integer_ratio()[](#l2.15)
(-12300000, 1)[](#l2.16)
>>> Decimal('0.00').as_integer_ratio()[](#l2.17)
(0, 1)[](#l2.18)
"""[](#l2.20)
if self._is_special:[](#l2.21)
if self.is_nan():[](#l2.22)
raise ValueError("Cannot pass NaN "[](#l2.23)
"to decimal.as_integer_ratio.")[](#l2.24)
else:[](#l2.25)
raise OverflowError("Cannot pass infinity "[](#l2.26)
"to decimal.as_integer_ratio.")[](#l2.27)
if not self:[](#l2.29)
return 0, 1[](#l2.30)
# Find n, d in lowest terms such that abs(self) == n / d;[](#l2.32)
# we'll deal with the sign later.[](#l2.33)
n = int(self._int)[](#l2.34)
if self._exp >= 0:[](#l2.35)
# self is an integer.[](#l2.36)
n, d = n * 10**self._exp, 1[](#l2.37)
else:[](#l2.38)
# Find d2, d5 such that abs(self) = n / (2**d2 * 5**d5).[](#l2.39)
d5 = -self._exp[](#l2.40)
while d5 > 0 and n % 5 == 0:[](#l2.41)
n //= 5[](#l2.42)
d5 -= 1[](#l2.43)
# (n & -n).bit_length() - 1 counts trailing zeros in binary[](#l2.45)
# representation of n (provided n is nonzero).[](#l2.46)
d2 = -self._exp[](#l2.47)
shift2 = min((n & -n).bit_length() - 1, d2)[](#l2.48)
if shift2:[](#l2.49)
n >>= shift2[](#l2.50)
d2 -= shift2[](#l2.51)
d = 5**d5 << d2[](#l2.53)
if self._sign:[](#l2.55)
n = -n[](#l2.56)
return n, d[](#l2.57)
+ def repr(self): """Represents the number as an instance of Decimal.""" # Invariant: eval(repr(d)) == d
--- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -2047,6 +2047,39 @@ class UsabilityTest(unittest.TestCase): d = Decimal( (1, (0, 2, 7, 1), 'F') ) self.assertEqual(d.as_tuple(), (1, (0,), 'F'))
# exceptional cases[](#l3.10)
self.assertRaises(OverflowError,[](#l3.11)
Decimal.as_integer_ratio, Decimal('inf'))[](#l3.12)
self.assertRaises(OverflowError,[](#l3.13)
Decimal.as_integer_ratio, Decimal('-inf'))[](#l3.14)
self.assertRaises(ValueError,[](#l3.15)
Decimal.as_integer_ratio, Decimal('-nan'))[](#l3.16)
self.assertRaises(ValueError,[](#l3.17)
Decimal.as_integer_ratio, Decimal('snan123'))[](#l3.18)
for exp in range(-4, 2):[](#l3.20)
for coeff in range(1000):[](#l3.21)
for sign in '+', '-':[](#l3.22)
d = Decimal('%s%dE%d' % (sign, coeff, exp))[](#l3.23)
pq = d.as_integer_ratio()[](#l3.24)
p, q = pq[](#l3.25)
# check return type[](#l3.27)
self.assertIsInstance(pq, tuple)[](#l3.28)
self.assertIsInstance(p, int)[](#l3.29)
self.assertIsInstance(q, int)[](#l3.30)
# check normalization: q should be positive;[](#l3.32)
# p should be relatively prime to q.[](#l3.33)
self.assertGreater(q, 0)[](#l3.34)
self.assertEqual(math.gcd(p, q), 1)[](#l3.35)
# check that p/q actually gives the correct value[](#l3.37)
self.assertEqual(Decimal(p) / Decimal(q), d)[](#l3.38)
+ def test_subclassing(self): # Different behaviours when subclassing Decimal Decimal = self.decimal.Decimal
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -123,6 +123,8 @@ Core and Builtins Library ------- +- Issue #25928: Add Decimal.as_integer_ratio(). +
- Issue #25768: Have the functions in compileall return booleans instead of ints and add proper documentation and tests for the return values.
--- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3380,6 +3380,106 @@ dec_as_long(PyObject *dec, PyObject *con return (PyObject ) pylong; } +/ Convert a Decimal to its exact integer ratio representation. */ +static PyObject * +dec_as_integer_ratio(PyObject *self, PyObject *args UNUSED) +{
- PyObject *numerator = NULL;
- PyObject *denominator = NULL;
- PyObject *exponent = NULL;
- PyObject *result = NULL;
- PyObject *tmp;
- mpd_ssize_t exp;
- PyObject *context;
- uint32_t status = 0;
- PyNumberMethods *long_methods = PyLong_Type.tp_as_number;
- if (mpd_isspecial(MPD(self))) {
if (mpd_isnan(MPD(self))) {[](#l5.22)
PyErr_SetString(PyExc_ValueError,[](#l5.23)
"cannot convert NaN to integer ratio");[](#l5.24)
}[](#l5.25)
else {[](#l5.26)
PyErr_SetString(PyExc_OverflowError,[](#l5.27)
"cannot convert Infinity to integer ratio");[](#l5.28)
}[](#l5.29)
return NULL;[](#l5.30)
- }
- if (!mpd_qcopy(MPD(tmp), MPD(self), &status)) {
Py_DECREF(tmp);[](#l5.41)
PyErr_NoMemory();[](#l5.42)
return NULL;[](#l5.43)
- }
- /* context and rounding are unused here: the conversion is exact */
- numerator = dec_as_long(tmp, context, MPD_ROUND_FLOOR);
- Py_DECREF(tmp);
- if (numerator == NULL) {
goto error;[](#l5.53)
- }
- exponent = PyLong_FromSsize_t(exp < 0 ? -exp : exp);
- if (exponent == NULL) {
goto error;[](#l5.58)
- }
- Py_SETREF(exponent, long_methods->nb_power(tmp, exponent, Py_None));
- Py_DECREF(tmp);
- if (exponent == NULL) {
goto error;[](#l5.69)
- }
- if (exp >= 0) {
Py_SETREF(numerator, long_methods->nb_multiply(numerator, exponent));[](#l5.73)
if (numerator == NULL) {[](#l5.74)
goto error;[](#l5.75)
}[](#l5.76)
denominator = PyLong_FromLong(1);[](#l5.77)
if (denominator == NULL) {[](#l5.78)
goto error;[](#l5.79)
}[](#l5.80)
- }
- else {
denominator = exponent;[](#l5.83)
exponent = NULL;[](#l5.84)
tmp = _PyLong_GCD(numerator, denominator);[](#l5.85)
if (tmp == NULL) {[](#l5.86)
goto error;[](#l5.87)
}[](#l5.88)
Py_SETREF(numerator, long_methods->nb_floor_divide(numerator, tmp));[](#l5.89)
Py_SETREF(denominator, long_methods->nb_floor_divide(denominator, tmp));[](#l5.90)
Py_DECREF(tmp);[](#l5.91)
if (numerator == NULL || denominator == NULL) {[](#l5.92)
goto error;[](#l5.93)
}[](#l5.94)
- }
+} + static PyObject * PyDec_ToIntegralValue(PyObject *dec, PyObject *args, PyObject kwds) { @@ -4688,6 +4788,7 @@ static PyMethodDef dec_methods [] = / Miscellaneous */ { "from_float", dec_from_float, METH_O|METH_CLASS, doc_from_float }, { "as_tuple", PyDec_AsTuple, METH_NOARGS, doc_as_tuple },
- { "as_integer_ratio", dec_as_integer_ratio, METH_NOARGS, doc_as_integer_ratio }, /* Special methods */ { "copy", dec_copy, METH_NOARGS, NULL },
--- a/Modules/_decimal/docstrings.h +++ b/Modules/_decimal/docstrings.h @@ -70,6 +70,15 @@ PyDoc_STRVAR(doc_as_tuple, Return a tuple representation of the number.\n[](#l6.4) \n"); +PyDoc_STRVAR(doc_as_integer_ratio, +"as_integer_ratio($self, /)\n--\n\n[](#l6.8) +Decimal.as_integer_ratio() -> (int, int)\n[](#l6.9) +\n[](#l6.10) +Return a pair of integers, whose ratio is exactly equal to the original\n[](#l6.11) +Decimal and with a positive denominator. The ratio is in lowest terms.\n[](#l6.12) +Raise OverflowError on infinities and a ValueError on NaNs.\n[](#l6.13) +\n"); + PyDoc_STRVAR(doc_canonical, "canonical($self, /)\n--\n\n[](#l6.17) Return the canonical encoding of the argument. Currently, the encoding\n[](#l6.18)
--- a/Modules/_decimal/tests/deccheck.py +++ b/Modules/_decimal/tests/deccheck.py @@ -50,8 +50,8 @@ Functions = { 'abs', 'bool', 'ceil', 'complex', 'copy', 'floor', 'float', 'hash', 'int', 'neg', 'pos', 'reduce', 'repr', 'str', 'trunc',
'adjusted', 'as_tuple', 'canonical', 'conjugate', 'copy_abs',[](#l7.7)
'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',[](#l7.8)
'adjusted', 'as_integer_ratio', 'as_tuple', 'canonical', 'conjugate',[](#l7.9)
),'copy_abs', 'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',[](#l7.10) 'is_nan', 'is_qnan', 'is_signed', 'is_snan', 'is_zero', 'radix'[](#l7.11)
@@ -128,7 +128,7 @@ ContextFunctions = { Unary with optional context: