Skip to main content

Objects/longobject.c (part 9)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/longobject.c

This annotation covers the compact int representation added in Python 3.12 and the conversion helpers. See objects_longobject8_detail for bit_length, to_bytes, and from_bytes.

Map

LinesSymbolRole
1-80Compact int layoutSingle-digit ints store value inline (no ob_digit heap array)
81-180_PyLong_IsCompact / _PyLong_CompactValueInline value access without digit-array indirection
181-300PyLong_AsLongConvert to C long; raise OverflowError if too large
301-400PyLong_AsDoubleConvert to double; may lose precision
401-500Small integer cache_Py_SINGLETON(small_ints) for -5 to 256

Reading

Compact int layout

// CPython: Objects/longobject.c:38 compact layout (Python 3.12+)
/* For integers that fit in a single 30-bit digit (roughly -2^30 to 2^30-1),
the value is stored inline in the PyLongObject header rather than in a
separate ob_digit array. This eliminates one level of indirection. */

struct _PyLongValue {
uintptr_t lv_tag; /* bottom 3 bits: sign + ndigits; upper bits: value if compact */
digit ob_digit[1]; /* flexible array; unused for compact ints */
};

#define _PyLong_IsNonNegativeCompact(op) (((op)->long_value.lv_tag & 3) == 1)

Compact ints (Python 3.12+) pack the value into lv_tag when it fits in PYLONG_COMPACT_BITS bits. This eliminates a pointer dereference for common small values.

_PyLong_IsCompact

// CPython: Objects/longobject.c:80 _PyLong_IsCompact
static inline int
_PyLong_IsCompact(const PyLongObject *op)
{
/* Returns 1 if the value fits in a single digit and is stored
inline in lv_tag. */
return op->long_value.lv_tag < (2 << NON_SIZE_BITS);
}

static inline Py_ssize_t
_PyLong_CompactValue(const PyLongObject *op)
{
assert(_PyLong_IsCompact(op));
/* Extract the value from the tag bits */
return (Py_ssize_t)op->long_value.lv_tag >> (NON_SIZE_BITS + 1);
}

Hot code like BINARY_OP_ADD_INT checks _PyLong_IsCompact first and uses _PyLong_CompactValue directly to avoid ob_digit allocation.

PyLong_AsLong

// CPython: Objects/longobject.c:240 PyLong_AsLong
long
PyLong_AsLong(PyObject *vv)
{
/* Fast path for compact ints */
if (PyLong_CheckExact(vv) && _PyLong_IsCompact((PyLongObject *)vv)) {
Py_ssize_t val = _PyLong_CompactValue((PyLongObject *)vv);
if (val >= LONG_MIN && val <= LONG_MAX) return (long)val;
}
/* General: check digit count and extract value */
long res;
int overflow;
res = PyLong_AsLongAndOverflow(vv, &overflow);
if (overflow) {
PyErr_SetString(PyExc_OverflowError,
"Python int too large to convert to C long");
return -1;
}
return res;
}

PyLong_AsLong is used extensively in the C API. The compact fast path avoids digit-array access for the common case.

Small integer cache

// CPython: Objects/longobject.c:420 small int cache
/* _Py_SINGLETON(small_ints)[i+_PY_NSMALLNEGINTS] is the PyLongObject for integer i,
where -5 <= i <= 256. These are immortal objects (never freed). */
#define _PY_NSMALLNEGINTS 5 /* cache -5 to -1 */
#define _PY_NSMALLPOSINTS 257 /* cache 0 to 256 */

PyObject *
_PyLong_GetSmallInt_impl(int value)
{
assert(-_PY_NSMALLNEGINTS <= value && value < _PY_NSMALLPOSINTS);
return (PyObject *)&_Py_SINGLETON(small_ints)[value + _PY_NSMALLNEGINTS];
}

PyLong_FromLong(42) returns the cached singleton. The cache covers -5 to 256, which covers nearly all loop counters, small indices, and ASCII character codes used in typical programs.

gopy notes

objects.Int in objects/int.go uses Go's int64 for small values and *big.Int for large ones. The compact layout is approximated: values fitting in int64 are stored directly. PyLong_AsLong is objects.IntAsLong. The small int cache is objects.SmallInts[261] — an array of pre-allocated objects.Int values for -5 to 255.