How to generate a random number in C++?

Learn how to generate a random number in c++? with practical examples, diagrams, and best practices. Covers c++, random development techniques with visual explanations.

Generating Random Numbers in C++: A Comprehensive Guide

Hero image for How to generate a random number in C++?

Learn the correct and modern ways to generate pseudo-random numbers in C++, avoiding common pitfalls and ensuring statistical quality for various applications.

Generating random numbers is a fundamental task in many programming domains, from simulations and games to cryptography and statistical analysis. In C++, the approach to generating pseudo-random numbers has evolved significantly. This article will guide you through the modern C++ <random> library, explaining why older methods like rand() and srand() are often insufficient and how to use the new facilities effectively.

The Problem with rand() and srand()

Historically, C++ programmers relied on the C standard library functions rand() and srand() for random number generation. While simple to use, these functions suffer from several limitations that make them unsuitable for most modern applications:

  1. Poor Quality: rand() often produces low-quality pseudo-random numbers with short periods and predictable patterns, especially on older compilers.
  2. Global State: rand() and srand() operate on a global state, making them difficult to use safely in multi-threaded environments or when multiple independent random sequences are needed.
  3. Limited Range: rand() typically returns an integer between 0 and RAND_MAX (which is often only 32767), requiring manual scaling and modulo operations to fit a desired range, which can introduce bias.
  4. Non-Uniform Distribution: Simple modulo operations on rand() results can lead to non-uniform distributions, where some numbers are more likely to appear than others.
#include <iostream>
#include <cstdlib> // For rand() and srand()
#include <ctime>   // For time()

int main() {
    // Seed the random number generator (only once per program execution)
    srand(time(0)); 

    // Generate a random number between 0 and RAND_MAX
    int randomNumber = rand();
    std::cout << "Raw rand(): " << randomNumber << std::endl;

    // Generate a random number between 1 and 100 (biased method)
    int biasedNumber = (rand() % 100) + 1;
    std::cout << "Biased rand() (1-100): " << biasedNumber << std::endl;

    return 0;
}

Example of using rand() and srand() (demonstrates common pitfalls).

The Modern C++ <random> Library

C++11 introduced the <random> library, a powerful and flexible framework for generating high-quality pseudo-random numbers. It separates the concerns of random number generation into three main components:

  1. Random Number Engines: These are the actual generators that produce sequences of raw, uniformly distributed unsigned integer values. Examples include std::mt19937 (Mersenne Twister) and std::default_random_engine.
  2. Seed Sequences: Used to initialize random number engines with high-quality seed values, especially when multiple engines need to be initialized differently.
  3. Random Number Distributions: These take the raw output from an engine and transform it into numbers that follow a specific statistical distribution (e.g., uniform, normal, Bernoulli) within a desired range.
flowchart TD
    A[Seed Source] --> B[Seed Sequence (optional)]
    B --> C[Random Number Engine]
    C --> D[Random Number Distribution]
    D --> E[Desired Random Number]

    subgraph Components
        C -- "Raw Uniform Integers" --> D
    end

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#ccf,stroke:#333,stroke-width:2px
    style D fill:#cfc,stroke:#333,stroke-width:2px
    style E fill:#ffc,stroke:#333,stroke-width:2px

Conceptual flow of random number generation using the C++ <random> library.

Generating Uniform Random Integers

The most common use case is generating uniformly distributed integers within a specific range. This is achieved using a random number engine (e.g., std::mt19937) and a std::uniform_int_distribution.

#include <iostream>
#include <random> // For random number generation
#include <chrono> // For high-resolution clock seeding

int main() {
    // 1. Create a random number engine
    //    Using std::mt19937 (Mersenne Twister) is a good general-purpose choice.
    //    Seed it with a high-resolution clock for better randomness.
    std::mt19937 engine(std::chrono::high_resolution_clock::now().time_since_epoch().count());

    // 2. Define a distribution
    //    For integers between 1 and 100 (inclusive).
    std::uniform_int_distribution<int> dist(1, 100);

    // 3. Generate and print random numbers
    std::cout << "Random numbers (1-100):\n";
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

Generating uniform random integers using std::mt19937 and std::uniform_int_distribution.

Generating Uniform Random Floating-Point Numbers

Similar to integers, you can generate uniformly distributed floating-point numbers within a specified range using std::uniform_real_distribution.

#include <iostream>
#include <random>
#include <chrono>
#include <iomanip> // For std::fixed and std::setprecision

int main() {
    std::mt19937 engine(std::chrono::high_resolution_clock::now().time_since_epoch().count());

    // For floating-point numbers between 0.0 and 1.0 (inclusive)
    std::uniform_real_distribution<double> dist(0.0, 1.0);

    std::cout << "Random doubles (0.0-1.0):\n";
    std::cout << std::fixed << std::setprecision(5);
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    // For floating-point numbers between -5.0 and 5.0
    std::uniform_real_distribution<double> dist2(-5.0, 5.0);
    std::cout << "Random doubles (-5.0-5.0):\n";
    for (int i = 0; i < 5; ++i) {
        std::cout << dist2(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

Generating uniform random floating-point numbers.

Other Distributions and Best Practices

The <random> library offers a variety of other distributions, such as std::normal_distribution for Gaussian (bell curve) numbers, std::bernoulli_distribution for true/false outcomes, and many more. When working with random numbers, consider these best practices:

  • Use a single engine instance: Create one instance of your random number engine (e.g., std::mt19937) and pass it by reference to functions that need to generate random numbers. Avoid creating new engines or re-seeding repeatedly.
  • Thread safety: If generating random numbers in multiple threads, each thread should have its own independent random number engine instance, seeded differently, to avoid contention and ensure independent sequences.
  • Cryptographic randomness: For security-sensitive applications (e.g., generating keys, nonces), the <random> library is generally not sufficient. Use std::random_device directly or a dedicated cryptographic library for true hardware-based randomness.
#include <iostream>
#include <random>
#include <chrono>

// Function to generate a random integer within a range
int getRandomInt(std::mt19937& engine, int min, int max) {
    std::uniform_int_distribution<int> dist(min, max);
    return dist(engine);
}

int main() {
    // Seed the engine once
    std::mt19937 engine(std::chrono::high_resolution_clock::now().time_since_epoch().count());

    std::cout << "Generating 3 random numbers between 10 and 20:\n";
    for (int i = 0; i < 3; ++i) {
        std::cout << getRandomInt(engine, 10, 20) << " ";
    }
    std::cout << std::endl;

    // Example of normal distribution
    std::normal_distribution<double> normal_dist(0.0, 1.0); // Mean 0, Std Dev 1
    std::cout << "\nGenerating 3 numbers from a normal distribution (mean 0, std dev 1):\n";
    for (int i = 0; i < 3; ++i) {
        std::cout << normal_dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

Demonstrating passing an engine by reference and using std::normal_distribution.