跳到主要内容

伪随机数


需要掌握:1. 第一个程序 - 7. 函数

动机

在计算机科学的各个领域,例如在密码学、网络安全或创建电脑游戏时,都需要生成随机数的能力。例如:

  • 🔑 从随机字符创建密码
  • 💥 游戏世界中的随机事件
  • 🎲 基于掷骰子造成伤害的几率

为什么是“伪随机”

如你所见,文章标题是“伪随机数”。计算机本身无法生成真正的随机数,但它可以通过一些技巧给我们随机的错觉,这已经足够了。

生成数字

注意

在本文中,我们将重点介绍一种非常原始但简单的方法。在课程的后续内容中,您将学习到来自库<random>的更强大的工具。一旦您学会了它们,您就不应该再使用std::rand了。

在本文中,我们将使用以下头文件

必需的头文件
#include <cstdlib>
#include <ctime>

让我们看一个使用示例

生成5个随机数
#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)。

Range from 0 to RAND_MAX

RAND_MAX的值可以很容易地检查

检查从rand能得到的最大值。
#include <iostream>
#include <cstdlib>

int main() {
std::cout << "RAND_MAX: " << RAND_MAX;
}

可能的结果

Visual Studio 2022 预览版。

RAND_MAX: 32767

以上结果清楚地表明了这个问题。在 Windows 上我们得到 215 - 1,而在 Linux 上得到 231 - 1

限制范围

0到1之间的实数

Range from 0 to 1

我们可以简单地将获取的数字除以RAND_MAX,以获得0到1之间的值。

从0到1的数字
float randomFloat() {
return float( std::rand() ) / RAND_MAX;
}
转换为浮点数

请注意,我们需要将这些数字中的至少一个转换为浮点类型。RAND_MAXrand()这两个值都是整数,因此对它们进行操作也会得到整数结果。

简单来说

int / int = int

转换后,它将看起来像这样

float / int = float

如果您想知道为什么这有效,请查看此简单分析

  • 对于数字0,我们将得到0 / RAND_MAX,所以仍然是0
  • 对于RAND_MAX(即最大数),我们得到RAND_MAX / RAND_MAX,即1
  • 对于所有中间值,我们得到一个大于0且小于1的数字

AB的实数

Range from A to B

使用之前的函数randomFloat(),我们可以定义一个类似的函数,它将在AB的范围内生成一个实数。

我们需要做什么

  • 计算一个新的范围,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;
}

AB的整数

与上面完全相同,我们可以创建一个函数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

🎲 随机几率 (30%)
int randomChance = randomInt(1, 100);

if (randomChance <= 30)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}

或者,您可以放弃百分比,使用简单的分数

🎲 随机几率 (30%)
float randomChance = randomFloat();

if (randomChance <= 0.30f)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}

伪随机数


需要掌握:1. 第一个程序 - 7. 函数

动机

在计算机科学的各个领域,例如在密码学、网络安全或创建电脑游戏时,都需要生成随机数的能力。例如:

  • 🔑 从随机字符创建密码
  • 💥 游戏世界中的随机事件
  • 🎲 基于掷骰子造成伤害的几率

为什么是“伪随机”

如你所见,文章标题是“伪随机数”。计算机本身无法生成真正的随机数,但它可以通过一些技巧给我们随机的错觉,这已经足够了。

生成数字

注意

在本文中,我们将重点介绍一种非常原始但简单的方法。在课程的后续内容中,您将学习到来自库<random>的更强大的工具。一旦您学会了它们,您就不应该再使用std::rand了。

在本文中,我们将使用以下头文件

必需的头文件
#include <cstdlib>
#include <ctime>

让我们看一个使用示例

生成5个随机数
#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)。

Range from 0 to RAND_MAX

RAND_MAX的值可以很容易地检查

检查从rand能得到的最大值。
#include <iostream>
#include <cstdlib>

int main() {
std::cout << "RAND_MAX: " << RAND_MAX;
}

可能的结果

Visual Studio 2022 预览版。

RAND_MAX: 32767

以上结果清楚地表明了这个问题。在 Windows 上我们得到 215 - 1,而在 Linux 上得到 231 - 1

限制范围

0到1之间的实数

Range from 0 to 1

我们可以简单地将获取的数字除以RAND_MAX,以获得0到1之间的值。

从0到1的数字
float randomFloat() {
return float( std::rand() ) / RAND_MAX;
}
转换为浮点数

请注意,我们需要将这些数字中的至少一个转换为浮点类型。RAND_MAXrand()这两个值都是整数,因此对它们进行操作也会得到整数结果。

简单来说

int / int = int

转换后,它将看起来像这样

float / int = float

如果您想知道为什么这有效,请查看此简单分析

  • 对于数字0,我们将得到0 / RAND_MAX,所以仍然是0
  • 对于RAND_MAX(即最大数),我们得到RAND_MAX / RAND_MAX,即1
  • 对于所有中间值,我们得到一个大于0且小于1的数字

AB的实数

Range from A to B

使用之前的函数randomFloat(),我们可以定义一个类似的函数,它将在AB的范围内生成一个实数。

我们需要做什么

  • 计算一个新的范围,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;
}

AB的整数

与上面完全相同,我们可以创建一个函数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

🎲 随机几率 (30%)
int randomChance = randomInt(1, 100);

if (randomChance <= 30)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}

或者,您可以放弃百分比,使用简单的分数

🎲 随机几率 (30%)
float randomChance = randomFloat();

if (randomChance <= 0.30f)
{
std::cout << "Winner :)";
}
else
{
std::cout << "Loser :(";
}