The rand() function from cstdlib/stdlib.h is used heavily in FSO and even in FRED, but it has two serious issues:
1. Its behavior is implementation-defined. This is particularly problematic for multiplayer, where players need to be kept in sync.
2. Its output doesn't even remotely resemble true randomness.
A good alternative is the
Mersenne Twister, which since C++11 is in the C++ standard library as std::mt19937.
As proof of rand()'s implementation-defined behavior, here's a C++ program I wrote:
#include <cstdlib>
#include <iostream>
#include <random>
int main() {
constexpr unsigned int seed = 1234567890;
srand(seed);
std::cout << "RAND_MAX: " << RAND_MAX << std::endl; // maximum value rand() can produce
std::cout << "with seed " << seed << " rand() returns: ";
for (int i = 0; i < 10; ++i) {
std::cout << rand() << " ";
}
std::cout << "..." << std::endl << std::endl;
std::mt19937 mt;
mt.seed(seed);
std::cout << "mt19937 min value: " << std::mt19937::min() << " max value: " << std::mt19937::max() << std::endl;
std::cout << "with seed " << seed << " mt19937 returns: ";
for (int i = 0; i < 10; ++i) {
std::cout << mt() << " ";
}
std::cout << "..." << std::endl;
return 0;
}
The output on Windows (MSVC 2019 16.8.3):
RAND_MAX: 32767
with seed 1234567890 rand() returns: 1178 3658 9904 19467 9096 19670 28241 27551 24744 17441 ...
mt19937 min value: 0 max value: 4294967295
with seed 1234567890 mt19937 returns: 2657703298 1462474751 2541004134 640082991 3816866956 998313779 3829628193 1854614443 1965237353 3628085564 ...
And on Ubuntu 20.04:
RAND_MAX: 2147483647
with seed 1234567890 rand() returns: 1727406014 657320857 2087271152 332581333 1147476310 1208088455 121233336 1334120027 2004824482 1990688704 ...
mt19937 min value: 0 max value: 4294967295
with seed 1234567890 mt19937 returns: 2657703298 1462474751 2541004134 640082991 3816866956 998313779 3829628193 1854614443 1965237353 3628085564 ...
To perform statistical tests of RNG quality, I used PractRand on Ubuntu, using the same RNG seed I used in the code above.
Since the high-order bit of values from rand() is 0 (2147483647 == 0x7fffffff), I have to use
every other byte of rand()'s output to get data that has any chance of passing PractRand, but even that doesn't help:
RNG_test using PractRand version 0.94
RNG = RNG_stdin, seed = unknown
test set = core, folding = standard(unknown format)
rng=RNG_stdin, seed=unknown
length= 256 megabytes (2^28 bytes), time= 3.2 seconds
Test Name Raw Processed Evaluation
BCFN(2+1,13-2,T) R= +27.3 p = 1.6e-13 FAIL
[Low1/8]BCFN(2+0,13-4,T) R= +64.8 p = 2.9e-28 FAIL !!!
[Low1/8]BCFN(2+2,13-5,T) R= +97.2 p = 3.2e-38 FAIL !!!
[Low1/8]BCFN(2+3,13-5,T) R= +47.8 p = 7.0e-19 FAIL !
[Low1/8]BCFN(2+4,13-5,T) R= +20.8 p = 2.4e-8 very suspicious
[Low1/8]DC6-9x1Bytes-1 R=+376.8 p = 6.4e-253 FAIL !!!!!!
[Low1/8]BRank(12):1K(1) R=+238.9 p~= 6.1e-73 FAIL !!!!
[Low1/32]BRank(12):512(1) R=+238.9 p~= 6.1e-73 FAIL !!!!
...and 205 test result(s) without anomalies
The results with std::mt19937 using all bytes of its generated numbers:
RNG_test using PractRand version 0.94
RNG = RNG_stdin, seed = unknown
test set = core, folding = standard(unknown format)
rng=RNG_stdin, seed=unknown
length= 256 megabytes (2^28 bytes), time= 3.6 seconds
no anomalies in 213 test result(s)
rng=RNG_stdin, seed=unknown
length= 512 megabytes (2^29 bytes), time= 7.3 seconds
no anomalies in 229 test result(s)
rng=RNG_stdin, seed=unknown
length= 1 gigabyte (2^30 bytes), time= 14.4 seconds
no anomalies in 248 test result(s)
rng=RNG_stdin, seed=unknown
length= 2 gigabytes (2^31 bytes), time= 27.8 seconds
no anomalies in 266 test result(s)
rng=RNG_stdin, seed=unknown
length= 4 gigabytes (2^32 bytes), time= 53.0 seconds
no anomalies in 282 test result(s)
rng=RNG_stdin, seed=unknown
length= 8 gigabytes (2^33 bytes), time= 107 seconds
no anomalies in 299 test result(s)
For a bit on how Mersenne Twister makes for a strong RNG for games, see
this Gamasutra article (scroll to the section on Pokemon).
Thoughts?
EDIT: Here are PractRand results for MT up to 64 GB, using the same seed as before, with PractRand configured to evaluate the input as 32-bit values:
RNG_test using PractRand version 0.94
RNG = RNG_stdin32, seed = unknown
test set = core, folding = standard (32 bit)
rng=RNG_stdin32, seed=unknown
length= 256 megabytes (2^28 bytes), time= 2.7 seconds
no anomalies in 165 test result(s)
rng=RNG_stdin32, seed=unknown
length= 512 megabytes (2^29 bytes), time= 5.3 seconds
no anomalies in 178 test result(s)
rng=RNG_stdin32, seed=unknown
length= 1 gigabyte (2^30 bytes), time= 10.4 seconds
no anomalies in 192 test result(s)
rng=RNG_stdin32, seed=unknown
length= 2 gigabytes (2^31 bytes), time= 20.2 seconds
no anomalies in 204 test result(s)
rng=RNG_stdin32, seed=unknown
length= 4 gigabytes (2^32 bytes), time= 39.4 seconds
no anomalies in 216 test result(s)
rng=RNG_stdin32, seed=unknown
length= 8 gigabytes (2^33 bytes), time= 78.6 seconds
no anomalies in 229 test result(s)
rng=RNG_stdin32, seed=unknown
length= 16 gigabytes (2^34 bytes), time= 155 seconds
no anomalies in 240 test result(s)
rng=RNG_stdin32, seed=unknown
length= 32 gigabytes (2^35 bytes), time= 305 seconds
no anomalies in 251 test result(s)
rng=RNG_stdin32, seed=unknown
length= 64 gigabytes (2^36 bytes), time= 654 seconds
no anomalies in 263 test result(s)