C/C++ Arena

Step 6 of 7

Random numbers with <random>

C's rand() works in C++ too, but modern code uses <random>, which separates two jobs:

#include <iostream>
#include <random>

int main() {
    std::mt19937 gen(42);                          // seeded: the same sequence every run
    std::cout << gen() << " " << gen() << "\n";    // raw 32-bit numbers

    std::uniform_int_distribution<int> die(1, 6);          // 1 to 6, inclusive
    std::uniform_real_distribution<double> unit(0.0, 1.0); // from 0.0 up to (not including) 1.0
    int good = 0;
    for (int i = 0; i < 1000; i++) {
        int r = die(gen);
        double x = unit(gen);
        if (r >= 1 && r <= 6 && x >= 0.0 && x < 1.0) good++;
    }
    std::cout << good << " of 1000 in range\n";
}
1608637542 3421126067
1000 of 1000 in range

Seeding

What's the same everywhere, and what isn't

The C++ standard defines exactly what each engine produces: the two numbers above are the same with every compliant compiler, and the standard even requires that the 10,000th number from a default-constructed std::mt19937 is 4123659995. The distributions are only required to have the right statistical behavior; each library implements them its own way. With seed 1, uniform_int_distribution<int>(1, 6) rolls 6 4 5 1 2 on this site (Clang's library) but 3 6 5 6 1 with GCC's library. std::shuffle differs the same way. So a test can rely on a fixed seed on one platform, but never hard-code random results that must match across compilers.

Two more rules: never reduce with gen() % n (a distribution does the range correctly), and don't use mt19937 for passwords or tokens. Its future output can be predicted after seeing enough of it.

Your turn: write roll(gen, sides), a number from 1 to sides using a uniform_int_distribution, and deal(n, seed), the numbers 0 to n - 1 in a random order, shuffled with std::shuffle and a std::mt19937 seeded with seed, so the same seed always deals the same order.

Previous: Strict number parsing with from_chars Next: Challenge: a log formatter