🚀 Modern C (C11 → C23)
_Complex: C does imaginary numbers natively
▶ Open the interactive lesson — free, no signupThose swirling Mandelbrot fractal posters, every MP3, and every JPEG all run on the same math: complex numbers. C has them built into the language — ordinary + and * included, no add-on library needed. You'll compute the numbers at the heart of audio compression and analyze a real electronic circuit — each in about a dozen lines of C.
Here's a C feature that surprises even veterans: complex numbers (the two-part numbers from math class, built on i = √−1) are built into the language. Since C99, double complex is a real type with real operator support — you can add, multiply, and divide complex values with plain + * /, no special function calls, no wrapper library. If you've ever played with electronics, audio effects, or fractals, C speaks your language.
The basics: <complex.h> and I
Include <complex.h> and you get: the spelling complex for the keyword _Complex, the constant I (the imaginary unit, i = √−1), and a family of functions — creal/cimag (parts), cabs (magnitude), carg (angle), conj, cexp, csqrt, and complex versions of most of the math library (prefix c).
#include <stdio.h>
#include <complex.h>
int main(void) {
double complex z = 3.0 + 4.0 * I;
printf("z = %.1f%+.1fi\n", creal(z), cimag(z));
printf("|z| = %.1f\n", cabs(z)); /* magnitude */
printf("arg(z) = %.4f rad\n", carg(z)); /* angle */
printf("conj = %.1f%+.1fi\n", creal(conj(z)), cimag(conj(z)));
double complex w = (1.0 + 1.0 * I) * (1.0 - 1.0 * I);
printf("(1+i)(1-i) = %.1f%+.1fi\n", creal(w), cimag(w));
return 0;
}$ gcc -std=c17 cplx.c -o cplx -lm && ./cplx z = 3.0+4.0i |z| = 5.0 arg(z) = 0.9273 rad conj = 3.0-4.0i (1+i)(1-i) = 2.0+0.0i # the 3-4-5 triangle, and i^2 = -1 — all with ordinary operators
Note that last line: (1+i)(1−i) = 1 − i² = 2. The compiler did complex multiplication with the ordinary * operator. Under the hood, a double complex is stored exactly as you'd guess — two doubles, real part first (the standard guarantees this layout, so it's binary-compatible with Fortran and C++):
▶ This spot has an interactive memgrid widget — open the interactive lesson to play with it.
🧠 Checkpoint: What is cabs(3.0 + 4.0*I)?
- 3.0
- 4.0
- 5.0
- 7.0
Show answer
5.0 — cabs is the complex magnitude: sqrt(re² + im²) = sqrt(9 + 16) = 5. The classic 3-4-5 right triangle — cabs is to complex numbers what fabs is to reals.
A real computation: roots of unity
The n-th roots of unity — the n complex numbers whose n-th power is 1 — are e2πik/n for k = 0…n−1. They're the skeleton of the FFT, the algorithm behind basically all audio and image compression. In C, the formula transliterates directly:
#include <stdio.h>
#include <complex.h>
#define PI 3.14159265358979323846
int main(void) {
int n = 3; /* cube roots of 1 */
for (int k = 0; k < n; k++) {
double complex root = cexp(2.0 * PI * k * I / n);
printf("root %d: %+.3f%+.3fi\n", k, creal(root), cimag(root));
}
return 0;
}$ gcc -std=c17 roots.c -o roots -lm && ./roots root 0: +1.000+0.000i root 1: -0.500+0.866i root 2: -0.500-0.866i # three points, evenly spaced around the unit circle — # cube any of them and you get 1 back
Check: those are the corners of an equilateral triangle on the unit circle, and −0.5 ± 0.866i are exactly cos(±120°) + i·sin(±120°). One cexp call per root — this is where C beats a calculator: your calculator does one arithmetic operation at a time; C does a formula over a whole range, reproducibly, in nanoseconds.
🧠 Checkpoint: How do you multiply two double complex values in C?
cmul(a, b)a * b— the operator just works- Multiply parts manually with creal/cimag
- You can’t; complex is storage-only
Show answer
a * b — the operator just works — complex is a first-class arithmetic type: +, -, *, / (and comparisons == and !=) all work directly. The compiler generates the (a.re*b.re - a.im*b.im, ...) arithmetic for you. Functions like cexp/csqrt cover what operators can’t.
Engineering flavor: impedance of an RC circuit
Electrical engineers write AC circuit analysis in complex arithmetic: a resistor contributes R, a capacitor 1/(jωC) — and then series/parallel combinations are just + and /. (EEs write j for the imaginary unit; C's I is the same thing.) A 1 kΩ resistor in series with a 1 µF capacitor at 1 kHz:
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
Magnitude ≈ 1012.6 Ω and a phase of about −9° — the capacitor barely matters at this frequency. Change f to 100.0 and watch the capacitor dominate. That's a frequency-response sweep in a for loop, which is exactly how tools like SPICE begin.
Fine print
- Complex support is optional for freestanding/embedded implementations — a conforming compiler may define
__STDC_NO_COMPLEX__and skip it. On desktop gcc/clang it's always there. Link with-lmfor the functions. _Imaginaryexists on paper, and almost nowhere else. Annex G defines pure-imaginary types (double imaginary) that store only the imaginary part. GCC and Clang have never implemented them — you will likely never see one in the wild. Trivia keyword: unlocked.- Constructing from variables:
a + b*Imostly works, but has edge cases with infinities/NaN — theCMPLX(a, b)macro (C11) builds a complex value exactly.
🧠 Checkpoint: What’s the honest status of _Imaginary?
- Required since C99, used everywhere
- A C23 addition, too new to use
- Defined in Annex G but essentially unimplemented (gcc/clang skip it)
- A deprecated alias for _Complex
Show answer
Defined in Annex G but essentially unimplemented (gcc/clang skip it) — Pure imaginary types are optional Annex G material, and the mainstream compilers never implemented them — writing double imaginary in gcc is an error. _Complex, by contrast, is real, mature, and everywhere on hosted platforms.
You've now met every C11 headliner — time for the finale: a whirlwind tour of everything C23 added to the language.
▶ Practice this lesson interactively (with live gcc)