Skip to content

Commit b833af8

Browse files
committed
pythongh-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.
1 parent cecacee commit b833af8

File tree

6 files changed

+102
-70
lines changed

6 files changed

+102
-70
lines changed

Doc/library/decimal.rst

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,12 @@ function to temporarily change the active context.
10121012
.. versionchanged:: 3.11
10131013
:meth:`localcontext` now supports setting context attributes through the use of keyword arguments.
10141014

1015+
.. function:: IEEEContext(bits)
1016+
1017+
Return a context object initialized to the proper values for one of the
1018+
IEEE interchange formats. The argument must be a multiple of 32 and less
1019+
than :const:`IEEE_CONTEXT_MAX_BITS`.
1020+
10151021
New contexts can also be created using the :class:`Context` constructor
10161022
described below. In addition, the module provides three pre-made contexts:
10171023

@@ -1533,18 +1539,19 @@ Constants
15331539
The constants in this section are only relevant for the C module. They
15341540
are also included in the pure Python version for compatibility.
15351541

1536-
+---------------------+---------------------+-------------------------------+
1537-
| | 32-bit | 64-bit |
1538-
+=====================+=====================+===============================+
1539-
| .. data:: MAX_PREC | ``425000000`` | ``999999999999999999`` |
1540-
+---------------------+---------------------+-------------------------------+
1541-
| .. data:: MAX_EMAX | ``425000000`` | ``999999999999999999`` |
1542-
+---------------------+---------------------+-------------------------------+
1543-
| .. data:: MIN_EMIN | ``-425000000`` | ``-999999999999999999`` |
1544-
+---------------------+---------------------+-------------------------------+
1545-
| .. data:: MIN_ETINY | ``-849999999`` | ``-1999999999999999997`` |
1546-
+---------------------+---------------------+-------------------------------+
1547-
1542+
+---------------------------------+---------------------+-------------------------------+
1543+
| | 32-bit | 64-bit |
1544+
+=================================+=====================+===============================+
1545+
| .. data:: MAX_PREC | ``425000000`` | ``999999999999999999`` |
1546+
+---------------------------------+---------------------+-------------------------------+
1547+
| .. data:: MAX_EMAX | ``425000000`` | ``999999999999999999`` |
1548+
+---------------------------------+---------------------+-------------------------------+
1549+
| .. data:: MIN_EMIN | ``-425000000`` | ``-999999999999999999`` |
1550+
+---------------------------------+---------------------+-------------------------------+
1551+
| .. data:: MIN_ETINY | ``-849999999`` | ``-1999999999999999997`` |
1552+
+---------------------------------+---------------------+-------------------------------+
1553+
| .. data:: IEEE_CONTEXT_MAX_BITS | ``256`` | ``512`` |
1554+
+---------------------------------+---------------------+-------------------------------+
15481555

15491556
.. data:: HAVE_THREADS
15501557

Lib/_pydecimal.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP',
3939

4040
# Functions for manipulating contexts
41-
'setcontext', 'getcontext', 'localcontext',
41+
'setcontext', 'getcontext', 'localcontext', 'IEEEContext',
4242

4343
# Limits for the C version for compatibility
44-
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
44+
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', 'IEEE_CONTEXT_MAX_BITS',
4545

4646
# C version: compile time choice that enables the thread local context (deprecated, now always true)
4747
'HAVE_THREADS',
@@ -83,10 +83,12 @@
8383
MAX_PREC = 999999999999999999
8484
MAX_EMAX = 999999999999999999
8585
MIN_EMIN = -999999999999999999
86+
IEEE_CONTEXT_MAX_BITS = 512
8687
else:
8788
MAX_PREC = 425000000
8889
MAX_EMAX = 425000000
8990
MIN_EMIN = -425000000
91+
IEEE_CONTEXT_MAX_BITS = 256
9092

9193
MIN_ETINY = MIN_EMIN - (MAX_PREC-1)
9294

@@ -417,6 +419,31 @@ def sin(x):
417419
return ctx_manager
418420

419421

