rand()
和 srand()
学习过 C 语言的读者可能熟悉用 rand()
生成随机数的方法,rand()
会返回一个伪随机数,范围在 [0, RAND_MAX)
之间;根据实现的不同,RAND_MAX
的值也会不同,但至少应为 32767。
使用方法如下:
#include
#include
int main()
{
for (int i = 0; i < 5; i++) printf("%d ", rand());
return 0;
}
如果像上面的程序那样,不事先调用 srand()
,那么每次程序执行时都是假设调用了 srand(1)
,结果就是每次得到的随机数序列都是一样的。
也就是说,如果以不同的参数值调用 srand()
,rand()
生成随机数的起点就会不一样。
#include
#include
#include
int main()
{
srand(time(0));
for (int i = 0; i < 5; i++) printf("%d ", rand());
return 0;
}
time()
函数每次会返回一个 time_t
类型的值,这样,根据调用的时间不同,生成的随机数也就不同了。
假如想要一个 3 ~ 8 的随机值,你会怎么做呢?也许你会用 3 + rand() % (8 - 3 + 1)
。但是其实这样是不对的。
为了方便说明,我们假设随机数发生的范围是 1 到 32,且 1 到 32 每个数字出现的概率是相等的,那么调用 32 次 rand() % 5
得到的数据和次数可能如下表:
rand() % 5 的值 |
次数 |
---|---|
0 | 6 |
1 | 7 |
2 | 7 |
3 | 6 |
4 | 6 |
可见,数据 0 到 4 出现的概率并不是相等的。
那该怎么写呢?std::rand - cppreference.com 这里给出了一个示例,下面直接复制粘贴:
#include
#include
#include
int main()
{
std::srand(std::time(nullptr)); // use current time as seed for random generator
int random_variable = std::rand();
std::cout << "Random value on [0 " << RAND_MAX << "]: "
<< random_variable << '\n';
// roll 6-sided dice 20 times
for (int n=0; n != 20; ++n) {
int x = 7;
while(x > 6)
x = 1 + std::rand()/((RAND_MAX + 1u)/6);
// Note: 1+rand()%6 is biased
std::cout << x << ' ';
}
}
一共能产生的随机数数目为 RAND_MAX + 1
,所以通过 rand()/(RAND_MAX + 1)
可以均匀的得到 0 至 1 之间的浮点数,将这个数再乘以一个数 k 就可以得到 0 到 k 之间的数了。
生成 [ a , b ] [a,b] [a,b] 之间的随机数:
a + r a n d ( ) N × ( b − a + 1 ) = a + r a n d ( ) N ( b − a + 1 ) a+\cfrac{\tt rand()}{N}\times (b-a+1) = a+\cfrac{\tt rand()}{\cfrac{N}{(b-a+1)}} a+Nrand()×(b−a+1)=a+(b−a+1)Nrand()
C++ 中为我们提供了更好用,功能更全面的随机数生成。
在这篇微软的文档中是 不推荐使用 rand()
的:
关于
其他参考资料:
那么我们用 C++ 该怎么生成随机数呢?
以下内容主要来自Pseudo-random number generation - cppreference.com:
C++ 有一个随机数的库,定义在头文件
中,提供可以生成随机数和伪随机数的类,这些类可以分为两类:
一个 URBG 是一个函数对象;有好几种随机数引擎,比如线性同余法(linear congruential algorithm)、梅森旋转算法(Mersenne twister algorithm)、时滞斐波那契算法(lagged Fibonacci algorithm),这些引擎使用种子(seed)作为熵源生成伪随机数;也有好几种随机数引擎配接器,它们使用另一个随机数引擎作为自己的熵源,通常用来改变原来引擎的频谱特性(spectral characteristics)。
有一些预定义的随机数生成器,比如 mt19937
,是一个32位的梅森旋转算法(32-bit Mersenne Twister by Matsumoto and Nishimura, 1998)。
也可以用 std::random_device
生成不确定的随机数,也就是真随机数;当没有随机数的发生条件(比如随机数发生器)时,也可以用伪随机数生成引擎实现它。
至于随机数的分布,就又有很多种了,就不一一列举了。
那具体怎么在代码中使用呢?下面是从 std::uniform_int_distribution - cppreference.com 复制粘贴的一个生成均匀随机数(模拟掷骰子)的例子:
#include
#include
int main()
{
std::random_device rd; //如果可用的话,从一个随机数发生器上获得一个真正的随机数
std::mt19937 gen(rd()); //gen是一个使用rd()作种子初始化的标准梅森旋转算法的随机数发生器
std::uniform_int_distribution<> distrib(1, 6);
for (int n=0; n<10; ++n)
//使用`distrib`将`gen`生成的unsigned int转换到[1, 6]之间的int中
std::cout << distrib(gen) << ' ';
std::cout << '\n';
}
std::random_device
会向操作系统请求随机数。
下面是一个展示正态分布的例子,从 Pseudo-random number generation - cppreference.com 复制而来。
#include
#include
#include
#include
#include
#include
int main()
{
// 如果可用,从一个真实的随机数发生设备获得种子
std::random_device r;
// 在1和6之间随机的选择一个均值mean,用来生成正态分布
std::default_random_engine e1(r());
std::uniform_int_distribution<int> uniform_dist(1, 6);
int mean = uniform_dist(e1);
std::cout << "Randomly-chosen mean: " << mean << '\n';
// 围绕刚刚选择到的“均值”mean生成一个正态分布
std::seed_seq seed2{r(), r(), r(), r(), r(), r(), r(), r()};
std::mt19937 e2(seed2);
std::normal_distribution<> normal_dist(mean, 2);
std::map<int, int> hist;
for (int n = 0; n < 10000; ++n) {
++hist[std::round(normal_dist(e2))];
}
std::cout << "Normal distribution around " << mean << ":\n";
for (auto p : hist) {
std::cout << std::fixed << std::setprecision(1) << std::setw(2)
<< p.first << ' ' << std::string(p.second/200, '*') << '\n';
}
}
大家可以自己编译运行一下上边的代码,看会输出什么样的结果。
可以继续阅读下面的链接,它们包含更多信息,也有更多实用场景下的例子: