In this post I will just give an overview of the C programming language's support for complex numbers.
C99 introduced a new addition to the standard library to support complex numbers. The area of complex numbers is a vast one so I will just give an overview of the topic in C, as well as writing a function to draw an Argand diagram using SVG.
If you are not familiar with the basics of complex numbers you might want to read the Wikipedia article on the subject. When you are ready to get stuck into coding create a new folder and within it create the following empty files. You can download the source code as a zip or clone/download the Github repository if you prefer.
- main.c
- arganddiagram.h
- arganddiagram.c
Source Code Links
This project also uses the SVG library from this post so I won't repeat it here, but it is included in the source code zip on the Downloads page.
Open main.c and enter the following code.
main.c (part 1)
#include<stdio.h> #include<complex.h> #include<stdbool.h> #include"arganddiagram.h" //-------------------------------------------------------- // FUNCTION PROTOTYPES //-------------------------------------------------------- void complex_output(double complex, bool brackets, bool newline); void complex_sizes(); void complex_arithmetic(); void complex_conjugate(); void complex_abs_arg(); void argand_arithmetic(); void argand_conjugate(); //-------------------------------------------------------- // FUNCTION main //-------------------------------------------------------- void main(void) { puts("-------------------"); puts("| codedrome.com |"); puts("| Complex Numbers |"); puts("-------------------"); complex_sizes(); complex_arithmetic(); complex_conjugate(); complex_abs_arg(); argand_arithmetic(); argand_conjugate(); }
We have prototypes for seven functions here. The first is complex_output and will simply print a complex number in a standard format. The next function, complex_sizes, will print the sizes in bytes of the three complex types, float, double and long double. Next we have three functions called complex_arithmetic, complex_conjugate and complex_abs_arg which will perform various operations on complex numbers. Finally we have a couple of functions to create Argand diagrams, which plot the real and imaginary parts of complex numbers on a two-dimensional complex plain.
As I mentioned, C has three complex types based on float, double and long double. For simplicity this project uses only the double complex type but all code is equally applicable to the other two types.
Let's first implement the complex_output function which will be used in some of the other functions.
main.c (part 2)
//-------------------------------------------------------- // FUNCTION complex_output //-------------------------------------------------------- void complex_output(double complex z, bool brackets, bool newline) { if(brackets) { printf("("); } printf("%g + %gi", creal(z), cimag(z)); if(brackets) { printf(")"); } if(newline) { puts(""); } }
In addition to the complex number to be printed this function provides the options to enclose the output in brackets and to add a newline at the end. If you are printing more than one complex number, for example when showing arithmetic, it looks neater and clearer if they are enclosed in brackets. Also, the newline option saves an extra bit of code when the number is at the end of a line. The actual code in this function is straightforward, but note that we use the functions creal and cimag to get the two components of the complex number.
The next function is complex_sizes and simply prints out the sizes of the base types float, double and long double, along with the corresponding complex types. These may vary on different systems, but the sizes of complex types will always be twice the sizes of the underlying types.
main.c (part 3)
//-------------------------------------------------------- // FUNCTION complex_sizes //-------------------------------------------------------- void complex_sizes() { puts("\nsizes of complex types\n----------------------"); printf("sizeof(float) %ld\n", sizeof(float)); printf("sizeof(float complex) %ld\n", sizeof(float complex)); printf("sizeof(double) %ld\n", sizeof(double)); printf("sizeof(double complex) %ld\n", sizeof(double complex)); printf("sizeof(long double) %ld\n", sizeof(long double)); printf("sizeof(long double complex) %ld\n", sizeof(long double complex)); }
The complex_arithmetic function creates two complex numbers, z1 and z2, and then creates and initializes four more complex numbers by adding, subtracting, multiplying and dividing the first two. Finally we print out the values of the variables.
main.c (part 4)
//-------------------------------------------------------- // FUNCTION complex_arithmetic //-------------------------------------------------------- void complex_arithmetic() { puts("\ncomplex arithmetic\n------------------"); double complex z1 = 1 + 3 * I; double complex z2 = 2 + 4 * I; double complex z_add = z1 + z2; double complex z_subtract = z1 - z2; double complex z_multiply = z1 * z2; double complex z_divide = z1 / z2; complex_output(z1, true, false); printf(" + "); complex_output(z2, true, false); printf(" = "); complex_output(z_add, true, true); complex_output(z1, true, false); printf(" - "); complex_output(z2, true, false); printf(" = "); complex_output(z_subtract, true, true); complex_output(z1, true, false); printf(" * "); complex_output(z2, true, false); printf(" = "); complex_output(z_multiply, true, true); complex_output(z1, true, false); printf(" / "); complex_output(z2, true, false); printf(" = "); complex_output(z_divide, true, true); }
Next we have the complex_conjugate function, which creates two complex numbers and then finds their conjugates using the conj function.
main.c (part 5)
//-------------------------------------------------------- // FUNCTION complex_conjugate //-------------------------------------------------------- void complex_conjugate() { puts("\ncomplex conjugates\n------------------"); double complex z1 = 9 + 6 * I; double complex z2 = -4 - 5 * I; double complex z1conj = conj(z1); double complex z2conj = conj(z2); complex_output(z1, false, true); printf("conjugate = "); complex_output(z1conj, false, true); complex_output(z2, false, true); printf("conjugate = "); complex_output(z2conj, false, true); }
The function complex_abs_arg demonstrates the cabs and carg functions, which calculate the distance of the complex number from the origin and its angle - these can be understood more easily if you think of the complex numbers being plotted on an Argand diagram which we shall do later.
main.c (part 6)
//-------------------------------------------------------- // FUNCTION complex_abs_arg //-------------------------------------------------------- void complex_abs_arg() { puts("\ncomplex absolute and argument\n-----------------------------"); double complex z1 = 3 + 4 * I; double complex z2 = -6 - 8 * I; complex_output(z1, false, true); printf("absolute %f\n", cabs(z1)); printf("argument %f radians\n", carg(z1)); complex_output(z2, false, true); printf("absolute %f\n", cabs(z2)); printf("argument %f radians\n", carg(z2)); }
The first of two functions creating Argand diagrams is argand_arithmetic. This carries out the same calculations as the complex_arithmetic function, but instead of printing the results they are plotted in an Argand diagram. I will describe the argand_diagram function later, but for now note that we create and pass an array of complex numbers, and a struct to hold the configuration settings of the diagram.
main.c (part 7)
//-------------------------------------------------------- // FUNCTION argand_arithmetic //-------------------------------------------------------- void argand_arithmetic() { puts("\nArgand diagram - arithmetic\n---------------------------"); double complex cplxnums[6]; double complex z1 = 1 + 3 * I; double complex z2 = 2 + 4 * I; double complex z_add = z1 + z2; double complex z_subtract = z1 - z2; double complex z_multiply = z1 * z2; double complex z_divide = z1 / z2; cplxnums[0] = z1; cplxnums[1] = z2; cplxnums[2] = z_add; cplxnums[3] = z_subtract; cplxnums[4] = z_multiply; cplxnums[5] = z_divide; argandconfig config = (argandconfig){.height = 720, .width = 720, .margin = 64, .indexspacingreal = 1, .indexspacingimag = 1, .minreal = -10, .maxreal = 5, .minimag = -2, .maximag = 10}; argand_diagram(config, cplxnums, 6, "argand_arithmetic.svg"); }
The final function in main.c is argand_conjugate. This is similar to complex_conjugate but plots the results on an Argand diagram.
main.c (part 8)
//-------------------------------------------------------- // FUNCTION argand_conjugate //-------------------------------------------------------- void argand_conjugate() { puts("\nArgand diagram - conjugates\n---------------------------"); double complex cplxnums[4]; double complex z1 = 9 + 6 * I; double complex z2 = -4 - 5 * I; double complex z1conj = conj(z1); double complex z2conj = conj(z2); cplxnums[0] = z1; cplxnums[1] = z2; cplxnums[2] = z1conj; cplxnums[3] = z2conj; argandconfig config = (argandconfig){.height = 720, .width = 720, .margin = 64, .indexspacingreal = 1, .indexspacingimag = 1, .minreal = -6, .maxreal = 10, .minimag = -8, .maximag = 8}; argand_diagram(config, cplxnums, 4, "argand_conjugates.svg"); }
The code in main.c is now complete so to finish off the project we need to write the code to create an Argand diagram. Open the header file arganddiagram.h and enter the following.
arganddiagram.h
#include<complex.h> //-------------------------------------------------------- // STRUCT argandconfig //-------------------------------------------------------- typedef struct argandconfig { int width; int height; int margin; double indexspacingreal; double indexspacingimag; double minreal; double maxreal; double minimag; double maximag; } argandconfig; //-------------------------------------------------------- // FUNCTION PROTOTYPES //-------------------------------------------------------- void argand_diagram(argandconfig config, double complex cplxnums[], int complexnumbercount, char* filename);
The function argand_diagram needs no fewer than nine arguments just to configure its size and layout, so to avoid having a ridiculously long argument list I have combined them into a struct. As you can see from the argand_diagram function prototype it also takes an array of complex numbers to plot as well as the array size, and the filename to save the file to.
We can now write the argand_diagram function itself in arganddiagram.c.
arganddiagram.c
#include<stdio.h> #include<math.h> #include"svg.h" #include"arganddiagram.h" //-------------------------------------------------------- // FUNCTION argand_diagram //-------------------------------------------------------- void argand_diagram(argandconfig config, double complex cplxnums[], int complexnumbercount, char* filename) { if(config.minreal > 0) { config.minreal = 0.0; } if(config.minimag > 0) { config.minimag = 0.0; } int graph_height = config.height - (config.margin * 2); int graph_width = config.width - (config.margin * 2); double pixels_per_unit_real = (double)graph_width / (double)(config.maxreal - config.minreal); double pixels_per_unit_imag = (double)graph_height / (double)(config.maximag - config.minimag); double realzero = config.margin + (double)(fabs(config.minreal) * pixels_per_unit_real); double imagzero = config.margin + (double)(config.maximag * pixels_per_unit_imag); double x; double y; char number_string[8]; // Create svg struct svg* psvg; psvg = svg_create(config.width, config.height); if(psvg == NULL) { puts("psvg is NULL"); } else { svg_fill(psvg, "#FFFFFF"); // axis lines svg_line(psvg, "#808080", 2, realzero, config.margin, realzero, config.height - config.margin); svg_line(psvg, "#808080", 2, config.margin, imagzero, config.width - config.margin, imagzero); // real indexes x = config.margin; for(int i = config.minreal; i <= config.maxreal; i+= config.indexspacingreal) { svg_line(psvg, "#808080", 2, x, imagzero, x, imagzero + 4); if(i != 0) { sprintf(number_string, "%d", i); svg_text(psvg, x, imagzero + 16, "sans-serif", 10, "#000000", "#000000", "middle", number_string); } x += (config.indexspacingreal * pixels_per_unit_real); } // imaginary indexes y = config.margin; for(int i = config.maximag; i >= config.minimag; i-= config.indexspacingimag) { svg_line(psvg, "#808080", 2, realzero - 4, y, realzero, y); if(i != 0) { sprintf(number_string, "%d", i); svg_text(psvg, realzero - 8, y + 4, "sans-serif", 10, "#000000", "#000000", "end", number_string); } y += (config.indexspacingimag * pixels_per_unit_imag); } // complex numbers for(int c = 0; c < complexnumbercount; c++) { x = realzero + (creal(cplxnums[c]) * pixels_per_unit_real); y = imagzero - (cimag(cplxnums[c]) * pixels_per_unit_imag); svg_circle(psvg, "#0000FF", 0, "#0000FF", 3, x, y); } svg_finalize(psvg); svg_save(psvg, filename); puts("File saved"); svg_free(psvg); } }
In argand_diagram we first check that the minimum values for the two axes are not more than 0, and then create a number of variables for use when drawing the diagram. The graph_height and graph_width variables are the size of the actual diagram itself, within the margins. The pixels_per_unit_ variables will be used to calculate coordinates on the diagram from actual values being plotted, and realzero and imagzero are the coordinates of the two axis lines. x and y will be used later in a couple of loops to plot indexes and values, and number_string will hold numbers converted to strings.
We can then create an svg struct - this uses the SVG library from an earlier post which I mentioned and linked to above. We then start to draw the diagram, firstly filling the background with white, and then drawing the axis lines.
Next we have a couple of for loops to draw index marks and values on the real and imaginary axes. In each of these we set x or y to the margin size, incrementing them within the loops. Finally we draw a short line, sprintf the index number to number_string, and then draw it.
The last task is to iterate the array of complex numbers, calculating the x and y coordinates of each using the creal and cimag functions.
That's the diagram finished so we can call svg_finalize (which just adds a closing tag, SVG being an XML-like format), save the file, and call svg_free to free the dynamic memory used by the SVG library.
We have now finished coding so let's compile and run by entering the following in the terminal. Note that we need to include the three .c files in the command line arguments to gcc.
Compile and Run
gcc main.c svg.c arganddiagram.c -std=c11 -lm -o main ./main
Program Output
------------------- | codedrome.com | | Complex Numbers | ------------------- sizes of complex types ---------------------- sizeof(float) 4 sizeof(float complex) 8 sizeof(double) 8 sizeof(double complex) 16 sizeof(long double) 16 sizeof(long double complex) 32 complex arithmetic ------------------ (1 + 3i) + (2 + 4i) = (3 + 7i) (1 + 3i) - (2 + 4i) = (-1 + -1i) (1 + 3i) * (2 + 4i) = (-10 + 10i) (1 + 3i) / (2 + 4i) = (0.7 + 0.1i) complex conjugates ------------------ 9 + 6i conjugate = 9 + -6i -4 + -5i conjugate = -4 + 5i complex absolute and argument ----------------------------- 3 + 4i absolute 5.000000 argument 0.927295 radians -6 + -8i absolute 10.000000 argument -2.214297 radians Argand diagram - arithmetic --------------------------- File saved Argand diagram - conjugates --------------------------- File saved
The output shows the results of the first four functions called by the main function, so we see the sizes of various complex types, the results of complex arithmetic, complex conjugates, and absolutes and arguments. Lastly there are a couple of messages from the functions drawing Argand diagrams saying the diagrams have been created successfully.
If you open the folder where you saved your source code you'll find the program has created a couple of new .svg files. If you double-click one it will open in your system's default image viewer and you can scroll through the Argand diagrams shown below.