伪随机数生成及在VC++中的实现
摘 要伪随机数在计算机软件设计中有很广泛的用途。本文介绍了基于数学方法的利用计算机产生伪随机数的一种方法,即线性同余法,任何伪随机数的产生都是运用递推的原理来生成的。以及在Visual C++环境中产生伪随机数的两个重要函数,rand和srand函数,正确地使用这两个函数是产生性能良好的伪随机数的关键,最后介绍了利用伪随机数生成技术在MFC中生成基于C/S模式应用程序的随机校验码以及利用一种软件工具ImagePassword产生随机密码。
关键词伪随机数生成;线性同余法;Visual C++;随机校验码
为追求真正的随机序列,人们曾采用很多种原始的物理方法用于生成一定范围内满足精度(位数)的均匀分布序列,其缺点在于:速度慢、效率低、需占用大量存储空间且不可重现等。为满足计算机模拟研究的需求,人们转而研究用算法生成模拟各种概率分布的伪随机序列。伪随机数是指用数学递推公式所产生的随机数。从实用的角度看,获取这种数的最简单和最自然的方法是利用计算机语言的函数库提供的随机数发生器。典型情况下,它会输出一个均匀分布在0和1区间内的伪随机变量的值。其中应用的最为广泛、研究最彻底的一个算法即线性同余法。
线性同余法LCG(Linear Congruence Generator)
选取足够大的正整数M和任意自然数n0,a,b,由递推公式:ni+1=(af(ni)+b)mod M i=0,1,…,M-1
生成的数值序列称为是同余序列。当函数f(n)为线性函数时,即得到线性同余序列:ni+1=(a*ni+b)mod M i=0,1,…,M-1
以下是线性同余法生成伪随机数的伪代码:Random(n,m,seed,a,b)
{
r0 = seed;
for (i = 1;i<=n;i++)
ri = (a*ri-1 + b) mod m
}
其中种子参数seed可以任意选择,常常将它设为计算机当前的日期或者时间;m是一个较大数,可以把它取为2w,w是计算机的字长;a可以是0.01w和0.99w之间的任何整数。
应用递推公式产生均匀分布随机数时,式中参数n0,a,b,M的选取十分重要。
例如,选取M=10,a=b =n0=7,生成的随机序列为{6,9,0,7,6,9,……},周期为4。
取M=16,a=5,b =3,n0=7,生成的随机序列为{6,1,8,11,10,5,12,15,14,9,0,3,2,13,4,7,6,1……},周期为16。
取M=8,a=5,b =1,n0=1,生成的随机序列为{6,7,4,5,2,3,0,1,6,7……},周期为8。
Visual C++中伪随机数生成机制
用VC产生随机数有两个函数,分别为rand(void)和srand(seed)。rand()产生的随机整数是在0~RAND_MAX之间平均分布的,RAND_MAX是一个常量(定义为:#define RAND_MAX 0x7fff)。它是short型数据的最大值,如果要产生一个浮点型的随机数,可以将rand()/1000.0,这样就得到一个0~32.767之间平均分布的随机浮点数。如果要使得范围大一点,那么可以通过产生几个随机数的线性组合来实现任意范围内的平均分布的随机数。
其用法是先调用srand函数,如srand( (unsigned)time( NULL ) )
这样可以使得每次产生的随机数序列不同。如果计算伪随机序列的初始数值(称为种子)相同,则计算出来的伪随机序列就是完全相同的。要解决这个问题,需要在每次产生随机序列前,先指定不同的种子,这样计算出来的随机序列就不会完全相同了。以time函数值(即当前时间)作为种子数,因为两次调用rand函数的时间通常是不同的,这样就可以保证随机性了。也可以使用srand函数来人为指定种子数。
分析以下两个程序段,
程序段1://包含头文件
void main() {
int count=0;
for (int i=0;i<10;i++){
srand((unsigned)time(NULL));
count++;
cout<<"No"<
程序段1中由于将srand()函数放在循环体内,而程序执行的CPU时间较快,调用time函数获取的时间精度却较低(55ms),这样循环体内每次产生随机数用到的种子数都是一样的,因此产生的随机数也是一样的。而程序段2中第1次产生的随机数要用到随机种子,以后的每次产生随机数都是利用递推关系得到的。
基于MFC的随机校验码生成
Web应用程序中经常要利用到随机校验码,校验码的主要作用是防止黑客利用工具软件在线破译用户登录密码,校验码、用户名、密码三者配合组成了进入Web应用系统的钥匙。在利用VC开发的基于客户机/浏览器(Client/Server)模式的应用软件系统中,为了防止非法用户入侵系统,通常也要运用随机校验码生成技术。
本实现要用到以上介绍到的伪随机数生成技术。校验码数据将以16进制码方式显示。主要代码如下:void CRandompasswordDlg::OnCreatekey() {
int RanCheckNum = 0;
char out[25]={0};
char keytemp[5]={0};
memset(out,0x30,18);
srand((unsigned)timeGetTime());//产生随机数种子
for(int i=0;i<6;i++){
RanCheckNum = rand();//产生随机数
_itoa(RanCheckNum,keytemp,16);//将随机数转换成16进制
memcpy(&out[i*4],keytemp,strlen(keytemp));
}
out[24]=0x00;
strcpy(m_key.GetBuffer(18),out);
UpdateData(FALSE);
}
运行结果如图1所示:
图1 利用伪随机数生成随机校验码
程序运行时,由于每一次点击"产生随机校验码"的系统时间不同,生成随机数的种子就不一样,因此产生的随机数也是不一样的,从而保证了校验码生成的随机性。
利用ImagePassword工具产生随机密码
ImagePassword提供一个可选择的图形阵列,通过随机改变图形阵列中的阵点图形来产生随机密码。当随机点击图象阵列中的图象阵点,该阵点中的图象发生变化。其运行界面如图2所示:
图2 ImagePassword运行界面
点击OK按钮后所产生的随机密码如图3所示:
图3 ImagePassword运行结果
ImagePassword产生的密码的随机性依赖于用户对图象阵列中阵点图象的随机选择,一般来说用户在图象阵列中随机点击鼠标的次数越多,最后产生的密码的随机性越强。
结束语
伪随机数在不同的软件系统中都得到了很广泛的应用,如何选择随机数生成种子使得生成的伪随机数性能更佳是软件设计者追求的目标之一。本文提到了利用系统时间作为种子参数在一定条件下可以满足软件的随机性需要。利用所产生的随机数在游戏编程,如扑克类游戏中的随机发牌,俄罗斯方块的随机生成等等其他应用中都起到很重要的作用。