利用数学的方法产生随机数的优点具有速度快、可对模拟问题进行复算检查、具有较好的统计特性。通过检验符合均匀性、随机性、独立性就可以当作真正的随机数。
经典的随机数产生方法为是线性同余法,即Linear Congruence Generator (LCG),由Lehmer于1951年提出。
同余:对于两个整数 A、B ,如果它们同时除以一个自然数M的余数相同,就说 A、B 对于模M同余, A≡BmodM 。
线性同余发生器是用不连续分段线性方程计算产生伪随机数序列的算法。LCG背后的理论比较容易理解,易于实现。由于计算机产生的随机数都是伪随机数,后面就直接用随机数代替伪随机数的叫法。
LCG定义如下:
生成器不断往复运行,将会产生一序列 <m 的随机数,如果 a,c,m 取值合适,序列最大周期将达到 m 。这种情况下,序列中所有可能的整数都在某点固定出现。当 c=0 时候,LCG就变换成了乘法同余发生器,multiplicative congruential generator (MCG), c≠0 时叫做混合同余发生器,mixed congruential generator,MCG。
这里就用Wiki中的列子说明LCG是如何工作的:
当 m=9,a=2,c=0,seed=1 时,得:
X0=seed=1
X2=(a∗X1+c)modm=(2×1+0)%9=2
X3=(a∗X2+c)modm=(2×2+0)%9=4
X4=(a∗X3+c)modm=(2×4+0)%9=8
X5=(a∗X4+c)modm=(2×8+0)%9=7
X6=(a∗X5+c)modm=(2×7+0)%9=5
X7=(a∗X6+c)modm=(2×5+0)%9=1
一个周期结束,如果再继续的话,随机序列将会以周期6重复出现。改变 a,c,m 的值,LCG的周期性也发生了变换。
通常MCG最大周期位 m ,往往由于 a,c,m 的选择使得周期无法达到 m 。若想获得最大周期的随机数序列,需要同时满足下述条件:
这三个条件被称为Hull-Dobell定理,虽然LCG能够产生可以通过随机性的正式测试的伪随机数,但对参数 c,m和a 的选择非常敏感。
历史上,糟糕的因子选择导致了LCG的实施效率低下。在连续调用时,也无法避免序列的相关性。如果我们要自己设计LCG,选择不合适的 a,c,m 往往带来糟糕的结果,却不自知。
历史上有这样的例子RANDU,其中, a=65539,m=231 ,在IBM大型机上使用了许多年,并被广泛应用在其他系统上,现在出现了很多问题,由于使用了糟糕的因子。
ANSI C库中的LCG实现具有一定的缺陷,其中一定数量的实施方法不能很好的工作,这个责任应该由ANSI C委员会及其实施者承担。——“Numerical Recipes”。该评判有明确出处,非笔者臆断!
由ANSI C标准规定rand()返回一个整数值,RAND_MAX通常不是很大。ANSI C标准要求RAND_MAX最大32767。
打开头文件stdlib.h,可以看到定义的十六进制RAND_MAX,使用博客的方法转换为十进制为32767。
/* Maximum value that can be returned by the rand function. */
#define RAND_MAX 0x7fff
打开rand.h头文件查看随机数生成器代码,产生随机数的过程即线性同余法,代码简单、结构清楚清楚。
/***
*rand.c - random number generator
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* defines rand(), srand() - random number generator
*
*******************************************************************************/
#include
#include
#include
#include
/***
*void srand(seed) - seed the random number generator
*
*Purpose:
* Seeds the random number generator with the int given. Adapted from the
* BASIC random number generator.
*
*Entry:
* unsigned seed - seed to seed rand # generator with
*
*Exit:
* None.
*
*Exceptions:
*
*******************************************************************************/
void __cdecl srand (
unsigned int seed
)
{
_getptd()->_holdrand = (unsigned long)seed;
}
/***
*int rand() - returns a random number
*
*Purpose:
* returns a pseudo-random number 0 through 32767.
*
*Entry:
* None.
*
*Exit:
* Returns a pseudo-random number 0 through 32767.
*
*Exceptions:
*
*******************************************************************************/
int __cdecl rand (
void
)
{
_ptiddata ptd = _getptd();
return( ((ptd->_holdrand = ptd->_holdrand * 214013L
+ 2531011L) >> 16) & 0x7fff );
}
调用方式:
#include
#include
#include
using namespace std;
void main(){
//srand((unsigned)time(NULL));
srand(11);
for (int i = 0; i < 10; i++)
cout << rand() << '\t';
cout << endl;
}
均匀分布随机数即等概率随机数,如:扔骰子、掷硬币。使用《数值方法》中的LCG生成服从 [0,1] 分布的均匀分布。
#include
#define IA 16807
#define IM 2147483647
#define AM (1.0/IM)
#define IQ 127773
#define IR 2836
#define NTAB 32
#define NDIV (1+(IM-1)/NTAB)
#define EPS 1.2e-7
#define RNMX (1.0-EPS)
float ran1(long *idum)
{
int j;
long k;
static long iy = 0;
static long iv[NTAB];
float temp;
if (*idum <= 0 || !iy) {
if (-(*idum) < 1)
*idum = 1;
else
*idum = -(*idum);
for (j = NTAB + 7; j >= 0; j--) {
k = (*idum) / IQ;
*idum = IA*(*idum - k*IQ) - IR*k;
if (*idum < 0)
*idum += IM;
if (j < NTAB)
iv[j] = *idum;
}
iy = iv[0];
}
k = (*idum) / IQ;
*idum = IA*(*idum - k*IQ) - IR*k;
if(*idum < 0)
*idum += IM;
j = iy / NDIV;
iy = iv[j];
iv[j] = *idum;
if ((temp = AM*iy) > RNMX)
return RNMX;
else
return temp;
}
void main()
{
float ran1(long *idum);
long seed = 10;
for (int i = 0; i < 10;++i)
{
std::cout << ran1(&seed) << std::endl;
}
}
现实生活中更多的随机现象是服从正态分布的,如:20岁的成年人体重分布。正太分布随机数可以通过Box-Muller方法从均匀分布随机数转换为标准正态分布随机数。下面代码是《数值方法》中完整代码。
#include
#include
#define IA 16807
#define IM 2147483647
#define AM (1.0/IM)
#define IQ 127773
#define IR 2836
#define NTAB 32
#define NDIV (1+(IM-1)/NTAB)
#define EPS 1.2e-7
#define RNMX (1.0-EPS)
float ran1(long *idum)
{
int j;
long k;
static long iy = 0;
static long iv[NTAB];
float temp;
if (*idum <= 0 || !iy) {
if (-(*idum) < 1)
*idum = 1;
else
*idum = -(*idum);
for (j = NTAB + 7; j >= 0; j--) {
k = (*idum) / IQ;
*idum = IA*(*idum - k*IQ) - IR*k;
if (*idum < 0)
*idum += IM;
if (j < NTAB)
iv[j] = *idum;
}
iy = iv[0];
}
k = (*idum) / IQ;
*idum = IA*(*idum - k*IQ) - IR*k;
if(*idum < 0)
*idum += IM;
j = iy / NDIV;
iy = iv[j];
iv[j] = *idum;
if ((temp = AM*iy) > RNMX)
return RNMX;
else
return temp;
}
float gasdev(long *idum){
float ran1(long *idum);
static int iset = 0;
static float gset;
float fac, rsq, v1, v2;
if (*idum < 0)
iset = 0;
if (iset == 0) {
do {
v1 = 2.0*ran1(idum) - 1.0;
v2 = 2.0*ran1(idum) - 1.0;
rsq = v1*v1 + v2*v2;
} while (rsq >= 1.0 || rsq == 0.0);
fac = sqrt(-2.0*log(rsq) / rsq);
gset = v1*fac;
iset = 1;
return v2*fac;
}
else {
iset = 0;
return gset;
}
}
void main()
{
float ran1(long *idum);
long seed = 10;
for (int i = 0; i < 10;++i)
{
std::cout << gasdev(&seed) << std::endl;
}
}
这里详细叙述了LCG生成均匀分布随机数的方法,分析了获得最大周期的条件。随机序列具体服从的分布,可以通过卡方检验得到。由于ANSI C中的LCG随机数的缺陷,vs下编程不建议使用系统自带的随机数生成器。如果只是单独的使用LCG产生随机数,也不建议自己实现LCG(即使实现过程很简单)。最后建议使用《数值方法》中的LCG随机数生成器。
参考
https://en.wikipedia.org/wiki/Linear_congruential_generator
https://en.wikipedia.org/wiki/RANDU
http://baike.baidu.com/link?url=0_DsXFh4U0_s5s9mUrrEAwuW_y1ol3a473KOb8eGxNwUH7C5g_97Pc825aX2bOFqUjDYl-rM_Su-rdiDncwG_dwRyxmjjENIvlmEpyXEZJm
Press, W. H. (2007). Numerical recipes 3rd edition: The art of scientific computing. Cambridge university press.
https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
http://www.cnblogs.com/zztt/p/4025207.html
http://www.cnblogs.com/afarmer/archive/2011/05/01/2033715.html