Coming Soon

This lesson is currently being developed

Global random numbers (Random.h)

Organize random number generation in larger programs.

Control Flow
Chapter
Beginner
Difficulty
35min
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.15 — Global random numbers (Random.h)

In this lesson, you'll learn how to organize random number generation in larger programs by creating a centralized random number system. You'll build a custom Random.h header that provides convenient global access to high-quality random numbers while maintaining good programming practices.

Why create a global random number system?

As programs grow larger, you'll often need random numbers in many different parts of your code. Without proper organization, you might end up with:

  • Multiple generators scattered throughout your code
  • Inconsistent seeding practices
  • Repeated generator setup code
  • Poor performance from creating generators frequently

A centralized system solves these problems by providing:

  • Single, high-quality generator
  • Consistent seeding
  • Convenient access from anywhere
  • Better performance
  • Easier testing and debugging

Designing Random.h

Let's create a header file that provides a clean interface for random number generation:

Basic Random.h structure

// Random.h
#ifndef RANDOM_H
#define RANDOM_H

#include <random>

namespace Random
{
    // Initialize the global random number generator
    void init();
    void init(unsigned int seed);
    
    // Generate random integers
    int getInt(int min, int max);
    
    // Generate random floating-point numbers
    double getDouble(double min = 0.0, double max = 1.0);
    float getFloat(float min = 0.0f, float max = 1.0f);
    
    // Generate random boolean
    bool getBool(double probability = 0.5);
    
    // Utility functions
    void reseed();
    void reseed(unsigned int seed);
}

#endif

Complete Random.h implementation

Here's a full implementation with source file:

Random.h:

#ifndef RANDOM_H
#define RANDOM_H

#include <random>

namespace Random
{
    // Initialization functions
    void init();
    void init(unsigned int seed);
    
    // Basic random number generation
    int getInt(int min, int max);
    unsigned int getUInt(unsigned int min, unsigned int max);
    double getDouble(double min = 0.0, double max = 1.0);
    float getFloat(float min = 0.0f, float max = 1.0f);
    
    // Specialized random generation
    bool getBool(double probability = 0.5);
    char getChar(char min = 'a', char max = 'z');
    
    // Distribution-based generation
    double getNormal(double mean = 0.0, double stddev = 1.0);
    int getPoisson(double mean);
    
    // Utility functions
    void reseed();
    void reseed(unsigned int seed);
    
    // Template functions for containers
    template<typename T>
    void shuffle(std::vector<T>& container);
    
    template<typename T>
    T choose(const std::vector<T>& container);
    
    template<typename T>
    std::vector<T> sample(const std::vector<T>& population, size_t count);
}

// Template implementation (must be in header)
template<typename T>
void Random::shuffle(std::vector<T>& container)
{
    extern std::mt19937 generator;  // Declare external generator
    std::shuffle(container.begin(), container.end(), generator);
}

template<typename T>
T Random::choose(const std::vector<T>& container)
{
    if (container.empty())
    {
        throw std::runtime_error("Cannot choose from empty container");
    }
    return container[getInt(0, static_cast<int>(container.size()) - 1)];
}

template<typename T>
std::vector<T> Random::sample(const std::vector<T>& population, size_t count)
{
    if (count > population.size())
    {
        throw std::runtime_error("Sample size larger than population");
    }
    
    std::vector<T> result = population;
    shuffle(result);
    result.resize(count);
    return result;
}

#endif

Random.cpp:

#include "Random.h"
#include <algorithm>
#include <stdexcept>
#include <chrono>

namespace Random
{
    // Global generator (internal linkage)
    static std::random_device rd;
    static std::mt19937 generator;
    static bool initialized = false;
    
    void init()
    {
        generator.seed(rd());
        initialized = true;
    }
    
    void init(unsigned int seed)
    {
        generator.seed(seed);
        initialized = true;
    }
    
    void checkInitialized()
    {
        if (!initialized)
        {
            init();  // Auto-initialize if not done
        }
    }
    
    int getInt(int min, int max)
    {
        checkInitialized();
        if (min > max)
        {
            std::swap(min, max);
        }
        std::uniform_int_distribution<int> dist(min, max);
        return dist(generator);
    }
    
    unsigned int getUInt(unsigned int min, unsigned int max)
    {
        checkInitialized();
        if (min > max)
        {
            std::swap(min, max);
        }
        std::uniform_int_distribution<unsigned int> dist(min, max);
        return dist(generator);
    }
    
    double getDouble(double min, double max)
    {
        checkInitialized();
        if (min > max)
        {
            std::swap(min, max);
        }
        std::uniform_real_distribution<double> dist(min, max);
        return dist(generator);
    }
    
