@@ -17,7 +17,7 @@ Abstract
17
17
========
18
18
19
19
Add a new C API to import and export Python integers, :class: `int ` objects:
20
- especially `` PyLongWriter_Create() `` and `` PyLong_Export() ` ` functions.
20
+ especially :c:func: ` PyLongWriter_Create() ` and :c:func: ` PyLong_Export() ` functions.
21
21
22
22
23
23
Rationale
@@ -26,12 +26,12 @@ Rationale
26
26
Projects such as `gmpy2 <https://github.com/aleaxit/gmpy >`_, `SAGE
27
27
<https://www.sagemath.org/> `_ and `Python-FLINT
28
28
<https://github.com/flintlib/python-flint> `_ access directly Python
29
- "internals" (the `` PyLongObject ` ` structure) or use an inefficient
29
+ "internals" (the :c:type: ` PyLongObject ` structure) or use an inefficient
30
30
temporary format (hex strings for Python-FLINT) to import and
31
31
export Python :class: `int ` objects. The Python :class: `int ` implementation
32
32
changed in Python 3.12 to add a tag and "compact values".
33
33
34
- In the 3.13 alpha 1 release, the private undocumented `` _PyLong_New() ` `
34
+ In the 3.13 alpha 1 release, the private undocumented :c:func: ` ! _PyLong_New() `
35
35
function had been removed, but it is being used by these projects to
36
36
import Python integers. The private function has been restored in 3.13
37
37
alpha 2.
@@ -49,38 +49,46 @@ Specification
49
49
Layout API
50
50
----------
51
51
52
- API::
52
+ Data needed by ` GMP < https://gmplib.org/ >`_-like import-export functions.
53
53
54
- typedef struct PyLongLayout {
55
- // Bits per digit
56
- uint8_t bits_per_digit;
54
+ .. c :struct :: PyLongLayout
57
55
58
- // Digit size in bytes
59
- uint8_t digit_size;
56
+ Layout of an array of digits, used by Python :class: `int ` object.
60
57
61
- // Digits order:
62
- // * 1 for most significant digit first
63
- // * -1 for least significant digit first
64
- int8_t digits_order;
58
+ Use :c:func: `PyLong_GetNativeLayout ` to get the native layout of Python
59
+ :class: `int ` objects.
65
60
66
- // Endianness:
67
- // * 1 for most significant byte first (big endian)
68
- // * -1 for least significant byte first (little endian)
69
- int8_t endianness;
70
- } PyLongLayout;
61
+ See also :data: `sys.int_info ` which exposes similar information to Python.
71
62
72
- PyAPI_FUNC(const PyLongLayout*) PyLong_GetNativeLayout(void);
63
+ .. c : member :: uint8_t bits_per_digit
73
64
74
- Data needed by `GMP <https://gmplib.org/ >`_-like import-export functions.
65
+ Bits per digit.
66
+
67
+ .. c :member :: uint8_t digit_size
68
+
69
+ Digit size in bytes.
70
+
71
+ .. c :member :: int8_t digits_order
72
+
73
+ Digits order:
74
+
75
+ - ``1 `` for most significant digit first
76
+ - ``-1 `` for least significant digit first
75
77
76
- PyLong_GetNativeLayout()
77
- ^^^^^^^^^^^^^^^^^^^^^^^^
78
+ .. c :member :: int8_t endian
78
79
79
- API: :
80
+ Digit endianness :
80
81
81
- const PyLongLayout* PyLong_GetNativeLayout(void)
82
+ - ``1 `` for most significant byte first (big endian)
83
+ - ``-1 `` for least significant first (little endian)
84
+
85
+
86
+ .. c :function :: const PyLongLayout* PyLong_GetNativeLayout (void)
87
+
88
+ Get the native layout of Python :class:`int` objects.
89
+
90
+ See the :c:struct:`PyLongLayout` structure.
82
91
83
- Get the native layout of Python :class: `int ` objects.
84
92
85
93
The function must not be called before Python initialization nor after
86
94
Python finalization. The returned layout is valid until Python is
@@ -91,134 +99,112 @@ so it can be cached.
91
99
Export API
92
100
----------
93
101
94
- Export a Python integer as a digits array::
102
+ .. c:struct:: PyLongExport
95
103
96
- typedef struct PyLongExport {
97
- // use value, if digits set to NULL.
98
- int64_t value;
104
+ Export of a Python :class:`int` object.
99
105
100
- // 1 if the number is negative, 0 otherwise.
101
- uint8_t negative;
106
+ There are two cases:
102
107
103
- // Number of digits in the 'digits' array.
104
- Py_ssize_t ndigits;
108
+ * If :c:member:`digits` is ``NULL``, only use the :c:member:`value` member.
109
+ Calling :c:func:`PyLong_FreeExport` is optional in this case.
110
+ * If :c:member:`digits` is not ``NULL``, use :c:member:`negative`,
111
+ :c:member:`ndigits` and :c:member:`digits` members.
112
+ Calling :c:func:`PyLong_FreeExport` is mandatory in this case.
105
113
106
- // Read-only array of unsigned digits.
107
- const void *digits;
114
+ .. c:member:: int64_t value
108
115
109
- // Member used internally, must not be used for other purpose.
110
- Py_uintptr_t _reserved;
111
- } PyLongExport;
116
+ The native integer value of the exported :class:`int` object.
117
+ Only valid if :c:member:`digits` is ``NULL``.
112
118
113
- int PyLong_Export(PyObject *obj, PyLongExport *array);
114
- void PyLong_FreeExport(PyLongExport *array);
119
+ .. c:member:: uint8_t negative
115
120
116
- On CPython 3.14, no memory copy is needed, it's just a thin wrapper to
117
- expose Python int internal digits array .
121
+ 1 if the number is negative, 0 otherwise.
122
+ Only valid if :c:member:` digits` is not ``NULL`` .
118
123
119
- ``PyLongExport._reserved ``, if ``digits `` not ``NULL ``, stores a strong
120
- reference to the Python :class: `int ` object to make sure that that structure
121
- remains valid until ``PyLong_FreeExport() `` is called.
124
+ .. c:member:: Py_ssize_t ndigits
122
125
126
+ Number of digits in :c:member:`digits` array.
127
+ Only valid if :c:member:`digits` is not ``NULL``.
123
128
124
- PyLong_Export()
125
- ^^^^^^^^^^^^^^^
129
+ .. c:member:: const void *digits
126
130
127
- API::
131
+ Read-only array of unsigned digits. Can be ``NULL``.
128
132
129
- int PyLong_Export(PyObject *obj, PyLongExport *array)
130
133
131
- Export a Python :class: ` int ` object as a digits array.
134
+ .. c:function:: int PyLong_Export(PyObject * obj, PyLongExport *export_long)
132
135
133
- On success, set *\* array * and return 0.
134
- On error, set an exception and return -1.
136
+ Export a Python :class: `int ` object.
135
137
136
- If `` array->digits `` set to `` NULL ``, caller must use instead `` array->value ``
137
- to get value of an :class: ` int ` object .
138
+ On success, set * \* export_long * and return 0.
139
+ On error, set an exception and return -1 .
138
140
139
- CPython implementation detail: This function always succeeds if *obj * is a
140
- Python :class: ` int ` object or a subclass.
141
+ This function always succeeds if *obj * is a Python :class: ` int ` object or a
142
+ subclass.
141
143
142
- ``PyLong_FreeExport() `` must be called once done with using *array *.
144
+ If *export_long.digits * is not ``NULL ``, :c:func: `PyLong_FreeExport ` must be
145
+ called when the export is no longer needed.
143
146
147
+ On CPython 3.14, no memory copy is needed, it's just a thin wrapper to
148
+ expose Python int internal digits array.
144
149
145
- PyLong_FreeExport()
146
- ^^^^^^^^^^^^^^^^^^^
150
+ A private field of the :c:struct: `PyLongExport `, if
151
+ :c:member: `~PyLongExport.digits ` not ``NULL ``, stores a strong reference to the
152
+ Python :class: `int ` object to make sure that that structure remains valid until
153
+ :c:func: `PyLong_FreeExport() ` is called.
147
154
148
- API::
149
155
150
- void PyLong_FreeExport(PyLongExport *array )
156
+ .. c : function :: void PyLong_FreeExport (PyLongExport *export_long )
151
157
152
- Free the export *array * created by `` PyLong_Export() ` `.
158
+ Release the export *export_long * created by :c:func: ` PyLong_Export `.
153
159
154
160
155
161
Import API
156
162
----------
157
163
158
- Import a Python integer from a digits array::
159
-
160
- // A Python integer writer instance.
161
- // The instance must be destroyed by PyLongWriter_Finish().
162
- typedef struct PyLongWriter PyLongWriter;
163
-
164
- PyAPI_FUNC(PyLongWriter*) PyLongWriter_Create(
165
- int negative,
166
- Py_ssize_t ndigits,
167
- void **digits);
168
- PyAPI_FUNC(PyObject*) PyLongWriter_Finish(PyLongWriter *writer);
169
- PyAPI_FUNC(void) PyLongWriter_Discard(PyLongWriter *writer);
164
+ The :c:type: `PyLongWriter ` API can be used to import an integer.
170
165
171
- On CPython 3.14, the implementation is a thin wrapper to the private
172
- ``_PyLong_New() `` function.
173
-
174
- ``PyLongWriter_Finish() `` takes care of normalizing the digits and
175
- converts the object to a compact integer if needed.
166
+ .. c :struct :: PyLongWriter
176
167
168
+ A Python :class: `int ` writer instance.
177
169
178
- PyLongWriter_Create()
179
- ^^^^^^^^^^^^^^^^^^^^^
170
+ The instance must be destroyed by :c:func: `PyLongWriter_Finish `.
180
171
181
- API::
182
172
183
- PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
173
+ .. c : function :: PyLongWriter* PyLongWriter_Create (int negative, Py_ssize_t ndigits, void **digits)
184
174
185
- Create a `` PyLongWriter ` `.
175
+ Create a :c:type: ` PyLongWriter `.
186
176
187
- On success, set *\* digits * and return a writer.
188
- On error, set an exception and return ``NULL ``.
177
+ On success, set *\* digits * and return a writer.
178
+ On error, set an exception and return ``NULL ``.
189
179
190
- *negative * is ``1 `` if the number is negative, or ``0 `` otherwise.
180
+ *negative * is ``1 `` if the number is negative, or ``0 `` otherwise.
191
181
192
- *ndigits * is the number of digits in the *digits * array. It must be
193
- greater than or equal to 0.
182
+ *ndigits * is the number of digits in the *digits * array. It must be
183
+ greater than or equal to 0.
194
184
195
- The caller must initialize the digits array *digits * and then call
196
- `` PyLongWriter_Finish() ` ` to get a Python :class: `int `. Digits must be
197
- in the range [``0 ``; ``PyLong_BASE - 1 ``]. Unused digits must be set to
198
- ``0 ``.
185
+ The caller must initialize the array of digits *digits * and then call
186
+ :c:func: ` PyLongWriter_Finish ` to get a Python :class: `int `. Digits must be
187
+ in the range [``0 ``; ``PyLong_BASE - 1 ``]. Unused digits must be set to
188
+ ``0 ``.
199
189
190
+ On CPython 3.14, the implementation is a thin wrapper to the private
191
+ :c:func: `!_PyLong_New() ` function.
200
192
201
- PyLongWriter_Finish()
202
- ^^^^^^^^^^^^^^^^^^^^^
203
-
204
- API::
205
-
206
- PyObject* PyLongWriter_Finish(PyLongWriter *writer)
207
193
208
- Finish a `` PyLongWriter `` created by `` PyLongWriter_Create() ``.
194
+ .. c : function :: PyObject* PyLongWriter_Finish (PyLongWriter * writer)
209
195
210
- On success, return a Python :class: `int ` object.
211
- On error, set an exception and return ``NULL ``.
196
+ Finish a :c:type: `PyLongWriter ` created by :c:func: `PyLongWriter_Create `.
212
197
198
+ On success, return a Python :class: `int ` object.
199
+ On error, set an exception and return ``NULL ``.
213
200
214
- PyLongWriter_Discard()
215
- ^^^^^^^^^^^^^^^^^^^^^^
201
+ :c:func: ` PyLongWriter_Finish() ` takes care of normalizing the digits and
202
+ converts the object to a compact integer if needed.
216
203
217
- API::
218
204
219
- void PyLongWriter_Discard(PyLongWriter *writer)
205
+ .. c : function :: void PyLongWriter_Discard (PyLongWriter *writer)
220
206
221
- Discard the internal object and destroy the writer instance.
207
+ Discard the internal object and destroy the writer instance.
222
208
223
209
224
210
Optimize import for small integers
@@ -252,8 +238,8 @@ Implementation
252
238
Benchmarks
253
239
==========
254
240
255
- Export: PyLong_Export() with gmpy2
256
- ----------------------------------
241
+ Export: :c:func: ` PyLong_Export() ` with gmpy2
242
+ --------------------------------------------
257
243
258
244
Code::
259
245
@@ -284,14 +270,17 @@ Code::
284
270
if (long_export.value < 0) {
285
271
mpz_t tmp;
286
272
mpz_init(tmp);
287
- mpz_ui_pow_ui(tmp, 2, 8*sizeof(size_t) );
273
+ mpz_ui_pow_ui(tmp, 2, 64 );
288
274
mpz_sub(z, z, tmp);
289
275
mpz_clear(tmp);
290
276
}
291
277
}
292
278
}
293
279
}
294
280
281
+ Reference code: `mpz_set_PyLong() in the gmpy2 master for commit 9177648
282
+ <https://github.com/aleaxit/gmpy/blob/9177648c23f5c507e46b81c1eb7d527c79c96f00/src/gmpy2_convert_gmp.c#L42-L69> `_.
283
+
295
284
Benchmark:
296
285
297
286
.. code-block :: py
@@ -323,8 +312,8 @@ mode:
323
312
+----------------+---------+-----------------------+
324
313
325
314
326
- Import: PyLongWriter_Create() with gmpy2
327
- ----------------------------------------
315
+ Import: :c:func: ` PyLongWriter_Create() ` with gmpy2
316
+ --------------------------------------------------
328
317
329
318
Code::
330
319
@@ -353,6 +342,9 @@ Code::
353
342
return PyLongWriter_Finish(writer);
354
343
}
355
344
345
+ Reference code: `GMPy_PyLong_From_MPZ() in the gmpy2 master for commit 9177648
346
+ <https://github.com/aleaxit/gmpy/blob/9177648c23f5c507e46b81c1eb7d527c79c96f00/src/gmpy2_convert_gmp.c#L128-L156> `_.
347
+
356
348
Benchmark:
357
349
358
350
.. code-block :: py
@@ -393,16 +385,16 @@ Open Questions
393
385
==============
394
386
395
387
* Should we add *digits_order* and *endianness* members to :data:`sys.int_info`
396
- and remove `` PyLong_GetNativeLayout() ``? The
397
- `` PyLong_GetNativeLayout() ` ` function returns a C structure
388
+ and remove :c:func:` PyLong_GetNativeLayout()`? The
389
+ :c:func:` PyLong_GetNativeLayout()` function returns a C structure
398
390
which is more convenient to use in C than :data:`sys.int_info` which uses
399
391
Python objects.
400
392
* Currenly, all required information for :class:`int` import/export is
401
393
already available via :c:func:`PyLong_GetInfo()` or :data:`sys.int_info`.
402
394
Native endianness of "digits" and current order of digits (least
403
395
significant digit first) --- is a common denominator of all libraries
404
396
for aribitrary precision integer arithmetic. So, shouldn't we just remove
405
- from API both `` PyLongLayout `` and `` PyLong_GetNativeLayout() ` ` (which
397
+ from API both :c:struct:` PyLongLayout` and :c:func:` PyLong_GetNativeLayout()` (which
406
398
is actually just a minor convenience)?
407
399
408
400
@@ -416,8 +408,8 @@ It would be convenient to support arbitrary layout to import-export
416
408
Python integers.
417
409
418
410
For example, it was proposed to add a *layout* parameter to
419
- `` PyLongWriter_Create() ` ` and a *layout * member to the
420
- `` PyLongExport ` ` structure.
411
+ :c:func:` PyLongWriter_Create()` and a *layout* member to the
412
+ :c:struct:` PyLongExport` structure.
421
413
422
414
The problem is that it's more complex to implement and not really
423
415
needed. What's strictly needed is only an API to import-export using the
0 commit comments