Potential Double Value Generation Error in Generators
Apologies for the flurry of issue reports.
I noticed in AbstractGenerator that there are some constants defined that are used in the provided generators for the generation of doubles:
protected const double IntToDoubleMultiplier = 1.0 / (int.MaxValue + 1.0);
protected const double UIntToDoubleMultiplier = 1.0 / (uint.MaxValue + 1.0);
protected const double ULongToDoubleMultiplier = 1.0 / (ulong.MaxValue + 1.0);
As their name implies, these are multiplied by a value of the corresponding types to produce a random double in NextDouble functions. I only see IntToDoubleMultiplier used in the existing generators, but I assume the designed purpose of the other two constants is similar.
I believe, in at least the case of IntToDoubleMultiplier and UIntToDoubleMultiplier, this method of generating a double artificially restricts the range of doubles that can be generated. Let me use the code from XorShift128Generator as an example:
// In XorShift128Generator.NextDouble
return (int)((_x + _y) << ULongToIntShift >> ULongToIntShift) * IntToDoubleMultiplier;
Here, we xor two ulongs, shift the result so that the upper 32-bits as well as the sign-bit are zeroed, cast to an int, then multiply the result (which should be a positive integer) by the double constant. By definition, a positive integer can have only 2^31 possible values; and thus since the value it is being multiplied against is a constant, the resulting double can have only 2^31 distinct values. There are certainly more than 2^31 possible double values in range [0.0, 1.0) within C#'s double precision; C# uses 52 mantissa bits. So it would appear that there is a large number of doubles that this generator can never produce. Unless I'm missing something, this would appear to be a significant issue for statistical distribution, particularly when this range is extended via functions such as NextDouble(maxValue) and Next(maxValue).
Although I used code from XorShift128Generator in this example, the other generators (with the exception of StandardGenerator) appear to be doing similar things, that result in either 2^31 or 2^32 possible values. Similarly, although I didn't see the other two double constants used in existing generators, the UIntToDoubleMultiplier would appear to have a similar issue.
ULongToDoubleMultipliershould be capable of producing a larger variance of doubles as far as I can see right now, although offhand I'm not sure it would produce a desirably even distribution.
I have seen alternative methods used to generate doubles in other implementations, but beyond that I'm afraid I don't have a direct fix in mind as I have limited experience implementing RNGs.