Skip to main content

Objects/complexobject.c

cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c

complexobject.c defines CPython's built-in complex type. It holds a Py_complex struct (two C double fields, real and imag) and implements all arithmetic directly in C before wrapping results in a PyComplexObject. The file also handles the __complex__ protocol, hash computation, and the public C API surface (PyComplex_RealAsDouble, PyComplex_ImagAsDouble, etc.).

Map

LinesSymbolRolegopy
1-40includes, Py_TPFLAGS_BASETYPEHeader setup and type flagsnot ported
41-120c_sum, c_diff, c_prod, c_quotPure-C complex arithmetic on Py_complexnot ported
121-200c_pow, c_absPower and absolute value for Py_complexnot ported
201-280PyComplex_FromCComplex, PyComplex_FromDoublesAllocate and return a PyComplexObjectnot ported
281-360PyComplex_RealAsDouble, PyComplex_ImagAsDoubleExtract components from any object via __complex__not ported
361-480PyComplex_AsCComplexCoerce an arbitrary Python object to Py_complexnot ported
481-620complex_add, complex_sub, complex_mul, complex_divPyNumberMethods slots delegating to c_* helpersnot ported
621-720complex_pow, complex_neg, complex_pos, complex_absRemaining numeric slotsnot ported
721-800complex_bool, complex_richcompareBoolean and ==/!= comparison (order comparisons raise TypeError)not ported
801-880complex_hashHash via _Py_HashDouble on real and imag partsnot ported
881-980complex_repr, complex_formatString and __format__ outputnot ported
981-1060complex_newtp_new: parse real and imag args, call __complex__ when needednot ported
1061-1100PyComplexType definitionPyTypeObject struct with all slots wired upnot ported

Reading

Pure-C arithmetic layer (lines 41 to 200)

cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c#L41-200

Before any Python object is involved, CPython does all complex arithmetic on bare Py_complex structs. This keeps the hot path free of reference counting and GIL concerns. The four basic operations follow textbook definitions; c_quot guards against division by zero by checking the magnitude of the denominator:

Py_complex
c_quot(Py_complex a, Py_complex b)
{
Py_complex r;
double abs_breal = b.real < 0 ? -b.real : b.real;
double abs_bimag = b.imag < 0 ? -b.imag : b.imag;
if (abs_breal >= abs_bimag) {
double ratio = b.imag / b.real;
double denom = b.real + b.imag * ratio;
r.real = (a.real + a.imag * ratio) / denom;
r.imag = (a.imag - a.real * ratio) / denom;
} else {
double ratio = b.real / b.imag;
double denom = b.real * ratio + b.imag;
r.real = (a.real * ratio + a.imag) / denom;
r.imag = (a.imag * ratio - a.real) / denom;
}
return r;
}

The two-branch algorithm avoids catastrophic cancellation when one component dominates the other. c_pow is more elaborate: it special-cases zero exponents, integer exponents, and uses cexp/clog for the general case.

Coercion and the __complex__ protocol (lines 361 to 480)

cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c#L361-480

PyComplex_AsCComplex is the central coercion point. It first checks whether the argument is already a PyComplexObject; if so it returns the internal cval field directly. Otherwise it looks for a __complex__ method, calls it, and validates that the result is a complex instance:

Py_complex
PyComplex_AsCComplex(PyObject *op)
{
Py_complex cv;
PyObject *newop = NULL;

if (PyComplex_Check(op)) {
return ((PyComplexObject *)op)->cval;
}
/* Try __complex__ */
...
newop = _PyObject_CallMethod(op, &_Py_ID(__complex__), NULL);
if (!PyComplex_Check(newop)) {
PyErr_SetString(PyExc_TypeError,
"__complex__ should return a complex object");
...
}
cv = ((PyComplexObject *)newop)->cval;
...
return cv;
}

This is the mechanism that lets user-defined classes integrate with complex() and with arithmetic operators transparently.

complex_new and argument parsing (lines 981 to 1060)

cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c#L981-1060

complex_new handles all valid call signatures: complex(), complex(x), complex(x, y), complex("3+4j"). The string path delegates to PyOS_string_to_double and a custom parser. The two-argument path calls PyComplex_AsCComplex on each argument independently and then adds the imaginary parts:

cr = PyComplex_AsCComplex(r);
...
ci = PyComplex_AsCComplex(i);
...
/* real part = cr.real - ci.imag, imag part = cr.imag + ci.real */
return PyComplex_FromDoubles(cr.real - ci.imag, cr.imag + ci.real);

The subtraction of ci.imag from the real part is the correct identity: complex(a+bj, c+dj) equals (a - d) + (b + c)j.

gopy mirror

complexobject.c has no counterpart in gopy yet. When ported, the natural home is objects/complex.go. The pure-C arithmetic layer maps cleanly to Go functions operating on a struct{ Real, Imag float64 }, and Go's math/cmplx package can cross-check correctness. The __complex__ protocol lookup fits into gopy's existing protocol.go pattern.

CPython 3.14 changes

CPython 3.14 introduced _colorize support for REPL output; complex_repr now produces highlighted output when running interactively. The hash algorithm itself did not change. The __complex__ protocol gained a deprecation path for objects that define __complex__ but return a non-complex type, matching the deprecation applied to __int__ and __float__ in earlier releases.