Skip to main content

Objects/complexobject.c: complex number internals

Objects/complexobject.c implements Python's built-in complex type. At around 700 lines it is one of the smaller object files, but it is notable for splitting the arithmetic into a plain C layer (Py_complex struct and _Py_c_* helpers) and a Python object layer (PyComplexObject). This separation lets the math be reused without touching the GC or reference counts.

Map

Lines (approx)SymbolPurpose
1-50Py_complex layoutC struct with real and imag doubles
51-180_Py_c_sum, _Py_c_diffaddition and subtraction on bare C structs
181-310_Py_c_prod, _Py_c_quotmultiplication and division; NaN/inf handling
311-370_Py_c_pow, _Py_c_abspower and absolute value
371-480complex_new__new__ parsing of (real, imag) arguments
481-560complex_richcompareequality only; ordering raises TypeError
561-620complex_hashhash using sys.hash_info modulus and _Py_HashDouble
621-700number protocolnb_add, nb_mul, nb_neg, etc. wired to _Py_c_*

Reading

Py_complex C struct

The arithmetic layer works entirely on a plain C struct. No Python object is involved until the result is boxed by PyComplex_FromCComplex.

// CPython: Objects/complexobject.c:22 Py_complex
typedef struct {
double real;
double imag;
} Py_complex;

This means _Py_c_prod and friends can be called from the math module or the cmath module without allocating a PyComplexObject each time.

Multiplication and division

Multiplication follows the textbook formula. Division guards against the denominator being zero and against intermediate overflow when both components are very large, using the Smith (1962) robust algorithm.

// CPython: Objects/complexobject.c:209 _Py_c_quot
Py_complex
_Py_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;
}

Hash

The hash of a complex number whose imaginary part is zero must equal the hash of the equivalent real number (a Python invariant for numeric types). When the imaginary part is non-zero CPython combines the two hashes via a multiplier derived from sys.hash_info.

// CPython: Objects/complexobject.c:573 complex_hash
static Py_hash_t
complex_hash(PyComplexObject *v)
{
Py_uhash_t hashreal, hashimag, combined;
hashreal = (Py_uhash_t)_Py_HashDouble((PyObject *)v, v->cval.real);
if (hashreal == (Py_uhash_t)-1)
return -1;
hashimag = (Py_uhash_t)_Py_HashDouble((PyObject *)v, v->cval.imag);
if (hashimag == (Py_uhash_t)-1)
return -1;
combined = hashreal + _PyHASH_IMAG * hashimag;
if (combined == (Py_uhash_t)-1)
combined = (Py_uhash_t)-2;
return (Py_hash_t)combined;
}

gopy notes

  • objects/complex.go mirrors the two-layer split: a goComplex struct (two float64 fields) handles pure math, and ComplexObject wraps it for the Python object protocol.
  • _Py_c_quot is ported verbatim using the Smith-robust branch; the original algorithmic structure is preserved rather than delegating to cmplx.Div from math/cmplx, because Go's standard library does not use the same NaN-propagation rules in all edge cases.
  • complex_richcompare returns NotImplemented for <, <=, >, >=; gopy raises TypeError from the same comparison entry point.
  • The hash invariant (imaginary part zero implies hash equals real hash) is covered by a table-driven test in objects/complex_test.go.
  • cpython 3.14 @ ab2d84fe1023/

    is the reference for the _PyHASH_IMAG multiplier.