422+
def IEEEContext(bits, /):
423+
"""
424+
Return a context object initialized to the proper values for one of the
425+
IEEE interchange formats. The argument must be a multiple of 32 and less
426+
than IEEE_CONTEXT_MAX_BITS.
427+
"""
428+
import sys
429+
430+
if bits >= sys.maxsize:
431+
raise OverflowError("Python int too large to convert to C ssize_t")
432+
if bits <= 0 or bits > IEEE_CONTEXT_MAX_BITS or bits % 32:
433+
raise ValueError("argument must be a multiple of 32, "
434+
f"with a maximum of {IEEE_CONTEXT_MAX_BITS}")
435+
436+
ctx = Context()
437+
ctx.prec = 9 * (bits//32) - 2
438+
ctx.Emax = 3 * (1 << (bits//16 + 3))
439+
ctx.Emin = 1 - ctx.Emax
440+
ctx.rounding = ROUND_HALF_EVEN
441+
ctx.clamp = 1
442+
ctx.traps = {s: False for s in _signals}
443+
444+
return ctx
445+
446+
420447
##### Decimal class #######################################################
421448

422449
# Do not subclass Decimal from numbers.Real and do not register it as such

Lib/test/test_decimal.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4362,6 +4362,52 @@ class CContextSubclassing(ContextSubclassing, unittest.TestCase):
43624362
class PyContextSubclassing(ContextSubclassing, unittest.TestCase):
43634363
decimal = P
43644364

4365+
class IEEEContexts:
4366+
4367+
def test_ieee_context(self):
4368+
# issue 8786: Add support for IEEE 754 contexts to decimal module.
4369+
IEEEContext = self.decimal.IEEEContext
4370+
4371+
def assert_rest(self, context):
4372+
self.assertEqual(context.clamp, 1)
4373+
assert_signals(self, context, 'traps', [])
4374+
assert_signals(self, context, 'flags', [])
4375+
4376+
c = IEEEContext(32)
4377+
self.assertEqual(c.prec, 7)
4378+
self.assertEqual(c.Emax, 96)
4379+
self.assertEqual(c.Emin, -95)
4380+
assert_rest(self, c)
4381+
4382+
c = IEEEContext(64)
4383+
self.assertEqual(c.prec, 16)
4384+
self.assertEqual(c.Emax, 384)
4385+
self.assertEqual(c.Emin, -383)
4386+
assert_rest(self, c)
4387+
4388+
c = IEEEContext(128)
4389+
self.assertEqual(c.prec, 34)
4390+
self.assertEqual(c.Emax, 6144)
4391+
self.assertEqual(c.Emin, -6143)
4392+
assert_rest(self, c)
4393+
4394+
# Invalid values
4395+
self.assertRaises(OverflowError, IEEEContext, 2**63)
4396+
self.assertRaises(ValueError, IEEEContext, -1)
4397+
self.assertRaises(ValueError, IEEEContext, 1024)
4398+
4399+
def test_constants(self):
4400+
# IEEEContext
4401+
IEEE_CONTEXT_MAX_BITS = self.decimal.IEEE_CONTEXT_MAX_BITS
4402+
if IEEE_CONTEXT_MAX_BITS != 256:
4403+
self.assertEqual(IEEE_CONTEXT_MAX_BITS, 512)
4404+
4405+
@requires_cdecimal
4406+
class CIEEEContexts(IEEEContexts, unittest.TestCase):
4407+
decimal = C
4408+
class PyIEEEContexts(IEEEContexts, unittest.TestCase):
4409+
decimal = P
4410+
43654411
@skip_if_extra_functionality
43664412
@requires_cdecimal
43674413
class CheckAttributes(unittest.TestCase):
@@ -4373,6 +4419,7 @@ def test_module_attributes(self):
43734419
self.assertEqual(C.MAX_EMAX, P.MAX_EMAX)
43744420
self.assertEqual(C.MIN_EMIN, P.MIN_EMIN)
43754421
self.assertEqual(C.MIN_ETINY, P.MIN_ETINY)
4422+
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, P.IEEE_CONTEXT_MAX_BITS)
43764423

43774424
self.assertTrue(C.HAVE_THREADS is True or C.HAVE_THREADS is False)
43784425
self.assertTrue(P.HAVE_THREADS is True or P.HAVE_THREADS is False)
@@ -4848,42 +4895,6 @@ def test_py__round(self):
48484895
class CFunctionality(unittest.TestCase):
48494896
"""Extra functionality in _decimal"""
48504897

4851-
@requires_extra_functionality
4852-
def test_c_ieee_context(self):
4853-
# issue 8786: Add support for IEEE 754 contexts to decimal module.
4854-
IEEEContext = C.IEEEContext
4855-
DECIMAL32 = C.DECIMAL32
4856-
DECIMAL64 = C.DECIMAL64
4857-
DECIMAL128 = C.DECIMAL128
4858-
4859-
def assert_rest(self, context):
4860-
self.assertEqual(context.clamp, 1)
4861-
assert_signals(self, context, 'traps', [])
4862-
assert_signals(self, context, 'flags', [])
4863-
4864-
c = IEEEContext(DECIMAL32)
4865-
self.assertEqual(c.prec, 7)
4866-
self.assertEqual(c.Emax, 96)
4867-
self.assertEqual(c.Emin, -95)
4868-
assert_rest(self, c)
4869-
4870-
c = IEEEContext(DECIMAL64)
4871-
self.assertEqual(c.prec, 16)
4872-
self.assertEqual(c.Emax, 384)
4873-
self.assertEqual(c.Emin, -383)
4874-
assert_rest(self, c)
4875-
4876-
c = IEEEContext(DECIMAL128)
4877-
self.assertEqual(c.prec, 34)
4878-
self.assertEqual(c.Emax, 6144)
4879-
self.assertEqual(c.Emin, -6143)
4880-
assert_rest(self, c)
4881-
4882-
# Invalid values
4883-
self.assertRaises(OverflowError, IEEEContext, 2**63)
4884-
self.assertRaises(ValueError, IEEEContext, -1)
4885-
self.assertRaises(ValueError, IEEEContext, 1024)
4886-
48874898
@requires_extra_functionality
48884899
def test_c_context(self):
48894900
Context = C.Context
@@ -4904,12 +4915,6 @@ def test_constants(self):
49044915
C.DecSubnormal, C.DecUnderflow
49054916
)
49064917

4907-
# IEEEContext
4908-
self.assertEqual(C.DECIMAL32, 32)
4909-
self.assertEqual(C.DECIMAL64, 64)
4910-
self.assertEqual(C.DECIMAL128, 128)
4911-
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, 512)
4912-
49134918
# Conditions
49144919
for i, v in enumerate(cond):
49154920
self.assertEqual(v, 1<<i)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Expose function :func:`decimal.IEEEContext()` to support creation of
2+
contexts, corresponding to the IEEE 754 (2008) decimal interchange formats.
3+
Patch by Sergey B Kirpichev.

