Skip to main content

Objects/complexobject.c (part 5)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/complexobject.c

This annotation covers complex number creation and arithmetic. See objects_complexobject4_detail for __repr__, __format__, complex.real/complex.imag, and the Py_complex C struct.

Map

LinesSymbolRole
1-80PyComplex_AsCComplexExtract a C Py_complex from any Python object
81-200complex.__new__Parse real/imag arguments; handle string forms
201-340Arithmetic+, -, *, / on Py_complex structs
341-430complex.conjugateReturn complex conjugate (negate imaginary part)
431-500complex.__abs__Return modulus via hypot(real, imag)

Reading

PyComplex_AsCComplex

// CPython: Objects/complexobject.c:278 PyComplex_AsCComplex
Py_complex
PyComplex_AsCComplex(PyObject *op)
{
if (PyComplex_CheckExact(op)) {
return ((PyComplexObject *)op)->cval;
}
/* Try __complex__ protocol */
PyObject *newop = PyObject_CallMethodNoArgs(op, &_Py_ID(__complex__));
if (newop) {
Py_complex r = ((PyComplexObject *)newop)->cval;
Py_DECREF(newop);
return r;
}
/* Fall back to real */
double real = PyFloat_AsDouble(op);
return (Py_complex){real, 0.0};
}

Objects that implement __complex__ can be used wherever a complex number is expected. NumPy scalars rely on this path. If __complex__ is absent, CPython tries __float__ and treats the result as purely real.

complex.__new__

// CPython: Objects/complexobject.c:1200 complex_new_impl
static PyObject *
complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
{
/* complex('1+2j') or complex(real=1, imag=2) */
Py_complex cr, ci = {0.0, 0.0};
if (PyUnicode_Check(r)) {
/* Parse "1+2j" string — imaginary only allowed in string form */
return complex_from_string(r);
}
cr = PyComplex_AsCComplex(r);
if (i != Py_None) ci = PyComplex_AsCComplex(i);
/* Combine: (a+bj) + (c+dj)*j = (a-d) + (b+c)j */
return PyComplex_FromCComplex((Py_complex){
cr.real - ci.imag,
cr.imag + ci.real
});
}

complex(1, 2) is straightforward. complex(1+2j, 3+4j) produces (1-4) + (2+3)j = -3+5j because the second argument is multiplied by j. This matches the mathematical convention.

Complex division

// CPython: Objects/complexobject.c:180 c_quot
static Py_complex
c_quot(Py_complex a, Py_complex b)
{
/* (a+bj)/(c+dj) = ((ac+bd) + (bc-ad)j) / (c^2 + d^2)
Uses Smith's method to avoid overflow for large |b|/|d|. */
double abs_breal = fabs(b.real), abs_bimag = fabs(b.imag);
if (abs_breal >= abs_bimag) {
double ratio = b.imag / b.real;
double denom = b.real + ratio * b.imag;
return (Py_complex){
(a.real + a.imag * ratio) / denom,
(a.imag - a.real * ratio) / denom
};
} else {
double ratio = b.real / b.imag;
double denom = b.imag + ratio * b.real;
return (Py_complex){
(a.real * ratio + a.imag) / denom,
(a.imag * ratio - a.real) / denom
};
}
}

Smith's method chooses the larger of |real| or |imag| as the divisor to minimize floating-point cancellation. Without this, dividing by a number like 1e-300 + 1j could underflow.

complex.__abs__

// CPython: Objects/complexobject.c:1060 complex_abs
static PyObject *
complex_abs(PyComplexObject *v)
{
double result = hypot(v->cval.real, v->cval.imag);
return PyFloat_FromDouble(result);
}

abs(3+4j) returns 5.0. hypot is used instead of sqrt(a*a + b*b) to avoid intermediate overflow when a or b is very large.

gopy notes

PyComplex_AsCComplex is objects.ComplexAsCComplex in objects/complex.go. Complex arithmetic uses Go's cmplx package from math/cmplx. complex.__new__ string parsing uses a hand-written scanner matching CPython's complex_from_string. complex.__abs__ calls cmplx.Abs.