From b833af85a40dfa1302b891a3ffc095887fd14b62 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 19 Jul 2024 07:16:56 +0300 Subject: [PATCH 1/9] gh-53032: support IEEE 754 contexts in the decimal module 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. --- Doc/library/decimal.rst | 31 ++++--- Lib/_pydecimal.py | 31 ++++++- Lib/test/test_decimal.py | 89 ++++++++++--------- ...4-07-19-07-16-50.gh-issue-53032.paXN3p.rst | 3 + Modules/_decimal/_decimal.c | 13 +-- Modules/_decimal/docstrings.h | 5 +- 6 files changed, 102 insertions(+), 70 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index db323802a6f68c..7564987b9bc492 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1012,6 +1012,12 @@ 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`. + New contexts can also be created using the :class:`Context` constructor described below. In addition, the module provides three pre-made contexts: @@ -1533,18 +1539,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/Lib/_pydecimal.py b/Lib/_pydecimal.py index 613123ec7b4329..101cf29b6974b6 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,31 @@ 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. + """ + import sys + + if bits >= sys.maxsize: + raise OverflowError("Python int too large to convert to C ssize_t") + 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 = {s: False for s in _signals} + + 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 46755107de0102..70b0f8d7cd5425 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4362,6 +4362,52 @@ 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(OverflowError, IEEEContext, 2**63) + self.assertRaises(ValueError, IEEEContext, -1) + self.assertRaises(ValueError, IEEEContext, 1024) + + def test_constants(self): + # IEEEContext + IEEE_CONTEXT_MAX_BITS = self.decimal.IEEE_CONTEXT_MAX_BITS + if IEEE_CONTEXT_MAX_BITS != 256: + self.assertEqual(IEEE_CONTEXT_MAX_BITS, 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): @@ -4373,6 +4419,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) @@ -4848,42 +4895,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 @@ -4904,12 +4915,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<PyDecContext_Type, NULL); if (context == NULL) { return NULL; @@ -1552,7 +1551,6 @@ ieee_context(PyObject *dummy UNUSED, PyObject *v) return NULL; } -#endif static PyObject * context_copy(PyObject *self, PyObject *args UNUSED) @@ -5751,9 +5749,7 @@ static PyMethodDef _decimal_methods [] = { "getcontext", (PyCFunction)PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, { "setcontext", (PyCFunction)PyDec_SetCurrentContext, METH_O, doc_setcontext}, { "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext}, -#ifdef EXTRA_FUNCTIONALITY { "IEEEContext", (PyCFunction)ieee_context, METH_O, doc_ieee_context}, -#endif { NULL, NULL, 1, NULL } }; @@ -5770,11 +5766,8 @@ static struct ssize_constmap ssize_constants [] = { struct int_constmap { const char *name; int val; }; static struct int_constmap int_constants [] = { /* int constants */ -#ifdef EXTRA_FUNCTIONALITY - {"DECIMAL32", MPD_DECIMAL32}, - {"DECIMAL64", MPD_DECIMAL64}, - {"DECIMAL128", MPD_DECIMAL128}, {"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS}, +#ifdef EXTRA_FUNCTIONALITY /* int condition flags */ {"DecClamped", MPD_Clamped}, {"DecConversionSyntax", MPD_Conversion_syntax}, diff --git a/Modules/_decimal/docstrings.h b/Modules/_decimal/docstrings.h index a1823cdd32b74c..6942cc09d8b5fb 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 /******************************************************************************/ From 7a34bb2ec1d4b7c7e54ccf925a76bd9e111ca203 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 25 Sep 2024 06:55:32 +0300 Subject: [PATCH 2/9] + make lint happy --- .../next/Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 6273f22b8db2f3..90ca2a3b9ff13f 100644 --- 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 @@ -1,3 +1,3 @@ -Expose function :func:`decimal.IEEEContext()` to support creation of +Expose function :func:`decimal.IEEEContext` to support creation of contexts, corresponding to the IEEE 754 (2008) decimal interchange formats. Patch by Sergey B Kirpichev. From 042d643d5eb9cbe9d6b9957870b63559538f90d1 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 24 Jan 2025 13:46:28 +0300 Subject: [PATCH 3/9] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/_pydecimal.py | 2 +- .../Library/2024-07-19-07-16-50.gh-issue-53032.paXN3p.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index d6846cc2845e83..ed9d565a3b5b82 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -439,7 +439,7 @@ def IEEEContext(bits, /): ctx.Emin = 1 - ctx.Emax ctx.rounding = ROUND_HALF_EVEN ctx.clamp = 1 - ctx.traps = {s: False for s in _signals} + ctx.traps = dict.fromkeys(_signals, False) return ctx 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 index 90ca2a3b9ff13f..86c19bf660d756 100644 --- 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 @@ -1,3 +1,3 @@ -Expose function :func:`decimal.IEEEContext` to support creation of -contexts, corresponding to the IEEE 754 (2008) decimal interchange formats. +Expose :func:`decimal.IEEEContext` to support creation of contexts +corresponding to the IEEE 754 (2008) decimal interchange formats. Patch by Sergey B Kirpichev. From 11dd047c33bf1f7ccf7eab6396d72a2aed9aba15 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 24 Jan 2025 14:43:42 +0300 Subject: [PATCH 4/9] address reviews --- Doc/library/decimal.rst | 2 ++ Lib/test/test_decimal.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 68ea9f7f726424..6b9bbb97045ffb 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1035,6 +1035,8 @@ function to temporarily change the active context. IEEE interchange formats. The argument must be a multiple of 32 and less than :const:`IEEE_CONTEXT_MAX_BITS`. + .. versionadded:: 3.14 + New contexts can also be created using the :class:`Context` constructor described below. In addition, the module provides three pre-made contexts: diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 5f0685dcd0a31e..9f972c0625612f 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4417,15 +4417,15 @@ def assert_rest(self, context): assert_rest(self, c) # Invalid values - self.assertRaises(OverflowError, IEEEContext, 2**63) + self.assertRaises(OverflowError, IEEEContext, sys.maxsize + 1) 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 - if IEEE_CONTEXT_MAX_BITS != 256: - self.assertEqual(IEEE_CONTEXT_MAX_BITS, 512) + self.assertIn(IEEE_CONTEXT_MAX_BITS, {256, 512}) @requires_cdecimal class CIEEEContexts(IEEEContexts, unittest.TestCase): From 4053dbbf3db399369e858df3549fb0377abcd102 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 25 Jan 2025 02:50:52 +0300 Subject: [PATCH 5/9] address review: keep DECIMALXY in EXTRA_FUNCTIONALITY --- Modules/_decimal/_decimal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index c6ead73eb97a97..8ecb3eeec2c39a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -5875,6 +5875,9 @@ 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}, /* int condition flags */ {"DecClamped", MPD_Clamped}, {"DecConversionSyntax", MPD_Conversion_syntax}, From 3d016c9aad1cacea24a43b6c1288cab64acbfd1d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Jan 2025 13:04:34 +0300 Subject: [PATCH 6/9] Update Doc/library/decimal.rst --- Doc/library/decimal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 6b9bbb97045ffb..fcfb197ff7a440 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1035,7 +1035,7 @@ function to temporarily change the active context. IEEE interchange formats. The argument must be a multiple of 32 and less than :const:`IEEE_CONTEXT_MAX_BITS`. - .. versionadded:: 3.14 + .. versionadded:: next New contexts can also be created using the :class:`Context` constructor described below. In addition, the module provides three pre-made contexts: From 35f70353d87ea1235614f89a029cc7e9a9d52a47 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 27 Jan 2025 16:17:55 +0300 Subject: [PATCH 7/9] address review: drop OverflowError in Lib/_pydecimal.py --- Lib/_pydecimal.py | 2 -- Lib/test/test_decimal.py | 1 - 2 files changed, 3 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index aa993b8e416e9f..4d0743d5bd51e6 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -427,8 +427,6 @@ def IEEEContext(bits, /): """ import sys - if bits >= sys.maxsize: - raise OverflowError("Python int too large to convert to C ssize_t") 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}") diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 9f972c0625612f..c19e39956666a3 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4417,7 +4417,6 @@ def assert_rest(self, context): assert_rest(self, c) # Invalid values - self.assertRaises(OverflowError, IEEEContext, sys.maxsize + 1) self.assertRaises(ValueError, IEEEContext, -1) self.assertRaises(ValueError, IEEEContext, 123) self.assertRaises(ValueError, IEEEContext, 1024) From 1b8ede35c1aec8f0fdd21ca9eb6d7655cd1b976e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 15 Feb 2025 11:31:22 +0300 Subject: [PATCH 8/9] address review: add What's New entry --- Doc/whatsnew/3.14.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ece5afd4597ab8..9f33bc4ed3f65e 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -428,6 +428,10 @@ decimal :meth:`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 ------- From 702fb8cdd300c28947b838799c9e939299589638 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 28 Apr 2025 15:11:29 +0300 Subject: [PATCH 9/9] Update Lib/_pydecimal.py --- Lib/_pydecimal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 4d0743d5bd51e6..4ca56e2b9ddb1c 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -425,8 +425,6 @@ def IEEEContext(bits, /): IEEE interchange formats. The argument must be a multiple of 32 and less than IEEE_CONTEXT_MAX_BITS. """ - import sys - 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}")