2.5. Random numbers#

Throughout this chapter, we discussed different operations. Simple operations include +, -, /, *, %, =, +=, -=, *=, /=, %=, and complicated operations from the math library like rint, fmax, fmin, ceil, floor. These operations were on different data types like int and double. In some cases, we need to generate a random number, e.g. if we were to develop a head or tail game, we would need to generate two random numbers, maybe $$0$$ representing a head and $$1$$ representing a tail. In this section, we will discuss how to generate a random number in C.

2.5.1. Generating a random number#

We will need the rand function from stdlib.h library, which is referred to as the standard library. The prototype of the rand function is as follows:

int rand();


rand does not take as input anything. It produces an int value that can range from 0 to RAND_MAX, where RAND_MAX is a constant defined in stdlib.h library. It is the maximum +ve value that can be represented by an int. The value of RAND_MAX is $$2^{31} - 1 = 2147483647$$.

Let’s try running the following code.

Code

#include <stdio.h>
#include <stdlib.h>

int main() {
printf("Random number 1: %d\n", rand());
printf("Random number 2: %d\n", rand());
printf("Random number 3: %d\n", rand());
return 0;
}


Output

Random number 1: 16807
Random number 2: 282475249
Random number 3: 1622650073


Wait! I tried running the code again, and I got the same numbers. How is rand() then generating a random number? The answer is that rand() is generating pseudo-random numbers. A pseudo-random number is a number that appears to be random, but is actually not 🤯. Who decides these pseudo-random numbers? It is a pseudo-random number generator. As programmers, we can choose our pseudo-random number generator. We can do so by setting a seed. What on earth is a seed?

A seed is used to initialize a pseudo-random number generator. This generator is responsible for generating a set of random numbers every time rand is called. To choose a seed, we will use a function whose prototype is void srand(unsigned int);. It takes the seed with a type unsigned int as input and sets the seed of the pseudo-random number generator.

I hear you saying “Salma (your author), I feel lost.” Don’t worry, I will explain it with an example. Let’s say we want to generate the set of random numbers from seed 1 in the following figure, i.e., $$16807, 282475249, 1622650073 ...$$. In our code, a line of code with srand(1); will initialize a pseudo-random number generator with seed 1. Fantastic! Next, the first time rand() is called after srand(1);, it will return 16807, which is the first random number in seed 1 in the figure below. The next time rand() is called, it will return 282475249, which is the second random number in seed 1, and so on. The default seed if we did not set the seed using srand() is 1.

In your code, if you re-initialize the seed using srand(1);, you will get the same set of random numbers again when you call rand(). This means, you will again get 16807 in the next rand() call. This is why we only initialize the seed with srand once in our code. This is illustrated in the following figure.

The following code illustrates the subsequent calls to rand() after srand(1);. Download seed.c to get the following code.

Code

#include <stdio.h>
#include <stdlib.h>

int main() {
srand(1);
printf("Random number 1: %d\n", rand());
printf("Random number 2: %d\n", rand());
printf("Random number 3: %d\n", rand());
srand(1);
printf("Random number 4: %d\n", rand());
srand(1);
printf("Random number 5: %d\n", rand());
printf("Random number 6: %d\n", rand());
srand(1);
printf("Random number 7: %d\n", rand());
printf("Random number 8: %d\n", rand());
printf("Random number 9: %d\n", rand());
return 0;
}


Output

Random number 1: 16807
Random number 2: 282475249
Random number 3: 1622650073
Random number 4: 16807
Random number 5: 16807
Random number 6: 282475249
Random number 7: 16807
Random number 8: 282475249
Random number 9: 1622650073


2.5.2. Are we generating random numbers?#

So far we found a way to generate pseudo-random numbers, and how to generate the same set of random number every run. If you have srand(1) with a fixed seed or no srand(x) at all, your program will generate the same set of pseudo-random numbers. Can we get closer to really random numbers? Yes, we can.

If we use the current time as a seed, every time we run our program, our seed will be different. Hence, the set of random numbers change with every run! Voilà. We can use the time function from time.h library to get the current time. The prototype of the time function is as follows:

time_t time(time_t *tloc);
// You are not required to know what is time_t or tloc in this course!


When the time is called like this time(NULL), it returns the Unix time, which is the current time in terms of the number of seconds since January 1, 1970.

Fun fact 😎

The data type time_t is a signed 32-bit int type representing the number of seconds since January 1, 1970. Hence, the maximum number of seconds that can be represented is $$2^{31} -1 = 2147483647$$. This will be 03:14:07 UTC January 19, 2038. After that, the Unix time will overflow and be a negative number representing 20:45:52 on Friday, 13 December 1901 🤯😵‍💫. This is called the Unix time overflow or Year 2038 problem. No one knows what will happen after that. Vulnerable systems include some databases, which includes bank accounts. You may have guessed the solution. Solution is as simple as switching to a 64-bit time_t data type.

The following code will now generate different random numbers every time you run it. Download time-seed.c to get the following code.

Code

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
srand(time(NULL));
printf("Random number 1: %d\n", rand());
printf("Random number 2: %d\n", rand());
return 0;
}


Potential Output

Random number 1: 1783039037
Random number 2: 1550284621


Question: If we do srand(rand()); instead of srand(time(NULL));, would this make the seed random? The answer is NO. The first call to rand() in srand() would return the same random number everytime we run the program. The default seed has a default of 1. This makes srand(rand()); always pick a fixed seed.

2.5.3. Random numbers within a range#

Random numbers generated by rand() range from 0 to RAND_MAX. What if I want to generate numbers between 0 and 1 representing heads and tails? Recall the handy modulo operator %. If we do rand() % 2, it will always generate a number between 0 and 1.

Remember!

The result of any modulo operation is between 0 and 2nd operand - 1. For example, % 5 returns numbers between 0 and 4.

0 % 5 = 0

1 % 5 = 1

2 % 5 = 2

3 % 5 = 3

4 % 5 = 4

5 % 5 = 0 (repeats)

6 % 5 = 1

To generate a number between 0 and 5, we need to do rand() % 6;. While to shift this range from 05 to 1 to 6, we can add 1 to the range and make it (rand() % 6) + 1;. In general to generate a number between [MIN, MAX] (inclusive), we need to rand() % (MAX - MIN + 1) + MIN.

For example, let’s develop a code for rolling a dice 🎲! We expect that every time we run the program we do not get the same dice number. Download roll-dice.c to get the following code.

Code

#include <stdlib.h>
#include <time.h>

int main(void) {
srand(time(NULL));

printf("Dice roll is %d\n", rand() % 6 + 1);

return 0;
}


Potential output

Dice roll is 3