我正在用 Excel VBA 编写实验室数据处理脚本。 这个想法是使用 RtlGenRandom 获取改进的随机数。 困难在于 RtlGenRandom 以字节方式为字节数组播种。 因此,为了减少时间并保持“随机”分布,我想应用按位掩码将数字减少到 [0 到 1] 值。 下面是一个助记符解释,无论具体实现如何,我都想做什么:
longlong buffer
longlong mask
double rand_val
RtlGenRandom(buffer,8)
buffer=not(buffer or mask)
rand_val=typeless_copy(rand_val, buffer)
所以我有点迷失了那个掩码对于所述瓣膜截断的价值应该是多少。 按位,或 0xHex.
我想,应该是0xC000 0000 0000 0000,但是不对劲
你似乎忘记了 IEEE-754 浮点数的两个细节:
当人类用科学计数法写数字时(例如 3.4567e89),粗体部分称为 significand 或 mantissa。从表面上讲浮点数,我们常说它们存储指数和尾数;但严格来说这是错误的。除了指数,它们只存储符号和尾数的 fraction 部分。由于尾数应始终以单个非零数字开头,而浮点数使用二进制系统,因此浪费一位应该始终为
1
. 是没有意义的
对于大多数情况,我们可以把float想象成一个表单文档,比如...
fraction exponent>0
(-1)^[] x 1.[][][][] x 2^([][][] - bias)
sign
...如果你可以填写
[]
框,但不能更改尾数开头的1.
。
因此,您的方法从区间 [0, 2).
生成数字由于指数表示,接近于零的浮点数较多。例如。对于 64 位浮点数(又名双精度数),区间 [0.5, 1.0) 包含 2^52 个浮点数,但区间 (0, 0.5) 包含 2^(10+52) - 2 个浮点数。因此,您的数字 way 更可能接近 0 而不是接近 1.
为了解决这两个问题,让我们退一步。浮点数可以非常接近地表示 10^-17 = 0.00000000000000001,但不能表示 0.5 + 10^-17 = 0.50000000000000001。如果我们生成前者的数字而不是后者的数字,那将是不公平的。因此,我们应该将区间[0, 1]平均分割成都可以精确表示的数。区间 [0, 1) 中最大的 ULP 是 2^-53 = ca. 10^-16 = 0.0000000000000001.
区间[0, 1)包含2^53个这样的数(
0
, 1 * 2^-53
, 2 * 2^-53
, ..., (2^53 - 1) * 2^-53
)。因此我们生成 2^53 位整数并将其除以 2^53 以生成介于 0(含)和 1(不含)之间的随机数。
' pseudo code for generating one random Double
' from [0,1) with uniform distribution
LongLong buffer
RtlGenRandom(buffer, 8)
buffer = buffer and 0x1FFFFFFFFFFFFF ' clamp to 53 bits
Double rand = buffer / CDbl(0x20000000000000)
如果您确实也需要能够生成 1,则有 2^53 + 1 个可能的数字,在保持均匀分布的同时不能再轻易地限制这些数字。我相信拒绝抽样将是您案例中最简单(甚至可能最快)的解决方案。
' pseudo code for generating one random Double
' from [0,1] with uniform distribution
LongLong buffer
Double rand
Do
RtlGenRandom(buffer, 8)
buffer = buffer and 0x3FFFFFFFFFFFFF ' clamp to 54 bits
Loop While buffer > 0x20000000000000
If buffer == 0x20000000000000 Then
rand = 1.0
Else
rand = buffer / CDbl(0x20000000000000)
End If
为了加快这两种方法的速度,最好只在非常大的缓冲区上调用一次
RtlGenRandom
。如果您知道需要 N 个随机数,则可以为第一种方法生成 8*N 个随机字节,或者为第二种方法生成 8*N*1.5(平均)个随机字节。由于每个 LongLong 只需要 7 个随机字节,您甚至可以将总缓冲区大小减少 1/8。