我在用 C# 编写的应用程序中实现了 xoshiro256** PRNG 算法。这非常适合生成介于 0 和 UInt64.MaxValue 之间的伪随机值,但我遇到了一个问题,我需要一个介于 0 和 1 之间的伪随机双精度浮点值(因此介于 0 和 0.99999999 之间...)。
我知道我可以使用 BitConverter 进行从 ulong 到 double 的“残酷转换”,但我相当确定这会给我的值介于普朗克长度的英里数和立方毫米数之间在宇宙中,它们的负数,以及获得无穷大、负无穷大、负 0 和 NaN 的可能性。对于我正在尝试做的事情来说,这有点雄心勃勃(阅读:完全不合适),所以我希望有某种方式来控制我得到的输出,以便它可以用于处理事情的百分比机会。
我不太了解 IEEE 浮点值的工作原理,无法确定将哪些位放在哪里才能获得我想要的值。我认为(但我错了)我可以将 ulong 右移 12 位(从而将前 52 位变为后 52 位),加上 2^52(将指数的底部位设置为 1) ,然后 BitConverter 将由此产生的混乱变成了双倍。所以像这样:
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;
}
如果这行得通,我希望将 UInt64.MaxValue 传递给它会给我一些非常接近 1 但不完全是的值。我实际上从上面的算法中得到的是一些非常奇怪的小值。
任何线索在这里做什么?在 Mono 6.8.0.105、Raspberry Pi OS 64 位、Raspberry Pi 4 Model B 上使用 C# 4.0。请注意,我对使用“真正的”.NET 不感兴趣。
(相关:How to convert a uint64_t to a double/float with maximum accuracy (C++)? 这没有回答我的问题,因为它通过数学运算将 ulong 转换为 double,我相信这不会'保证结果的统计随机性
关于如何使用 C# 的内置随机特性的答案也没有帮助,因为我想使用我自己的 PRNG 实现。关于如何生成随机 64 位浮点数的答案也没有帮助我。非常具体地说,我想获取一些随机生成的位,并将它们强制转换为落在 0 和 1 之间的浮点数。问题是关于如何进行转换,而不是随机事物的生成。)
我想经过一些实验我明白了。
指数字段使用值 1023 表示 0 - 较大的值是正值,较小的值是负值。因此,指数中的值 1023 允许其余的尾数字段“按原样”解释,即 - 数字中小数点后的所有内容。
这样做会使
UInt64.MaxValue
映射到 2,因为在 IEEE754 浮点数学中有一个隐含的 1.0,然后将所有有效位设置为 1 会产生另一个整数 1(就像 0.99999999... 等于 1 ,所以 0.1111111... 二进制也等于 2,我猜?)。 UInt64.MaxValue / 2
映射到 1.5,UInt64.MaxValue / 4
映射到 1.25.
这很棒,除了所有这些值都有一个额外的 1.0。值得庆幸的是,看起来我可以通过简单地从最终结果中减去 1.0 来摆脱它。乍一看这似乎有效(
UInt64.MaxValue / 4
现在映射到 0.25)。我不确定这是否会导致较小值的错误,但希望不会。
最终的我认为可行的代码是:
public static double DoubleFromRand(ulong rand) {
ulong resultUL = rand >> 12;
resultUL += ((ulong)1023 << 52);
return BitConverter.ToDouble(BitConverter.GetBytes(resultUL), 0) - 1.0;
}
请注意,这种混乱可能无法在所有处理器上移植 - 根据 Wikipedia(我从中获得了有关 IEEE754 浮点数学的所有信息),一些 CPU 在处理浮点数时会以字节顺序做一些非常奇怪的事情,所以根据您的 CPU,这可能会给您带来严重混乱的结果。但到目前为止,在我特定的 64 位 ARM CPU 上,这似乎有效。