CS253: Software Development with C++

Fall 2020

Random Numbers

Show Lecture.RandomNumbers as a slide show.

CS253 Random Numbers

Philosophy

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

Old Stuff

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';
3350188630
420138396
2500820400

Cloudflare

The hosting service Cloudflare uses a unique source of randomness.

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';
1411371976
1565436068
1657351439
1803010278
2039367369

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';
1603748058
2032000662
388378677
2042362804
215645408

Y2038

int biggest = 0x7fffffff;
time_t epoch = 0,
       now = time(nullptr),
       end = biggest,
       endp1 = biggest + 1;
cout << "epoch:" << setw(12) << epoch << ' ' << ctime(&epoch);
cout << "now:  " << setw(12) << now   << ' ' << ctime(&now);
cout << "end:  " << setw(12) << end   << ' ' << ctime(&end);
cout << "end+1:" << setw(12) << endp1 << ' ' << ctime(&endp1);
epoch:           0 Wed Dec 31 17:00:00 1969
now:    1714203066 Sat Apr 27 01:31:06 2024
end:    2147483647 Mon Jan 18 20:14:07 2038
end+1: -2147483648 Fri Dec 13 13:45:52 1901

I hope that nobody’s still using 32-bit signed time representations by then!

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: 1714203066014798178
261633426
2103262086
2127257734
780012962
174905851

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';
1141082355
1142172775
141508892
1075550615
1409329506

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

Not good enough.

Caution

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';
}
5 1 1 6 5 1 5 2 3 2 3 6 2 3 5 6 6 1 2 1 4 2 3 5 3 4 1 5 4 3 2 4 5 3 6 4 1 5 6 4 
5 5 1 5 5 3 4 6 6 5 1 4 6 5 5 6 4 6 4 5 3 2 6 5 4 1 4 2 3 1 2 1 5 6 4 3 6 6 6 2 
4 2 2 3 3 4 1 5 4 3 1 1 1 4 3 2 6 6 2 1 1 3 5 6 4 3 6 1 1 4 4 5 1 2 5 4 6 2 3 3 
6 2 1 5 2 4 1 4 2 2 2 2 5 1 1 3 5 2 2 4 2 2 4 1 1 6 5 2 2 1 3 1 1 3 2 1 4 3 4 3 
4 5 2 2 6 4 3 3 4 3 4 2 6 5 5 2 2 4 2 1 5 1 5 4 1 2 4 3 3 3 1 4 6 4 3 2 4 6 2 1 
3 3 5 6 5 3 5 1 4 6 2 3 1 4 2 2 5 2 2 4 6 2 1 5 5 1 1 6 6 6 2 3 2 1 5 1 4 4 2 2 
3 6 3 1 6 6 4 3 2 5 1 5 2 2 3 2 2 6 2 4 2 2 6 2 5 2 4 3 4 1 2 4 2 2 6 5 1 5 3 4 
4 1 3 5 6 5 1 5 3 5 5 4 4 6 2 2 3 1 4 3 6 1 1 4 4 2 3 6 2 4 2 5 1 1 4 3 3 4 3 3 
1 3 6 1 2 3 6 5 3 6 6 4 6 2 1 5 5 4 4 2 2 3 3 3 2 2 3 6 3 4 2 5 5 3 2 5 1 4 1 3 
1 1 4 4 6 6 2 4 4 2 1 6 5 3 4 2 1 2 4 1 5 1 1 2 2 2 4 1 2 4 4 2 4 3 5 6 6 2 4 1 

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';
}
20.512 24.728 18.533 22.482 19.890 23.017 24.929 19.635 22.365 21.944 
21.156 23.784 22.177 22.816 20.378 22.220 21.752 20.557 21.832 24.071 
20.919 24.384 21.033 18.749 23.679 24.796 23.272 18.805 24.677 20.476 
18.892 23.275 22.617 23.986 22.754 20.475 22.919 21.020 18.437 19.882 
23.074 23.164 24.131 21.560 22.570 21.808 24.463 23.725 23.813 18.175 
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.0689%

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';
15: 
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('!','~');
for (int y=0; y<8; y++) {
    string pw;
    for (int x=0; x<32; x++)
        pw += dist(gen);
    cout << "Password: " << pw << '\n';
}
Password: &K)s/{qaQ_bJSDfMt|UWzns+7R@jA&e(
Password: Y]ZHKl@q0fk4@B~tx)HGfdeG7QlMk(,b
Password: rbuiYt-LX\}z>*(/maR}`8_s\bK5>jKd
Password: F%1kFP8iw<l+5gYf7k9p/ux"}MA>9|p9
Password: [gH]Lku&AR-_.>q~An?{igx{ITFQl)50
Password: ]y6jAKOB>n9G]F&6HwBU"$>BtrTIU'\r
Password: NTxV&Lf-?[%<c"t\|rL4p-ERJSvpmMm-
Password: >>3IB0CQA9b,o-^*oo62|5gTg9~`[]=[

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.