https://github.com/python/cpython/commit/5217328f93f599755bd70418952392c54f705a71
commit: 5217328f93f599755bd70418952392c54f705a71
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-10-14T08:24:01Z
summary:
gh-121798: Add class method Decimal.from_number() (GH-121801)
It is an alternate constructor which only accepts a single numeric argument.
Unlike to Decimal.from_float() it accepts also Decimal.
Unlike to the standard constructor, it does not accept strings and tuples.
files:
A Misc/NEWS.d/next/Library/2024-07-15-19-25-25.gh-issue-121798.GmuBDu.rst
M Doc/library/decimal.rst
M Doc/whatsnew/3.14.rst
M Lib/_pydecimal.py
M Lib/test/test_decimal.py
M Modules/_decimal/_decimal.c
M Modules/_decimal/docstrings.h
diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst
index 916f17cadfaa7e..c9a3e448cad063 100644
--- a/Doc/library/decimal.rst
+++ b/Doc/library/decimal.rst
@@ -598,6 +598,23 @@ Decimal objects
.. versionadded:: 3.1
+ .. classmethod:: from_number(number)
+
+ Alternative constructor that only accepts instances of
+ :class:`float`, :class:`int` or :class:`Decimal`, but not strings
+ or tuples.
+
+ .. doctest::
+
+ >>> Decimal.from_number(314)
+ Decimal('314')
+ >>> Decimal.from_number(0.1)
+ Decimal('0.1000000000000000055511151231257827021181583404541015625')
+ >>> Decimal.from_number(Decimal('3.14'))
+ Decimal('3.14')
+
+ .. versionadded:: 3.14
+
.. method:: fma(other, third, context=None)
Fused multiply-add. Return self*other+third with no rounding of the
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index b22d1bd1e99d4e..25e69a59bdec62 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -239,6 +239,12 @@ ctypes
to help match a non-default ABI.
(Contributed by Petr Viktorin in :gh:`97702`.)
+decimal
+-------
+
+* Add alternative :class:`~decimal.Decimal` constructor
+ :meth:`Decimal.from_number() <decimal.Decimal.from_number>`.
+ (Contributed by Serhiy Storchaka in :gh:`121798`.)
dis
---
diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index 75df3db262470b..5b60570c6c592a 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -582,6 +582,21 @@ def __new__(cls, value="0", context=None):
raise TypeError("Cannot convert %r to Decimal" % value)
+ @classmethod
+ def from_number(cls, number):
+ """Converts a real number to a decimal number, exactly.
+
+ >>> Decimal.from_number(314) # int
+ Decimal('314')
+ >>> Decimal.from_number(0.1) # float
+ Decimal('0.1000000000000000055511151231257827021181583404541015625')
+ >>> Decimal.from_number(Decimal('3.14')) # another decimal instance
+ Decimal('3.14')
+ """
+ if isinstance(number, (int, Decimal, float)):
+ return cls(number)
+ raise TypeError("Cannot convert %r to Decimal" % number)
+
@classmethod
def from_float(cls, f):
"""Converts a float to a decimal number, exactly.
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index d1e7e69e7e951b..bc6c6427740949 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -812,6 +812,29 @@ def test_explicit_context_create_from_float(self):
x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
self.assertEqual(x, float(nc.create_decimal(x))) # roundtrip
+ def test_from_number(self, cls=None):
+ Decimal = self.decimal.Decimal
+ if cls is None:
+ cls = Decimal
+
+ def check(arg, expected):
+ d = cls.from_number(arg)
+ self.assertIs(type(d), cls)
+ self.assertEqual(d, expected)
+
+ check(314, Decimal(314))
+ check(3.14, Decimal.from_float(3.14))
+ check(Decimal('3.14'), Decimal('3.14'))
+ self.assertRaises(TypeError, cls.from_number, 3+4j)
+ self.assertRaises(TypeError, cls.from_number, '314')
+ self.assertRaises(TypeError, cls.from_number, (0, (3, 1, 4), 0))
+ self.assertRaises(TypeError, cls.from_number, object())
+
+ def test_from_number_subclass(self, cls=None):
+ class DecimalSubclass(self.decimal.Decimal):
+ pass
+ self.test_from_number(DecimalSubclass)
+
def test_unicode_digits(self):
Decimal = self.decimal.Decimal
diff --git
a/Misc/NEWS.d/next/Library/2024-07-15-19-25-25.gh-issue-121798.GmuBDu.rst
b/Misc/NEWS.d/next/Library/2024-07-15-19-25-25.gh-issue-121798.GmuBDu.rst
new file mode 100644
index 00000000000000..5706e4bffeb4a1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-07-15-19-25-25.gh-issue-121798.GmuBDu.rst
@@ -0,0 +1,2 @@
+Add alternative :class:`~decimal.Decimal` constructor
+:meth:`Decimal.from_number() <decimal.Decimal.from_number>`.
diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c
index a33c9793b5ad17..c564813036e504 100644
--- a/Modules/_decimal/_decimal.c
+++ b/Modules/_decimal/_decimal.c
@@ -2857,6 +2857,51 @@ dec_from_float(PyObject *type, PyObject *pyfloat)
return result;
}
+/* 'v' can have any numeric type accepted by the Decimal constructor. Attempt
+ an exact conversion. If the result does not meet the restrictions
+ for an mpd_t, fail with InvalidOperation. */
+static PyObject *
+PyDecType_FromNumberExact(PyTypeObject *type, PyObject *v, PyObject *context)
+{
+ decimal_state *state = get_module_state_by_def(type);
+ assert(v != NULL);
+ if (PyDec_Check(state, v)) {
+ return PyDecType_FromDecimalExact(type, v, context);
+ }
+ else if (PyLong_Check(v)) {
+ return PyDecType_FromLongExact(type, v, context);
+ }
+ else if (PyFloat_Check(v)) {
+ if (dec_addstatus(context, MPD_Float_operation)) {
+ return NULL;
+ }
+ return PyDecType_FromFloatExact(type, v, context);
+ }
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "conversion from %s to Decimal is not supported",
+ Py_TYPE(v)->tp_name);
+ return NULL;
+ }
+}
+
+/* class method */
+static PyObject *
+dec_from_number(PyObject *type, PyObject *number)
+{
+ PyObject *context;
+ PyObject *result;
+
+ decimal_state *state = get_module_state_by_def((PyTypeObject *)type);
+ CURRENT_CONTEXT(state, context);
+ result = PyDecType_FromNumberExact(state->PyDec_Type, number, context);
+ if (type != (PyObject *)state->PyDec_Type && result != NULL) {
+ Py_SETREF(result, PyObject_CallFunctionObjArgs(type, result, NULL));
+ }
+
+ return result;
+}
+
/* create_decimal_from_float */
static PyObject *
ctx_from_float(PyObject *context, PyObject *v)
@@ -5052,6 +5097,7 @@ static PyMethodDef dec_methods [] =
/* Miscellaneous */
{ "from_float", dec_from_float, METH_O|METH_CLASS, doc_from_float },
+ { "from_number", dec_from_number, METH_O|METH_CLASS, doc_from_number },
{ "as_tuple", PyDec_AsTuple, METH_NOARGS, doc_as_tuple },
{ "as_integer_ratio", dec_as_integer_ratio, METH_NOARGS,
doc_as_integer_ratio },
diff --git a/Modules/_decimal/docstrings.h b/Modules/_decimal/docstrings.h
index a1823cdd32b74c..b34bff83d3f4e9 100644
--- a/Modules/_decimal/docstrings.h
+++ b/Modules/_decimal/docstrings.h
@@ -189,6 +189,19 @@ Decimal.from_float(0.1) is not the same as
Decimal('0.1').\n\
\n\
\n");
+PyDoc_STRVAR(doc_from_number,
+"from_number($type, number, /)\n--\n\n\
+Class method that converts a real number to a decimal number, exactly.\n\
+\n\
+ >>> Decimal.from_number(314) # int\n\
+ Decimal('314')\n\
+ >>> Decimal.from_number(0.1) # float\n\
+ Decimal('0.1000000000000000055511151231257827021181583404541015625')\n\
+ >>> Decimal.from_number(Decimal('3.14')) # another decimal instance\n\
+ Decimal('3.14')\n\
+\n\
+\n");
+
PyDoc_STRVAR(doc_fma,
"fma($self, /, other, third, context=None)\n--\n\n\
Fused multiply-add. Return self*other+third with no rounding of the\n\
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]