Skip to content

Commit 5bf0f36

Browse files
skirpichevpicnixz
andauthored
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 <[email protected]>
1 parent e20ca6d commit 5bf0f36

File tree

7 files changed

+101
-65
lines changed

7 files changed

+101
-65
lines changed

Doc/library/decimal.rst

+21-12
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,14 @@ function to temporarily change the active context.
10311031
.. versionchanged:: 3.11
10321032
:meth:`localcontext` now supports setting context attributes through the use of keyword arguments.
10331033

1034+
.. function:: IEEEContext(bits)
1035+
1036+
Return a context object initialized to the proper values for one of the
1037+
IEEE interchange formats. The argument must be a multiple of 32 and less
1038+
than :const:`IEEE_CONTEXT_MAX_BITS`.
1039+
1040+
.. versionadded:: next
1041+
10341042
New contexts can also be created using the :class:`Context` constructor
10351043
described below. In addition, the module provides three pre-made contexts:
10361044

@@ -1552,18 +1560,19 @@ Constants
15521560
The constants in this section are only relevant for the C module. They
15531561
are also included in the pure Python version for compatibility.
15541562

1555-
+---------------------+---------------------+-------------------------------+
1556-
| | 32-bit | 64-bit |
1557-
+=====================+=====================+===============================+
1558-
| .. data:: MAX_PREC | ``425000000`` | ``999999999999999999`` |
1559-
+---------------------+---------------------+-------------------------------+
1560-
| .. data:: MAX_EMAX | ``425000000`` | ``999999999999999999`` |
1561-
+---------------------+---------------------+-------------------------------+
1562-
| .. data:: MIN_EMIN | ``-425000000`` | ``-999999999999999999`` |
1563-
+---------------------+---------------------+-------------------------------+
1564-
| .. data:: MIN_ETINY | ``-849999999`` | ``-1999999999999999997`` |
1565-
+---------------------+---------------------+-------------------------------+
1566-
1563+
+---------------------------------+---------------------+-------------------------------+
1564+
| | 32-bit | 64-bit |
1565+
+=================================+=====================+===============================+
1566+
| .. data:: MAX_PREC | ``425000000`` | ``999999999999999999`` |
1567+
+---------------------------------+---------------------+-------------------------------+
1568+
| .. data:: MAX_EMAX | ``425000000`` | ``999999999999999999`` |
1569+
+---------------------------------+---------------------+-------------------------------+
1570+
| .. data:: MIN_EMIN | ``-425000000`` | ``-999999999999999999`` |
1571+
+---------------------------------+---------------------+-------------------------------+
1572+
| .. data:: MIN_ETINY | ``-849999999`` | ``-1999999999999999997`` |
1573+
+---------------------------------+---------------------+-------------------------------+
1574+
| .. data:: IEEE_CONTEXT_MAX_BITS | ``256`` | ``512`` |
1575+
+---------------------------------+---------------------+-------------------------------+
15671576

15681577
.. data:: HAVE_THREADS
15691578

Doc/whatsnew/3.14.rst

+4
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,10 @@ decimal
750750
:meth:`Decimal.from_number() <decimal.Decimal.from_number>`.
751751
(Contributed by Serhiy Storchaka in :gh:`121798`.)
752752

753+
* Expose :func:`decimal.IEEEContext` to support creation of contexts
754+
corresponding to the IEEE 754 (2008) decimal interchange formats.
755+
(Contributed by Sergey B Kirpichev in :gh:`53032`.)
756+
753757
difflib
754758
-------
755759

Lib/_pydecimal.py

