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 Apr 29 01:16 state 15039276 16323925 14283486 7150092 15039276 16323925 14283486 7150092
random_device a, b, c; cout << a() << '\n' << b() << '\n' << c() << '\n';
1547924466 2013979236 4029786719
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';
1744175433 1204720881 1268023051 35705329 951526990
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';
1312563790 1556669649 1547818349 1635961802 95993211
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';
1081591864 2045924927 364192981 658251509 290549927
|
|
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 3 2 5 3 4 5 4 1 1 3 1 1 3 2 6 5 6 3 4 6 4 1 1 4 1 2 6 5 3 5 1 6 3 6 1 6 6 1 2 2 5 1 1 1 3 1 1 4 4 4 4 1 1 1 6 6 3 3 1 2 2 3 6 1 6 1 6 3 1 6 4 2 5 6 1 6 5 4 4 4 1 5 1 5 2 6 3 3 1 2 2 5 6 3 4 1 5 6 6 1 6 3 5 5 2 3 6 2 1 5 6 3 2 3 6 1 1 3 6 2 3 4 4 5 5 5 6 2 1 5 6 2 4 2 2 6 4 1 4 6 4 3 1 3 1 5 6 3 3 3 1 4 6 2 1 6 3 4 4 5 6 6 2 3 2 1 1 4 2 4 2 4 4 4 5 1 2 2 5 5 1 1 4 4 4 2 4 5 5 5 6 3 1 4 5 1 1 3 3 3 3 5 6 5 5 2 5 2 4 4 1 5 1 1 6 6 3 4 1 2 4 6 5 1 3 2 5 5 1 2 5 2 5 5 6 2 2 3 2 5 3 6 4 6 6 6 6 2 2 2 1 5 6 5 3 2 2 3 1 5 3 3 1 6 1 5 3 4 6 2 3 6 1 1 5 5 5 6 4 4 5 5 3 3 1 2 4 2 2 6 6 4 5 6 4 3 6 6 4 3 6 1 2 5 6 4 4 5 2 2 2 4 6 4 6 4 3 2 6 2 4 1 4 1 6 1 4 6 6 3 2 6 6 2 3 3 5 1 5 1 4 2 1 2 5 3 2 1 5 5 6 2 6 1 2 5 2 2 1 2 1 2 3 2 6 5 4 3 5 4 2 3 3 3 3 2 4 4 4 3 3 5 1 1 6 5 5 6 5 3 4 5 4 4 5 6 4 4 1
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'; }
23.807 24.696 20.495 21.270 23.027 22.570 20.478 20.472 19.556 19.845 18.252 18.716 18.112 18.013 19.430 18.144 23.610 22.549 19.702 18.244 21.874 18.896 20.716 24.366 18.808 18.633 22.016 21.507 23.856 18.188 24.906 20.105 24.098 20.726 24.114 21.968 24.206 20.154 22.552 22.208 19.598 24.176 21.261 19.947 23.809 19.891 24.743 24.027 18.880 23.210 24.690 18.917 24.302 18.304 20.689 19.046 20.782 24.050 24.373 22.115 19.421 23.370 20.882 18.219 24.364 18.358 21.328 23.769 19.007 24.005 24.712 21.792 20.509 18.915 19.672 22.179 21.317 19.200 22.139 24.171 21.780 24.463 24.484 23.364 24.134 19.537 20.097 19.486 24.130 18.834 19.151 24.793 24.377 23.512 20.493 24.341 21.757 24.258 20.811 20.668
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'; }
18.903 22.405 18.975 24.661 21.501 19.735 23.305 22.372 24.782 18.994 18.977 19.551 23.821 23.356 24.533 18.791 19.919 22.863 20.671 19.336 18.874 23.171 24.506 20.269 24.970 24.367 21.120 20.787 19.842 21.268 23.465 19.147 19.624 24.079 21.563 22.702 18.993 21.993 24.274 20.711 18.275 24.697 18.064 22.518 23.201 21.531 19.005 21.189 19.463 22.051 24.271 24.583 20.205 21.077 19.971 22.531 24.883 21.803 23.524 18.169 23.144 24.326 18.946 23.484 18.442 24.233 23.997 21.127 24.346 19.460 22.392 23.774 21.331 22.155 19.017 24.399 22.920 20.375 22.671 20.289 22.667 24.495 23.599 23.716 21.478 20.014 19.862 21.514 22.985 18.480 23.065 21.124 23.317 22.492 21.076 20.978 21.798 24.073 23.831 24.904
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'; }
20.305 21.909 24.621 21.190 22.446 23.809 20.605 19.520 20.306 24.075 23.551 18.796 22.912 22.871 18.007 20.111 19.674 23.151 24.007 19.192 20.610 23.895 19.319 24.525 19.796 22.093 24.383 20.191 22.899 19.750 23.129 24.989 23.700 23.888 23.437 23.665 23.814 24.495 22.687 24.796 22.994 18.522 23.621 23.952 21.300 24.267 22.909 18.934 19.577 19.822 21.068 18.854 20.461 21.406 19.448 19.301 20.265 21.277 18.584 18.951 19.163 23.475 18.742 23.704 20.167 24.118 21.453 20.028 19.908 20.993 24.232 22.151 19.097 19.722 19.041 23.228 22.121 19.667 19.326 20.634 20.433 21.320 18.650 23.936 22.890 19.211 19.012 21.669 20.284 18.030 24.861 24.968 20.414 21.672 24.423 23.390 22.315 22.393 21.677 22.077
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.104%
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';
15: 16: 17: 18: #### 19: ########### 20: #################### 21: ########################## 22: ##################### 23: ########## 24: ### 25: 26:
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: baqmwxjevxvk Password: wolepnphwaih Password: hbfwhlgoyuyy Password: swzqfrzeroan Password: xytopgcpmdub Password: zhausthkiish Password: wgmculzisnmi Password: skgwfiygmozo
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: qdnsmhgwsfjm Password: pvulxmwzdtti Password: oagkynxujfie Password: qcdrvovluwsq Password: wmoyqvtpouxl Password: nfvsjzutwbha Password: nwoggxnrgvep Password: uttmfqanldrf
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: xwhtamcivcdr Password: vpnpsfoxluqe Password: prmsgjyvjatc Password: gtyyjauqosej Password: pggggmgszcoe Password: prlhexfyqgtg Password: cjrvezbdymaw Password: mahgnxxyuojr