Modules/_decimal/_decimal.c

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,10 +1516,9 @@ init_extended_context(PyObject *v)
15161516
CtxCaps(v) = 1;
15171517
}
15181518

1519-
#ifdef EXTRA_FUNCTIONALITY
15201519
/* Factory function for creating IEEE interchange format contexts */
15211520
static PyObject *
1522-
ieee_context(PyObject *dummy UNUSED, PyObject *v)
1521+
ieee_context(PyObject *self, PyObject *v)
15231522
{
15241523
PyObject *context;
15251524
mpd_ssize_t bits;
@@ -1536,7 +1535,7 @@ ieee_context(PyObject *dummy UNUSED, PyObject *v)
15361535
goto error;
15371536
}
15381537

1539-
decimal_state *state = get_module_state_by_def(Py_TYPE(v));
1538+
decimal_state *state = get_module_state(self);
15401539
context = PyObject_CallObject((PyObject *)state->PyDecContext_Type, NULL);
15411540
if (context == NULL) {
15421541
return NULL;
@@ -1552,7 +1551,6 @@ ieee_context(PyObject *dummy UNUSED, PyObject *v)
15521551

15531552
return NULL;
15541553
}
1555-
#endif
15561554

15571555
static PyObject *
15581556
context_copy(PyObject *self, PyObject *args UNUSED)
@@ -5751,9 +5749,7 @@ static PyMethodDef _decimal_methods [] =
57515749
{ "getcontext", (PyCFunction)PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext},
57525750
{ "setcontext", (PyCFunction)PyDec_SetCurrentContext, METH_O, doc_setcontext},
57535751
{ "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext},
5754-
#ifdef EXTRA_FUNCTIONALITY
57555752
{ "IEEEContext", (PyCFunction)ieee_context, METH_O, doc_ieee_context},
5756-
#endif
57575753
{ NULL, NULL, 1, NULL }
57585754
};
57595755

@@ -5770,11 +5766,8 @@ static struct ssize_constmap ssize_constants [] = {
57705766
struct int_constmap { const char *name; int val; };
57715767
static struct int_constmap int_constants [] = {
57725768
/* int constants */
5773-
#ifdef EXTRA_FUNCTIONALITY
5774-
{"DECIMAL32", MPD_DECIMAL32},
5775-
{"DECIMAL64", MPD_DECIMAL64},
5776-
{"DECIMAL128", MPD_DECIMAL128},
57775769
{"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS},
5770+
#ifdef EXTRA_FUNCTIONALITY
57785771
/* int condition flags */
57795772
{"DecClamped", MPD_Clamped},
57805773
{"DecConversionSyntax", MPD_Conversion_syntax},

Modules/_decimal/docstrings.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,12 @@ exiting the with-statement. If no context is specified, a copy of the current\n\
3737
default context is used.\n\
3838
\n");
3939

40-
#ifdef EXTRA_FUNCTIONALITY
4140
PyDoc_STRVAR(doc_ieee_context,
4241
"IEEEContext($module, bits, /)\n--\n\n\
4342
Return a context object initialized to the proper values for one of the\n\
4443
IEEE interchange formats. The argument must be a multiple of 32 and less\n\
45-
than IEEE_CONTEXT_MAX_BITS. For the most common values, the constants\n\
46-
DECIMAL32, DECIMAL64 and DECIMAL128 are provided.\n\
44+
than IEEE_CONTEXT_MAX_BITS.\n\
4745
\n");
48-
#endif
4946

5047

5148
/******************************************************************************/

0 commit comments

Comments
 (0)