1
Multiplying random numbers together generates bias. Don’t do it.
Your best hope (and the
correct way) is to assume that the distribution of ones and zeros in the resulting number is random and simply chain the two with a bit-shift. (Alas, as already noted,
rand()
often fails this litmus.)
2
Tossing random values requires an RNG, so you are left with the conundrum of using yet another RNG (or, just as bad, using the same RNG). And, as already noted, chaining LCGs is still predictable, even if more difficult to decipher. (In large part because the observer knows how it works! But even if he didn’t, it wouldn’t take much for modern computers to break such a system.)
Worse yet, you again introduce bias. Remember, bias in an LCG is eliminated by balancing the parameters between y-intercept, slope, and modulus (and proving it with a
whole lot of testing). Tossing values adds bias by removing some of the possible results from an output cycle!
I believe I mentioned this earlier. By ignoring some pigeons we break the uniformity of the distribution. The reason that it worked before was that we knew we did not need the ignored pigeons — they were
expressly ignored. Even so, they still appeared with the same likelihood as the pigeons we wanted.
But when we chain the LCGs we are suddenly ignoring pigeons without having specifically excluded them. Sure, it may be possible that those particular pigeons may appear in another cycle, or we could consider the cycle to be some longer combination of the two RNG cycles, but either way the distribution of outputs is no longer uniform — I can no longer expect every pigeon to appear with equal probability.
3
The C crowd seems to hate C++ with a frothing, animalistic passion. <random> provides a fabulous collection of basic PRNGs all easy and ready to use. But no, “you’ll pry rand() from my cold, dead fingers”, and “oh, and your fancy stuff isn’t
good enough, because it doesn’t also do X, Y, and Z”.
It doesn’t help that the C++ committee, at least for a while, had too many tight neckties to do something like mandate a proper CSPRNG instead of saying, "We could, uh, if you like, I mean... if it isn’t too much of a bother... make a... hey, you can just make the
interface available, so...”
...which lead to the bullcrap GCC pulled with the
1 2 3 4 5 6 7 8 9
|
struct random_device
{
//int getRandomNumber()
value_type operator () ()
{
return 4; // chosen by fair dice roll
// guaranteed to be random
}
};
|
Someone who thought they understood Randall Munroe’s joke was clearly behind the cueball of reality, both in terms of what Randall was expressing and in terms of what the consequence would be to people just wanting easy access to a CSPRNG.
4
As far as a readable choice of which PRNG to use...
it doesn’t matter.
Seriously, just pick one. Even very awesome PRNGs are typically only a few lines of code long. MT is kind of unique for its class because of its size and complexity.
If you are working in a specialized-enough context that it makes any difference, you should should already know enough about that context and how PRNGs work to make a good choice. (Or you should learn, because working in that context requires that knowledge.)
5
AFAIK,
std::random_device
is supposed to work properly on all modern compilers, but I still hate the thing. Hence my
shameless plug little CSPRNG library:
https://github.com/Duthomhas/CSPRNG
I agree that beginners should be just given working
<random>
code. It really is as easy as:
1 2 3 4 5 6
|
#include <random>
int random( int min, int max )
{
return std::uniform_int_distribution( min, max )( std::random_device{}() );
}
|
Assuming your
std::random_device
works because you have a sufficiently modern compiler (droll shove: make sure you are using a sufficiently modern compiler), that will always work.