Coming Soon

This lesson is currently being developed

Introduction to random number generation

Generate random numbers for games and simulations.

Control Flow
Chapter
Beginner
Difficulty
40min
Estimated Time

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

In Progress

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 integers
  • std::uniform_real_distribution: Even spread of floating-point numbers
  • std::normal_distribution: Bell curve (Gaussian) distribution
  • std::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

  1. What's the difference between true random and pseudo-random numbers?
  2. What are the three main components of C++ random number generation?
  3. Why should you avoid using rand() and srand() from C?
  4. What happens if you use the same seed value multiple times?
  5. Which distribution would you use for simulating dice rolls?

Practice exercises

  1. Lottery number generator: Create a program that generates lottery numbers (6 unique numbers between 1-49).

  2. Monte Carlo simulation: Write a program that estimates π using random points in a square and counting how many fall within a circle.

  3. Random walk simulator: Create a 2D random walk where a point moves randomly in four directions, tracking and displaying the path.

  4. 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.

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion