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
| Lines | Symbol | Role |
|---|---|---|
| 1-50 | PyLong_SHIFT, PyLong_BASE | Bits per digit (30 on 64-bit, 15 on 32-bit) |
| 51-100 | digit type | uint32_t on 64-bit; each stores up to 2^30 |
| 101-140 | Compact int layout | ob_digit[1] inlined for ` |
| 141-160 | _PyLong_IsZero, _PyLong_IsOne, _PyLong_IsNegative | Fast predicate macros |
| 161-180 | Small int cache | Pre-allocated integers for [-5, 256] |
| 181-200 | _PyLong_GetZero, _PyLong_GetOne | Cached 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.