提问人:BlueOyster 提问时间:11/17/2023 最后编辑:BlueOyster 更新时间:11/18/2023 访问量:103
在 0..1 之间将 u64 转换为 f64
Converting u64 to f64 between 0..1
问:
我需要一个非常快速的伪随机数生成器来处理我一直在做的项目。到目前为止,我已经实现了 xorshift 算法,可以生成伪随机 u64。但是,我需要将这些 u64 转换为 0 到 1 之间的浮点值。
我主要使用这个和这个作为参考。
出于某种原因,我无法接近我想要的行为;这让我感到困惑,因为我使用了与此处完全相同的方法。尽管我没有看到实现的差异,但我得到了不同的结果。
let seeds: [u64; 64] = core::array::from_fn(|i| i as u64);
let bitshift12 = u64x64::splat(12);
let bitshift25 = u64x64::splat(25);
let bitshift27 = u64x64::splat(27);
let bitshift52 = u64x64::splat(52);
let mut random_states = Simd::from(seeds);
random_states ^= random_states >> bitshift12;
random_states ^= random_states << bitshift25;
random_states ^= random_states >> bitshift27;
random_states = random_states | ((u64x64::splat(1023) + u64x64::splat(0)) << bitshift52);
let mut generated = Simd::<f64, 64>::from_bits(random_states);
println!("{:?}", generated);
输出:
[1.0, 1.0000000074505808, 1.0000000149011616, 1.0000000223517425, 1.0000000298023235, 1.0000000372529039, ...]
显然,我没有正确地做某事,因为最后几位小数是根据需要“随机”的。为什么我不能正确地向上移动这些?
如果有人指出我的错误,将不胜感激。
答:
这个序列看起来就像你用 1.0 的指数将小整数塞进位模式的尾数中得到的,所以你得到的是 1.0 加上微小的数量。没有那么小;https://www.binaryconvert.com/result_double.html?decimal=049046048048048048048048048048055052053048053056048056 显示该数字由位模式表示,该位模式在尾数中仅设置了 2 位。f64
0, 1, 2, 3, ...
f64
0x3FF0000002000001
不过,这看起来像您在以 seed = 1 开头的 xoshiro 迭代后获得的位模式。请注意,第一个移位是向右移,移出唯一留下 0 的位。下一步是左边,导致两个设置位。然后,最后的右移 27 将它们都移出,再次以 0 进行 xoping,使它们保持不变。
因此,在 xoshiro 的一步之后,你极其非随机的种子种子 [i] = i
会导致这些非随机尾数。(并且永远不会变成非零;xoshiro 需要非零种子,因为 shift 和 xor 永远不能从零创建非零位。seeds[0]
如果您确实有统一的随机值(例如,使用真实种子,或让生成器对非零种子运行多次迭代),则使用指数 from 进行 ORing 也会使指数随机化,从而导致巨大的值。但大小总是大于 1.0,除了具有全 1 指数的 NaN(如果尾数为零,则为无穷大)。也是一个随机标志。OR无法清除位,并且由于指数偏差,IEEE浮点幅度随着整数位模式的增加而单调增加。https://en.wikipedia.org/wiki/Double-precision_floating-point_formatu64
1.0
如果你屏蔽随机数以仅保留低 52 位,所以你只是随机化尾数,你可以很容易地得到统一的随机数。正如 Chux 所说,在你链接的问答中(如何将一些伪随机位转换为 0 到 1 之间的统计随机浮点值?),从中减去是获得数字的标准方法。u64
[1.0, 2.0)
1.0
[0.0, 1.0)
越接近 0.0(指数越小),尾数在减去两个邻近数字后尾数的尾随零就越多:指数越小,可表示值越接近,但我们想要均匀分布。这种方法只有 52 位熵。这可能没问题,但理论上您可以检查指数字段并使用变量计数移位 + OR 来随机化低尾数位。
Chux 的另一种方法(保值转换,如 C 强制转换)然后除法(实际上是乘以逆法)无法在没有 AVX-512 的情况下在 x86 上有效地完成从 到 的打包转换。如何使用 SSE/AVX 高效地执行 double/int64 转换?- 它需要多个指令,而不是替换指数和减法。(使用 AVX-512,替换指数字段也变得更加高效,只需使用覆盖指数+符号字段的位掩码即可。u64
f64
vpternlogd
顺便说一句,除非编译器立即优化回标量,否则使用移位计数的 SIMD 向量看起来效率不高。至少在 x86 和 AArch64 上,向量移位可以使用标量计数,所以我希望它能编译为(使用 AVX2),而不需要带有向量常量的 AVX2 变量计数移位,例如 .(Zen 2 每 2 个周期一次吞吐量,而即时计数班次每周期一次:https://uops.info/。但在 Zen 3 及更高版本以及 Intel Skylake 及更高版本上,吞吐量是相同的。但是,如果编译器实际上必须从 64x 的数组中加载计数向量,那就太糟糕了)。u64x64 bitshift12
random_states >> 12
vpsrlq ymm, ymm, 12
vpsrlvq ymm, ymm, ymm
u64
我假设那里有不同的指数场,但为什么要向量加法?只是会给你 1.0 的指数字段,使用标量常量进行所有数学运算,甚至不会诱使编译器在运行时这样做。u64x64::splat(1023) + u64x64::splat(0)
u64x64::splat((1023 + offset) << 52)
评论
上一个:重新定义一个大规模稀疏刚度矩阵
评论
from_bits
from_bits