https://github.com/python/cpython/commit/5bf0f3666e272798789ff900b1071760c73b46fd commit: 5bf0f3666e272798789ff900b1071760c73b46fd branch: main author: Sergey B Kirpichev <skirpic...@gmail.com> committer: vstinner <vstin...@python.org> date: 2025-04-28T15:05:56+02:00 summary:
gh-53032: support IEEE 754 contexts in the decimal module (#122003) This was in C version from beginning, but available only on conditional compilation (EXTRA_FUNCTIONALITY). Current patch adds function to create IEEE contexts to the pure-python module as well. Co-authored-by: Bénédikt Tran <10796600+picn...@users.noreply.github.com> files: A Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.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 06f71a4a83da24..deaad059ef8fb1 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1031,6 +1031,14 @@ function to temporarily change the active context. .. versionchanged:: 3.11 :meth:`localcontext` now supports setting context attributes through the use of keyword arguments. +.. function:: IEEEContext(bits) + + Return a context object initialized to the proper values for one of the + IEEE interchange formats. The argument must be a multiple of 32 and less + than :const:`IEEE_CONTEXT_MAX_BITS`. + + .. versionadded:: next + New contexts can also be created using the :class:`Context` constructor described below. In addition, the module provides three pre-made contexts: @@ -1552,18 +1560,19 @@ Constants The constants in this section are only relevant for the C module. They are also included in the pure Python version for compatibility. -+---------------------+---------------------+-------------------------------+ -| | 32-bit | 64-bit | -+=====================+=====================+===============================+ -| .. data:: MAX_PREC | ``425000000`` | ``999999999999999999`` | -+---------------------+---------------------+-------------------------------+ -| .. data:: MAX_EMAX | ``425000000`` | ``999999999999999999`` | -+---------------------+---------------------+-------------------------------+ -| .. data:: MIN_EMIN | ``-425000000`` | ``-999999999999999999`` | -+---------------------+---------------------+-------------------------------+ -| .. data:: MIN_ETINY | ``-849999999`` | ``-1999999999999999997`` | -+---------------------+---------------------+-------------------------------+ - ++---------------------------------+---------------------+-------------------------------+ +| | 32-bit | 64-bit | ++=================================+=====================+===============================+ +| .. data:: MAX_PREC | ``425000000`` | ``999999999999999999`` | ++---------------------------------+---------------------+-------------------------------+ +| .. data:: MAX_EMAX | ``425000000`` | ``999999999999999999`` | ++---------------------------------+---------------------+-------------------------------+ +| .. data:: MIN_EMIN | ``-425000000`` | ``-999999999999999999`` | ++---------------------------------+---------------------+-------------------------------+ +| .. data:: MIN_ETINY | ``-849999999`` | ``-1999999999999999997`` | ++---------------------------------+---------------------+-------------------------------+ +| .. data:: IEEE_CONTEXT_MAX_BITS | ``256`` | ``512`` | ++---------------------------------+---------------------+-------------------------------+ .. data:: HAVE_THREADS diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 13ac8d63aef33c..95a4d0d5822362 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -750,6 +750,10 @@ decimal :meth:`Decimal.from_number() <decimal.Decimal.from_number>`. (Contributed by Serhiy Storchaka in :gh:`121798`.) +* Expose :func:`decimal.IEEEContext` to support creation of contexts + corresponding to the IEEE 754 (2008) decimal interchange formats. + (Contributed by Sergey B Kirpichev in :gh:`53032`.) + difflib ------- diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index d666c4133c3c25..4b09207eca6fac 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -38,10 +38,10 @@ 'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP', # Functions for manipulating contexts - 'setcontext', 'getcontext', 'localcontext', + 'setcontext', 'getcontext', 'localcontext', 'IEEEContext', # Limits for the C version for compatibility - 'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', + 'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', 'IEEE_CONTEXT_MAX_BITS', # C version: compile time choice that enables the thread local context (deprecated, now always true) 'HAVE_THREADS', @@ -83,10 +83,12 @@ MAX_PREC = 999999999999999999 MAX_EMAX = 999999999999999999 MIN_EMIN = -999999999999999999 + IEEE_CONTEXT_MAX_BITS = 512 else: MAX_PREC = 425000000 MAX_EMAX = 425000000 MIN_EMIN = -425000000 + IEEE_CONTEXT_MAX_BITS = 256 MIN_ETINY = MIN_EMIN - (MAX_PREC-1) @@ -417,6 +419,27 @@ def sin(x): return ctx_manager +def IEEEContext(bits, /): + """ + Return a context object initialized to the proper values for one of the + IEEE interchange formats. The argument must be a multiple of 32 and less + than IEEE_CONTEXT_MAX_BITS. + """ + if bits <= 0 or bits > IEEE_CONTEXT_MAX_BITS or bits % 32: + raise ValueError("argument must be a multiple of 32, " + f"with a maximum of {IEEE_CONTEXT_MAX_BITS}") + + ctx = Context() + ctx.prec = 9 * (bits//32) - 2 + ctx.Emax = 3 * (1 << (bits//16 + 3)) + ctx.Emin = 1 - ctx.Emax + ctx.rounding = ROUND_HALF_EVEN + ctx.clamp = 1 + ctx.traps = dict.fromkeys(_signals, False) + + return ctx + + ##### Decimal class ####################################################### # Do not subclass Decimal from numbers.Real and do not register it as such diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 92dafc56dc2d0b..9e298401dc3dcc 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4399,6 +4399,51 @@ class CContextSubclassing(ContextSubclassing, unittest.TestCase): class PyContextSubclassing(ContextSubclassing, unittest.TestCase): decimal = P +class IEEEContexts: + + def test_ieee_context(self): + # issue 8786: Add support for IEEE 754 contexts to decimal module. + IEEEContext = self.decimal.IEEEContext + + def assert_rest(self, context): + self.assertEqual(context.clamp, 1) + assert_signals(self, context, 'traps', []) + assert_signals(self, context, 'flags', []) + + c = IEEEContext(32) + self.assertEqual(c.prec, 7) + self.assertEqual(c.Emax, 96) + self.assertEqual(c.Emin, -95) + assert_rest(self, c) + + c = IEEEContext(64) + self.assertEqual(c.prec, 16) + self.assertEqual(c.Emax, 384) + self.assertEqual(c.Emin, -383) + assert_rest(self, c) + + c = IEEEContext(128) + self.assertEqual(c.prec, 34) + self.assertEqual(c.Emax, 6144) + self.assertEqual(c.Emin, -6143) + assert_rest(self, c) + + # Invalid values + self.assertRaises(ValueError, IEEEContext, -1) + self.assertRaises(ValueError, IEEEContext, 123) + self.assertRaises(ValueError, IEEEContext, 1024) + + def test_constants(self): + # IEEEContext + IEEE_CONTEXT_MAX_BITS = self.decimal.IEEE_CONTEXT_MAX_BITS + self.assertIn(IEEE_CONTEXT_MAX_BITS, {256, 512}) + +@requires_cdecimal +class CIEEEContexts(IEEEContexts, unittest.TestCase): + decimal = C +class PyIEEEContexts(IEEEContexts, unittest.TestCase): + decimal = P + @skip_if_extra_functionality @requires_cdecimal class CheckAttributes(unittest.TestCase): @@ -4410,6 +4455,7 @@ def test_module_attributes(self): self.assertEqual(C.MAX_EMAX, P.MAX_EMAX) self.assertEqual(C.MIN_EMIN, P.MIN_EMIN) self.assertEqual(C.MIN_ETINY, P.MIN_ETINY) + self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, P.IEEE_CONTEXT_MAX_BITS) self.assertTrue(C.HAVE_THREADS is True or C.HAVE_THREADS is False) self.assertTrue(P.HAVE_THREADS is True or P.HAVE_THREADS is False) @@ -4893,42 +4939,6 @@ def test_py__round(self): class CFunctionality(unittest.TestCase): """Extra functionality in _decimal""" - @requires_extra_functionality - def test_c_ieee_context(self): - # issue 8786: Add support for IEEE 754 contexts to decimal module. - IEEEContext = C.IEEEContext - DECIMAL32 = C.DECIMAL32 - DECIMAL64 = C.DECIMAL64 - DECIMAL128 = C.DECIMAL128 - - def assert_rest(self, context): - self.assertEqual(context.clamp, 1) - assert_signals(self, context, 'traps', []) - assert_signals(self, context, 'flags', []) - - c = IEEEContext(DECIMAL32) - self.assertEqual(c.prec, 7) - self.assertEqual(c.Emax, 96) - self.assertEqual(c.Emin, -95) - assert_rest(self, c) - - c = IEEEContext(DECIMAL64) - self.assertEqual(c.prec, 16) - self.assertEqual(c.Emax, 384) - self.assertEqual(c.Emin, -383) - assert_rest(self, c) - - c = IEEEContext(DECIMAL128) - self.assertEqual(c.prec, 34) - self.assertEqual(c.Emax, 6144) - self.assertEqual(c.Emin, -6143) - assert_rest(self, c) - - # Invalid values - self.assertRaises(OverflowError, IEEEContext, 2**63) - self.assertRaises(ValueError, IEEEContext, -1) - self.assertRaises(ValueError, IEEEContext, 1024) - @requires_extra_functionality def test_c_context(self): Context = C.Context @@ -4949,12 +4959,6 @@ def test_constants(self): C.DecSubnormal, C.DecUnderflow ) - # IEEEContext - self.assertEqual(C.DECIMAL32, 32) - self.assertEqual(C.DECIMAL64, 64) - self.assertEqual(C.DECIMAL128, 128) - self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, 512) - # Conditions for i, v in enumerate(cond): self.assertEqual(v, 1<<i) diff --git a/Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst b/Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst new file mode 100644 index 00000000000000..86c19bf660d756 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst @@ -0,0 +1,3 @@ +Expose :func:`decimal.IEEEContext` to support creation of contexts +corresponding to the IEEE 754 (2008) decimal interchange formats. +Patch by Sergey B Kirpichev. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index c7a0eed1118434..602b23cfca8945 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1557,7 +1557,6 @@ init_extended_context(PyObject *v) CtxCaps(v) = 1; } -#ifdef EXTRA_FUNCTIONALITY /* Factory function for creating IEEE interchange format contexts */ static PyObject * ieee_context(PyObject *module, PyObject *v) @@ -1593,7 +1592,6 @@ ieee_context(PyObject *module, PyObject *v) return NULL; } -#endif static PyObject * context_copy(PyObject *self, PyObject *Py_UNUSED(dummy)) @@ -5886,9 +5884,7 @@ static PyMethodDef _decimal_methods [] = { "getcontext", PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, { "setcontext", PyDec_SetCurrentContext, METH_O, doc_setcontext}, { "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext}, -#ifdef EXTRA_FUNCTIONALITY { "IEEEContext", ieee_context, METH_O, doc_ieee_context}, -#endif { NULL, NULL, 1, NULL } }; @@ -5905,11 +5901,11 @@ static struct ssize_constmap ssize_constants [] = { struct int_constmap { const char *name; int val; }; static struct int_constmap int_constants [] = { /* int constants */ + {"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS}, #ifdef EXTRA_FUNCTIONALITY {"DECIMAL32", MPD_DECIMAL32}, {"DECIMAL64", MPD_DECIMAL64}, {"DECIMAL128", MPD_DECIMAL128}, - {"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS}, /* int condition flags */ {"DecClamped", MPD_Clamped}, {"DecConversionSyntax", MPD_Conversion_syntax}, diff --git a/Modules/_decimal/docstrings.h b/Modules/_decimal/docstrings.h index 5abd7b9d807e19..77017a92252cb8 100644 --- a/Modules/_decimal/docstrings.h +++ b/Modules/_decimal/docstrings.h @@ -37,15 +37,12 @@ exiting the with-statement. If no context is specified, a copy of the current\n\ default context is used.\n\ \n"); -#ifdef EXTRA_FUNCTIONALITY PyDoc_STRVAR(doc_ieee_context, "IEEEContext($module, bits, /)\n--\n\n\ Return a context object initialized to the proper values for one of the\n\ IEEE interchange formats. The argument must be a multiple of 32 and less\n\ -than IEEE_CONTEXT_MAX_BITS. For the most common values, the constants\n\ -DECIMAL32, DECIMAL64 and DECIMAL128 are provided.\n\ +than IEEE_CONTEXT_MAX_BITS.\n\ \n"); -#endif /******************************************************************************/ _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com