+25-2
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,27 @@ 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+
if bits <= 0 or bits > IEEE_CONTEXT_MAX_BITS or bits % 32:
429+
raise ValueError("argument must be a multiple of 32, "
430+
f"with a maximum of {IEEE_CONTEXT_MAX_BITS}")
431+
432+
ctx = Context()
433+
ctx.prec = 9 * (bits//32) - 2
434+
ctx.Emax = 3 * (1 << (bits//16 + 3))
435+
ctx.Emin = 1 - ctx.Emax
436+
ctx.rounding = ROUND_HALF_EVEN
437+
ctx.clamp = 1
438+
ctx.traps = dict.fromkeys(_signals, False)
439+
440+
return ctx
441+
442+
420443
##### Decimal class #######################################################
421444

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

Lib/test/test_decimal.py

+46-42
Original file line numberDiff line numberDiff line change
@@ -4399,6 +4399,51 @@ class CContextSubclassing(ContextSubclassing, unittest.TestCase):
43994399
class PyContextSubclassing(ContextSubclassing, unittest.TestCase):
44004400
decimal = P
44014401

4402+
class IEEEContexts:
4403+
4404+
def test_ieee_context(self):
4405+
# issue 8786: Add support for IEEE 754 contexts to decimal module.
4406+
IEEEContext = self.decimal.IEEEContext
4407+
4408+
def assert_rest(self, context):
4409+
self.assertEqual(context.clamp, 1)
4410+
assert_signals(self, context, 'traps', [])
4411+
assert_signals(self, context, 'flags', [])
4412+
4413+
c = IEEEContext(32)
4414+
self.assertEqual(c.prec, 7)
4415+
self.assertEqual(c.Emax, 96)
4416+
self.assertEqual(c.Emin, -95)
4417+
assert_rest(self, c)
4418+
4419+
c = IEEEContext(64)
4420+
self.assertEqual(c.prec, 16)
4421+
self.assertEqual(c.Emax, 384)
4422+
self.assertEqual(c.Emin, -383)
4423+
assert_rest(self, c)
4424+
4425+
c = IEEEContext(128)
4426+
self.assertEqual(c.prec, 34)
4427+
self.assertEqual(c.Emax, 6144)
4428+
self.assertEqual(c.Emin, -6143)
4429+
assert_rest(self, c)
4430+
4431+
# Invalid values
4432+
self.assertRaises(ValueError, IEEEContext, -1)
4433+
self.assertRaises(ValueError, IEEEContext, 123)
4434+
self.assertRaises(ValueError, IEEEContext, 1024)
4435+
4436+
def test_constants(self):
4437+
# IEEEContext
4438+
IEEE_CONTEXT_MAX_BITS = self.decimal.IEEE_CONTEXT_MAX_BITS
4439+
self.assertIn(IEEE_CONTEXT_MAX_BITS, {256, 512})
4440+
4441+
@requires_cdecimal
4442+
class CIEEEContexts(IEEEContexts, unittest.TestCase):
4443+
decimal = C
4444+
class PyIEEEContexts(IEEEContexts, unittest.TestCase):
4445+
decimal = P
4446+
44024447
@skip_if_extra_functionality
44034448
@requires_cdecimal
44044449
class CheckAttributes(unittest.TestCase):
@@ -4410,6 +4455,7 @@ def test_module_attributes(self):
44104455
self.assertEqual(C.MAX_EMAX, P.MAX_EMAX)
44114456
self.assertEqual(C.MIN_EMIN, P.MIN_EMIN)
44124457
self.assertEqual(C.MIN_ETINY, P.MIN_ETINY)
4458+
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, P.IEEE_CONTEXT_MAX_BITS)
44134459

44144460
self.assertTrue(C.HAVE_THREADS is True or C.HAVE_THREADS is False)
44154461
self.assertTrue(P.HAVE_THREADS is True or P.HAVE_THREADS is False)
@@ -4893,42 +4939,6 @@ def test_py__round(self):
48934939
class CFunctionality(unittest.TestCase):
48944940
"""Extra functionality in _decimal"""
48954941

4896-
@requires_extra_functionality
4897-
def test_c_ieee_context(self):
4898-
# issue 8786: Add support for IEEE 754 contexts to decimal module.
4899-
IEEEContext = C.IEEEContext
4900-
DECIMAL32 = C.DECIMAL32
4901-
DECIMAL64 = C.DECIMAL64
4902-
DECIMAL128 = C.DECIMAL128
4903-
4904-
def assert_rest(self, context):
4905-
self.assertEqual(context.clamp, 1)
4906-
assert_signals(self, context, 'traps', [])
4907-
assert_signals(self, context, 'flags', [])
4908-
4909-
c = IEEEContext(DECIMAL32)
4910-
self.assertEqual(c.prec, 7)
4911-
self.assertEqual(c.Emax, 96)
4912-
self.assertEqual(c.Emin, -95)
4913-
assert_rest(self, c)
4914-
4915-
c = IEEEContext(DECIMAL64)
4916-
self.assertEqual(c.prec, 16)
4917-
self.assertEqual(c.Emax, 384)
4918-
self.assertEqual(c.Emin, -383)
4919-
assert_rest(self, c)
4920-
4921-
c = IEEEContext(DECIMAL128)
4922-
self.assertEqual(c.prec, 34)
4923-
self.assertEqual(c.Emax, 6144)
4924-
self.assertEqual(c.Emin, -6143)
4925-
assert_rest(self, c)
4926-
4927-
# Invalid values
4928-
self.assertRaises(OverflowError, IEEEContext, 2**63)
4929-
self.assertRaises(ValueError, IEEEContext, -1)
4930-
self.assertRaises(ValueError, IEEEContext, 1024)
4931-
49324942
@requires_extra_functionality
49334943
def test_c_context(self):
49344944
Context = C.Context
@@ -4949,12 +4959,6 @@ def test_constants(self):
49494959
C.DecSubnormal, C.DecUnderflow
49504960
)
49514961

4952-
# IEEEContext
4953-
self.assertEqual(C.DECIMAL32, 32)
4954-
self.assertEqual(C.DECIMAL64, 64)
4955-
self.assertEqual(C.DECIMAL128, 128)
4956-
self.assertEqual(C.IEEE_CONTEXT_MAX_BITS, 512)
4957-
49584962
# Conditions
49594963
for i, v in enumerate(cond):
49604964
self.assertEqual(v, 1<<i)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Expose :func:`decimal.IEEEContext` to support creation of contexts
2+
corresponding to the IEEE 754 (2008) decimal interchange formats.
3+
Patch by Sergey B Kirpichev.

Modules/_decimal/_decimal.c

+1-5
Original file line numberDiff line numberDiff line change
@@ -1557,7 +1557,6 @@ init_extended_context(PyObject *v)
15571557
CtxCaps(v) = 1;
15581558
}
15591559

