Skip to content

gh-53032: support IEEE 754 contexts in the decimal module #122003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions Doc/library/decimal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,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:

Expand Down Expand Up @@ -1550,18 +1558,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

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,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
-------

Expand Down
27 changes: 25 additions & 2 deletions Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
88 changes: 46 additions & 42 deletions Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4387,6 +4387,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):
Expand All @@ -4398,6 +4443,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)
Expand Down Expand Up @@ -4874,42 +4920,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
Expand All @@ -4930,12 +4940,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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 1 addition & 5 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,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)
Expand Down Expand Up @@ -1575,7 +1574,6 @@ ieee_context(PyObject *module, PyObject *v)

return NULL;
}
#endif

static PyObject *
context_copy(PyObject *self, PyObject *Py_UNUSED(dummy))
Expand Down Expand Up @@ -5867,9 +5865,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 }
};

Expand All @@ -5886,11 +5882,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},
Expand Down
5 changes: 1 addition & 4 deletions Modules/_decimal/docstrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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


/******************************************************************************/
Expand Down
Loading