CS253: Software Development with C++

Spring 2022

Random Numbers

Show Lecture.RandomNumbers as a slide show.

CS253 Random Numbers

Inclusion

To use C++ random numbers, you need to:

    
#include <random>

To use old C random numbers (don’t ), you need to:

    
#include <cstdlib>

Philosophy

“Computers can’t do anything truly random. Only a person can do that.”

Old Stuff

Patron Saint of Randomness

Traditional Method

Traditional random number generators work like this:

unsigned long n = 1;
for (int i=0; i<5; i++) {
    n = n * 16807 % 2147483647;
    cout << n << '\n';
}
16807
282475249
1622650073
984943658
1144108930

Overview

Generators

EngineDescription
default_random_engineDefault random engine
minstd_randMinimal Standard minstd_rand generator
minstd_rand0Minimal Standard minstd_rand0 generator
mt19937Mersenne Twister 19937 generator
mt19937_64Mersenne Twister 19937 generator (64 bit)
ranlux24_baseRanlux 24 base generator
ranlux48_baseRanlux 48 base generator
ranlux24Ranlux 24 generator
ranlux48Ranlux 48 generator
knuth_bKnuth-B generator
random_deviceTrue random number generator

Default Engine

Define a random-number generator, and use () to generate a number. This is not a function call, because gen is an object, not a function. It’s operator().







🤨 That sequence looks familiar …

#include <random>
#include <iostream>
using namespace std;

int main() {
    default_random_engine gen;
    for (int i=0; i<5; i++)
        cout << gen() << '\n';
}
16807
282475249
1622650073
984943658
1144108930

I won’t bother with the #includes in subsequent examples.

Mersenne Twister

mt19937_64 gen;
cout << "range is " << gen.min() << "…" << gen.max() << "\n\n";
for (int i=0; i<3; i++)
    cout << gen() << '\n';
range is 0…18446744073709551615

14514284786278117030
4620546740167642908
13109570281517897720

Ranges

Generators have varying ranges:

ranlux24 rl;
minstd_rand mr;
random_device rd;
mt19937_64 mt;

cout << "ranlux24:      " << rl.min() << "…" << rl.max() << '\n'
     << "minstd_rand:   " << mr.min() << "…" << mr.max() << '\n'
     << "random_device: " << rd.min() << "…" << rd.max() << '\n'
     << "mt19937_64:    " << mt.min() << "…" << mt.max() << '\n';
ranlux24:      0…16777215
minstd_rand:   1…2147483646
random_device: 0…4294967295
mt19937_64:    0…18446744073709551615

Hey, look! Zero is not a possible return value for minstd_rand.

Save/Restore

A generator can save & restore state to an I/O stream:

ranlux24 gen;
cout << gen() << ' ';
cout << gen() << endl;
ofstream("state") << gen;
system("wc -c state");
cout << gen() << ' ';
cout << gen() << '\n';
ifstream("state") >> gen;
cout << gen() << ' ';
cout << gen() << '\n';
15039276 16323925
209 state
14283486 7150092
14283486 7150092
endl! Isn’t that a sin? 😈 🔥

Needed to flush output before wc ran.

True randomness

random_device a, b, c;
cout << a() << '\n'
     << b() << '\n'
     << c() << '\n';
2291251825
2348050194
3568347640

Cloudflare

The hosting service Cloudflare uses a unique source of randomness.

a picture of Cloudfare’s wall of lava lamps

Seeding

minstd_rand a, b, c(123);
cout << a() << ' ' << a() << '\n';
cout << b() << ' ' << b() << '\n';
cout << c() << ' ' << c() << '\n';
48271 182605794
48271 182605794
5937333 985676192

Seed with process ID

auto seed = getpid();
minstd_rand a(seed);
for (int i=0; i<5; i++)
    cout << a() << '\n';
557899908
941525688
1190063987
391159227
970822093

Seed with time

// seconds since start of 1970
auto seed = time(nullptr);
minstd_rand a(seed);
for (int i=0; i<5; i++)
    cout << a() << '\n';
313766817
1783344763
1953064778
1857795538
878799725

Seed with more accurate time

Nanoseconds make more possibilities:

auto seed = chrono::high_resolution_clock::now()
            .time_since_epoch().count();
cout << "Seed: " << seed << '\n';
minstd_rand a(seed);
for (int i=0; i<5; i++)
    cout << a() << '\n';
Seed: 1719692862239684333
1279490034
633743494
527647359
909612869
472152937

Better Seeding

Seed with random_device

random_device rd;
auto seed = rd();
minstd_rand0 a(seed);
for (int i=0; i<5; i++)
    cout << a() << '\n';
2143835022
954585388
2013773026
1140971262
1422516371

You can seed with random_device, if you know that it’s truly random.

Not good enough.

Caution

Resist the urge to hack your own distribution—it’s hard. Just use the standard distributions.

