标题：伪随机数
生成方法
一般地，伪随机数的生成方法主要有以下3种：  
（1） 直接法（Direct Method），根据分布函数的物理意义生成。缺点是仅适用于某些具有特殊分布的随机数，如二项式分布、泊松分布。
（2） 逆转法（Inversion Method），假设U服从[0，1]区间上的均匀分布，令X=F-1（U），则X的累计分布函数（CDF）为F。该方法原理简单、编程方便、适用性广。    
（3）接受拒绝法（Acceptance-Rejection Method）：假设希望生成的随机数的概率密度函数（PDF）为f，则首先找到一个PDF为g的随机数发生器与常数c，使得f  伪随机数发生器（x）≤cg（x），然后根据接收拒绝算法求解。由于算法平均运算c次才能得到一个希望生成的随机数，因此c的取值必须尽可能小。显然，该算法的缺点是较难确定g与c。   
因此，伪随机数生成器（PRNG）一般采用逆转法，其基础是均匀分布，均匀分布PRNG的优劣决定了整个随机数体系的优劣[7]。下文研究均匀分布的PRNG。
程序实例
C语言程序例下面看这样一个C程序： 
//rand01.c 
#include 
static unsigned int RAND_SEED; 
unsigned int random(void) 
{  伪随机数RAND_SEED=(RAND_SEED*123+59)%65536; 
return(RAND_SEED); 
} 
void random_start(void) 
{ 
int temp[2]; 
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4); 
RAND_SEED=temp[0]; 
} 
main() 
{ 
unsigned int i,n; 
random_start(); 
for(i=0;i<10;i++) 
printf("%u\t",random()); 
printf("\n"); 
} 
这个程序（rand01.c）完整地阐述了随机数产生的过程： 
首先，主程序调用random_start()方法，random_start()方法中的这一句我很感兴趣： 
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4); 
这个函数用来移动内存数据，其中FP_SEG（far pointer to segment）是取temp数组段地址的函数，FP_OFF（far pointer to offset）是取temp数组相对地  伪随机数理论推导址的函数，movedata函数的作用是把位于0040:006CH存储单元中的双字放到数组temp的声明的两个存储单元中。这样可以通过temp数组把0040:006CH处的一个16位的数送给RAND_SEED。 
random用来根据随机种子RAND_SEED的值计算得出随机数，其中这一句： 
RAND_SEED=(RAND_SEED*123+59)%65536; 
是用来计算随机数的方法，随机数的计算方法在不同的计算机中是不同的，即使在相同的计算机中安装的不同的操作系统中也是不同的。我在linux和windows下分别试过，相同的随机种子在这两种操作系统中生成的随机数是不同的，这说明它们的计算方法不同。 
现在，我们明白随机种子是从哪儿获得的，而且知道随机数是怎样通过随机种子计算出来的了。那么，随机种子为什么要在内存的0040:006CH处取？0040:006CH处存放的是什么？ 
学过《计算机组成原理与接口技术》这门课的人可能会记得在编制ROM BIOS时钟中断服务程序时会用到Intel 8253定时/计数器，它与Intel 8259中断芯片的通信使得中断服务程序得以运转，主板每秒产生的18.2次中断正是处理器根据定时/记数器值控制中断芯片产生的。在我们计算机的主机板上都会有这样一个定时/记数器用来计算当前系统时间，每过一个时钟信号周期都会使记数器加一，而这个记数器的值存放在哪儿呢？没错，就在内存的0040:006CH处，其实这一段内存空间是这样定义的： 
TIMER_LOW DW ? ；地址为 0040:006CH 
TIMER_HIGH DW ? ；地址为 0040:006EH 
TIMER_OFT DB ? ；地址为 0040:0070H 
时钟中断服务程序中，每当TIMER_LOW转满时，此时，记数器也会转满，记数器的值归零，即TIMER_LOW处的16位二进制归零，而TIMER_HIGH加一。rand01.c中的 
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4); 
正是把TIMER_LOW和TIMER_HIGH两个16位二进制数放进temp数组，再送往RAND_SEED，从而获得了“随机种子”。 
现在，可以确定的一点是，随机种子来自系统时钟，确切地说，是来自计算机主板上的定时/计数器在内存中的记数值。这样，我们总结一下前面的分析，并讨论一下这些结论在程序中的应用： 
1.随机数是由随机种子根据一定的计算方法计算出来的数值。所以，只要计算方法一定，随机种子一定，那么产生的随机数就不会变。C++程序例看下面这个C++程序： 
//rand02.cpp  伪随机数理论推导#include 
#include 
using namespace std; 
int main() 
{ 
unsigned int seed=5; 
srand(seed); 
unsigned int r=rand(); 
cout<<"r = "<<r<<endl; //根据C++ 98标准,可以不用return语句来介绍main函数
} 
在相同的平台环境下，编译生成exe后，每次运行它，显示的随机数都是一样的。这是因为在相同的编译平台环境下，由随机种子生成随机数的计算方法都是一样的，再加上随机种子一样，所以产生的随机数就是一样的。 
2.只要用户或第三方不设置随机种子，那么在默认情况下随机种子来自系统时钟（即定时/计数器的值）C++程序例2看下面这个C++程序： 
//rand03.cpp 
#include <iostream>
#include <cstdlib>
using namespace std; 
int main() 
{ 
srand((unsigned)time(NULL)); 
unsigned int r=rand(); 
cout<<"r = "<<r<<endl; //根据C++ 98标准,可以不用return语句来介绍main函数
return 0;
} 
这里用户和其他程序没有设定随机种子，则使用系统定时/计数器的值做为随机种子，所以，在相同的平台环境下，编译生成exe后，每次运行它，显示的随机数会是伪随机数，即每次运行显示的结果会有不同。 
3.建议：如果想在一个程序中生成随机数序列，需要至多在生成随机数之前设置一次随机种子。生成一个随机字符串看下面这个用来生成一个随机字符串的C++程序：（原来的程序我编译不了，就改了改，加了一些头文件）
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<stdlib.h>
#include<time.h>
#include<cmath>
using namespace std;
inline void keep_window_open(){char ch;cin>>ch;}
#define RAND_MAX 0x7fff
int main()
{
int rNum=0;
int m=20;
char *ch=new char[m];
srand((unsigned)time(NULL));
for(int i=0;i<m;++i){
rNum=1+(int)((rand()/(double)RAND_MAX)*36);
switch(rNum){
case 1:ch[i]='a';
break;
case 2:ch[i]='b';
break;
case 3:ch[i]='c';
break;
case 4:ch[i]='d';
break;
case 5:ch[i]='e';
break;
case 6:ch[i]='f';
break;
case 7:ch[i]='g';
break;
case 8:ch[i]='h';
break;
case 9:ch[i]='i';
break;
case 10:ch[i]='j';
break;
case 11:ch[i]='k';
break;
case 12:ch[i]='l';
break;
case 13:ch[i]='m';
break;
case 14:ch[i]='n';
break;
case 15:ch[i]='o';
break;
case 16:ch[i]='p';
break;
case 17:ch[i]='q';
break;
case 18:ch[i]='r';
break;
case 19:ch[i]='s';
break;
case 20:ch[i]='t';
break;
case 21:ch[i]='u';
break;
case 22:ch[i]='v';
break;
case 23:ch[i]='w';
break;
case 24:ch[i]='x';
break;
case 25:ch[i]='y';
break;
case 26:ch[i]='z';
break;
default:cout<<"no!\n";
break;
}
cout<<ch[i]<<'\n';
}
return 0;
}

伪随机数生成器的缺点
重复做N=10000次试验，每次产生S=20与S=100个随机分布的样本，同时采用Kolmogorov- Smirnov假设检验（hypothesis test）来确定样本是否满足均匀分布。规定：    
① 0假设（null hypothesis）为样本服从均匀分布；② 1假设（alternative hypothesis）为样本不服从均匀分布。
采用P值（∈[0， 1]）衡量，P值越趋近于0，表示越有理由拒绝0假设，即样本不服从均匀分布；P值越趋近于1，表示越有理由接受0假设，即样本服从均匀分布。
如图1与图2所示：随着P值下降，样本也越来越不服从均匀分布。实践中希望P值越大越好。然而统计学的结论显示，P值一定服从均匀分布，与N、S大小无关，这表明由于随机性，总会出现某次抽样得到的样本不服从、甚至远离均匀分布。另外，样本大小的不同，造成检验标准的不同，直观上看S=100对应的均匀分布普遍比S=20对应的更均匀。因此，小样本情况下均匀分布PRNG的差异性尤为严重。