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