CS253: Software Development with C++

Fall 2019

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

There are several C random number generators, of varying degrees of standardization:

They still work ok, but avoid them for new C++ code. They mix up generation and distribution something terrible.

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 << "min=" << gen.min() << '\n'
     << "max=" << gen.max() << "\n\n";
for (int i=0; i<5; i++)
    cout << gen() << '\n';
min=0
max=18446744073709551615

14514284786278117030
4620546740167642908
13109570281517897720
17462938647148434322
355488278567739596

Ranges

Not all generators have the same range:

mt19937_64 mt;
minstd_rand mr;

cout << "mt19937_64: " << mt.min() << "…" << mt.max() << '\n'
     << "minstd_rand " << mr.min() << "…" << mr.max() << '\n';
mt19937_64: 0…18446744073709551615
minstd_rand 1…2147483646

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? 😈 🔥

True randomness

random_device a, b, c;
cout << a() << '\n'
     << b() << '\n'
     << c() << '\n';
2263880775
3175910240
3644290424

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 random_device

random_device gen;
auto seed = gen();
minstd_rand0 a(seed);
for (int i=0; i<5; i++)
    cout << a() << '\n';
844380101
924418131
1798825319
582353967
1540143990

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

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';
1460199668
589912194
18357354
1364572370
1654451486

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:    1719761100 Sun Jun 30 09:25:00 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: 1719761101000077205
247525316
1843000375
1813541003
1414369505
230270431

Better Seeding

Not good enough.

Distributions

  • Uniform:
    • uniform_int_distribution
    • uniform_real_distribution
  • Bernoulli (yes/no) trials:
    • bernoulli_distribution
    • binomial_distribution
    • geometric_distribution
    • negative_binomial_distribution
  • Piecewise distributions:
    • discrete_distribution
    • piecewise_constant_distribution
    • piecewise_linear_distribution
  • Related to Normal distribution:
    • normal_distribution
    • lognormal_distribution
    • chi_squared_distribution
    • cauchy_distribution
    • fisher_f_distribution
    • student_t_distribution
  • Rate-based distributions:
    • poisson_distribution
    • exponential_distribution
    • gamma_distribution
    • weibull_distribution
    • extreme_value_distribution

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

uniform_real_distribution

auto seed = random_device()();
ranlux48 gen(seed);
uniform_real_distribution<> dist(18.0, 25.0);
for (int y=0; y<10; y++) {
    for (int x=0; x<10; x++)
        cout << fixed << setprecision(3) << dist(gen) << ' ';
    cout << '\n';
}
22.504 18.663 20.055 21.426 23.532 20.936 18.595 19.810 21.514 22.121 
20.769 18.272 21.359 19.536 24.033 24.209 21.397 20.363 19.224 20.613 
22.536 22.949 24.651 22.919 20.867 22.834 24.034 21.489 22.701 18.158 
23.259 20.070 18.914 19.759 18.144 24.757 23.607 18.751 22.271 19.306 
24.175 23.694 23.146 23.205 18.952 24.225 22.816 24.490 23.746 23.015 
20.922 23.191 24.949 20.374 18.708 19.425 19.009 19.192 21.982 23.026 
22.725 23.712 19.574 22.290 22.955 18.972 18.385 21.367 23.132 23.682 
23.034 18.376 24.014 24.133 24.397 19.229 19.319 23.245 19.736 22.021 
20.146 18.391 21.200 20.448 21.867 19.984 18.634 22.357 21.681 21.751 
24.011 20.844 20.179 24.693 21.200 24.979 19.491 23.901 18.014 23.315 

OMG—what’s that <> doing there?

Binding

auto seed = random_device()();
minstd_rand gen(seed);
uniform_real_distribution<> dist(18.0, 25.0);
auto r = bind(dist, gen);
for (int y=0; y<10; y++) {
    for (int x=0; x<10; x++)
        cout << fixed << setprecision(3) << r() << ' ';
    cout << '\n';
}
18.042 23.024 20.007 19.584 21.524 23.354 22.297 20.779 18.725 24.040 
20.626 22.031 22.085 24.774 22.483 22.006 20.436 21.571 20.290 24.894 
24.042 18.274 20.988 21.375 22.468 19.308 24.581 21.296 22.641 19.459 
23.508 19.110 19.680 21.923 22.127 21.039 24.884 19.451 23.369 23.156 
22.305 19.140 18.461 20.668 24.281 22.610 20.592 18.055 21.411 18.429 
21.336 18.886 21.759 20.958 18.783 22.802 24.515 19.724 21.537 23.433 
18.457 24.570 20.896 21.580 22.022 24.283 21.311 18.298 22.500 18.836 
21.879 22.872 23.098 24.665 21.886 21.645 20.922 20.348 18.995 24.971 
19.944 20.414 18.289 20.403 24.908 20.637 18.648 22.819 18.229 21.315 
20.242 24.641 19.468 21.158 20.731 21.534 18.857 21.111 23.868 22.032 

