原文链接如下:
http://www.cs.ucl.ac.uk/staff/d.jones/GoodPracticeRNG.pdf
我只是进行了简单的翻译,目的是进一步了解KISS算法。
平时,当code中需要随机数的时候,我们往往求助于系统的函数,例如c语言的rand()函数。事实上,这些函数本省存在缺陷,它们不能够提供很好的随机数。当然在code中,不可能提供完全随机的数字,最好的状态只能是伪随机的数字。如果code中需要随机数的时候,我需要注意一下几点:
2. 采用好的RNG算法,并且把它实现到你的code中。
3. 恰当地给RNG提供种子(seed)。
一. 不要采用系统提供的函数
以下给出的函数均有缺陷:
Perl –- rand
Python – random
Java.Util.Random
C – rand, random
Matlab -- rand
二. 采用好的RNG算法,并且把它实现到你的code中
从软件的角度来看,它不可能实现完全随机的数字。因为这些数字是由算法产生的,而算法的定义是非随机。例如C语言经典的rand函数会交替的产生奇数和偶数,这本省就不是随机。可以参看这个链接。http://www.iro.umontreal.ca/~lecuyer/myftp/papers/testu01.pdf
一个好的RNG算法能通过如下的test case。http://www.phy.duke.edu/~rgb/General/dieharder.php
目前来看,KISS (keep it simple and stupid)算法能够通过上述的测试。
算法的基本表述如下:
static unsigned int x = 123456789,y = 362436000,z = 521288629,c = 7654321;
/* Seed variables */
unsigned int KISS() {
unsigned long long t, a = 698769069ULL;
x = 69069*x+12345;
y ^= (y<<13); y ^= (y>>17); y ^= (y<<5); /* y must never be set to zero! */
t = a*z+c; c = (t>>32); /* Also avoid setting z=c=0! */
return x+y+(z=t);
}
这个短小精悍的算法组合了3个不同的随机数发生器,其周期大约是10^37。该算法的种子x,y和z需要设置成大的数值。需要注意的是,上述的code采用的数据类型必须是unsigned long long。
基于上述算法。许多类似KISS的算法被提出来了,我提出的KISS算法如下:
/* Public domain code for JKISS RNG */
static unsigned int x = 123456789,y = 987654321,z = 43219876,c = 6543217;
/* Seed variables */
unsigned int JKISS()
{ unsigned long long t;
x = 314527869 * x + 1234567;
y ^= y << 5; y ^= y >> 7; y ^= y << 22;
t = 4294584393ULL * z + c; c = t >> 32; z = t;
return x + y + z;
}
算法改进之处,包含了三个发生器。
当然,对于32位整数,也有对应的算法,Marsaglia提出的add-with-carry避免了乘法运算。算法的周期是2^121.
/* Implementation of a 32-bit KISS generator which uses no multiply instructions */
static unsigned int x=123456789,y=234567891,z=345678912,w=456789123,c=0;
unsigned int JKISS32()
{
int t;
y ^= (y<<5); y ^= (y>>7); y ^= (y<<22);
t = z+w+c; z = w; c = t < 0; w = t&2147483647;
x += 1411392427;
return x + y + w;
}
三. 恰当地给RNG提供种子(seed)
如若产生的数字具有很好的随机性,条件在于种子。平时,我们采用time的函数提供种子。问题在于time提供的种子值为32位,从而导致出现相同种子的概率变高。
一种简单的想法是组合出seed = (gettimeofday()+getpid()+gethostid()),来避免在两台不同的机器上产生一样的seed。
另外一种方法,从/dev/urandom中返回32位seed。
unsigned int devrand(void)
{
int fn;
unsigned int r;
fn = open("/dev/urandom", O_RDONLY);
if (fn == -1)
exit(-1); /* Failed! */
if (read(fn, &r, 4) != 4)
exit(-1); /* Failed! */
close(fn);
return r;
}
/* Initialise KISS generator using /dev/urandom */
void init_KISS()
{
x = devrand();
while (!(y = devrand())); /* y must not be zero! */
z = devrand();
/* We don’t really need to set c as well but let's anyway… */
/* NOTE: offset c by 1 to avoid z=c=0 */
c = devrand() % 698769068 + 1; /* Should be less than 698769069 */
}
如果想了解的更多。还是看看原文吧。。。