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)