用C++的random库生成更好的随机数

用 C++ 的 random 库生成更好的随机数

C 语言中的 rand()srand()

学习过 C 语言的读者可能熟悉用 rand() 生成随机数的方法,rand() 会返回一个伪随机数,范围在 [0, RAND_MAX) 之间;根据实现的不同,RAND_MAX 的值也会不同,但至少应为 32767。

使用方法如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    for (int i = 0; i < 5; i++) printf("%d ", rand());  
    return 0;
}

如果像上面的程序那样,不事先调用 srand(),那么每次程序执行时都是假设调用了 srand(1),结果就是每次得到的随机数序列都是一样的。

也就是说,如果以不同的参数值调用 srand()rand() 生成随机数的起点就会不一样。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

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 的值次数
06
17
27
36
46

可见,数据 0 到 4 出现的概率并不是相等的。

那该怎么写呢?std::rand - cppreference.com 这里给出了一个示例,下面直接复制粘贴:

#include <cstdlib>
#include <iostream>
#include <ctime>
 
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()×(ba+1)=a+(ba+1)Nrand()

使用 C++ 的 random 库

C++ 中为我们提供了更好用,功能更全面的随机数生成。

在这篇微软的文档中是 不推荐使用 rand() 的:

关于 <random> 的介绍:<random> | Microsoft Docs 机器翻译的中文版

其他参考资料:

那么我们用 C++ 该怎么生成随机数呢?

以下内容主要来自Pseudo-random number generation - cppreference.com

C++ 有一个随机数的库,定义在头文件 <random> 中,提供可以生成随机数和伪随机数的类,这些类可以分为两类:

  • 均匀随机数生成器(uniform random bit generators, URBGs),包括用来生成伪随机数的随机数生成引擎,以及,真正的随机数生成器,如果有可用的设备的话;
  • 随机数分布(random number distributions),将URBGs输出的数据转换到多种多样的统计学分布上(如均匀分布、正态分布等)。

一个 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 <random>
#include <iostream>
 
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 <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <cmath>
 
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';
    }
}

大家可以自己编译运行一下上边的代码,看会输出什么样的结果。

可以继续阅读下面的链接,它们包含更多信息,也有更多实用场景下的例子:

  • 25
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 C++生成高效高质量的随机数,可以使用以下步骤: 1. 使用 C++11 引入的随机数C++11 引入了一个更好随机数,其中包括了更高质量的随机数生成器和分布函数。这些位于 `<random>` 头文件中。 2. 使用适当的随机数生成器:`std::mt19937_64` 是一个优秀的伪随机数生成器,使用 Mersenne Twister 算法,可以生成高质量的伪随机数。它的周期非常长,可以满足大多数应用场景。 3. 初始化随机数生成器:使用不同的种子来初始化随机数生成器,可以产生不同的随机序列。可以使用 `std::random_device` 来获得真正的随机种子,或者使用时间戳、进程 ID 等值作为种子。 4. 使用适当的分布函数:根据需求选择合适的分布函数来生成随机数。常见的分布函数包括均匀分布 (`std::uniform_int_distribution`、`std::uniform_real_distribution`)、正态分布 (`std::normal_distribution`)、泊松分布 (`std::poisson_distribution`) 等。 5. 避免频繁初始化随机数生成器:初始化随机数生成器是一个相对较耗时的操作,尽量避免在循环中频繁初始化生成器。如果需要多次生成随机数,可以将生成器对象放在循环外,并重复使用。 下面是一个示例代码: ```cpp #include <iostream> #include <random> int main() { std::random_device rd; std::mt19937_64 rng(rd()); std::uniform_int_distribution<int> dist(1, 10); for (int i = 0; i < 10; ++i) { int random_number = dist(rng); std::cout << "随机数为:" << random_number << std::endl; } return 0; } ``` 在上述示例中,我们使用 `std::random_device` 获得一个真正的随机种子,并将其传递给 `std::mt19937_64` 初始化随机数生成器。然后,使用 `std::uniform_int_distribution` 定义了一个范围为1-10的均匀分布,并使用 `rng` 生成随机数。我们循环生成了10个随机数,并打印结果。 记住,要生成高效高质量的随机数,最好使用 C++11 引入的随机数,并遵循上述步骤来选择适当的随机数生成器和分布函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值