伪随机数
动机
在计算机科学的各个领域,例如在密码学、网络安全或创建电脑游戏时,都需要生成随机数的能力。例如:
- 🔑 从随机字符创建密码
- 💥 游戏世界中的随机事件
- 🎲 基于掷骰子造成伤害的几率
为什么是“伪随机”
如你所见,文章标题是“伪随机数”。计算机本身无法生成真正的随机数,但它可以通过一些技巧给我们随机的错觉,这已经足够了。
生成数字
在本文中,我们将重点介绍一种非常原始但简单的方法。在课程的后续内容中,您将学习到来自库<random>
的更强大的工具。一旦您学会了它们,您就不应该再使用std::rand
了。
在本文中,我们将使用以下头文件
#include <cstdlib>
#include <ctime>
让我们看一个使用示例
#include <iostream>
#include <cstdlib>
#include <ctime>
int main() {
std::srand( std::time(0) );
std::cout << "Generating 5 random numbers:\n";
for (int i = 0; i < 5; ++i)
std::cout << std::rand() << '\n';
}
Generating 5 random numbers:
570368048
1028036926
1798519773
2028832115
1034913436
获取下一个数字
这里的关键是
std::rand()
(来自随机),它生成并返回序列中的下一个伪随机数。这个序列非常不可预测,从而产生了真随机的错觉。
设置种子
这个序列由哪些数字组成取决于所谓的种子,它也是一个数字。以下函数用于设置种子
std::srand( <seed> )
(来自种子随机)
如果我们保留默认种子,生成的序列将始终相同。
最好在程序开始时设置一次,就像示例中那样。我们使用当前时间作为种子,它以每秒递增的数字形式存在,所以每次启动程序时,我们都会得到不同的效果。我们通过函数调用实现了这一点
std::time(0)
std::rand
的缺点
std::rand()
函数使用简单,其优点也仅限于此。问题在于,它返回的数字范围没有严格定义,并且根据例如编译器或操作系统而不同。
我们只能确定返回的数字总是在范围[0; RAND_MAX]
内,并且RAND_MAX
是一个系统或编译器相关的常量(不小于32767
)。

RAND_MAX
的值可以很容易地检查
#include <iostream>
#include <cstdlib>
int main() {
std::cout << "RAND_MAX: " << RAND_MAX;
}
可能的结果
- MSVC (Windows)
- GCC 11.2 (Windows)
- GCC 12.0 (Linux)
- Clang 13.0 (Linux)
Visual Studio 2022 预览版。
RAND_MAX: 32767
来自MSYS2包的GCC 11.2。
RAND_MAX: 32767
GCC 12.0.0,来自https://wandbox.org
RAND_MAX: 2147483647
Clang 13.0.0,来自https://wandbox.org
RAND_MAX: 2147483647
以上结果清楚地表明了这个问题。在 Windows 上我们得到 215 - 1,而在 Linux 上得到 231 - 1
限制范围
0到1之间的实数

我们可以简单地将获取的数字除以RAND_MAX
,以获得0到1之间的值。
float randomFloat() {
return float( std::rand() ) / RAND_MAX;
}
请注意,我们需要将这些数字中的至少一个转换为浮点类型。RAND_MAX
和rand()
这两个值都是整数,因此对它们进行操作也会得到整数结果。
简单来说
int / int = int
转换后,它将看起来像这样
float / int = float
如果您想知道为什么这有效,请查看此简单分析
- 对于数字
0
,我们将得到0 / RAND_MAX
,所以仍然是0
- 对于
RAND_MAX
(即最大数),我们得到RAND_MAX / RAND_MAX
,即1
- 对于所有中间值,我们得到一个大于
0
且小于1
的数字
从A
到B
的实数

使用之前的函数randomFloat()
,我们可以定义一个类似的函数,它将在A
到B
的范围内生成一个实数。
我们需要做什么
- 计算一个新的范围,
float Length = B - A
- 将一个数字
[0; 1]
乘以长度,得到一个范围[0; Length]
- 将整个范围移动到
A
以获得:[A; Length + A]
,即[A; B]
// We can use the same functio name, because
// it has different parameters (function overload)
float randomFloat(float from, float to)
{
float length = to - from;
return randomFloat()*length + from;
}
或者更简单
float randomFloat(float from, float to)
{
return randomFloat()*(to - from) + from;
}
从A
到B
的整数
与上面完全相同,我们可以创建一个函数randomInt
int randomInt(int from, int to)
{
return int( randomFloat()*(to - from) ) + from;
}
使用示例
本文中的函数
使用上面创建的函数非常简单方便
- 代码
- 运行 ▶
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <iomanip>
// Declare functions
float randomFloat(); // od 0 do 1
float randomFloat(float from, float to); // od "from" do "to"
int randomInt(int from, int to); // od "from" do "to" (int)
int main()
{
// Set the seed
std::srand( std::time(0) );
// We set formatting of the cout
std::cout << std::fixed;
std::cout.precision(2);
std::cout << "Generating 5 floats from 0 to 1:\n";
for (int i = 0; i < 5; ++i)
std::cout << randomFloat() << ' ';
std::cout << "\n\nGenerating 5 floats from 10 to 30:\n";
for (int i = 0; i < 5; ++i)
std::cout << randomFloat(10, 30) << ' ';
std::cout << "\n\nGenerating 5 integers from 0 to 100:\n";
for (int i = 0; i < 5; ++i)
std::cout << randomInt(0, 100) << ' ';
std::cout << std::endl;
}
// Function implementations
/////////////////////////////////////
float randomFloat()
{
return float( std::rand() ) / RAND_MAX;
}
/////////////////////////////////////
float randomFloat(float from, float to)
{
return randomFloat()*(to - from) + from;
}
/////////////////////////////////////
int randomInt(int from, int to)
{
return int( randomFloat()*(to - from) ) + from;
}
(在私密浏览模式下可能无法工作)
事件的随机几率
如果我们想让某个事件有例如30%的发生几率,我们可以生成一个范围在[1; 100]
的整数,并检查number <= 30
int randomChance = randomInt(1, 100);
if (randomChance <= 30)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}
或者,您可以放弃百分比,使用简单的分数
float randomChance = randomFloat();
if (randomChance <= 0.30f)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}