Coming Soon
This lesson is currently being developed
Global random numbers (Random.h)
Organize random number generation in larger programs.
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.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
- What are the advantages of using a global random number system over local generators?
- Why should the internal generator use static linkage in the implementation file?
- How can you make your random system work well for testing?
- What's the purpose of the
checkInitialized()
function? - When might you need thread-local generators instead of a global one?
Practice exercises
-
Extended Random system: Add more distributions to the Random namespace, such as exponential, geometric, and uniform on a sphere.
-
Random system benchmarks: Create performance tests comparing your global system against creating local generators each time.
-
Serializable random state: Extend the system to save and restore the generator state, useful for game save files or reproducible simulations.
-
Weighted random selection: Add functions for weighted random selection from containers, useful for probability-based game mechanics or sampling.
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions