Coming Soon
This lesson is currently being developed
Introduction to random number generation
Generate random numbers for games and simulations.
What to Expect
Comprehensive explanations with practical examples
Interactive coding exercises to practice concepts
Knowledge quiz to test your understanding
Step-by-step guidance for beginners
Development Status
Content is being carefully crafted to provide the best learning experience
Preview
Early Preview Content
This content is still being developed and may change before publication.
8.13 — Introduction to random number generation
In this lesson, you'll learn about generating random numbers in C++. Random numbers are essential for games, simulations, testing, cryptography, and many other applications. You'll discover how computers generate "random" numbers and learn to use C++'s random number facilities.
What are random numbers?
Random numbers are values that cannot be predicted and appear to occur by chance. However, computers are deterministic machines, so they generate pseudo-random numbers - sequences that appear random but are actually calculated using mathematical algorithms.
True randomness vs pseudo-randomness:
- True random: Unpredictable, from physical processes (radioactive decay, atmospheric noise)
- Pseudo-random: Generated by algorithms, reproducible if you know the starting conditions
For most programming purposes, pseudo-random numbers are perfectly adequate.
Why use random numbers?
Random numbers are crucial for many applications:
#include <iostream>
int main()
{
std::cout << "Applications of random numbers:\n";
std::cout << "• Games: dice rolls, card shuffling, enemy AI\n";
std::cout << "• Simulations: weather modeling, traffic patterns\n";
std::cout << "• Testing: generating test data, stress testing\n";
std::cout << "• Security: password generation, encryption keys\n";
std::cout << "• Statistics: sampling, Monte Carlo methods\n";
std::cout << "• Art: procedural generation, random music\n";
return 0;
}
The old C-style approach (avoid this)
Before learning the modern C++ way, let's see the old C approach (so you'll recognize it):
#include <iostream>
#include <cstdlib> // For rand() and srand()
#include <ctime> // For time()
int main()
{
// Seed the random number generator
std::srand(std::time(nullptr)); // Use current time as seed
std::cout << "Old C-style random numbers (1-6):\n";
for (int i = 0; i < 10; ++i)
{
int randomNum = std::rand() % 6 + 1; // Range: 1-6
std::cout << randomNum << " ";
}
std::cout << std::endl;
return 0;
}
Output (will vary each run):
Old C-style random numbers (1-6):
3 1 6 2 4 5 1 3 6 2
Problems with this approach:
- Poor quality randomness
- Difficult to get uniform distributions
- Thread-safety issues
- Limited control over ranges
The modern C++ approach
C++11 introduced a comprehensive random number library with better quality and more control:
#include <iostream>
#include <random>
int main()
{
// Create a random number generator
std::random_device rd; // Hardware random number generator
std::mt19937 gen(rd()); // Mersenne Twister generator, seeded with rd()
// Define the distribution (uniform integers 1-6)
std::uniform_int_distribution<int> dist(1, 6);
std::cout << "Modern C++ random numbers (1-6):\n";
for (int i = 0; i < 10; ++i)
{
int randomNum = dist(gen); // Generate random number
std::cout << randomNum << " ";
}
std::cout << std::endl;
return 0;
}
Output (will vary each run):
Modern C++ random numbers (1-6):
4 2 6 1 3 5 6 2 4 1
Understanding the components
The modern approach has three main components:
1. Random Number Generator (Engine)
The engine generates raw random bits:
#include <iostream>
#include <random>
int main()
{
// Different engines available
std::mt19937 mersenneTwister; // High quality, most common
std::linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647> lcg;
std::random_device hardware; // True random (when available)
std::cout << "Raw engine output:\n";
std::cout << "Mersenne Twister: " << mersenneTwister() << std::endl;
std::cout << "LCG: " << lcg() << std::endl;
std::cout << "Random Device: " << hardware() << std::endl;
return 0;
}
Sample Output:
Raw engine output:
Mersenne Twister: 3499211612
LCG: 16807
Random Device: 2847192837
2. Distribution
The distribution shapes the raw numbers into the desired range and pattern:
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
// Different distributions
std::uniform_int_distribution<int> uniformInt(1, 10);
std::uniform_real_distribution<double> uniformReal(0.0, 1.0);
std::normal_distribution<double> normal(0.0, 1.0); // Mean=0, StdDev=1
std::cout << "Different distributions:\n";
std::cout << "Uniform int (1-10): " << uniformInt(gen) << std::endl;
std::cout << "Uniform real (0-1): " << uniformReal(gen) << std::endl;
std::cout << "Normal (bell curve): " << normal(gen) << std::endl;
return 0;
}
Sample Output:
Different distributions:
Uniform int (1-10): 7
Uniform real (0-1): 0.432156
Normal (bell curve): -0.789234
3. Seeding
Seeding determines the starting point for the pseudo-random sequence:
#include <iostream>
#include <random>
void demonstrateSeeding(unsigned int seed)
{
std::mt19937 gen(seed); // Use specific seed
std::uniform_int_distribution<int> dist(1, 6);
std::cout << "Seed " << seed << ": ";
for (int i = 0; i < 5; ++i)
{
std::cout << dist(gen) << " ";
}
std::cout << std::endl;
}
int main()
{
std::cout << "Same seed produces same sequence:\n";
demonstrateSeeding(12345);
demonstrateSeeding(12345); // Same sequence!
std::cout << "\nDifferent seeds produce different sequences:\n";
demonstrateSeeding(12345);
demonstrateSeeding(67890);
return 0;
}
Output:
Same seed produces same sequence:
Seed 12345: 6 6 5 2 4
Seed 12345: 6 6 5 2 4
Different seeds produce different sequences:
Seed 12345: 6 6 5 2 4
Seed 67890: 1 3 3 6 2
Practical examples
Example 1: Dice rolling simulator
#include <iostream>
#include <random>
class DiceRoller
{
private:
std::random_device rd;
std::mt19937 gen;
std::uniform_int_distribution<int> dist;
public:
DiceRoller(int sides = 6) : gen(rd()), dist(1, sides) {}
int roll()
{
return dist(gen);
}
void rollMultiple(int count)
{
std::cout << "Rolling " << count << " dice: ";
for (int i = 0; i < count; ++i)
{
std::cout << roll() << " ";
}
std::cout << std::endl;
}
};
int main()
{
DiceRoller d6; // Standard 6-sided die
DiceRoller d20(20); // 20-sided die
std::cout << "Rolling standard dice:\n";
d6.rollMultiple(5);
std::cout << "\nRolling 20-sided dice:\n";
d20.rollMultiple(3);
std::cout << "\nIndividual rolls:\n";
std::cout << "D6: " << d6.roll() << std::endl;
std::cout << "D20: " << d20.roll() << std::endl;
return 0;
}
Sample Output:
Rolling standard dice:
Rolling 5 dice: 3 6 1 4 2
Rolling 20-sided dice:
Rolling 3 dice: 17 3 12
Individual rolls:
D6: 5
D20: 8
Example 2: Random password generator
#include <iostream>
#include <random>
#include <string>
class PasswordGenerator
{
private:
std::random_device rd;
std::mt19937 gen;
std::string lowercase = "abcdefghijklmnopqrstuvwxyz";
std::string uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::string digits = "0123456789";
std::string symbols = "!@#$%^&*";
public:
PasswordGenerator() : gen(rd()) {}
std::string generatePassword(int length, bool useUppercase = true,
bool useDigits = true, bool useSymbols = true)
{
std::string charset = lowercase;
if (useUppercase) charset += uppercase;
if (useDigits) charset += digits;
if (useSymbols) charset += symbols;
std::uniform_int_distribution<int> dist(0, charset.length() - 1);
std::string password;
for (int i = 0; i < length; ++i)
{
password += charset[dist(gen)];
}
return password;
}
};
int main()
{
PasswordGenerator generator;
std::cout << "Generated passwords:\n";
std::cout << "Simple (lowercase only): "
<< generator.generatePassword(8, false, false, false) << std::endl;
std::cout << "Medium (letters + numbers): "
<< generator.generatePassword(10, true, true, false) << std::endl;
std::cout << "Strong (all characters): "
<< generator.generatePassword(12) << std::endl;
return 0;
}
Sample Output:
Generated passwords:
Simple (lowercase only): mplqwxyz
Medium (letters + numbers): K8mP2nQ7vX
Strong (all characters): P@7k9#Rm2$wQ
Example 3: Random number guessing game
#include <iostream>
#include <random>
class NumberGuessingGame
{
private:
std::random_device rd;
std::mt19937 gen;
std::uniform_int_distribution<int> dist;
int secretNumber;
int attempts;
int maxAttempts;
public:
NumberGuessingGame(int min = 1, int max = 100, int maxAttempts = 7)
: gen(rd()), dist(min, max), maxAttempts(maxAttempts)
{
secretNumber = dist(gen);
attempts = 0;
}
void play()
{
std::cout << "Guess the number between " << dist.min()
<< " and " << dist.max() << "!\n";
std::cout << "You have " << maxAttempts << " attempts.\n\n";
while (attempts < maxAttempts)
{
int guess;
std::cout << "Attempt " << (attempts + 1) << ": ";
std::cin >> guess;
++attempts;
if (guess == secretNumber)
{
std::cout << "Congratulations! You guessed it in "
<< attempts << " attempts!\n";
return;
}
else if (guess < secretNumber)
{
std::cout << "Too low! ";
}
else
{
std::cout << "Too high! ";
}
std::cout << "Try again.\n\n";
}
std::cout << "Game over! The number was " << secretNumber << std::endl;
}
};
int main()
{
std::cout << "Welcome to the Number Guessing Game!\n\n";
NumberGuessingGame game;
game.play();
return 0;
}
Example 4: Random data generation for testing
#include <iostream>
#include <random>
#include <vector>
#include <string>
class TestDataGenerator
{
private:
std::random_device rd;
std::mt19937 gen;
public:
TestDataGenerator() : gen(rd()) {}
std::vector<int> generateIntVector(int size, int min = 0, int max = 100)
{
std::uniform_int_distribution<int> dist(min, max);
std::vector<int> data;
data.reserve(size);
for (int i = 0; i < size; ++i)
{
data.push_back(dist(gen));
}
return data;
}
std::vector<double> generateDoubleVector(int size, double min = 0.0, double max = 1.0)
{
std::uniform_real_distribution<double> dist(min, max);
std::vector<double> data;
data.reserve(size);
for (int i = 0; i < size; ++i)
{
data.push_back(dist(gen));
}
return data;
}
std::string generateName(int length = 6)
{
std::string charset = "abcdefghijklmnopqrstuvwxyz";
std::uniform_int_distribution<int> dist(0, charset.length() - 1);
std::string name;
name.reserve(length);
// First letter uppercase
name += std::toupper(charset[dist(gen)]);
// Rest lowercase
for (int i = 1; i < length; ++i)
{
name += charset[dist(gen)];
}
return name;
}
};
int main()
{
TestDataGenerator generator;
// Generate random integers
std::cout << "Random integers (1-10): ";
auto integers = generator.generateIntVector(10, 1, 10);
for (int num : integers)
{
std::cout << num << " ";
}
std::cout << std::endl;
// Generate random doubles
std::cout << "Random doubles (0-1): ";
auto doubles = generator.generateDoubleVector(5, 0.0, 1.0);
for (double num : doubles)
{
std::cout << num << " ";
}
std::cout << std::endl;
// Generate random names
std::cout << "Random names: ";
for (int i = 0; i < 5; ++i)
{
std::cout << generator.generateName() << " ";
}
std::cout << std::endl;
return 0;
}
Sample Output:
Random integers (1-10): 7 3 9 1 5 8 2 6 4 10
Random doubles (0-1): 0.234567 0.789123 0.456789 0.123456 0.987654
Random names: Klemtz Npqwer Mxcvbn Qazwsx Plmnko
Different distribution types
C++ provides many distribution types for different needs:
Uniform distributions
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
// Uniform integer distribution
std::uniform_int_distribution<int> intDist(1, 6);
std::cout << "Uniform int (1-6): " << intDist(gen) << std::endl;
// Uniform real distribution
std::uniform_real_distribution<double> realDist(0.0, 1.0);
std::cout << "Uniform real (0-1): " << realDist(gen) << std::endl;
return 0;
}
Normal (Gaussian) distribution
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
// Normal distribution (bell curve)
std::normal_distribution<double> normalDist(100.0, 15.0); // Mean=100, StdDev=15
std::cout << "Normal distribution samples (mean=100, stddev=15):\n";
for (int i = 0; i < 10; ++i)
{
std::cout << normalDist(gen) << " ";
}
std::cout << std::endl;
return 0;
}
Boolean distribution
#include <iostream>
#include <random>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
// Bernoulli distribution (weighted coin flip)
std::bernoulli_distribution coinFlip(0.5); // 50% probability
std::bernoulli_distribution biasedCoin(0.7); // 70% probability of true
std::cout << "Fair coin flips: ";
for (int i = 0; i < 10; ++i)
{
std::cout << (coinFlip(gen) ? "H" : "T") << " ";
}
std::cout << std::endl;
std::cout << "Biased coin flips (70% heads): ";
for (int i = 0; i < 10; ++i)
{
std::cout << (biasedCoin(gen) ? "H" : "T") << " ";
}
std::cout << std::endl;
return 0;
}
Common patterns and best practices
Pattern 1: Global generator (simple approach)
#include <iostream>
#include <random>
// Global generator (simple but not always ideal)
std::random_device rd;
std::mt19937 gen(rd());
int randomInt(int min, int max)
{
std::uniform_int_distribution<int> dist(min, max);
return dist(gen);
}
double randomDouble(double min, double max)
{
std::uniform_real_distribution<double> dist(min, max);
return dist(gen);
}
int main()
{
std::cout << "Random integers: ";
for (int i = 0; i < 5; ++i)
{
std::cout << randomInt(1, 10) << " ";
}
std::cout << std::endl;
std::cout << "Random doubles: ";
for (int i = 0; i < 5; ++i)
{
std::cout << randomDouble(0.0, 1.0) << " ";
}
std::cout << std::endl;
return 0;
}
Pattern 2: Reusable generator class
#include <iostream>
#include <random>
class RandomGenerator
{
private:
std::random_device rd;
std::mt19937 gen;
public:
RandomGenerator() : gen(rd()) {}
int getInt(int min, int max)
{
std::uniform_int_distribution<int> dist(min, max);
return dist(gen);
}
double getDouble(double min, double max)
{
std::uniform_real_distribution<double> dist(min, max);
return dist(gen);
}
bool getBool(double probability = 0.5)
{
std::bernoulli_distribution dist(probability);
return dist(gen);
}
};
int main()
{
RandomGenerator rng;
std::cout << "Random values:\n";
std::cout << "Int (1-100): " << rng.getInt(1, 100) << std::endl;
std::cout << "Double (0-10): " << rng.getDouble(0.0, 10.0) << std::endl;
std::cout << "Bool (30% true): " << (rng.getBool(0.3) ? "true" : "false") << std::endl;
return 0;
}
Summary
Random number generation in C++ involves several key concepts:
Components:
- Engine: Generates raw random bits (use
std::mt19937
for most cases) - Distribution: Shapes numbers into desired ranges and patterns
- Seeding: Determines starting point (use
std::random_device
for varying seeds)
Key advantages of modern C++ approach:
- Higher quality randomness
- Better control over distributions
- Thread-safe when used properly
- More predictable behavior
- Better performance
Common distributions:
std::uniform_int_distribution
: Even spread of integersstd::uniform_real_distribution
: Even spread of floating-point numbersstd::normal_distribution
: Bell curve (Gaussian) distributionstd::bernoulli_distribution
: Weighted true/false
Best practices:
- Use
std::mt19937
engine for most applications - Seed with
std::random_device
for unpredictable sequences - Reuse generator objects for better performance
- Choose appropriate distribution for your needs
- Consider thread safety in multi-threaded applications
Applications:
- Games (dice, cards, AI behavior)
- Simulations (modeling real-world randomness)
- Testing (generating test data)
- Security (passwords, keys)
- Art and creativity (procedural generation)
Modern C++ random number generation provides powerful, flexible tools for any application that needs randomness.
Quiz
- What's the difference between true random and pseudo-random numbers?
- What are the three main components of C++ random number generation?
- Why should you avoid using
rand()
andsrand()
from C? - What happens if you use the same seed value multiple times?
- Which distribution would you use for simulating dice rolls?
Practice exercises
-
Lottery number generator: Create a program that generates lottery numbers (6 unique numbers between 1-49).
-
Monte Carlo simulation: Write a program that estimates π using random points in a square and counting how many fall within a circle.
-
Random walk simulator: Create a 2D random walk where a point moves randomly in four directions, tracking and displaying the path.
-
Statistics generator: Build a program that generates samples from different distributions (uniform, normal) and calculates basic statistics (mean, variance) to verify the distributions work correctly.
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions