summaryrefslogtreecommitdiffstats
path: root/Doc/c-api/decimal.rst
blob: 94cc4a7b845791379f469bef8545e441eac6a237 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
.. sectionauthor:: Stefan Krah

.. highlight:: c


Decimal capsule API
===================

Capsule API functions can be used in the same manner as regular library
functions, provided that the API has been initialized.


Initialize
----------

Typically, a C extension module that uses the decimal API will do these
steps in its init function:

.. code-block:: c

    #include "pydecimal.h"

    static int decimal_initialized = 0;
    if (!decimal_initialized) {
        if (import_decimal() < 0) {
            return NULL;
        }

        decimal_initialized = 1;
    }


Type checking, predicates, accessors
------------------------------------

.. c:function:: int PyDec_TypeCheck(const PyObject *dec)

   Return 1 if ``dec`` is a Decimal, 0 otherwise.  This function does not set
   any exceptions.


.. c:function:: int PyDec_IsSpecial(const PyObject *dec)

   Return 1 if ``dec`` is ``NaN``, ``sNaN`` or ``Infinity``, 0 otherwise.

   Set TypeError and return -1 if ``dec`` is not a Decimal.  It is guaranteed that
   this is the only failure mode, so if ``dec`` has already been type-checked, no
   errors can occur and the function can be treated as a simple predicate.


.. c:function:: int PyDec_IsNaN(const PyObject *dec)

   Return 1 if ``dec`` is ``NaN`` or ``sNaN``, 0 otherwise.

   Set TypeError and return -1 if ``dec`` is not a Decimal.  It is guaranteed that
   this is the only failure mode, so if ``dec`` has already been type-checked, no
   errors can occur and the function can be treated as a simple predicate.


.. c:function:: int PyDec_IsInfinite(const PyObject *dec)

   Return 1 if ``dec`` is ``Infinity``, 0 otherwise.

   Set TypeError and return -1 if ``dec`` is not a Decimal.  It is guaranteed that
   this is the only failure mode, so if ``dec`` has already been type-checked, no
   errors can occur and the function can be treated as a simple predicate.


.. c:function:: int64_t PyDec_GetDigits(const PyObject *dec)

   Return the number of digits in the coefficient.  For ``Infinity``, the
   number of digits is always zero.  Typically, the same applies to ``NaN``
   and ``sNaN``, but both of these can have a payload that is equivalent to
   a coefficient.  Therefore, ``NaNs`` can have a nonzero return value.

   Set TypeError and return -1 if ``dec`` is not a Decimal.  It is guaranteed that
   this is the only failure mode, so if ``dec`` has already been type-checked, no
   errors can occur and the function can be treated as a simple accessor.


Exact conversions between decimals and primitive C types
--------------------------------------------------------

This API supports conversions for decimals with a coefficient up to 38 digits.

Data structures
~~~~~~~~~~~~~~~

The conversion functions use the following status codes and data structures:

.. code-block:: c

   /* status cases for getting a triple */
   enum mpd_triple_class {
     MPD_TRIPLE_NORMAL,
     MPD_TRIPLE_INF,
     MPD_TRIPLE_QNAN,
     MPD_TRIPLE_SNAN,
     MPD_TRIPLE_ERROR,
   };

   typedef struct {
     enum mpd_triple_class tag;
     uint8_t sign;
     uint64_t hi;
     uint64_t lo;
     int64_t exp;
   } mpd_uint128_triple_t;

The status cases are explained below.  ``sign`` is 0 for positive and 1 for negative.
``((uint128_t)hi << 64) + lo`` is the coefficient, ``exp`` is the exponent.

The data structure is called "triple" because the decimal triple (sign, coeff, exp)
is an established term and (``hi``, ``lo``) represents a single ``uint128_t`` coefficient.


Functions
~~~~~~~~~

