我需要一个非常快的伪随机数发生器的一个项目,我一直在工作.到目前为止,我已经实现了xorshift算法,可以产生伪随机u64.但是,我需要将这些u64转换为0和1之间的浮点值.

我主要使用thisthis作为参考.

由于某些原因,我无法接近我想要的行为;这让我感到困惑,因为我使用了与Found here完全相同的方法.尽管我看不到实现有什么不同,但我得到了不同的结果.

    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开始的f64个位模式的尾数中填充小整数得到的,所以你得到的是1.0加上很小的量.不小于0, 1, 2, 3, ...https://www.binaryconvert.com/result_double.html?decimal=049046048048048048048048048048055052053048053056048056表示该数字由f64位模式0x3FF0000002000001表示,该模式仅在尾数中设置了2位.

不过,这看起来像是在以Seed=1开始的xoshio迭代之后得到的位模式.请注意,第一个移位是向右移位,移出唯一留下0的位.下一步是左侧,通向两个设置位.然后,最后一个右移27将它们移出,再次用0进行异运算,保持它们不变.

So your extremely non-random seed of 100 after just one step of xoshiro leads to these non-random mantissas.(和seeds[0]永远不会变成非零;xoshio需要非零种子,因为Shift和XOR永远不能从零创建非零位.)

如果您确实有统一的随机u64值(例如,使用真实的种子,或让生成器对非零种子运行多次迭代),则将它们与1.0中的指数进行OR也会使指数随机化,从而产生巨大的值.但震级总是大于1.0,除了全一指数的NaN(如果尾数为零,则为无穷大).也是一个随机的符号.或者不能清除比特,由于指数偏置,IEEE浮点幅度随着整数比特模式的增加而单调增加.https://en.wikipedia.org/wiki/Double-precision_floating-point_format

如果你屏蔽随机u64,只保留低52位,那么你只需随机化尾数,你就可以很容易地得到[1.0, 2.0)的均匀随机数.正如朱克斯所说,在你链接的问答(100)中,从这个数字中减go 1.0是得到[0.0, 1.0)的标准方法.

指数越接近0.0(指数越小),减go 附近两个数字后尾数的尾数零就越多:指数越小,可表示的值越接近,但我们希望得到均匀的分布.这种方法只有52比特的信息量.这可能很好,但理论上您可以判断指数字段,并使用可变计数的Shift+OR来随机化低尾数位.

Chux的另一种方法(保值转换,如C强制转换)和除法(实际上是乘以一个倒数)不能在没有AVX-512的x86上高效地完成从u64f64的压缩转换.100-它需要多条指令,比替换指数和减法更多.(使用AVX-512,替换指数字段也变得更高效,只需使用一个位掩码覆盖指数+符号字段的单个vpternlogd即可.)


顺便说一句,除非编译器将u64x64 bitshift12优化回标量立即数,否则使用移位计数的SIMD向量看起来效率不高.至少在x86和AArch64上,向量移位可以使用标量计数,所以我希望random_states >> 12将编译为vpsrlq ymm, ymm, 12(使用AVX2),而不需要像vpsrlvq ymm, ymm, ymm这样的具有向量常量的AVX2变量计数移位.(Zen 2上每2周期一次的吞吐量与立即计数班次的每周期一次:https://uops.info/.但在Zen 3和更高版本上,英特尔Skylake和更高版本的吞吐量是相同的.但如果编译器实际上必须从64xu64的数组中加载计数向量,那就太糟糕了).

我假设u64x64::splat(1023) + u64x64::splat(0)是用来玩不同指数场的,但为什么要向量相加呢?只要u64x64::splat((1023 + offset) << 52)就会给出从1.0开始的指数字段,用标量常量进行所有的计算,甚至不会诱使编译器在运行时这样做.

Rust相关问答推荐

何时可以在Rust中退出异步操作?

如何导出 rust 色二进制文件中的符号

两个相关特征的冲突实现错误

零拷贝按步骤引用一段字节

如何将实现多个特征的 struct 传递给接受这些特征为&;mut?

装箱特性如何影响传递给它的参数的生命周期 ?(举一个非常具体的例子)

为什么我需要 to_string 函数的参考?

当我try 使用 SKI 演算中的S I I实现递归时,为什么 Rust 会失败?

使用启用优化的 alloc 会导致非法指令崩溃

在 Rust 中,在第一个空格上分割字符串一次

为什么我的trait 对象类型不匹配?

如何为整数切片定义一个带有额外函数的特性别名?

部署Rust发布二进制文件的先决条件

如何将 &[T] 或 Vec<T> 转换为 Arc<Mutex<[T]>>?

如何限制通用 const 参数中允许的值?

如何将 Rust 字符串转换为 i8(c_char) 数组?

返回引用字符串的future

相互调用的递归异步函数:检测到循环

A 有一个函数,它在 Option<> 类型中时无法编译,但在 Option<> 类型之外会自行编译.为什么?

为什么可以从不可变 struct 的字段中移动?