    float getFloat(float min, float max)
    {
        checkInitialized();
        if (min > max)
        {
            std::swap(min, max);
        }
        std::uniform_real_distribution<float> dist(min, max);
        return dist(generator);
    }
    
    bool getBool(double probability)
    {
        checkInitialized();
        if (probability < 0.0 || probability > 1.0)
        {
            throw std::invalid_argument("Probability must be between 0.0 and 1.0");
        }
        std::bernoulli_distribution dist(probability);
        return dist(generator);
    }
    
    char getChar(char min, char max)
    {
        checkInitialized();
        if (min > max)
        {
            std::swap(min, max);
        }
        std::uniform_int_distribution<char> dist(min, max);
        return dist(generator);
    }
    
    double getNormal(double mean, double stddev)
    {
        checkInitialized();
        std::normal_distribution<double> dist(mean, stddev);
        return dist(generator);
    }
    
    int getPoisson(double mean)
    {
        checkInitialized();
        if (mean <= 0.0)
        {
            throw std::invalid_argument("Mean must be positive for Poisson distribution");
        }
        std::poisson_distribution<int> dist(mean);
        return dist(generator);
    }
    
    void reseed()
    {
        generator.seed(rd());
    }
    
    void reseed(unsigned int seed)
    {
        generator.seed(seed);
        initialized = true;
    }
}

// Make generator available for templates
std::mt19937 generator;

Using the Random system

Basic usage example

#include <iostream>
#include <vector>
#include "Random.h"

int main()
{
    // Initialize the random system
    Random::init();
    
    std::cout << "Basic random number generation:\n";
    
    // Generate various types of random numbers
    std::cout << "Random int (1-10): " << Random::getInt(1, 10) << std::endl;
    std::cout << "Random double (0-1): " << Random::getDouble() << std::endl;
    std::cout << "Random float (5.0-15.0): " << Random::getFloat(5.0f, 15.0f) << std::endl;
    std::cout << "Random bool (70% true): " << (Random::getBool(0.7) ? "true" : "false") << std::endl;
    std::cout << "Random char (A-Z): " << Random::getChar('A', 'Z') << std::endl;
    
    // Normal distribution
    std::cout << "Normal distribution (mean=100, stddev=15): " 
              << Random::getNormal(100.0, 15.0) << std::endl;
    
    // Container operations
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    std::cout << "\nContainer operations:\n";
    std::cout << "Original: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    Random::shuffle(numbers);
    std::cout << "Shuffled: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    std::cout << "Random choice: " << Random::choose(numbers) << std::endl;
    
    auto sample = Random::sample(numbers, 3);
    std::cout << "Random sample of 3: ";
    for (int n : sample) std::cout << n << " ";
    std::cout << std::endl;
    
    return 0;
}

Sample Output:

Basic random number generation:
Random int (1-10): 7
Random double (0-1): 0.456789
Random float (5.0-15.0): 12.3456
Random bool (70% true): true
Random char (A-Z): M
Normal distribution (mean=100, stddev=15): 98.7654

Container operations:
Original: 1 2 3 4 5 6 7 8 9 10 
Shuffled: 3 8 1 10 5 2 9 4 7 6 
Random choice: 5
Random sample of 3: 3 8 1 

Practical applications

Example 1: Game development utilities

#include <iostream>
#include <vector>
#include <string>
#include "Random.h"

class GameUtils
{
public:
    // Roll multiple dice and return total
    static int rollDice(int count, int sides)
    {
        int total = 0;
        for (int i = 0; i < count; ++i)
        {
            total += Random::getInt(1, sides);
        }
        return total;
    }
    
    // Generate random name from parts
    static std::string generateName()
    {
        std::vector<std::string> prefixes = {"Fire", "Shadow", "Storm", "Iron", "Golden"};
        std::vector<std::string> suffixes = {"blade", "heart", "wing", "claw", "eye"};
        
        std::string prefix = Random::choose(prefixes);
        std::string suffix = Random::choose(suffixes);
        
        return prefix + suffix;
    }
    
    // Critical hit calculation
    static bool isCriticalHit(double critChance)
    {
        return Random::getBool(critChance);
    }
    
    // Random encounter
    static std::string randomEncounter()
    {
        std::vector<std::string> encounters = {
            "A group of goblins blocks your path",
            "You find a treasure chest",
            "A friendly merchant offers to trade",
            "You discover a hidden cave",
            "A wild dragon appears!"
        };
        
        return Random::choose(encounters);
    }
    