Binding with temporaries

auto seed = random_device()();
auto r = bind(uniform_real_distribution<>(18.0, 25.0), mt19937(seed));
for (int y=0; y<10; y++) {
    for (int x=0; x<10; x++)
        cout << fixed << setprecision(3) << r() << ' ';
    cout << '\n';
}
18.029 24.303 20.860 19.774 21.553 23.992 22.848 18.252 18.850 23.855 
24.783 19.172 24.348 19.986 20.922 19.150 24.660 23.763 23.639 23.945 
21.670 20.426 24.897 24.374 22.020 19.023 21.586 24.974 22.886 24.906 
24.307 22.854 20.158 19.901 21.581 21.362 20.000 20.723 22.956 18.255 
21.307 20.066 18.232 21.828 22.551 23.067 21.939 18.647 24.336 22.145 
18.479 23.743 18.629 19.585 20.270 24.826 20.088 18.132 22.083 24.690 
18.339 21.128 22.224 20.499 24.991 21.376 18.957 19.770 22.230 19.763 
22.589 24.972 20.789 23.480 23.268 21.165 23.085 24.732 21.940 23.089 
19.289 24.443 19.922 22.103 24.743 18.652 19.545 23.196 21.610 21.671 
24.955 24.290 23.656 24.647 20.353 22.970 22.364 22.490 18.753 21.671 

Boolean Values

Yield true 42% of time:

auto seed = random_device()();
constexpr int nrolls=100000;

auto r = bind(bernoulli_distribution(0.42), knuth_b(seed));

int count=0;
for (int i=0; i<nrolls; i++)
    if (r())
        count++;
cout << "true: " << count*100.0/nrolls << "%\n";
true: 42.194%

Histogram

auto seed = random_device()();
mt19937_64 gen(seed);
normal_distribution<> dist(21.5, 1.5);
auto r = bind(dist, gen);
map<int,int> tally;
for (int i=0; i<10000; i++)
    tally[r()]++;
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('a','z');
for (int y=0; y<8; y++) {
    string pw;
    for (int x=0; x<12; x++)
        pw += dist(gen);
    cout << "Password: " << pw << '\n';
}
Password: uchwcaigrjxo
Password: yoaplskiexjy
Password: pjkkhzgoluue
Password: jlqfrogtksyn
Password: lljljteeornm
Password: ymhpbjyqndwj
Password: bqjfypckovdk
Password: rxpalcersyis

Even though we’re using uniform_int_distribution, it’s uniform_int_distribution<char>, so we get characters. Think of them as 8-bit integers that display differently.

Passwords

With binding:

auto seed = random_device()();
ranlux24 gen(seed);
uniform_int_distribution<char> dist('a','z');
auto r = bind(dist, gen);
for (int y=0; y<8; y++) {
    string pw;
    for (int x=0; x<12; x++)
        pw += r();
    cout << "Password: " << pw << '\n';
}
Password: cqiuninzzxvt
Password: wboyrwanglda
Password: mnpocczdbakf
Password: nvfrjgdenjgr
Password: dpgzdygxfsgr
Password: bdtoafdxejst
Password: zivunkndtfij
Password: lbdjejqwoylv

Passwords

With extreme binding:

auto r = bind(uniform_int_distribution<char>('a','z'),
              ranlux24((random_device())()));
for (int y=0; y<8; y++) {
    string pw;
    for (int x=0; x<12; x++)
        pw += r();
    cout << "Password: " << pw << '\n';
}
Password: oultlufohmci
Password: bpwuiknwlpfn
Password: xfldlqefgkyu
Password: rndmvfoqcvng
Password: axkhoesaftwj
Password: oerdtybsqquw
Password: wqjiaudkobjr
Password: yflsjfibwqdm