Jules, it’s fun to look through your clean code. I’m just starting out by looking at some of the math routines I’m familiar with.
There’s a problem with Random::nextBool(), which is pulling samples from the lower entropy bits of the random pool. This means that the output has an unnecessarily short period of 2^17. Here’s an example which shows that with a seed of 123… the first bool is false and it’s repeated after a cycle of 2^17.
Random r(123);
for (unsigned long i=0; i<0x00400000; ++i) {
bool z(r.nextBool());
if ((i&0x1FFFF)==0) std::cout << std::hex << i << " " << z << std::endl;
}
output:
0 0
20000 0
40000 0
60000 0
80000 0
a0000 0
c0000 0
e0000 0
100000 0
120000 0
140000 0
160000 0
180000 0
1a0000 0
1c0000 0
1e0000 0
200000 0
220000 0
240000 0
260000 0
280000 0
2a0000 0
2c0000 0
2e0000 0
Not a critical problem, but the fix is just changing one constant:
bool Random::nextBool() throw()
{
return (nextInt() & 0x80000000) != 0;
}
Also, even more minor, but the nextInt, nextDouble, nextFloat are also throwing out entropy, though just one bit. You can double the period of the generator by changing the references from:
to
return ((uint32)nextInt()) / (double) 0xffffffff;
with the side benefit of getting an extra bit of range at the tail decimal of the double.
And finally, the nextInt(const int maxValue) method has a similar problem as the nextBool() call. The easy way to fix this is below, but a little inelegant since it uses floating point:
int Random::nextInt (const int maxValue) throw()
{
jassert (maxValue > 0);
return (int)floor(nextDouble()*maxValue);
}
All of these problems are disguised by the >>16 shift in the core Random::nextInt() call. Changing the LCG to be 64 bit (instead of 48 bit like now) would help a lot, with no extra storage or speed penalty.