Skip to main content

Include/internal/pycore_long.h

Source:

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_long.h

pycore_long.h exposes the internal representation of Python's arbitrary-precision integer. The layout changed significantly in 3.12 to inline small integers.

Map

LinesSymbolRole
1-50PyLong_SHIFT, PyLong_BASEBits per digit (30 on 64-bit, 15 on 32-bit)
51-100digit typeuint32_t on 64-bit; each stores up to 2^30
101-140Compact int layoutob_digit[1] inlined for `
141-160_PyLong_IsZero, _PyLong_IsOne, _PyLong_IsNegativeFast predicate macros
161-180Small int cachePre-allocated integers for [-5, 256]
181-200_PyLong_GetZero, _PyLong_GetOneCached singleton access

Reading

Digit representation

// CPython: Include/internal/pycore_long.h:28 PyLong_SHIFT
#if PYLONG_BITS_IN_DIGIT == 30
typedef uint32_t digit;
typedef uint64_t twodigits;
#define PyLong_SHIFT 30
#define PyLong_BASE ((digit)1 << PyLong_SHIFT)
#define PyLong_MASK ((digit)(PyLong_BASE - 1))
#endif

A digit holds 30 bits (not 32) so that digit + digit never overflows twodigits, simplifying carry propagation.

New compact layout (3.12+)

// CPython: Include/internal/pycore_long.h:110 _PyLongValue
typedef union {
uintptr_t lv_tag; /* sign + ndigits encoded in low bits */
digit ob_digit[1]; /* for compatibility */
} _PyLongValue;

struct _longobject {
PyObject_HEAD
_PyLongValue long_value;
};

Tag encoding (3.12+):

  • Bits 0: sign (0 = positive/zero, 1 = negative)
  • Bits 1+: ndigits (0 = compact single digit stored in tag itself)

For integers |n| < 2^30, ndigits=0 and the value is stored directly in lv_tag >> 3. No ob_digit array is allocated.

Fast predicates

// CPython: Include/internal/pycore_long.h:145 _PyLong_IsZero
static inline int
_PyLong_IsZero(PyLongObject *op)
{
return (op->long_value.lv_tag & ~_PyLong_NON_SIZE_BITS) == 0;
}

static inline int
_PyLong_IsNegative(PyLongObject *op)
{
return (op->long_value.lv_tag & _PyLong_SIGN_MASK) != 0;
}

static inline Py_ssize_t
_PyLong_DigitCount(PyLongObject *op)
{
return (Py_ssize_t)(op->long_value.lv_tag >> _PyLong_NON_SIZE_BITS);
}

Small int cache

// CPython: Include/internal/pycore_long.h:168 _PyLong_IsSmall
#define _PY_NSMALLPOSINTS 257 /* 0 to 256 */
#define _PY_NSMALLNEGINTS 5 /* -5 to -1 */
/* The cache lives in runtime state: runtime->small_ints[-5..256] */

static inline PyObject *
_PyLong_GetZero(void)
{
return (PyObject *)&_Py_SINGLETON(small_ints)[_PY_NSMALLNEGINTS];
}

int(0) returns a pointer to the pre-allocated singleton. PyLong_FromLong(42) returns the cached object for values in [-5, 256].

gopy notes

In gopy objects.Int wraps *big.Int. The compact layout is not used. The small int cache is approximated: objects.IntCache stores pre-allocated *objects.Int for [-5, 256]. _PyLong_IsZero maps to obj.Value.Sign() == 0. _PyLong_IsNegative maps to obj.Value.Sign() < 0. _PyLong_DigitCount has no direct equivalent since big.Int manages its own digit array.