我正在尝试实现rand_r
接口的可容忍质量版本,该接口具有令人遗憾的接口要求,即其整个状态存储在unsigned
类型的单个对象中,对于我而言目的意味着正好32位。另外,我需要它的输出范围为[0,2³¹-1]
。标准解决方案是使用LCG并丢弃低位(具有最短周期),但这仍然会为接下来的几位留下非常差的周期。
我最初的想法是使用两次或三次LCG迭代来产生输出的高/低或高/中/低位。但是,这种方法并不能保持无偏差的分布;而不是每个输出值具有相同的频率,许多输出值多次发生,而有些输出值根本不会发生。
由于只有32位状态,PRNG的周期以2 32为界,并且为了无偏差,PRNG必须输出每个值,如果它具有完整周期,则必须输出两次,如果有期间2³¹。较短的时期不能没有偏见。
是否有任何知名的PRNG算法符合这些标准?
答案 0 :(得分:2)
提供非常高质量的一个好(但可能不是最快)的可能性是在block cipher中使用32位CTR mode。基本上,您的RNG状态只是一个32位计数器,对于每个RNG呼叫增加1,并且输出将是使用具有一些任意选择的固定密钥的分组密码对该计数器值的加密。为了获得额外的随机性,您甚至可以提供(非标准)功能,让用户设置自定义密钥。
通常使用的32位块密码并不多,因为这种短块大小会给加密使用带来问题。 (基本上,birthday paradox允许您在仅仅约2 16 = 65536输出之后,以及在2 之后,以不可忽略的概率区分这种密码的输出与随机函数。 32 输出非随机性显然变得确定。)但是,一些具有可调块大小的密码,例如 XXTEA 或HPC,会让你走低至32位,应该适合您的目的。
(编辑:我的不好,XXTEA只降到64位。但是,正如评论中的CodesInChaos所建议的那样,Skip32可能是另一种选择。或者你可以建立自己的32位Feistel cipher。)
CTR模式构造保证RNG将具有2个 32 输出的完整周期,而(非破坏)块密码的标准安全声明基本上是在计算上不可行的将它们的输出与32位整数集的random permutation区分开来。 (当然,如上所述,这种置换仍然很容易与采用32位值的random function区分开来。)
使用CTR模式还提供了一些您可能会觉得方便的额外功能(即使它们不是您正在开发的官方API的一部分),例如能够快速搜索到RNG输出流中的任何点通过在州内增加或减去。
另一方面,您可能不想要通过将内部状态设置为种子值来遵循种子RNG的常规做法,因为这会导致生成的输出流附近的种子非常相似(基本上只是相同的流因种子的差异而移动)。避免此问题的一种方法是在播种过程中添加额外的加密步骤,即使用密码加密种子并将内部计数器值设置为等于结果。
答案 1 :(得分:2)
阐述我的评论......
计数器模式下的分组密码以大致以下形式给出一个生成器(除了使用更大的数据类型):
uint32_t state = 0;
uint32_t rand()
{
state = next(state);
return temper(state);
}
由于没有指定加密安全性(并且在32位中它或多或少是徒劳的),一个更简单的临时调节功能应该可以解决问题。
一种方法是next()
函数很简单(例如,return state + 1;
)和temper()
通过复杂来补偿(如在分组密码中)。
更平衡的方法是在next()
中实施LCG,因为我们知道它也会以随机(ish)顺序访问所有可能的状态,并找到temper()
的实现足够的工作来弥补LCG的其余问题。
Mersenne Twister在其输出中包含这样的调节功能。这可能是合适的。此外,this question要求满足要求的操作。
我有一个最喜欢的,就是将该位反转,然后将它乘以一些常数(奇数)。如果位反转不是架构上的本机操作,那可能会过于复杂。
答案 2 :(得分:1)
32位最大周期Galois LFSR可能适合您。尝试:
r = (r >> 1) ^ (-(r & 1) & 0x80200003);
LFSR的一个问题是你不能产生值0.所以这一个的范围是1到2 ^ 32-1。您可能想要调整输出,或者坚持使用良好的LCG。
答案 3 :(得分:1)
除了使用Lehmer MCG之外,您还可以使用以下几种方法:
Xorshift的32位变体在32位状态下的保证周期为2 32 −1 :
uint32_t state;
uint32_t xorshift32(void) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}
这是2003年的原始32位建议(请参见论文)。根据您对“体面质量”的定义,应该没问题。但是,它没有通过Diehard的二进制等级测试和SmallCrush的5/10测试。
具有更好的混合和常量(通过SmallCrush和Crush)的备用版本:
uint32_t xorshift32a(void) {
int s = __builtin_bswap32(state * 1597334677);
state ^= state << 25;
state ^= state >> 7;
state ^= state << 2;
return state + s;
}
还有Mulberry32,其周期完全为 2 32 :
uint32_t mulberry32(void) {
uint32_t z = state += 0x6D2B79F5;
z = (z ^ z >> 15) * (1 | z);
z ^= z + (z ^ z >> 7) * (61 | z);
return z ^ z >> 14;
}
这可能是您最好的选择。很好/很快。作者说:“它通过了gjrand的13项测试,没有失败,总P值 在4GB的内存中为0.984(其中1为完美,而0.1或更小则为失败) 生成的数据。这是整个周期的四分之一。”似乎比SplitMix32有所改善。
“ SplitMix32”,取自xxHash / MurmurHash3(Weyl序列):
uint32_t splitmix32(void) {
uint32_t z = state += 0x9e3779b9;
z ^= z >> 15; // 16 for murmur3
z *= 0x85ebca6b;
z ^= z >> 13;
z *= 0xc2b2ae3d; // 0xc2b2ae35 for murmur3
return z ^= z >> 16;
}
也有一个完整的周期2 32 。这里的质量可能值得怀疑,但是它的64位老大哥有一个lot of fans(通过BigCrush)。因此,一般结构值得一看。