    // Random loot generation
    static std::vector<std::string> generateLoot(int itemCount)
    {
        std::vector<std::string> allLoot = {
            "Gold Coin", "Health Potion", "Magic Sword", "Shield",
            "Bow", "Arrow", "Scroll", "Gem", "Key", "Map"
        };
        
        if (itemCount > static_cast<int>(allLoot.size()))
        {
            itemCount = static_cast<int>(allLoot.size());
        }
        
        return Random::sample(allLoot, itemCount);
    }
};

int main()
{
    Random::init();
    
    std::cout << "Game Development Examples:\n\n";
    
    // Dice rolling
    std::cout << "Dice rolls:\n";
    std::cout << "1d6: " << GameUtils::rollDice(1, 6) << std::endl;
    std::cout << "3d6: " << GameUtils::rollDice(3, 6) << std::endl;
    std::cout << "2d20: " << GameUtils::rollDice(2, 20) << std::endl;
    
    // Name generation
    std::cout << "\nRandom names:\n";
    for (int i = 0; i < 5; ++i)
    {
        std::cout << GameUtils::generateName() << std::endl;
    }
    
    // Combat mechanics
    std::cout << "\nCombat simulation:\n";
    for (int i = 0; i < 10; ++i)
    {
        bool crit = GameUtils::isCriticalHit(0.15);  // 15% crit chance
        std::cout << "Attack " << (i + 1) << ": " << (crit ? "CRITICAL HIT!" : "Normal hit") << std::endl;
    }
    
    // Random encounters
    std::cout << "\nRandom encounters:\n";
    for (int i = 0; i < 3; ++i)
    {
        std::cout << GameUtils::randomEncounter() << std::endl;
    }
    
    // Loot generation
    std::cout << "\nRandom loot (3 items):\n";
    auto loot = GameUtils::generateLoot(3);
    for (const auto& item : loot)
    {
        std::cout << "- " << item << std::endl;
    }
    
    return 0;
}

Example 2: Testing and data generation

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include "Random.h"

class TestDataGenerator
{
public:
    // Generate test integers within range
    static std::vector<int> generateInts(int count, int min, int max)
    {
        std::vector<int> data;
        data.reserve(count);
        
        for (int i = 0; i < count; ++i)
        {
            data.push_back(Random::getInt(min, max));
        }
        
        return data;
    }
    
    // Generate test strings
    static std::vector<std::string> generateStrings(int count, int minLength, int maxLength)
    {
        std::vector<std::string> strings;
        strings.reserve(count);
        
        for (int i = 0; i < count; ++i)
        {
            int length = Random::getInt(minLength, maxLength);
            std::string str;
            str.reserve(length);
            
            for (int j = 0; j < length; ++j)
            {
                str += Random::getChar('a', 'z');
            }
            
            strings.push_back(str);
        }
        
        return strings;
    }
    
    // Generate user-like data
    static std::vector<std::string> generateUsernames(int count)
    {
        std::vector<std::string> prefixes = {"user", "player", "gamer", "pro", "newbie"};
        std::vector<std::string> usernames;
        usernames.reserve(count);
        
        for (int i = 0; i < count; ++i)
        {
            std::string username = Random::choose(prefixes) + std::to_string(Random::getInt(100, 9999));
            usernames.push_back(username);
        }
        
        return usernames;
    }
    
    // Generate test scores (bell curve around average)
    static std::vector<int> generateTestScores(int count, double average = 75.0, double stddev = 12.0)
    {
        std::vector<int> scores;
        scores.reserve(count);
        
        for (int i = 0; i < count; ++i)
        {
            double score = Random::getNormal(average, stddev);
            // Clamp to valid score range
            score = std::max(0.0, std::min(100.0, score));
            scores.push_back(static_cast<int>(std::round(score)));
        }
        
        return scores;
    }
    
    // Analyze generated data
    static void analyzeData(const std::vector<int>& data)
    {
        if (data.empty()) return;
        
        int sum = 0;
        int min = data[0];
        int max = data[0];
        
        for (int value : data)
        {
            sum += value;
            min = std::min(min, value);
            max = std::max(max, value);
        }
        
        double average = static_cast<double>(sum) / data.size();
        
        std::cout << "Data analysis:\n";
        std::cout << "  Count: " << data.size() << std::endl;
        std::cout << "  Sum: " << sum << std::endl;
        std::cout << "  Average: " << average << std::endl;
        std::cout << "  Min: " << min << std::endl;
        std::cout << "  Max: " << max << std::endl;
    }
};

