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
A complex number is simply a pair of the corresponding types, so essentially
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.
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.
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. :-)
If the compiler sees a variable or literal that is not of type complex, it will up-cast it to the appropriate complex type.
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.
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.
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.