Skip to the content of the web site.

Lesson 312: Built-in complex number types

Previous lesson Next lesson


Up to now, we have used a complex number class in C++. With the C 1999 standard (C99), complex numbers were introduced as a built-in type in C; although this change has never been migrated to C++.

In order to work seamlessly with complex numbers in C, it is necessary to include the complex.h header file. This should immediately raise your hackles, for it was just pointed out that this is a built-in type; you do not need a library to use int, double or char. There is a reason for this, however: when introducing a new type name into the language, there is always the possibility of breaking existing code; after all, it is very likely that there is a lot of source code written between 1970 and 1999 using the word complex.

Consequently, the authors of the standard decided to identify the type with the name _Complex, and as no rational and sane programmer would use any identifier starting with an underscore, for all identifiers starting with an underscore are reserved for the standard library. Thus, the correct type names are

	float _Complex variable_name;
	double _Complex variable_name;
	long double _Complex variable_name;

in a similar vein to the multiple-identifier types such as

	unsigned short variable_name;
	long double variable_name;

When you include the complex.h standard library, this includes the definition:

#ifndef complex
#define complex _Complex
#endif

and thus you can use the clearer:

	float complex variable_name;
	double complex variable_name;
	long double complex variable_name;

One defined complex number is I, defined to be the complex number $0 + j$. Being an engineer, you may wish to switch to the more natural J.

#ifndef J
#define J _Imaginary_I
#endif

To access the real and imaginary parts of a complex number, we use creal(...) and cimag(...). At this point, you can start using complex numbers more naturally; however, to print complex numbers, we must print the real and imaginary parts separately:

#include <complex.h>
#include <stdio.h>

#ifndef J
#define J _Imaginary_I
#endif

int main();

int main() {
	double complex z = 4.0 - 3.0*J;
	double complex w = 1.0/z;

	printf( " z  = %f%+fj\n", crealf(z), cimagf(z) );
	printf( "1/z = %f%+fj\n", crealf(w), cimagf(w) );

	return 0;
}

The output is

 z  = 4.000000-3.000000j
1/z = 0.160000+0.120000j

Implementation

A complex number is simply a pair of the corresponding types, so essentially

  • float complex z; is float z[2];
  • double complex z; is double z[2];
  • long double complex z; is long double z[2];

You can even see this by casting the address of a double complex to a pointer to a double and interpreting it as a 2D array:

#include <complex.h>
#include <stdio.h>

#ifndef J
#define J _Imaginary_I
#endif

int main();

int main() {
	// Create the complex number 4 -3j
	double complex z = 4.0 - 3.0*J;
	// Cast the address of 'z' as a pointer to a double and
	// interpret it as a 'double array[2];'
	double *array = (double *)(∓z);

	printf( "z = %f%+fj\n", creal(z), cimag(z) );
	printf( "z = %f%+fj\n", array[0], array[1] );

	return 0;
}

If you were to run this, you would see that both give exactly the same printed output, and you can view both the source code and the output in the source directory.

Constructors

In order not require arithmetic to be performed to create the complex number $\alpha + \beta j$, three macros are included:

	double complex       z  = CMPLX(  4.0,  -3.0  );
	float complex        zf = CMPLXF( 4.0F, -3.0F );
	long double complex  zl = CMPLXL( 4.0L, -3.0L );

These were only included in the 2011 C Standard, so you may have to compile with the -std=c11 option depending on the defaults.

Simple functions

Recall that C does not have function overloading, so functions with different types must have different names. We will assume that these operations are performed on the complex number $z = \alpha + \beta j$.

Operation Mathematical expression double complex float complex long double complex
Real part $\Re(z) = \alpha$ creal(...) crealf(...) creall(...)
Imaginary part $\Im(z) = \beta$ cimg(...) cimgf(...) cimgl(...)
Absolute value $|z| = \sqrt{\alpha^2 + \beta^2}$ cabs(...) cabsf(...) cabsl(...)
Argument $\arg(z) = \tan^{-1}\left(\frac{\beta}{\alpha}\right)$ carg(...) cargf(...) creall(...)
Complex conjugate $z^* = \alpha - \beta j$ conj(...) conjf(...) conjl(...)
Square root $\sqrt{z}$ csqrt(...) csqrtf(...) csqrtl(...)
Projection on the Riemann sphere See cproj cproj(...) cprojf(...) cprojl(...)

Fortunately, the authors of the library choose to break with the convention and use conj as opposed to cconj. :-)

Up-casting

If the compiler sees a variable or literal that is not of type complex, it will up-cast it to the appropriate complex type.

The power function

To calculate $z^w$, use the appropriate power function:

double complex float complex long double complex
creal(double complex z, double complex w) crealf(float complex z, float complex w) creall(long double complex z, long double complex w)

The following program calculates the $n$th roots of unity and then raises these to the powers $1$ through $n$.

#include <complex.h>
#include <stdio.h>
#include <math.h>

#ifndef J
#define J _Imaginary_I
#endif

int main();

int main() {
	int i, j;

	char *ordinals[14] = {
		"zeroeth", "first",    "second",   "third",     "fourth", 
		"fifth",   "sixth",    "seventh",  "eighth",    "nineth", 
		"tenth",   "eleventh", "twelveth", "thirteenth"
	};

	for ( i = 1; i <= 13; ++i ) {
		double const PI = acos( -1.0 );
		double complex z, w;

		printf( "The %s roots of unity:\n", ordinals[i] );

		// The principal ith root of unity
		//   - must compile with the -std=c11 option
		z = CMPLX( cos(2.0*PI/i), sin(2.0*PI/i) );

		// If only C99 is available, use this instead:
		// z = cos(2.0*PI/i) + sin(2.0*PI/i)*J;

		for ( j = 1; j <= i; ++j ) {
			w = cpow( z, j );
			printf( "    %f%+fj\n", crealf(w), cimagf(w) );
		}

		printf( "\n" );
	}

	return 0;
}

You can view both the source file and the output (.txt) in the source directory.

Transcendental functions

Function double complex float complex long double complex
Exponential cexp(...) cexpf(...) cexpl(...)
Natural logarithm clog(...) clogf(...) clogl(...)
Sine csin(...) csinf(...) csinl(...)
Cosine ccos(...) ccosf(...) ccosl(...)
Tangent ctan(...) ctanf(...) ctanl(...)
Inverse sine casin(...) casinf(...) casinl(...)
Inverse cosine cacos(...) cacosf(...) cacosl(...)
Inverse tangent catan(...) catanf(...) catanl(...)
Hyperbolic sine csinh(...) csinhf(...) csinhl(...)
Hyperbolic cosine ccosh(...) ccoshf(...) ccoshl(...)
Hyperbolic tangent ctanh(...) ctanhf(...) ctanhl(...)
Inverse hyperbolic sine casinh(...) casinhf(...) casinhl(...)
Inverse hyperbolic cosine cacosh(...) cacoshf(...) cacoshl(...)
Inverse hyperbolic tangent catanh(...) catanhf(...) catanhl(...)

Thus, we now know that the Settlers of Catan are indeed mathematicians: the settlers of the complex inverse tangent.

The imaginary type

Another related type is _Imaginary, which in the complex.h library uses imaginary is a type for a purely imaginary number. This type uses only one instance, not two, of the corresponding real type:

	float imaginary variable_name;
	double imaginary variable_name;
	long double imaginary variable_name;

This type is not defined or supported in all libraries, but it is in your interest to not use imaginary as a variable name in your source code.


Previous lesson Next lesson