Show Lecture.RandomNumbers as a slide show.
“Computers can’t do anything truly random. Only a person can do that.”
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 random number generators work like this:
long long n = 1; for (int i=0; i<5; i++) { n = n * 16807 % 2147483647; cout << n << '\n'; }
16807 282475249 1622650073 984943658 1144108930
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 |
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.
Here’s a different, 64-bit generator. You can 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
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
You can save & restore the state of a generator to an I/O stream:
ranlux24 gen; ofstream("state") << gen; system("ls -l state"); cout << gen() << '\n'; cout << gen() << '\n'; cout << gen() << '\n'; cout << gen() << "\n\n"; ifstream("state") >> gen; cout << gen() << '\n'; cout << gen() << '\n'; cout << gen() << '\n'; cout << gen() << '\n';
-rw------- 1 cs253 class 208 Jul 17 08:42 state 15039276 16323925 14283486 7150092 15039276 16323925 14283486 7150092
random_device a, b, c; cout << a() << '\n' << b() << '\n' << c() << '\n';
132829499 3469946000 1029623485
random_device
is, ideally, truly random, and not pseudo-random.
The hosting service Cloudflare uses a unique source of randomness.
minstd_rand a, b, c(123); cout << a() << ' ' << a() << '\n'; cout << b() << ' ' << b() << '\n'; cout << c() << ' ' << c() << '\n';
48271 182605794 48271 182605794 5937333 985676192
random_device gen; auto seed = gen(); minstd_rand0 a(seed); for (int i=0; i<5; i++) cout << a() << '\n';
432423612 658985436 995055273 1438814122 1483083234
You can seed with random_device
, if you know that
it’s truly random.
// seconds since start of 1970 auto seed = time(nullptr); minstd_rand a(seed); for (int i=0; i<5; i++) cout << a() << '\n';
1371944655 1039735319 223269412 1364846006 1978232960
C++ provides a more accurate time—nanoseconds make more possibilities:
auto seed = chrono::high_resolution_clock::now() .time_since_epoch().count(); minstd_rand a(seed); for (int i=0; i<5; i++) cout << a() << '\n';
980786537 122445765 704525771 612458049 1702598677
|
|
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'; }
2 6 2 6 6 5 2 4 6 2 6 2 2 2 3 5 5 5 5 2 3 5 2 3 5 4 3 3 2 4 6 2 3 3 1 1 2 3 3 5 4 4 1 6 1 3 4 3 2 4 5 3 3 2 3 2 3 2 4 2 2 1 1 5 4 4 5 6 1 1 5 2 3 5 1 3 5 3 5 5 4 4 2 3 4 3 4 3 1 6 3 6 6 2 1 3 6 3 4 1 6 4 2 4 6 6 3 2 3 3 2 4 3 6 5 6 6 3 5 6 2 3 5 3 3 4 1 4 2 3 2 4 4 2 2 4 6 2 1 6 2 1 5 1 1 3 3 4 2 6 3 6 6 6 3 1 1 6 4 2 6 5 6 2 1 2 3 5 2 3 2 3 4 3 2 3 5 6 6 2 5 1 3 6 3 4 1 3 5 3 1 6 3 2 5 2 3 3 2 6 2 6 3 3 1 3 4 6 2 1 1 5 3 4 5 2 4 4 4 5 5 4 2 2 2 2 1 3 5 2 6 3 6 6 4 3 1 4 4 2 2 5 6 3 4 4 6 1 1 1 3 5 6 2 1 5 5 2 6 5 6 1 4 5 6 2 2 5 1 6 4 3 1 1 5 1 1 5 2 3 2 2 2 4 5 3 4 1 4 2 5 4 1 2 6 4 1 5 3 2 6 6 2 3 6 2 2 5 6 3 5 4 3 2 4 5 4 1 2 2 6 3 4 5 6 1 1 6 3 2 2 6 3 3 6 3 2 1 4 2 2 2 4 6 6 4 3 2 1 3 1 3 3 1 2 1 1 1 4 6 2 6 1 4 1 3 4 6 1 2 2 4 2 3 4 4 3 1 2 1 3 6 1 5 6 4 2 4 6 6 3 4 6 5 1 4 4 2 1 2
auto seed = random_device()(); ranlux48 gen(seed); uniform_real_distribution<float> 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'; }
19.627 19.958 21.222 23.682 19.411 18.099 24.324 24.259 22.658 20.817 22.057 22.609 24.819 20.316 20.263 23.320 24.985 22.111 24.381 19.705 18.683 19.126 23.991 20.393 20.474 24.770 23.474 18.388 24.993 24.860 21.511 24.079 21.540 20.947 22.545 24.611 24.343 23.346 23.374 21.587 20.470 23.661 24.715 24.746 24.552 23.430 23.472 20.162 19.019 24.797 23.798 21.780 22.472 21.135 23.830 20.888 21.550 18.620 22.167 22.132 18.330 21.858 22.553 19.952 21.749 20.696 20.538 23.027 24.382 23.194 21.511 24.504 21.501 21.561 24.418 22.930 24.494 24.842 18.215 22.139 23.588 21.116 23.492 21.939 24.413 20.230 19.100 20.099 23.839 19.750 20.336 19.007 19.511 19.051 20.002 19.764 18.206 18.649 23.744 23.603
auto seed = random_device()(); minstd_rand gen(seed); uniform_real_distribution<float> 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.011 23.048 19.500 21.385 23.820 22.104 18.185 24.679 22.164 20.331 24.028 18.613 23.550 22.909 21.422 21.309 21.615 23.691 21.662 23.801 21.736 21.187 20.219 23.336 18.150 19.530 22.584 21.095 18.176 20.135 21.214 24.577 23.362 21.452 22.582 18.353 19.166 21.067 19.477 20.690 19.465 22.136 23.560 22.297 18.947 20.552 20.314 22.438 22.799 20.931 24.124 18.861 24.549 24.352 24.196 21.313 21.732 21.732 21.015 21.490 18.280 22.426 24.363 20.712 19.468 20.035 20.367 23.983 23.946 20.276 24.388 18.987 20.877 23.599 23.971 24.667 24.676 22.062 22.371 23.545 18.467 24.168 21.253 24.321 23.631 24.770 20.750 23.996 23.479 22.615 21.185 20.873 18.751 22.840 19.764 19.401 20.171 20.026 22.821 23.123
auto seed = random_device()(); auto r = bind(uniform_real_distribution<float>(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'; }
19.132 24.844 22.683 23.843 21.159 22.697 19.280 23.672 20.761 18.501 23.012 20.368 23.374 22.339 19.992 22.069 22.675 20.592 23.095 19.271 18.992 22.638 19.729 18.061 19.321 24.574 20.796 19.024 21.295 18.076 22.977 24.975 21.819 18.170 19.741 20.517 20.399 22.283 24.112 20.890 20.837 23.476 24.550 18.898 19.300 22.379 24.838 23.359 18.860 19.384 23.590 22.588 21.699 21.989 23.084 21.981 20.908 20.635 22.891 22.584 19.413 24.307 24.882 21.498 24.177 18.770 20.591 23.509 19.464 20.218 22.408 19.543 19.627 21.587 23.125 18.008 22.935 19.949 24.775 23.673 23.078 23.010 18.454 18.374 21.432 24.417 21.499 20.337 18.210 18.261 21.554 24.465 19.137 21.014 23.076 22.654 18.095 19.865 23.163 20.128
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.02%
auto seed = random_device()(); mt19937_64 gen(seed); normal_distribution<double> 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:
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: vbqrmhgywgsm Password: eeuacvaklonb Password: jzlpodefiuhs Password: ukpqxhqupmcu Password: mlupyekkygqs Password: mkrmqqttqneg Password: yicqxsdpvxpi Password: xjexethpgevh
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.
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: gazpfwxjthml Password: tmvyxtjjzfgr Password: wvaigkejlfqg Password: vsrmaykfcqbg Password: lhoqcovzrmxz Password: gxxgtksbubuy Password: gygsgmcfgpvd Password: vooptzgzgsao
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: jmitmqwytgyv Password: oltejenedozw Password: unsbgcqidzne Password: slvmcmzpamfd Password: iffzfudrrknz Password: mydsuzuxxiys Password: imgdotuxpyhh Password: yeasswqaruay