Skip to content

Commit 39042e0

Browse files
author
Stefan Krah
authored
bpo-41324 Add a minimal decimal capsule API (#21519)
1 parent 416f0b7 commit 39042e0

File tree

8 files changed

+1096
-7
lines changed

8 files changed

+1096
-7
lines changed

Doc/c-api/concrete.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,4 @@ Other Objects
115115
coro.rst
116116
contextvars.rst
117117
datetime.rst
118+
decimal.rst

Doc/c-api/decimal.rst

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
.. sectionauthor:: Stefan Krah
2+
3+
.. highlight:: c
4+
5+
6+
Decimal capsule API
7+
===================
8+
9+
Capsule API functions can be used in the same manner as regular library
10+
functions, provided that the API has been initialized.
11+
12+
13+
Initialize
14+
----------
15+
16+
Typically, a C extension module that uses the decimal API will do these
17+
steps in its init function:
18+
19+
.. code-block::
20+
21+
#include "pydecimal.h"
22+
23+
static int decimal_initialized = 0;
24+
if (!decimal_initialized) {
25+
if (import_decimal() < 0) {
26+
return NULL;
27+
}
28+
29+
decimal_initialized = 1;
30+
}
31+
32+
33+
Type checking, predicates, accessors
34+
------------------------------------
35+
36+
.. c:function:: int PyDec_TypeCheck(const PyObject *dec)
37+
38+
Return 1 if ``dec`` is a Decimal, 0 otherwise. This function does not set
39+
any exceptions.
40+
41+
42+
.. c:function:: int PyDec_IsSpecial(const PyObject *dec)
43+
44+
Return 1 if ``dec`` is ``NaN``, ``sNaN`` or ``Infinity``, 0 otherwise.
45+
46+
Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that
47+
this is the only failure mode, so if ``dec`` has already been type-checked, no
48+
errors can occur and the function can be treated as a simple predicate.
49+
50+
51+
.. c:function:: int PyDec_IsNaN(const PyObject *dec)
52+
53+
Return 1 if ``dec`` is ``NaN`` or ``sNaN``, 0 otherwise.
54+
55+
Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that
56+
this is the only failure mode, so if ``dec`` has already been type-checked, no
57+
errors can occur and the function can be treated as a simple predicate.
58+
59+
60+
.. c:function:: int PyDec_IsInfinite(const PyObject *dec)
61+
62+
Return 1 if ``dec`` is ``Infinity``, 0 otherwise.
63+
64+
Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that
65+
this is the only failure mode, so if ``dec`` has already been type-checked, no
66+
errors can occur and the function can be treated as a simple predicate.
67+
68+
69+
.. c:function:: int64_t PyDec_GetDigits(const PyObject *dec)
70+
71+
Return the number of digits in the coefficient. For ``Infinity``, the
72+
number of digits is always zero. Typically, the same applies to ``NaN``
73+
and ``sNaN``, but both of these can have a payload that is equivalent to
74+
a coefficient. Therefore, ``NaNs`` can have a nonzero return value.
75+
76+
Set TypeError and return -1 if ``dec`` is not a Decimal. It is guaranteed that
77+
this is the only failure mode, so if ``dec`` has already been type-checked, no
78+
errors can occur and the function can be treated as a simple accessor.
79+
80+
81+
Exact conversions between decimals and primitive C types
82+
--------------------------------------------------------
83+
84+
This API supports conversions for decimals with a coefficient up to 38 digits.
85+
86+
Data structures
87+
~~~~~~~~~~~~~~~
88+
89+
The conversion functions use the following status codes and data structures:
90+
91+
.. code-block::
92+
93+
/* status cases for getting a triple */
94+
enum mpd_triple_class {
95+
MPD_TRIPLE_NORMAL,
96+
MPD_TRIPLE_INF,
97+
MPD_TRIPLE_QNAN,
98+
MPD_TRIPLE_SNAN,
99+
MPD_TRIPLE_ERROR,
100+
};
101+
102+
typedef struct {
103+
enum mpd_triple_class tag;
104+
uint8_t sign;
105+
uint64_t hi;
106+
uint64_t lo;
107+
int64_t exp;
108+
} mpd_uint128_triple_t;
109+
110+
The status cases are explained below. ``sign`` is 0 for positive and 1 for negative.
111+
``((uint128_t)hi << 64) + lo`` is the coefficient, ``exp`` is the exponent.
112+
113+
The data structure is called "triple" because the decimal triple (sign, coeff, exp)
114+
is an established term and (``hi``, ``lo``) represents a single ``uint128_t`` coefficient.
115+
116+
117+
Functions
118+
~~~~~~~~~
119+
120+
.. c:function:: mpd_uint128_triple_t PyDec_AsUint128Triple(const PyObject *dec)
121+
122+
Convert a decimal to a triple. As above, it is guaranteed that the only
123+
Python failure mode is a TypeError, checks can be omitted if the type is
124+
known.
125+
126+
For simplicity, the usage of the function and all special cases are
127+
explained in code form and comments:
128+
129+
.. code-block::
130+
131+
triple = PyDec_AsUint128Triple(dec);
132+
switch (triple.tag) {
133+
case MPD_TRIPLE_QNAN:
134+
/*
135+
* Success: handle a quiet NaN.
136+
* 1) triple.sign is 0 or 1.
137+
* 2) triple.exp is always 0.
138+
* 3) If triple.hi or triple.lo are nonzero, the NaN has a payload.
139+
*/
140+
break;
141+
142+
case MPD_TRIPLE_SNAN:
143+
/*
144+
* Success: handle a signaling NaN.
145+
* 1) triple.sign is 0 or 1.
146+
* 2) triple.exp is always 0.
147+
* 3) If triple.hi or triple.lo are nonzero, the sNaN has a payload.
148+
*/
149+
break;
150+
151+
case MPD_TRIPLE_INF:
152+
/*
153+
* Success: handle Infinity.
154+
* 1) triple.sign is 0 or 1.
155+
* 2) triple.exp is always 0.
156+
* 3) triple.hi and triple.lo are always zero.
157+
*/
158+
break;
159+
160+
case MPD_TRIPLE_NORMAL:
161+
/* Success: handle a finite value. */
162+
break;
163+
164+
case MPD_TRIPLE_ERROR:
165+
/* TypeError check: can be omitted if the type of dec is known. */
166+
if (PyErr_Occurred()) {
167+
return NULL;
168+
}
169+
170+
/* Too large for conversion. PyDec_AsUint128Triple() does not set an
171+
exception so applications can choose themselves. Typically this
172+
would be a ValueError. */
173+
PyErr_SetString(PyExc_ValueError,
174+
"value out of bounds for a uint128 triple");
175+
return NULL;
176+
}
177+
178+
.. c:function:: PyObject *PyDec_FromUint128Triple(const mpd_uint128_triple_t *triple)
179+
180+
Create a decimal from a triple. The following rules must be observed for
181+
initializing the triple:
182+
183+
1) ``triple.sign`` must always be 0 (for positive) or 1 (for negative).
184+
185+
2) ``MPD_TRIPLE_QNAN``: ``triple.exp`` must be 0. If ``triple.hi`` or ``triple.lo``
186+
are nonzero, create a ``NaN`` with a payload.
187+
188+
3) ``MPD_TRIPLE_SNAN``: ``triple.exp`` must be 0. If ``triple.hi`` or ``triple.lo``
189+
are nonzero, create an ``sNaN`` with a payload.
190+
191+
4) ``MPD_TRIPLE_INF``: ``triple.exp``, ``triple.hi`` and ``triple.lo`` must be zero.
192+
193+
5) ``MPD_TRIPLE_NORMAL``: ``MPD_MIN_ETINY + 38 < triple.exp < MPD_MAX_EMAX - 38``.
194+
``triple.hi`` and ``triple.lo`` can be chosen freely.
195+
196+
6) ``MPD_TRIPLE_ERROR``: It is always an error to set this tag.
197+
198+
199+
If one of the above conditions is not met, the function returns ``NaN`` if
200+
the ``InvalidOperation`` trap is not set in the thread local context. Otherwise,
201+
it sets the ``InvalidOperation`` exception and returns NULL.
202+
203+
Additionally, though extremely unlikely give the small allocation sizes,
204+
the function can set ``MemoryError`` and return ``NULL``.
205+
206+
207+
Advanced API
208+
------------
209+
210+
This API enables the use of ``libmpdec`` functions. Since Python is compiled with
211+
hidden symbols, the API requires an external libmpdec and the ``mpdecimal.h``
212+
header.
213+
214+
215+
Functions
216+
~~~~~~~~~
217+
218+
.. c:function:: PyObject *PyDec_Alloc(void)
219+
220+
Return a new decimal that can be used in the ``result`` position of ``libmpdec``
221+
functions.
222+
223+
.. c:function:: mpd_t *PyDec_Get(PyObject *v)
224+
225+
Get a pointer to the internal ``mpd_t`` of the decimal. Decimals are immutable,
226+
so this function must only be used on a new Decimal that has been created by
227+
PyDec_Alloc().
228+
229+
.. c:function:: const mpd_t *PyDec_GetConst(const PyObject *v)
230+
231+
Get a pointer to the constant internal ``mpd_t`` of the decimal.

0 commit comments

Comments
 (0)