I have an implementation of the xoshiro256** PRNG algorithm in an application I'm writing in C#. This works great for generating pseudorandom values between 0 and UInt64.MaxValue, but I've hit a spot where I need a pseudorandom double-precision floating-point value between 0 inclusive and 1 exclusive (so between 0 and 0.99999999...).
I know I can use BitConverter to do a "brutal conversion" from a ulong to a double, but I'm fairly certain this will give me values that are somewhere between the number of miles in a planck length and the number of cubic millimeters in the universe, and their negatives, along with the possibility of getting infinity, negative infinity, negative 0, and NaN. That's a bit ambitious (read: entirely unsuitable) for what I'm trying to do, so I'm hoping for some way of controlling the output I get so that it can be used for dealing with percent chances of things.
I don't know enough about how IEEE floating-point values work to know for sure what bits to put where in order to get the values I'm going for. I think (and am wrong) that I can just right-shift a ulong by 12 bits (thus turning the top 52 bits into the bottom 52 bits), add 2^52 (setting the bottom bit of the exponent to 1), and then BitConverter the resulting mess into a double. So something like this:
public static double DoubleFromRand(ulong rand) {
ulong resultUL = rand >> 12;
resultUL += ULongPow(2UL, 52UL); // adapted from https://stackoverflow.com/a/383596/19474638
return BitConverter.ToDouble(BitConverter.GetBytes(resultUL), 0);
}
public static ulong ULongPow(ulong x, ulong pow) {
ulong ret = 1UL;
while (pow != 0UL) {
if ((pow & 1UL) == 1UL) {
ret *= x;
}
x *= x;
pow >>= 1;
}
return ret;
}
If this worked, I would expect that passing UInt64.MaxValue to this would give me some value very close to 1 but not quite there. What I actually get from the above algorithm is some really weird small value.
Any clue what to do here? Using C# 4.0 on Mono 6.8.0.105, on Raspberry Pi OS 64-bit, on a Raspberry Pi 4 Model B. Note that I am not interested in using "real" .NET.
(Related: How to convert a uint64_t to a double/float between 0 and 1 with maximum accuracy (C++)? This doesn't answer my question as it converts a ulong to a double with mathematical operations, which I believe doesn't guarantee the statistical randomness of the result.
Also answers relating on how to use the built-in random features of C# are not helpful as I want to use my own implementation of a PRNG. I also am not helped by answers about how to generate random 64-bit floats. I want, very specifically, to take some quantity of randomly generated bits and force-convert them into a float that will land between 0 and 1. The question is about how to do the conversion, not the generation of random things.)