Your C programs aren’t meant to suffer with an inability to handle values such as the square root of -1, the imaginary number i. No, you can easily manage such mathematical mysteries, making rare the possibility of a -nan
result, as shown in last week’s Lesson.
I suppose somewhere at some time, a programmer desperately needed to calculate the √-1 and then gleefully gave up when it was discovered that the computer itself was at a loss. Then along comes the C99 standard.
The C99 standard introduced the possibility for programs to deal with complex numbers. A complex number consists of a real number plus an imaginary portion. This configuration happens often in mathematics, where the result of an operation includes the imaginary value, but also some other number. For example, 4.0 + 2i.
For C programming, the C99 standard defines several things: the I
constant representing the imaginary number i, a complex data type, complex number versions of common math functions, and the complex.h
header to support all this fun stuff.
By the way, big I
is chosen to represent the imaginary number as little i
is used to often in C code.
The following code outputs the real and imaginary parts of complex constant I
:
2025_05_10-Lesson-a.c
#include <stdio.h> #include <math.h> #include <complex.h> int main() { printf("I = %f + %.0fi\n",creal(I),cimag(I)); return 0; }
Two functions are used to obtain the complex number real and imaginary parts, creal() and cimag() respectively. Both functions accept a single complex data type as their argument; they return a double. Above, the constant I
represents the complex data type. Its value is output for both real and imaginary parts:
I = 0.000000 + 1i
The complex constant I
has no real portion, no floating point value attached. It has only one i, which makes sense as I
represents the complex number i.
The following code calculates i2. Complex constant I
is multiplied by itself, with the result stored in complex variable z
. The real and imaginary portions of z
are then output.
2025_05_10-Lesson-b.c
#include <stdio.h> #include <math.h> #include <complex.h> int main() { double complex z; z = I * I; printf("I * I = %f + %.0fi\n",creal(z),cimag(z)); return 0; }
Here is the output:
I * I = -1.000000 + 0i
The value of i2 is -1, with no imaginary portion. This result makes sense as (√-1)2 is -1.
The following code calculates √-4 and outputs the result:
2025_05_10-Lesson-c.c
#include <stdio.h> #include <math.h> #include <complex.h> int main() { double complex z; z = csqrt(-4.0); printf("sqrt(-4.0) = %f + %.0fi\n",creal(z),cimag(z)); return 0; }
The csqrt() function is the complex version of the standard C library sqrt() function. Its argument is a complex data type, declared as a complex value, -4.0. The function returns a complex value, declared as z
in the above code. The printf() statement outputs the real and imaginary parts of the result:
sqrt(-4.0) = 0.000000 + 2i
The square root of -4 is 2i.
Additional complex math functions are available, including cpow() to obtain powers of complex values. To see the gamut of complex functions available, view the complex man page: man complex
Remember that in Linux, you must link in the math library to build some of these programs. At the command prompt, specify the -lm switch as the final argument.
I could go on about these complex functions, but the topic frightens me. The documentation elsewhere on the Interwebs is rather weak and repetitive. Still, it’s a fun math thing that your C compiler can do, should you cross into those dimensions and time warps that require use of the complex number i.
The usual way to avoid using i if you need it for something else is to use j, and I looks a bit odd. There are various constants using upper case but i isn’t a constant as such, just the number 1 in a different set than the real numbers.
I was just thinking “I ought to write an article on this subject” and then I thought “err, maybe I already have” and then found that yes, I did, back in 2018, although I think I first wrote it a few years before that for a C-specific blog I had back then. I hope you don’t mind me linking to it Dan, just delete this if so.
https://www.codedrome.com/complex-numbers-in-c/
The article actually isn’t very good and I think it needs a major re-write. I’ll add it to my insanely long todo list.
I thought that some remarks on portability might be interesting. Writing math code to work with complex numbers, that works under both Linux and Windows, is possible.
There are, however, a few gotchas:
* The <complex.h> header file thatʼs shipped with Visual C++ defines several Microsoft-specific types:
Microsoftʼs non-standard complex types (Stack Overflow), i.e.:
_Dcomplex
instead ofdouble _Complex
,_Fcomplex
instead offloat _Complex
* As per Microsoft C/C++ language conformance by Visual Studio version: from version v16.8 (VS2019) onwards, Microsoftʼs Visual C++ compiler supports type-generic math macros like creal() and cimag(), if the option
/std:c11
is specified on the command line. (In this case the preprocessor macro_CRT_HAS_C11
will be set to 1.)To be compatible with as many compilers as possible, I think it may nonetheless be preferable to just call the functions behind these macros directly.
* Along with these non-standard data types, Microsoft provides its own functions to initialize values of such types:
_Dcomplex _Cbuild (double r, double i); and _Fcomplex _FCbuild (float r, float i);
(Instead of the CMPLX() and CMPLXF() macros the standard specifies.)
* To not be too Microsoft-centric: GCC comes with an interesting language extension—Clang also supports it—in that it is possible to get read as well as write access to the real and imaginary parts of complex values by prefixing them with the (non-standard and thus unportable)
__real__
/__imag__
keywords:Directly access (read/write) real and imag part (Stack Overflow)
As the real part and imginary part of a complex value are stored right after each other in memory, one could also use type type punning to get at them:
*((double *)&z + 0) = 10.0; // real part
*((double *)&z + 1) = 20.0; // complex part
But, of course, this isnʼt really what one might call safe programming practices. Because of C99ʼs strict aliasing rule it is, in fact, undefined behavior… at least unless GCC/Clang is invoked with
-fno-strict-aliasing
.* The good news: it is possible to work around these differences with custom macros and typedefʼs (as illustrated, for example, by the following Mandelbrot program I wrote a few years ago).
Just for fun, hereʼs a variant of the example of this blog post that compiles under GCC, Clang and MSVC (and probably others):
#include <stdio.h>
#include <complex.h>
/* Compatibility definitions/macros: */
#if defined(_MSC_VER)
#define complex_t _Fcomplex /* Microsoft Visual C++ */
#define CMPLXF(real, imag) _FCbuild(real, imag)
#else
#define complex_t float _Complex /* GCC, Clang, … */
#ifndef CMPLXF /* #if __STDC_VERSION__ < 201112L */
#define CMPLXF(x, y) __builtin_complex ((float)(x), (float)(y))
#endif
#endif
/* Program entry point: */
int main (void)
{ complex_t z;
z = CMPLXF(-4.0f, 0.0f); /* -4.0 + 0.0‧ */
z = csqrtf (z);
printf ("sqrt(-4.0) = %.1f + %.1fi\n", crealf (z), cimagf (z));
return (0);
}
I do admit though, that it is cumbersome to program with compatibility in mind… surely nothing one should worry too much about as a beginner.
Again Microsoft plants it flag in the sand and declares its own “standard.” I’ve been computer nerding for decades and they haven’t changed.
I agree in principle—even when Visual Studio 2012 was released, it was by no means certain that Microsoft would ever release a fully C99 compatible compiler, for example.
Since 2014, when Satya Nadella took over from Steve Ballmer, there has been a noticeable push at Microsoft to become more standards-compliant, however. Nowadays they even support C11… unthinkable before the new CEO.
They first added support for complex numbers in Visual Studio 2005, that’s when the above (and most of what’s not standards compatible) was chosen. They do approach things differently nowadays, I think.