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';
1614317079
1962303252
1047224771

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';
130127110
907985124
487183486
1885186838
361458428

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';
154089852
1335376331
1081725349
2046928421
1559211621

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:    1732279678 Fri Nov 22 05:47:58 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: 1732279678707992954
208536863
1027060384
424321422
1867819923
1582067485

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

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.770 24.599 18.238 19.364 23.214 21.287 24.583 22.747 24.339 23.237 
20.758 21.551 18.011 21.124 24.941 20.768 24.385 19.059 21.709 24.265 
23.748 22.451 20.250 19.344 20.259 20.934 24.262 22.618 21.250 19.385 
23.617 21.491 18.928 24.357 18.760 18.576 18.077 21.868 22.229 23.479 
18.188 23.849 22.787 21.766 19.647 21.490 24.343 18.593 24.819 22.949 
23.882 21.536 20.412 23.286 21.141 20.194 21.779 23.865 19.998 23.976 
23.671 18.384 23.039 24.451 23.352 22.746 24.563 20.606 19.282 19.943 
19.336 21.425 24.675 24.093 23.005 21.960 19.844 21.701 19.404 22.214 
23.173 21.468 23.022 23.706 24.245 19.443 24.802 19.031 18.648 24.619 
24.893 19.205 22.888 20.920 20.230 23.062 19.257 18.239 19.298 24.831 

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';
}
24.513 22.525 23.211 23.015 21.749 24.908 23.029 18.947 18.338 24.043 
22.851 24.119 20.583 18.458 18.519 20.103 24.966 19.096 24.594 24.820 
21.381 23.848 24.227 24.300 19.319 18.758 19.708 18.671 21.911 19.268 
24.142 23.301 24.970 23.197 18.072 20.827 22.028 19.355 18.235 18.974 
22.700 22.034 18.642 21.624 20.862 23.021 24.569 18.914 21.832 24.115 
24.714 22.708 23.463 19.720 20.011 24.251 20.316 20.497 22.718 24.923 
22.740 23.622 19.674 18.722 21.543 21.354 18.766 21.054 19.140 20.568 
22.669 23.380 23.063 24.647 23.395 20.361 22.110 23.339 24.331 24.436 
19.962 19.554 21.473 18.238 23.561 18.720 24.228 20.611 22.634 24.919 
20.223 18.731 20.051 24.838 20.141 20.698 18.641 24.558 23.775 18.136 

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';
}
24.417 23.629 19.932 18.776 20.386 21.379 22.860 19.106 18.342 19.266 
24.676 20.308 18.957 20.614 21.590 19.187 23.698 19.577 19.957 21.063 
22.017 19.205 23.664 20.716 20.680 23.807 20.548 21.601 21.647 21.789 
21.788 23.141 24.870 24.106 21.595 24.231 21.546 20.934 20.881 24.951 
23.374 18.606 19.915 20.293 21.447 23.178 24.388 18.685 18.612 21.004 
23.738 20.274 22.791 18.138 18.320 19.780 23.412 18.037 18.344 19.808 
19.261 20.095 19.345 18.412 23.177 18.632 20.535 18.340 19.153 18.270 
23.142 18.254 22.119 18.743 21.609 24.583 23.401 19.924 21.702 18.583 
21.044 24.144 19.065 20.602 19.507 20.964 19.675 24.178 23.214 19.776 
22.363 23.496 24.511 18.545 18.145 20.610 21.246 18.143 24.203 24.028 

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: 41.91%

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';
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: oxcpyelbwdqa
Password: plfdrlkflvyp
Password: aljhtcmiesfz
Password: uqqsgrcigrqb
Password: xhqdacqpukmm
Password: sxstyuagwsjz
Password: wgnyajldxfzv
Password: duyhcarsjbss

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: wnthhuyfvrhm
Password: auminmjsswsd
Password: ggcmyjbjrrxu
Password: wcdpjftkjoxc
Password: uzfevgmxclte
Password: orhzppjrmbrd
Password: qtjythvcjlyp
Password: vyxfuqqwnlsv

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: kalnrothhijf
Password: jkrynqztpblx
Password: ufhwcgxtgfoj
Password: npehzafmorbg
Password: nxjyculbhpjq
Password: jvqwfqrlbqyd
Password: wowlzxyahpqh
Password: iybgfmkwowtw