Pseudo-random numbers
Requires knowledge of: 1. First program - 7. Functions
Motivation
In various fields of computer science, e.g. in cryptography, cybersecurity or when creating computer games, it is necessary to be able to generate random numbers. For example:
- 🔑 creating a password from random characters
- 💥 random events in the game world
- 🎲 chance to deal damage based on a die roll
Why "pseudorandom"
As you can see, the article is called "pseudo random numbers". The computer is not able to generate truly random numbers on its own, but it can, by some tricks, give us the illusion of randomness, which is perfectly sufficient.
Generating numbers
In this article, we will focus on a very primitive, yet easy, method. Later in the course you will
learn about much more powerful tools from the library <random>
.
Once you learn them, you shouldn't use std::rand
anymore.
In this article we'll be using following headers:
#include <cstdlib>
#include <ctime>
Let's see an example of how it is used:
#include <iostream>
#include <cstdlib>
#include <ctime>
int main() {
std::srand( std::time(0) );
std::cout << "Generating 5 random numbers:\n";
for (int i = 0; i < 5; ++i)
std::cout << std::rand() << '\n';
}
Generating 5 random numbers:
570368048
1028036926
1798519773
2028832115
1034913436
Getting next numbers
The key thing here is
std::rand()
(from random), which generates and returns the next pseudo-random number from the sequence. This sequence is very unpredictable, which gives the illusion of true randomness.
Setting the seed
What numbers this sequence will consist of depends on the so-called seed which is also a number. The following function is used to set the seed:
std::srand( <seed> )
(from seed random)
If we leave the default seed, the generated sequence will always be the same.
It's a good idea to set it once at the beginning of the program like in the example. We used the current time as a seed, in the form of a number that increases every second, so each time we start the program, we will get a different effect. We got this with a function call:
std::time(0)
Downsides of std::rand
The function std::rand()
is simple to use and its benefits end there.
The problem is, among others the fact that the range of numbers returned by it is not strictly
defined and differs depending, for example, on the compiler or operating system used.
We can only be sure that the number returned is always in range [0; RAND_MAX]
,
and that RAND_MAX
is some system or compiler-dependent constant (not less than 32767
).
The value of RAND_MAX
can be easily checked:
#include <iostream>
#include <cstdlib>
int main() {
std::cout << "RAND_MAX: " << RAND_MAX;
}
Possible result:
- MSVC (Windows)
- GCC 11.2 (Windows)
- GCC 12.0 (Linux)
- Clang 13.0 (Linux)
Visual Studio 2022 Preview.
RAND_MAX: 32767
GCC 11.2 from MSYS2 package.
RAND_MAX: 32767
GCC 12.0.0, from https://wandbox.org
RAND_MAX: 2147483647
Clang 13.0.0, from https://wandbox.org
RAND_MAX: 2147483647
The above results clearly show this problem. On Windows we got 215 - 1, and on Linux 231 - 1
Limiting the range
Real numbers from 0
to 1
We can just divide the obtained number by RAND_MAX
to get a value in the range from 0
to 1
.
float randomFloat() {
return float( std::rand() ) / RAND_MAX;
}
Note that we need to convert at least one of these numbers to a type float.
Value of both of these things - RAND_MAX
and rand()
are integers, therefore operations on them
also result in an integer.
Simply speaking:
int / int = int
After conversion, it will look like this:
float / int = float
If you're wondering why this works, check out this simple analysis:
- for the number
0
we will get0 / RAND_MAX
, so that is still0
- for
RAND_MAX
(i.e. max. number) we getRAND_MAX / RAND_MAX
, i.e.1
- for all intermediate values, we obtain a number greater than
0
and less than1
Real numbers from A
to B
Using the previous function randomFloat()
, we can define a similar function that will
generate a real number in the range A
to B
.
What we have to do:
- calculate a new range,
float Length = B - A
- multiply a number
[0; 1]
by the length to get a range[0; Length]
- move the entire range o
A
to get:[A; Length + A]
that is[A; B]
// We can use the same functio name, because
// it has different parameters (function overload)
float randomFloat(float from, float to)
{
float length = to - from;
return randomFloat()*length + from;
}
or simpler:
float randomFloat(float from, float to)
{
return randomFloat()*(to - from) + from;
}
Integers numbers from A
to B
In exactly the same way as above, we can create a function randomInt
:
int randomInt(int from, int to)
{
return int( randomFloat()*(to - from) ) + from;
}
Example usage
Functions in this article
Using the functions created above is very simple and convenient:
- Code
- Run ▶
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <iomanip>
// Declare functions
float randomFloat(); // od 0 do 1
float randomFloat(float from, float to); // od "from" do "to"
int randomInt(int from, int to); // od "from" do "to" (int)
int main()
{
// Set the seed
std::srand( std::time(0) );
// We set formatting of the cout
std::cout << std::fixed;
std::cout.precision(2);
std::cout << "Generating 5 floats from 0 to 1:\n";
for (int i = 0; i < 5; ++i)
std::cout << randomFloat() << ' ';
std::cout << "\n\nGenerating 5 floats from 10 to 30:\n";
for (int i = 0; i < 5; ++i)
std::cout << randomFloat(10, 30) << ' ';
std::cout << "\n\nGenerating 5 integers from 0 to 100:\n";
for (int i = 0; i < 5; ++i)
std::cout << randomInt(0, 100) << ' ';
std::cout << std::endl;
}
// Function implementations
/////////////////////////////////////
float randomFloat()
{
return float( std::rand() ) / RAND_MAX;
}
/////////////////////////////////////
float randomFloat(float from, float to)
{
return randomFloat()*(to - from) + from;
}
/////////////////////////////////////
int randomInt(int from, int to)
{
return int( randomFloat()*(to - from) ) + from;
}
(May not work in private browser mode)
Random chance of an event
If we want a certain event to have e.g. a 30% chance of occurrence, we can generate an integer
from the range [1; 100]
and check if number <= 30
:
int randomChance = randomInt(1, 100);
if (randomChance <= 30)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}
Alternatively, you can forgo percentages and use simple fractions:
float randomChance = randomFloat();
if (randomChance <= 0.30f)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}