Ch12.2: Distributions

Overview

Random number engines produce raw random bits, but most applications need random values in specific ranges or following specific statistical distributions. C++'s <random> library provides distributions that transform raw random bits into the desired output format.

Common distributions include:

uniform_int_distribution

Generates uniformly distributed integers in a specified range [min, max].


#include <fast_io.h>
#include <random>

int main() {
    using namespace ::fast_io::io;

    ::std::mt19937_64 eng{12345u};  // Seeded for reproducibility

    // Generate integers in [1, 6] (like a die roll)
    ::std::uniform_int_distribution<int> die_dist(1, 6);

    println("Rolling a die 10 times:");
    for (::std::size_t i{}; i != 10; ++i) {
        print(die_dist(eng), " ");
    }
    print("\n");

    // Generate integers in [0, 99]
    ::std::uniform_int_distribution<int> percent_dist(0, 99);

    println("Random percentages:");
    for (::std::size_t i{}; i != 10; ++i) {
        print(percent_dist(eng), "% ");
    }
    print("\n");
}
Never use modulo to generate ranges! Using eng() % 100 to get values in [0, 99] creates biased distributions due to the pigeonhole principle. Always use ::std::uniform_int_distribution which uses rejection sampling to ensure perfect uniformity.

bernoulli_distribution

Generates boolean values (true or false) with a specified probability p of being true.


#include <fast_io.h>
#include <random>

int main() {
    using namespace ::fast_io::io;

    ::std::mt19937_64 eng{12345u};

    // Fair coin: 50% heads
    ::std::bernoulli_distribution fair_coin(0.5);

    println("Flipping a fair coin 10 times:");
    for (::std::size_t i{}; i != 10; ++i) {
        print(fair_coin(eng) ? "H" : "T", " ");
    }
    print("\n");

    // Biased coin: 70% heads
    ::std::bernoulli_distribution biased_coin(0.7);

    println("Flipping a biased coin (70% heads):");
    for (::std::size_t i{}; i != 10; ++i) {
        print(biased_coin(eng) ? "H" : "T", " ");
    }
    print("\n");

    // Rare event: 5% chance
    ::std::bernoulli_distribution rare_event(0.05);

    println("Simulating rare events (5% chance):");
    ::std::size_t count{};
    for (::std::size_t i{}; i != 100; ++i) {
        if (rare_event(eng)) {
            ++count;
        }
    }
    println("Occurred ", count, " times out of 100");
}

Combining Distributions

You can use multiple distributions together for complex scenarios:


#include <fast_io.h>
#include <random>

int main() {
    using namespace ::fast_io::io;

    ::std::mt19937_64 eng{12345u};

    // Distribution for number of items (1-5)
    ::std::uniform_int_distribution<int> count_dist(1, 5);

    // Distribution for item values (10-99)
    ::std::uniform_int_distribution<int> value_dist(10, 99);

    // Generate random number of random values
    int num_items = count_dist(eng);
    println("Generating ", num_items, " items:");

    for (int i{}; i != num_items; ++i) {
        println("  Item ", i + 1, ": ", value_dist(eng));
    }
}

Distribution Properties

All distributions provide these methods:

Method Description
operator()(gen) Generates the next random value using the engine
min() Returns the minimum value the distribution can produce
max() Returns the maximum value the distribution can produce
reset() Resets the distribution's internal state
param() Gets or sets the distribution parameters

Key Takeaways