minstd_rand r;
int first_half = 0;
for (int i=0; i<100'000'000; i++)
    if (r() % 1'000'000'000 < 500'000'000)
        first_half++;
cout << first_half << '\n';
53435616
Shouldn’t the result be close to 500 million?

minstd_rand, on this computer, produces a number 1…2,147,483,646. If you take that mod a billion, the range 1…147,473,646 appears three times, whereas 147,473,647…999,999,999 only appears twice, so 1…147,473,646 is overrepresented. Tricky to get right!

Distributions

uniform_int_distribution

auto seed = random_device()();  //❓❓❓
mt19937 gen(seed);
uniform_int_distribution<int> dist(1,6);
for (int y=0; y<10; y++) {
    for (int x=0; x<40; x++)
        cout << dist(gen) << ' ';
    cout << '\n';
}
6 4 2 3 4 2 5 3 6 3 6 5 5 1 3 5 1 3 4 4 3 3 1 4 3 5 2 6 4 5 6 3 2 6 3 6 5 4 6 3 
1 1 4 2 1 6 3 3 1 4 3 6 1 2 3 4 6 6 1 1 4 6 4 4 6 5 6 3 6 1 1 3 6 2 3 6 1 5 1 6 
5 6 6 6 6 4 4 2 2 3 6 6 4 4 2 5 6 5 6 4 6 2 2 5 2 3 2 5 5 5 1 2 1 1 5 1 1 5 3 6 
5 3 5 2 6 2 3 4 6 4 6 3 3 4 1 6 4 6 5 5 4 3 6 1 4 1 2 3 2 1 2 5 4 5 4 6 5 5 6 4 
3 5 6 2 4 3 4 2 5 3 2 6 6 4 5 3 6 1 3 3 3 5 6 3 4 4 6 1 1 5 6 6 3 6 5 6 1 3 4 5 
1 4 5 6 5 6 5 2 1 5 5 2 3 2 4 4 1 1 2 1 2 1 4 2 5 3 2 1 5 6 1 3 6 3 5 5 2 1 6 2 
2 2 2 5 5 4 3 5 5 5 3 1 1 6 1 3 4 4 6 4 4 2 3 6 2 6 6 6 5 6 1 1 1 5 6 4 4 1 4 3 
4 6 3 5 5 3 5 6 3 2 6 1 3 1 6 3 1 6 1 6 6 1 5 5 2 6 2 5 4 4 3 1 1 1 4 2 3 2 1 3 
1 4 1 4 4 4 6 6 4 2 4 2 4 1 2 4 3 3 1 5 6 5 4 1 5 1 4 2 4 5 4 3 6 3 5 6 3 4 6 2 
2 4 3 5 1 4 2 1 5 2 4 1 1 3 6 1 4 3 4 1 1 5 3 5 2 2 1 1 3 3 3 4 4 2 3 4 6 2 3 6 

uniform_real_distribution

auto seed = random_device()();
ranlux48 gen(seed);
uniform_real_distribution<> dist(18.0, 25.0);
for (int y=0; y<5; y++) {
    for (int x=0; x<10; x++)
        cout << fixed << setprecision(3) << dist(gen) << ' ';
    cout << '\n';
}
24.182 21.283 23.143 22.396 20.928 20.561 22.785 22.161 18.622 22.668 
20.589 21.499 21.139 22.331 22.260 21.162 21.411 23.010 19.145 18.861 
20.346 22.793 24.145 21.573 21.788 20.863 23.739 20.027 18.460 24.038 
18.668 18.608 20.618 19.179 22.996 22.735 21.380 21.080 24.617 22.631 
19.919 18.678 22.507 19.401 20.707 23.038 24.133 19.413 22.276 23.246 
OMG—what’s that <> doing there?

uniform_real_distribution’s template argument defaults to double, because … real.

Boolean Values

Yield true 42% of time:

random_device rd;
knuth_b gen(rd());
bernoulli_distribution dist(0.42);
constexpr int nrolls = 1'000'000;

int count=0;
for (int i=0; i<nrolls; i++)
    if (dist(gen))
        count++;

cout << "true: " << count*100.0/nrolls << "%\n";
true: 42.0206%

Histogram

random_device rd;
mt19937_64 gen(rd());
normal_distribution<> dist(21.5, 1.5);
map<int,int> tally;
for (int i=0; i<10000; i++)
    tally[dist(gen)]++;
for (auto p : tally)
    cout << p.first << ": " << string(p.second/100,'#') << '\n';
16: 
17: 
18: ###
19: ###########
20: #####################
21: ##########################
22: #####################
23: ##########
24: ###
25: 
26: 
27: 

Passwords

random_device rd;
auto seed = rd();
ranlux24 gen(seed);
uniform_int_distribution<char> dist('A','~');
for (int y=0; y<8; y++) {
    string pw;
    for (int x=0; x<32; x++)
        pw += dist(gen);
    cout << "Password: " << pw << '\n';
}
Password: wsKGAFsSkrZIFxD_EgvoBA{ONjEjmiIh
Password: dhUMw^hCt|OwUMkoogVJBEVH}yT}oTvV
Password: v_l\eTUKwvFPxuwh[vztqh~K`i[KPCmw
Password: vwuPMbiERJe_UvCWX^bPgK|]mppI|T{~
Password: FfcVnWa`FdftaGzQ~RqyIbDP_vJVlQp]
Password: _VTiPCHlGlOUn^yxuTCNyeXfQsPiOZwg
Password: ^\gX^C[f]OQNQJRcS`A]VnO\DgtzSpcI
Password: nN[Ow{`}f\_Rhp_T\cbIAT\cM^lBWEKJ

Even though we’re using uniform_int_distribution, which has int right there in its name, it’s uniform_int_distribution<char>, so we get characters. Think of them as 8-bit integers that display differently.