Pi in C

From LQWiki
Jump to navigation Jump to search

Here we try out a small program that computes the value of Pi using the following formula:

Pi = 4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - ... )

Here is the code:

/*  1 */ #include <stdio.h>
/*  2 */ #include <math.h>
/*  3 */ #include <stdlib.h>
  
/*  4 */ int main(int argc, char *argv[]){
/*  5 */       double t, k = 3.0, l = -1.0;
/*  6 */       int i, s;

/*  7 */       if(argc < 2){
/*  8 */               fprintf(stderr, "%s <number of iterations>\n", argv[0]);
/*  9 */               exit(1);
/* 10 */       }
       
/* 11 */       t = 1.0; 
/* 12 */       for(i = 0, s = atoi(argv[1]); i < s; i++){
/* 13 */               t += l/k; 
/* 14 */               k += 2.0; 
/* 15 */               l *= -1.0; 
/* 16 */       }
/* 17 */       t *= 4; 
/* 18 */       printf("My value of Pi: %1.6f, math.h's value of Pi: %.16f\n", t, M_PI); 
/* 19 */       printf("Absolute difference: %.16f\n", fabs(M_PI - t)); 

/* 20 */       return 0; 
/* 21 */}

To compile the program, remove all lines numberings, those are just there to make it easier to go through the code. Then type in your shell, provided that you save the code in a file called pi.c:

$ g++ -o pi pi.c -lm

We compile pi.c to a binary called pi. -lm tells the linker (a part of the compiler) that we want to use math libraries. Using the -lm flag is not always neseccary though. Anyway, to run the program type the following in your shell:

$ ./pi 1000

The number 1000 is the number of iterations we would like to do in the main loop, i.e. how many items we want to use in the formula above. You should get something like this:

$ ./pi 1000
My value of Pi: 3.142592, math.h's value of Pi: 3.1415926535897931
Absolute difference: 0.0009990007497471

Lets dissect the code. On line 1 and 2 we include some standard libraries.

/*  1 */ #include <stdio.h>
/*  2 */ #include <math.h>

stdio.h contains standard input/output functions such as printf and alike. math.h contains math functions such as sin, fabs, etc. What are those .h files anyway? It demands some explanation. Assume you would like to use some function like printf in your program. This function is nothing you have written yourself, the function comes in compiled form in your standard libraries. When the program is compiled a program called the linker (often ld, try man ld) tries to figure out how to call functions in these standard libraries. This is something that more or less just happens, as a new C programmer this is nothing you have to worry about. What is important here is that the compiler wants to know what printf looks like, i.e. how many arguments it wants, return type etc. Information about this is included in the .h files, which often is referred to as a header file (.h for header). We often talk about function prototypes, i.e. a description of the function. But why? So that the compiler can complain when you try to use a function is some way that is considerer incorrect, using the wrong number of arguments for instance. Hope that sorts it out.

What about this like then?