.. c:function:: mpd_uint128_triple_t PyDec_AsUint128Triple(const PyObject *dec)

   Convert a decimal to a triple.  As above, it is guaranteed that the only
   Python failure mode is a TypeError, checks can be omitted if the type is
   known.

   For simplicity, the usage of the function and all special cases are
   explained in code form and comments:

.. code-block:: c

    triple = PyDec_AsUint128Triple(dec);
    switch (triple.tag) {
    case MPD_TRIPLE_QNAN:
        /*
         * Success: handle a quiet NaN.
         *   1) triple.sign is 0 or 1.
         *   2) triple.exp is always 0.
         *   3) If triple.hi or triple.lo are nonzero, the NaN has a payload.
         */
        break;

    case MPD_TRIPLE_SNAN:
        /*
         * Success: handle a signaling NaN.
         *   1) triple.sign is 0 or 1.
         *   2) triple.exp is always 0.
         *   3) If triple.hi or triple.lo are nonzero, the sNaN has a payload.
         */
        break;

    case MPD_TRIPLE_INF:
        /*
         * Success: handle Infinity.
         *   1) triple.sign is 0 or 1.
         *   2) triple.exp is always 0.
         *   3) triple.hi and triple.lo are always zero.
         */
        break;

    case MPD_TRIPLE_NORMAL:
        /* Success: handle a finite value. */
        break;

    case MPD_TRIPLE_ERROR:
        /* TypeError check: can be omitted if the type of dec is known. */
        if (PyErr_Occurred()) {
            return NULL;
        }

        /* Too large for conversion.  PyDec_AsUint128Triple() does not set an
           exception so applications can choose themselves.  Typically this
           would be a ValueError. */
        PyErr_SetString(PyExc_ValueError,
            "value out of bounds for a uint128 triple");
        return NULL;
    }

.. c:function:: PyObject *PyDec_FromUint128Triple(const mpd_uint128_triple_t *triple)

   Create a decimal from a triple.  The following rules must be observed for
   initializing the triple:

   1) ``triple.sign`` must always be 0 (for positive) or 1 (for negative).

   2) ``MPD_TRIPLE_QNAN``: ``triple.exp`` must be 0.  If ``triple.hi`` or ``triple.lo``
      are nonzero,  create a ``NaN`` with a payload.

   3) ``MPD_TRIPLE_SNAN``: ``triple.exp`` must be 0. If ``triple.hi`` or ``triple.lo``
      are nonzero,  create an ``sNaN`` with a payload.

   4) ``MPD_TRIPLE_INF``: ``triple.exp``, ``triple.hi`` and ``triple.lo`` must be zero.

   5) ``MPD_TRIPLE_NORMAL``: ``MPD_MIN_ETINY + 38 < triple.exp < MPD_MAX_EMAX - 38``.
      ``triple.hi`` and ``triple.lo`` can be chosen freely.

   6) ``MPD_TRIPLE_ERROR``: It is always an error to set this tag.


   If one of the above conditions is not met, the function returns ``NaN`` if
   the ``InvalidOperation`` trap is not set in the thread local context.  Otherwise,
   it sets the ``InvalidOperation`` exception and returns NULL.

   Additionally, though extremely unlikely give the small allocation sizes,
   the function can set ``MemoryError`` and return ``NULL``.


Advanced API
------------

This API enables the use of ``libmpdec`` functions.  Since Python is compiled with
hidden symbols, the API requires an external libmpdec and the ``mpdecimal.h``
header.


Functions
~~~~~~~~~

.. c:function:: PyObject *PyDec_Alloc(void)

   Return a new decimal that can be used in the ``result`` position of ``libmpdec``
   functions.

.. c:function:: mpd_t *PyDec_Get(PyObject *v)

   Get a pointer to the internal ``mpd_t`` of the decimal.  Decimals are immutable,
   so this function must only be used on a new Decimal that has been created by
   PyDec_Alloc().

.. c:function:: const mpd_t *PyDec_GetConst(const PyObject *v)

   Get a pointer to the constant internal ``mpd_t`` of the decimal.