Skip to main content

Objects/floatobject.c

cpython 3.14 @ ab2d84fe1023/Objects/floatobject.c

Objects/floatobject.c implements Python's float type. It wraps a C double in PyFloatObject, exposes the full arithmetic slot table, handles float-to-integer and integer-to-float coercion with correct IEEE 754 edge cases (infinities, NaN, signed zero), provides __hash__ that is consistent with int.__hash__ for integer-valued floats, drives PyOS_double_to_string for repr() and str(), and registers PyFloat_Type. The file is 2 642 lines. Its most subtle section is float_richcompare, which carefully avoids rounding errors when comparing a float to an int.

Map

LinesSymbolRole
1-29headersIncludes; clinic/floatobject.c.h for Argument Clinic
32-121PyFloat_GetMax, PyFloat_GetMin, PyFloat_GetInfoPublic info API; populates sys.float_info
123-136PyFloat_FromDoublePrimary constructor; serves from freelist
138-147_PyFloat_FromDouble_ConsumeInputsStackref-consuming variant for the adaptive interpreter
149-237float_from_string_inner, PyFloat_FromStringString-to-float via PyOS_string_to_double
239-253_PyFloat_ExactDealloc, float_deallocReturn to freelist or call tp_free
256-313PyFloat_AsDoubleExtract C double; falls back to nb_index for integer types
348-364float_reprDelegates to PyOS_double_to_string with 'r' format
381-550float_richcompareNaN-safe, overflow-safe float/int comparison
551-623float_add, float_sub, float_mul, float_div, float_remCore arithmetic slots
624-695float_divmod, float_floor_divdivmod() and //
696-816float_pow** with special-case handling for 0.0, -0.0, infinity
817-972float_neg, float_abs, float_bool, float_int, float_truncUnary slots; conversion to int
973-1162float_new, float_vectorcallfloat.__new__ and vectorcall path
1163-1490float___format__, float_as_integer_ratioFormatting and ratio decomposition
1491-1808float_is_integer, float_conjugate, float_hex, float_fromhexMethods
1810-1844float_as_numberPyNumberMethods slot table
1846-1888PyFloat_TypeType object
1890-2642marshal helpers, freelist init/finiPyFloat_Pack*, PyFloat_Unpack*, freelist management

Reading

Allocation and the Float Freelist

PyFloat_FromDouble is deliberately minimal: pop one slot from the freelist, or call PyObject_Malloc for a fresh PyFloatObject, initialize it with _PyObject_Init, store fval, and return.

// CPython: Objects/floatobject.c:123 PyFloat_FromDouble
PyObject *
PyFloat_FromDouble(double fval)
{
PyFloatObject *op = _Py_FREELIST_POP(PyFloatObject, floats);
if (op == NULL) {
op = PyObject_Malloc(sizeof(PyFloatObject));
if (!op) {
return PyErr_NoMemory();
}
_PyObject_Init((PyObject*)op, &PyFloat_Type);
}
op->ob_fval = fval;
return (PyObject *) op;
}

Deallocation mirrors this: _PyFloat_ExactDealloc pushes back to the freelist via _Py_FREELIST_FREE. Subtype instances (where PyFloat_CheckExact fails) go through tp_free instead.

float_richcompare: Comparing Float to Int

Comparing a float to an int is one of the trickiest places in CPython. Converting the integer to double can silently drop bits; converting the double to integer discards fractional values. float_richcompare (line 381) handles both failure modes.

When the float is non-finite (infinity or NaN), the comparison degenerates to a sign check against the integer. When the float has a fractional part, it cannot equal any integer. For large integers that overflow double, CPython compares exponents directly. Only when the integer fits exactly in a double does the code perform i == j on two C doubles.

// CPython: Objects/floatobject.c:381 float_richcompare
static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
double i, j;
int r = 0;
assert(PyFloat_Check(v));
i = PyFloat_AS_DOUBLE(v);
/* if w is a float, j = (double)w directly */
/* if i is non-finite, compare by sign */
/* otherwise, extract integer mantissa and exponent and compare exactly */
}

Arithmetic Slots

The five core binary operators (float_add, float_sub, float_mul, float_div, float_rem) all follow the same pattern: expand both operands to double via the CONVERT_TO_DOUBLE macro, compute, and wrap the result with PyFloat_FromDouble. Division additionally checks for b == 0.0 and raises ZeroDivisionError.

// CPython: Objects/floatobject.c:552 float_add
static PyObject *
float_add(PyObject *v, PyObject *w)
{
double a,b;
CONVERT_TO_DOUBLE(v, a);
CONVERT_TO_DOUBLE(w, b);
a = a + b;
return PyFloat_FromDouble(a);
}

float_pow (line 696) is the exception: it handles a dozen special cases mandated by IEEE 754, including 0**0 == 1.0, (-1.0)**inf == 1.0, and negative-base fractional-exponent domain errors.

float_hex and float_fromhex

float_hex (line 1491) formats a double as a C99-style hexadecimal string such as 0x1.fffffffffffffp+1023. float_fromhex (line 1562) inverts this, carefully reconstructing the sign, mantissa, and biased exponent without going through strtod, which avoids platform rounding differences.

gopy notes

  • PyFloat_FromDouble maps to objects.NewFloat(fval float64) in objects/float.go.
  • The freelist is tracked in objects/freelist.go alongside the int freelist.
  • float_richcompare is the most important function to port correctly; the float-vs-int path requires the same exponent-comparison logic.
  • _PyFloat_FromDouble_ConsumeInputs is used by the tier-2 optimizer; port it alongside the BINARY_OP_ADD_FLOAT uop.
  • float_hex / float_fromhex correspond to the float.hex() and float.fromhex() methods; these are not yet ported.

CPython 3.14 changes

  • _PyFloat_FromDouble_ConsumeInputs (line 138) was added in 3.14 for the specializing interpreter; it consumes two _PyStackRef inputs before constructing the result.
  • float_vectorcall was added in 3.11 and remains in 3.14; PyFloat_Type sets .tp_vectorcall = float_vectorcall directly in the struct initializer.
  • _Py_TYPE_VERSION_FLOAT is assigned to tp_version_tag at line 1887, enabling the inline caching mechanism introduced in 3.12 for float attribute lookups.