1560-
#ifdef EXTRA_FUNCTIONALITY
15611560
/* Factory function for creating IEEE interchange format contexts */
15621561
static PyObject *
15631562
ieee_context(PyObject *module, PyObject *v)
@@ -1593,7 +1592,6 @@ ieee_context(PyObject *module, PyObject *v)
15931592

15941593
return NULL;
15951594
}
1596-
#endif
15971595

15981596
static PyObject *
15991597
context_copy(PyObject *self, PyObject *Py_UNUSED(dummy))
@@ -5886,9 +5884,7 @@ static PyMethodDef _decimal_methods [] =
58865884
{ "getcontext", PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext},
58875885
{ "setcontext", PyDec_SetCurrentContext, METH_O, doc_setcontext},
58885886
{ "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext},
5889-
#ifdef EXTRA_FUNCTIONALITY
58905887
{ "IEEEContext", ieee_context, METH_O, doc_ieee_context},
5891-
#endif
58925888
{ NULL, NULL, 1, NULL }
58935889
};
58945890

@@ -5905,11 +5901,11 @@ static struct ssize_constmap ssize_constants [] = {
59055901
struct int_constmap { const char *name; int val; };
59065902
static struct int_constmap int_constants [] = {
59075903
/* int constants */
5904+
{"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS},
59085905
#ifdef EXTRA_FUNCTIONALITY
59095906
{"DECIMAL32", MPD_DECIMAL32},
59105907
{"DECIMAL64", MPD_DECIMAL64},
59115908
{"DECIMAL128", MPD_DECIMAL128},
5912-
{"IEEE_CONTEXT_MAX_BITS", MPD_IEEE_CONTEXT_MAX_BITS},
59135909
/* int condition flags */
59145910
{"DecClamped", MPD_Clamped},
59155911
{"DecConversionSyntax", MPD_Conversion_syntax},

Modules/_decimal/docstrings.h

+1-4
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)