Show Lecture.RandomNumbers as a slide show.
CS253 Random Numbers
Philosophy
“Computers can’t do anything truly random. Only a person can do that.”
- Stop trying to prove your superiority.
- If you believe that you have something special that distinguishes you
from machines, you’re talking religion, not CS.
- My dog is pretty random.
- You’re somewhat predictable.
- An online rock-paper-scissors
program beats people 60% of the time over more than a million games,
because people are lousy at being random.
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
- It’s fast, simple, and good enough for many tasks. However …
- What happens if
n
is zero?
- What number always follows 16807?
- How many possible states does this RNG
(Random Number Generator) have?
Overview
- In C++, random numbers have:
- Generators
Generate uniformly-distributed random integers,
typically zero or one to a big number.
- Distributions
Take uniformly-distributed random integers, and transform them into
other distributions with different ranges.
- Examples:
- Picking a card (uniform, but discrete)
- Rolling 3d6 (bell-shaped, but discrete)
- Human height (bell-shaped, continuous)
Generators
Engine | Description |
default_random_engine | Default random engine |
minstd_rand | Minimal Standard minstd_rand generator |
minstd_rand0 | Minimal Standard minstd_rand0 generator |
mt19937 | Mersenne Twister 19937 generator |
mt19937_64 | Mersenne Twister 19937 generator (64 bit) |
ranlux24_base | Ranlux 24 base generator |
ranlux48_base | Ranlux 48 base generator |
ranlux24 | Ranlux 24 generator |
ranlux48 | Ranlux 48 generator |
knuth_b | Knuth-B generator |
random_device | True 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 #include
s in subsequent examples.
Mersenne Twister
- Here’s a different, 64-bit generator.
- Use
.min()
and .max()
to find out the range of a given generator.
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
random_device
is, ideally, truly random, and not pseudo-random.
- Intel computers have an RDRAND instruction.
- It might depend on random things like human typing intervals,
network packets arrival times, or radioactive decay.
- If true randomness isn’t available, it resorts to pseudo-random numbers.
- It could pause waiting for randomness to become available.
- Use it sparingly.
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
- Great—we can “seed” the random number generator with a value.
- This way, we can reproduce our pseudo-random sequences.
- Consider random testing: we want to be able to reproduce the sequence
if we find an error.
- How to choose the random seed?
- It should probably be … random.
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
- You can seed with a time-related value.
- Two runs may occur within the same second,
and so produce identical random sequences.
- OK for casual use, but the seed is easily guessed.
- There are only 86,400 seconds in a day.
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
- There are 86,400,000,000,000 nanoseconds in a day.
Better Seeding
- Many generators have more than 32 or 64 bits of state.
- Therefore, you can seed them with more than 32 or 64 bits.
- If you’re doing something very important, and somebody guessing
your seed, and hence predicting your sequence, would be catastrophic:
- on-line poker 🂺🂻🂽🂾🂱
- encryption of military communications. 🛦 💣 ☢
- encrypted email re: extra-marital affairs 💔
- That’s beyond the scope of this discussion.
Not good enough.
- Great, so we know how to generate a number 1…2,147,483,646
or perhaps 0…18,446,744,073,709,551,615
- How often do we want to do that?
- Sometimes, we want integers with different ranges.
- Or, perhaps we want floating-point numbers.
- Maybe spread out linearly, or a bell-shaped curve, Poisson, etc.
- This is a job for a distribution.
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