summaryrefslogtreecommitdiff
path: root/Modules/_decimal
diff options
context:
space:
mode:
authorStefan Krah <skrah@bytereef.org>2015-12-28 23:02:02 +0100
committerStefan Krah <skrah@bytereef.org>2015-12-28 23:02:02 +0100
commit53f2e0ad45e41c007f714e077ccf11643651ef28 (patch)
treec00ffd5dd955b9685de5c1a12fd014745b22f361 /Modules/_decimal
parentac1e7f6983ef4f0feef6389d458544bf924f6965 (diff)
downloadcpython-git-53f2e0ad45e41c007f714e077ccf11643651ef28.tar.gz
Issue #25928: Add Decimal.as_integer_ratio(). Python parts and docs by
Mark Dickinson.
Diffstat (limited to 'Modules/_decimal')
-rw-r--r--Modules/_decimal/_decimal.c101
-rw-r--r--Modules/_decimal/docstrings.h9
-rw-r--r--Modules/_decimal/tests/deccheck.py6
3 files changed, 113 insertions, 3 deletions
diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c
index 112b44fda7..3c2ad851ba 100644
--- a/Modules/_decimal/_decimal.c
+++ b/Modules/_decimal/_decimal.c
@@ -3380,6 +3380,106 @@ dec_as_long(PyObject *dec, PyObject *context, int round)
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))) {
+ PyErr_SetString(PyExc_ValueError,
+ "cannot convert NaN to integer ratio");
+ }
+ else {
+ PyErr_SetString(PyExc_OverflowError,
+ "cannot convert Infinity to integer ratio");
+ }
+ return NULL;
+ }
+
+ CURRENT_CONTEXT(context);
+
+ tmp = dec_alloc();
+ if (tmp == NULL) {
+ return NULL;
+ }
+
+ if (!mpd_qcopy(MPD(tmp), MPD(self), &status)) {
+ Py_DECREF(tmp);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ exp = mpd_iszero(MPD(tmp)) ? 0 : MPD(tmp)->exp;
+ MPD(tmp)->exp = 0;
+
+ /* 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;
+ }
+
+ exponent = PyLong_FromSsize_t(exp < 0 ? -exp : exp);
+ if (exponent == NULL) {
+ goto error;
+ }
+
+ tmp = PyLong_FromLong(10);
+ if (tmp == NULL) {
+ goto error;
+ }
+
+ Py_SETREF(exponent, long_methods->nb_power(tmp, exponent, Py_None));
+ Py_DECREF(tmp);
+ if (exponent == NULL) {
+ goto error;
+ }
+
+ if (exp >= 0) {
+ Py_SETREF(numerator, long_methods->nb_multiply(numerator, exponent));
+ if (numerator == NULL) {
+ goto error;
+ }
+ denominator = PyLong_FromLong(1);
+ if (denominator == NULL) {
+ goto error;
+ }
+ }
+ else {
+ denominator = exponent;
+ exponent = NULL;
+ tmp = _PyLong_GCD(numerator, denominator);
+ if (tmp == NULL) {
+ goto error;
+ }
+ Py_SETREF(numerator, long_methods->nb_floor_divide(numerator, tmp));
+ Py_SETREF(denominator, long_methods->nb_floor_divide(denominator, tmp));
+ Py_DECREF(tmp);
+ if (numerator == NULL || denominator == NULL) {
+ goto error;
+ }
+ }
+
+ result = PyTuple_Pack(2, numerator, denominator);
+
+
+error:
+ Py_XDECREF(exponent);
+ Py_XDECREF(denominator);
+ Py_XDECREF(numerator);
+ return result;
+}
+
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 },
diff --git a/Modules/_decimal/docstrings.h b/Modules/_decimal/docstrings.h
index 71029a994b..f7fd6e7952 100644
--- 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\
\n");
+PyDoc_STRVAR(doc_as_integer_ratio,
+"as_integer_ratio($self, /)\n--\n\n\
+Decimal.as_integer_ratio() -> (int, int)\n\
+\n\
+Return a pair of integers, whose ratio is exactly equal to the original\n\
+Decimal and with a positive denominator. The ratio is in lowest terms.\n\
+Raise OverflowError on infinities and a ValueError on NaNs.\n\
+\n");
+
PyDoc_STRVAR(doc_canonical,
"canonical($self, /)\n--\n\n\
Return the canonical encoding of the argument. Currently, the encoding\n\
diff --git a/Modules/_decimal/tests/deccheck.py b/Modules/_decimal/tests/deccheck.py
index ab7d5bdf4e..f907531e1f 100644
--- 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',
- 'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',
+ 'adjusted', 'as_integer_ratio', 'as_tuple', 'canonical', 'conjugate',
+ 'copy_abs', 'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',
'is_nan', 'is_qnan', 'is_signed', 'is_snan', 'is_zero', 'radix'
),
# Unary with optional context:
@@ -128,7 +128,7 @@ ContextFunctions = {
# Functions that require a restricted exponent range for reasonable runtimes.
UnaryRestricted = [
'__ceil__', '__floor__', '__int__', '__trunc__',
- 'to_integral', 'to_integral_value'
+ 'as_integer_ratio', 'to_integral', 'to_integral_value'
]
BinaryRestricted = ['__round__']