/*  4 */ int main(int argc, char *argv[]){

All C programs are composed of functions more or less. The compiler looks for a function called main. When the program is executed, that is by your shell, control is transfered from the shell to the place where main starts. I will later try (supposed I have some spare time) to explain how to write a shell, something that is easier than it might seem. Also there are two arguments to this function. int argc, char *argv[]. int argc is the number of arguments to the program and char *argv[] are those arguments. Arguments? These are the ones you provided via the command line when you started the program, "1000" as an example. However the programs name is always the first argument. If we look at the example where we did:

$ ./pi 1000

Then *argv[] looks like {"./pi", "1000"}. That also means that argc = 2, NOT 1 as you might expect. That means that the first argument that you are likely to be interested in is argv[1]. As an exercise you could try to write a program that prints it's own name.

Here we declare some variables that we use later on:

/*  5 */       double t, k = 3.0, l = -1.0;
/*  6 */       int i, s;

Nothing much about these really, just keep in mind that variables should be declared in the beginning of each code block. That is the beginning of functions, loops, if statements etc. Quiet frankly everything that looks like this is valid syntax:

{
    int a, ...
}

Newer versions of gcc allow you to declare variables everywhere, but try to avoid that. You probably want your program to be compatible with older versions of gcc and other compilers.

Here we check that the user has supplied the correct number of arguments to the program:

/*  7 */       if(argc < 2){
/*  8 */               fprintf(stderr, "%s <number of iterations>\n", argv[0]);
/*  9 */               exit(1);
/* 10 */       }
       

Recall that argc is the number of arguments to the program. If these are less than 2 we have a problem. We print out a message, on line 7, that tells the user that he/she tried to use the program in an incorrect way. Then we exit the program with the value 1. Why 1? I'll explain this at the end of this example, bare with me for a while. We use the function fprintf which could be interpreted as: File-PRINT-Format. FILE here refers to the file stderr, which is a standard file of type FILE*. See the example ConnectingASocketInC for a better explanation about this. PRINT simply refers to printing, that is writing something to the shell. Format means that want to format the output in some sense. The %s in "%s <number of iterations>" refers to a string, that is the second argument given to fprintf, namely argv[0]. Check your manpages for fprintf (that is man fprintf) for a lot more details.

Simply set t to 1.0.

/* 11 */       t = 1.0; 

This for-loop can be interpreted as follows. First set i to 0, then s to atoi(argv[0]). Observe that we do both these things before the first ;. This part is just done once, that is when we enter the loop. What is this atoi? The argument "1000" in the example above, i.e. the number of iterations we want to do, comes as a string, not a number. Therefor we have to convert this string to a number. atoi does this, (man atoi).

This loop will loop as long as i is less than s. That is i < s. This test is done at the beginning of the loop. In every iteration of the loop i will be incremented by one. This is done at the end of the loop.

/* 12 */       for(i = 0, s = atoi(argv[1]); i < s; i++){

Here, compute t += l/k. This corresponds to some random part of the formula, for example: -1/3 or 1/5.

/* 13 */               t += l/k; 

Increment k by two, so if k = 3 before this, then k = 5 after this.

/* 14 */               k += 2.0; 

Multiply l by -1.0, something that simply changes the sign of l. -1*-1 = 1, math you know.

/* 15 */               l *= -1.0; 

This loop is as said repeated say 1000 times, after that we multiply the result by 4, according to the formula.

/* 17 */       t *= 4; 

Now lets print the result. Here again we use formated printing. But where is the stderr thing? Shouldn't we write to a file? Well this is done implicitly. Since C programmers like to print a lot, there is a standard function printf that is the equivalent of fprintf(stdout, ..., ...). We could as well have written fprintf(stdout, "My value of Pi ... ", ...);

/* 18 */       printf("My value of Pi: %.16f, math.h's value of Pi: %.16f\n", t, M_PI); 
/* 19 */       printf("Absolute difference: %.16f\n", fabs(M_PI - t)); 

What is this format %.16f? f is for float, we want to print a floating point number, also we want 16 significant decimals. This .16 thing is just a way to tell the how many decimals se want, nice huh? M_PI is a constant from math.h that simply is set to the value of Pi, using as high accuracy as the computer allows. We also use the function fabs that calculates the absolute value of a value. fabs could well be something like:

double fabs(double a){
    if(a >= 0.0){
        return a; 
    }
    return -a; 
}

At last we return 0. What is the deal with returning things? Is there any function that checks the value? I haven't written anyone anyway. Simply put, if you return anything from main or use the function exit(value); This value is returned to the shell. The return value is normally interpreted in fashion that 0 means, "everything is alright, program did NOT crash", everything else means "The program crashed.". The return value is a way to tell the shell how things went wrong. What a value of 23 would mean is quiet undefined, it's more or less up to the program to decide that it means. But it's quiet useful when a shellscript executes a program. For example, shellscript start_daemon.sh tries to start a random daemon, like a mail server, if something goes wrong the error code can be used to print a message "/var/tmp is full, free at least 10 Mb space.".

/* 20 */        return 0;