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