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

Reply via email to