int main()
{
    Random::init();
    
    std::cout << "Test Data Generation Examples:\n\n";
    
    // Generate test integers
    auto testInts = TestDataGenerator::generateInts(20, 1, 100);
    std::cout << "Random test integers (1-100):\n";
    for (size_t i = 0; i < testInts.size(); ++i)
    {
        std::cout << testInts[i];
        if (i < testInts.size() - 1) std::cout << ", ";
    }
    std::cout << std::endl << std::endl;
    
    TestDataGenerator::analyzeData(testInts);
    std::cout << std::endl;
    
    // Generate test strings
    auto testStrings = TestDataGenerator::generateStrings(5, 3, 8);
    std::cout << "Random test strings (3-8 chars):\n";
    for (const auto& str : testStrings)
    {
        std::cout << "'" << str << "' ";
    }
    std::cout << std::endl << std::endl;
    
    // Generate usernames
    auto usernames = TestDataGenerator::generateUsernames(8);
    std::cout << "Generated usernames:\n";
    for (const auto& username : usernames)
    {
        std::cout << username << std::endl;
    }
    std::cout << std::endl;
    
    // Generate test scores with normal distribution
    auto scores = TestDataGenerator::generateTestScores(50, 78.0, 10.0);
    std::cout << "Test scores (normal distribution, mean=78, stddev=10):\n";
    for (size_t i = 0; i < std::min(scores.size(), size_t(20)); ++i)
    {
        std::cout << scores[i] << " ";
    }
    std::cout << "...\n\n";
    
    TestDataGenerator::analyzeData(scores);
    
    return 0;
}

Example 3: Simulation framework

#include <iostream>
#include <vector>
#include <map>
#include "Random.h"

class SimulationFramework
{
public:
    // Coin flip simulation
    static void coinFlipSimulation(int flips)
    {
        int heads = 0, tails = 0;
        
        std::cout << "Coin flip simulation (" << flips << " flips):\n";
        
        for (int i = 0; i < flips; ++i)
        {
            if (Random::getBool(0.5))
            {
                ++heads;
            }
            else
            {
                ++tails;
            }
        }
        
        std::cout << "Heads: " << heads << " (" << (100.0 * heads / flips) << "%)\n";
        std::cout << "Tails: " << tails << " (" << (100.0 * tails / flips) << "%)\n\n";
    }
    
    // Dice roll distribution
    static void diceDistribution(int rolls, int sides)
    {
        std::map<int, int> distribution;
        
        // Initialize distribution
        for (int i = 1; i <= sides; ++i)
        {
            distribution[i] = 0;
        }
        
        // Roll dice
        for (int i = 0; i < rolls; ++i)
        {
            int roll = Random::getInt(1, sides);
            ++distribution[roll];
        }
        
        std::cout << "Dice distribution (" << rolls << " rolls of d" << sides << "):\n";
        for (const auto& pair : distribution)
        {
            int value = pair.first;
            int count = pair.second;
            double percentage = 100.0 * count / rolls;
            
            std::cout << value << ": " << count << " (" << percentage << "%) ";
            
            // Visual bar
            int barLength = static_cast<int>(percentage / 2);
            for (int j = 0; j < barLength; ++j)
            {
                std::cout << "■";
            }
            std::cout << std::endl;
        }
        std::cout << std::endl;
    }
    
    // Monte Carlo π estimation
    static void monteCarloPI(int samples)
    {
        int pointsInCircle = 0;
        
        for (int i = 0; i < samples; ++i)
        {
            double x = Random::getDouble(-1.0, 1.0);
            double y = Random::getDouble(-1.0, 1.0);
            
            if (x * x + y * y <= 1.0)
            {
                ++pointsInCircle;
            }
        }
        
        double piEstimate = 4.0 * pointsInCircle / samples;
        double error = std::abs(piEstimate - 3.141592653589793);
        
        std::cout << "Monte Carlo π estimation (" << samples << " samples):\n";
        std::cout << "Estimated π: " << piEstimate << std::endl;
        std::cout << "Actual π:    3.141592653589793\n";
        std::cout << "Error:       " << error << std::endl << std::endl;
    }
    
