本篇文件介绍C/C++ 中使用rand函数生成随机数的方法,并且提供各种常用的示例代码。

在撰写C/C++ 程序时,如果需要产生一些简单的随机数,最方便的作法就是使用rand这个随机数生成函数,以下介绍这个函数的相关用法与示例。

rand只能提供基本的随机数,如果您需要更进阶的功能或是品质比较好的随机数,建议改用C++ 的 函数库。

基本随机数生成方法

C 语言中若要产生随机数,可以使用stdlib.h中的rand函数,而在调用rand函数之前,要先使用srand函数设定初始的随机数种子:

#include
#include /*乱数相关函数*/
#include /*时间相关函数*/
int main(){
/*设定随机数种子*/
srand( time( NULL ) );
/*生成随机数*/
int x = rand();
printf( "x = %d \n " , x);
return 0 ;
}

执行后的输出为:

x = 159136674

rand所产生的随机数是一个整数,其值介于0RAND_MAX之间(最小是0,最大则为RAND_MAX),若想要看RAND_MAX的实际数值,可以用printf将其输出后查看。

printf( "RAND_MAX = %d \n " , RAND_MAX );

在C++ 中的随机数生成方式也跟C 几乎相同,只是标头档换一下而已:

#include
#include /*随机数相关函数*/
#include /*时间相关函数*/
int main(){
/*固定随机数种子*/
srand( time( NULL ) );
/*生成随机数*/
int x = rand();
std::cout << "x = " << x << std::endl;
return 0 ;
}

接下来的例子我就以C 语言的写法为准,需要C++ 的人就自己将头文件改一下。

固定随机数种子

由于电脑实际上并没有办法自己产生「真正的随机数」,只能透过复杂的数学演算法模拟出类似随机数的数值资料,而在模拟随机数时,需要设定一个随机数种子,电脑会根据这个随机数种子来计算出一连串的随机数,相同的随机数种子就会产生相同的随机数序列,所以如果要让生成的随机数每次都不同,就要设定不同的随机数种子。

在上面的例子中,我们使用时间来当作随机数种子,所以每次产生的随机数都不同,如果是用于数值模拟的程序,通常都会需要让模拟的结果具有可重复性(repeatability),方便除错与验证,这种状况就可以将随机数种子固定不变,这样就可以确保每次的模拟结果完全相同:

#include
#include
#include
int main(){
/*固定乱数种子*/
srand( 5 );
int x = rand();
printf( "x = %d \n " , x);
return 0 ;
}
x = 84035

随机数种子是使用一个整数来做设定的,若要固定随机数种子的话,其数值要设定为多少其实不重要,只要每次执行程序时都使用相同的随机数种子即可。

最常用到固定随机数种子的时机可能就是除错或是验证演算法是否有错误的时候,如果没有固定随机数种子的话,每次跑出来的结果都不同,会让除错的难度提高。

[0, 1)浮点数随机数

若要产生01之间的浮点数随机数,可以这样写:

#include
#include
#include
int main(){
srand( time( NULL ) );
/*产生[ 0 , 1 ) 的浮点数随机数*/
double x = ( double ) rand() / ( RAND_MAX + 1.0 );
printf( "x = %f \n " , x);
return 0 ;
}

上面的代码中我们将rand函数所产生整数除以RAND_MAX + 1.0,就可以得到[0, 1)这个范围的浮点数随机数(也就是0 <= x < 1)。

特定范围浮点数随机数

若要产生特定范围的浮点数随机数,可以这样写:

#include
#include
#include
int main(){
srand( time( NULL ) );
/*指定随机数范围*/
double min = 3.6 ;
double max = 7.8 ;
/*产生[min , max) 的浮点数随机数*/
double x = (max - min) * rand() / ( RAND_MAX + 1.0 ) + min;
printf( "x = %f \n " , x);
return 0 ;
}

这样即可产生[min , max)之间的浮点数随机数。

特定范围整数随机数

若想要产生特定范围的整数随机数,可以这样写:

#include
#include
#include
int main(){
srand( time( NULL ) );
/*指定随机数范围*/
int min = 4 ;
int max = 10 ;
/*产生[min , max] 的整数随机数*/
int x = rand() % (max - min + 1 ) + min;
printf( "x = %d \n " , x);
return 0 ;
}

这样会将rand产生出来的整数转换为[min , max]的整数随机数(也就是min <= x <= max)。

上面这种使用余数运算(%)的方式只是比较方便的写法,事实上使用余数运算所产生的整数随机数在理论上不是标准的均匀分布,我们以一个简单的例子来解释,假设RAND_MAX的值为10,而我们要产生介于35之间的整数随机数(亦即min = 3max = 5),以下是所有的可能性对照表:

转换后的整数随机数rand函数产生的随机数出现机率
303694/11
4147104/11
52583/11

rand函数所产生的每一个整数其出现的机率是均等的,但是经过于数运算的转换之后,因为RAND_MAX通常不会被整除,所以转换之后的整数随机数出现机率就存在有细微的偏差,以这个例子来说,345三个数字出现的机率比是4:4:3

另外有些人会先产生固定范围的浮点数随机数,再将浮点数转型为整数,例如产生[3, 6)的浮点数随机数,然后转型为[3, 5]的整数随机数,其实这种方式跟余数运算一样会有每个整数出现机率不均等的问题,简单来说就是现在有11个球要放进3个篮子里,不管怎么放,每个篮子的球都不可能一样多。

如果您需要非常标准的均匀分布(uniform distribution),可以使用这个版本:

/*产生[ 0 , n) 均匀分布的整数随机数*/
int randint( int n) {
if ((n - 1 ) == RAND_MAX ) {
return rand();
} else {
/*计算可以被整除的长度*/
long end = RAND_MAX / n;
assert (end > 0L );
end *= n;
/*将尾端会造成偏差的几个随机数去除,
若产生的乱数超过limit,则将其舍去*/
int r;
while ((r = rand()) >= end);
return r % n;
}
}

使用这个randint函数产生特定范围整数随机数:

/*指定随机数范围*/
int min = 4 ;
int max = 10 ;
/*产生[min , max] 的整数随机数*/
int x = randint(max - min + 1 ) + min;

这种作法就好像要把11个球要放进3个篮子里,而最后多出来的2颗球就直接丢掉,确保每个篮子都一样只有3颗,这样大家的机率就可以相等了。

这种使用截断分布(truncated distribution)来校正机率的方式虽然在理论上是正确的,但是rand函数是使用LCG(Linear Congruential Generator)来产生随机数的,他的优点只是快速、方便而已,但它本身所产生的随机数品质没有非常好,再怎么校正效果都有限,若需要高品质的随机数,请改用C++11 标准的函数库。