The C Path — learn C, visually

🚀 Modern C (C11 → C23)

_Complex: C does imaginary numbers natively

⏱ 11 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

Those 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).

cplx.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;
}
terminal
$ 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:

roots.c
#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;
}
terminal
$ 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

🧠 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)