    // Random walk simulation
    static void randomWalk(int steps)
    {
        int x = 0, y = 0;
        int minX = 0, maxX = 0, minY = 0, maxY = 0;
        
        std::cout << "2D Random Walk (" << steps << " steps):\n";
        std::cout << "Starting at (0, 0)\n";
        
        for (int i = 0; i < steps; ++i)
        {
            // Choose direction: 0=up, 1=right, 2=down, 3=left
            int direction = Random::getInt(0, 3);
            
            switch (direction)
            {
                case 0: ++y; break;  // up
                case 1: ++x; break;  // right
                case 2: --y; break;  // down
                case 3: --x; break;  // left
            }
            
            // Track bounds
            minX = std::min(minX, x);
            maxX = std::max(maxX, x);
            minY = std::min(minY, y);
            maxY = std::max(maxY, y);
        }
        
        double distance = std::sqrt(x * x + y * y);
        
        std::cout << "Final position: (" << x << ", " << y << ")\n";
        std::cout << "Distance from origin: " << distance << std::endl;
        std::cout << "Bounds: X[" << minX << ", " << maxX << "], Y[" << minY << ", " << maxY << "]\n\n";
    }
};

int main()
{
    Random::init();
    
    std::cout << "Simulation Framework Examples:\n\n";
    
    // Coin flip simulation
    SimulationFramework::coinFlipSimulation(1000);
    
    // Dice distribution
    SimulationFramework::diceDistribution(6000, 6);
    
    // Monte Carlo π estimation
    SimulationFramework::monteCarloPI(100000);
    
    // Random walk
    SimulationFramework::randomWalk(1000);
    
    return 0;
}

Best practices for global random systems

1. Thread safety considerations

For multi-threaded applications, consider thread-local generators:

// ThreadSafeRandom.h
#ifndef THREAD_SAFE_RANDOM_H
#define THREAD_SAFE_RANDOM_H

#include <random>

namespace ThreadSafeRandom
{
    // Thread-local generator
    thread_local std::mt19937& getGenerator();
    
    // Thread-safe random functions
    int getInt(int min, int max);
    double getDouble(double min = 0.0, double max = 1.0);
    bool getBool(double probability = 0.5);
}

#endif

2. Testing with deterministic seeds

class RandomTest
{
public:
    static void runDeterministicTest()
    {
        // Use fixed seed for reproducible tests
        Random::init(12345);
        
        // Test expected sequence
        std::vector<int> expected = {6, 6, 5, 2, 4, 5, 3, 3, 3, 1};
        std::vector<int> actual;
        
        for (int i = 0; i < 10; ++i)
        {
            actual.push_back(Random::getInt(1, 6));
        }
        
        std::cout << "Deterministic test: ";
        if (actual == expected)
        {
            std::cout << "PASSED\n";
        }
        else
        {
            std::cout << "FAILED\n";
            std::cout << "Expected: ";
            for (int n : expected) std::cout << n << " ";
            std::cout << "\nActual:   ";
            for (int n : actual) std::cout << n << " ";
            std::cout << std::endl;
        }
    }
};

3. Configuration and customization

namespace Random
{
    struct Config
    {
        bool autoInitialize = true;
        bool validateRanges = true;
        bool throwOnError = true;
        unsigned int defaultSeed = 0;  // 0 means use random_device
    };
    
    void configure(const Config& config);
    Config getConfig();
}

Summary

Creating a global random number system provides:

Benefits:

  • Centralized, high-quality random number generation
  • Consistent interface across your entire application
  • Better performance through generator reuse
  • Easier testing with controlled seeding
  • Convenient utility functions for common tasks

Key components:

  • Header file with clean interface
  • Implementation file with generator management
  • Template functions for container operations
  • Error handling and validation
  • Initialization and seeding control

Best practices:

  • Use namespace to avoid global pollution
  • Provide both seeded and unseeded initialization
  • Include error checking and validation
  • Support common distributions and operations
  • Consider thread safety for multi-threaded applications
  • Use static linkage for internal generator

Common applications:

  • Game development (dice, encounters, AI)
  • Simulations (Monte Carlo, random walks)
  • Testing (data generation, stress testing)
  • Procedural content generation
  • Statistical analysis

A well-designed global random system makes random number generation convenient and reliable throughout your application while maintaining good software engineering practices.

Quiz

  1. What are the advantages of using a global random number system over local generators?
  2. Why should the internal generator use static linkage in the implementation file?
  3. How can you make your random system work well for testing?
  4. What's the purpose of the checkInitialized() function?
  5. When might you need thread-local generators instead of a global one?

Practice exercises

  1. Extended Random system: Add more distributions to the Random namespace, such as exponential, geometric, and uniform on a sphere.

  2. Random system benchmarks: Create performance tests comparing your global system against creating local generators each time.

  3. Serializable random state: Extend the system to save and restore the generator state, useful for game save files or reproducible simulations.

  4. Weighted random selection: Add functions for weighted random selection from containers, useful for probability-based game mechanics or sampling.

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