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';
1077244674
932689371
1868660257

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';
1183168828
1967404623
1363785902
1056690483
107187091

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';
597958562
1877530622
28300221
280368399
221044735

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:    1716273168 Tue May 21 00:32:48 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: 1716273168190029273
715337008
643153055
1617516873
946538957
519919775

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

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';
}
23.449 19.727 23.565 24.066 22.091 18.998 22.067 23.655 22.401 18.536 
21.554 21.577 23.993 22.102 24.515 19.054 22.131 20.167 20.378 19.173 
23.486 20.761 24.395 22.355 19.403 24.667 24.985 21.284 20.875 23.734 
24.904 23.209 23.722 21.640 24.135 22.322 20.933 18.335 21.688 19.803 
21.541 23.957 18.616 19.544 23.541 18.993 21.772 20.561 18.526 23.869 
22.342 20.245 19.737 18.454 23.262 24.037 18.613 20.716 19.167 24.401 
21.236 23.497 23.082 22.139 21.436 20.306 22.714 21.565 18.494 18.746 
19.865 20.967 23.967 23.091 22.143 19.792 23.588 22.571 22.921 23.629 
18.971 22.310 21.142 23.035 20.194 20.069 19.812 19.693 23.741 19.689 
22.086 18.849 18.116 19.892 23.210 22.384 18.177 23.191 19.177 21.622 

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';
}
23.092 19.276 23.229 24.575 22.570 19.650 18.733 18.928 20.933 18.590 
20.068 21.128 21.334 21.829 24.673 23.819 22.725 20.410 19.135 24.572 
23.686 23.298 18.567 20.667 20.596 23.039 24.959 22.347 22.633 22.800 
18.657 18.838 18.250 21.689 21.945 20.312 23.576 21.576 18.559 20.475 
23.756 18.602 24.564 21.898 23.747 20.813 20.791 23.066 24.038 20.162 
18.811 18.808 24.431 19.077 19.406 21.211 21.949 23.277 19.702 19.449 
20.803 21.749 21.797 21.794 24.049 21.226 22.731 18.453 19.165 20.554 
22.920 24.375 23.404 18.310 20.838 21.634 23.419 22.263 24.455 21.934 
24.615 18.134 18.186 23.669 19.998 23.838 20.336 19.651 24.409 19.441 
24.860 20.954 23.646 22.627 23.605 20.560 18.996 22.306 24.513 23.415 

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';
}
22.403 24.622 23.836 20.708 24.672 23.091 24.611 19.430 21.408 20.289 
24.628 18.273 22.838 24.284 20.011 19.053 22.105 20.590 21.748 18.216 
19.831 23.462 19.703 19.765 21.025 19.188 20.007 19.930 20.785 24.751 
21.241 22.633 24.972 21.921 19.066 24.534 20.929 18.201 19.238 18.536 
21.258 21.906 24.749 22.740 24.023 19.212 20.829 19.148 21.796 19.137 
23.628 21.562 23.776 24.124 22.673 22.475 24.308 24.267 24.493 22.687 
22.681 21.772 20.426 23.060 22.763 19.039 18.767 21.010 23.870 22.656 
21.724 21.430 18.806 23.084 20.278 22.294 20.522 21.741 18.724 20.177 
19.878 22.094 18.127 24.772 18.350 18.382 21.513 19.330 24.201 24.616 
18.927 18.460 20.771 21.336 18.213 24.878 19.700 20.624 21.219 23.722 

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.041%

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: zmgurfknknni
Password: jklsnshzemib
Password: sugxiypvyrzg
Password: gukjywgcvppk
Password: zksbjyqpijiv
Password: hzplftkqvvfv
Password: etdmpgflliyv
Password: jndhcfpincxg

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: wfbingkthvdf
Password: lbvqrmyqmiwh
Password: dlkvrdfdhten
Password: slcsducolvvi
Password: ybpqknukijvb
Password: jrxsjmqdxchf
Password: alyizyafhmon
Password: poyfnjxpwqos

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: xmnhhicdfgut
Password: ccqukcfakijy
Password: fcmtfovfjold
Password: cuxpfguvfztw
Password: qlvopiwhqlmk
Password: mwjxakxphiut
Password: ekvpcsvehkky
Password: dkrelmvbzjav