The Python/C API manual mentions conversion functions from⁽¹⁾ and to⁽²⁾ void pointers, which seem to be the only way to use arbitrary length python integers in C.
(1) : PyLong_FromVoidPtr() and format 0& with Py_BuildValue()
(2) : PyLong_AsVoidPtr() and formats 0, 0& and 0! with PyArg_…Parse…()

However, I haven't found⁽³⁾ in the manual, any indication about the way to use those void pointers to do whatever in C with those arbitrary long integers.
(3) : I tried a search for «voidptr», «void *» and «0&» but haven't thoroughly read it all yet.

Where can I find information about their inner structure or primitives to compute on them ?

2 Answers

5
jdehesa On Best Solutions

Actually, those functions are not to have "a pointer to an arbitrarily large integer", but literally just integer values as void * pointer, as in, casted to the type void *. See the implementations for PyLong_FromVoidPtr and PyLong_AsVoidPtr. It's there just to allow you to hold arbitrary pointers within Python, making sure the casting is done correctly.

As far as I can tell, the most practical way get arbitrary long integers from and into Python would be with int.to_bytes and int.from_bytes. There is actually a internal-ish API _PyLong_FromByteArray / _PyLong_AsByteArray for that which you can probably use. See the related question Python extension - construct and inspect large integers efficiently.

Note: Interestingly, there does not seem to be any C API, official or otherwise, to tell the bit or byte length of a Python integer value. In Python there is int.bit_length, but it does not appear to map to any publicly available function.

3
Antti Haapala On

There is documentation in Include/longintrepr.h:

/* Parameters of the integer representation.  There are two different
   sets of parameters: one set for 30-bit digits, stored in an unsigned 32-bit
   integer type, and one set for 15-bit digits with each digit stored in an
   unsigned short.  The value of PYLONG_BITS_IN_DIGIT, defined either at
   configure time or in pyport.h, is used to decide which digit size to use.

   Type 'digit' should be able to hold 2*PyLong_BASE-1, and type 'twodigits'
   should be an unsigned integer type able to hold all integers up to
   PyLong_BASE*PyLong_BASE-1.  x_sub assumes that 'digit' is an unsigned type,
   and that overflow is handled by taking the result modulo 2**N for some N >
   PyLong_SHIFT.  The majority of the code doesn't care about the precise
   value of PyLong_SHIFT, but there are some notable exceptions:

   - long_pow() requires that PyLong_SHIFT be divisible by 5

   - PyLong_{As,From}ByteArray require that PyLong_SHIFT be at least 8

   - long_hash() requires that PyLong_SHIFT is *strictly* less than the number
     of bits in an unsigned long, as do the PyLong <-> long (or unsigned long)
     conversion functions

   - the Python int <-> size_t/Py_ssize_t conversion functions expect that
     PyLong_SHIFT is strictly less than the number of bits in a size_t

   - the marshal code currently expects that PyLong_SHIFT is a multiple of 15

   - NSMALLNEGINTS and NSMALLPOSINTS should be small enough to fit in a single
     digit; with the current values this forces PyLong_SHIFT >= 9

  The values 15 and 30 should fit all of the above requirements, on any
  platform.
*/

The length of the int is the length of the variable length portion times 15/16 in bits - the digits are either 30 bits in uint32_t, #if PYLONG_BITS_IN_DIGIT == 30, else 15 bits in uint16_t; the structure of long object is

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

There is a member ob_size that will tell the size in bytes - so if the PYLONG_BITS_IN_DIGIT is 30 the ob_digit is an array of ob_size / sizeof(uint32_t) uint32_ts, 30 bits are significant in each; otherwise ob_digit is an array of ob_size / sizeof(uint16_t) uint16_ts, 15 significant bits are stored in each digit.

This is all part of Include/longintrepr.h, but they're revealed only #ifndef Py_LIMITED_API!