如何使用C++11标准库生成随机数

23
新的C++11标准专门为随机数生成器设立了一个章节。但是,如何执行最简单、最常见的任务,而不依赖于标准C库,以前通常会这样编码:

srand((unsigned int)time(0));
int i = rand();

是否有合理的默认值可用于开箱即用的随机数引擎、分布和种子?


2
你的代码有什么问题?据我所知,新的随机数生成器是为更“严肃”的应用程序添加的,其中随机数生成的方面确实很重要。 - GManNickG
3
公平地说,新标准中的几个随机数生成器可以被描述为简单快速,我不认为它们特别“严肃”。 - CB Bailey
6
每次在 C++ 中使用标准 C 库,我都觉得自己在做一些不合适的事情。再看看这个强制类型转换!肯定有更好的方法。 - Bartosz Milewski
至少在我的系统上,“arc4random()”,“random()”和“rand()”都在“stdlib.h”中声明。@Charles Bailey - sbooth
1
@Charles Bailey 我也不确定!你说得对,只有 rand 是标准 C 的一部分,而 random 是 POSIX 的一部分,arc4random 则提供在 BSD 上。 - sbooth
显示剩余6条评论
7个回答

30

你应该能够做到类似以下方式:

std::default_random_engine e((unsigned int)time(0));
int i = e();

default_random_engine 的质量是取决于实现的。你也可以使用 std::min_rand0std::min_rand

可能更好的随机引擎种子方式是使用实现中可用的真正随机数,而不是使用 time

例如:

std::random_device rd;
std::default_random_engine e( rd() );

8
信息性的:这个答案和原始问题例子最大的不同在于编码者控制引擎状态的位置:它在e里面。在原始问题的代码中,状态被隐藏为rand内部的静态数据。当处理多线程程序时,这是一个重要的区别。rand必须有一些保护使其线程安全。default_random_engine则没有。如果您想从多个线程调用它,则需要自己提供同步机制外部。这意味着如果不需要同步,default_random_engine可能会更快。 - Howard Hinnant
1
@Charles:我刚在libc++上测试了你的代码。在正确的包含下,它能正常工作。或许需要在time前加上std::<耸肩>。符合C++11标准的托管平台需要使用std::default_random_engine,在libc++中它等价于minstd_rand,是一个线性同余引擎。 - Howard Hinnant
1
@Howard:关于种子,标准库中是否有默认的方法使用某些实现相关的魔法来生成随机数种子?或者time(0)仍然是唯一实用且可移植的方式?我并不需要将随机数用于像加密这样的高级操作,只是为了使我的测试更具随机性。 - Bartosz Milewski
1
@Bartosz:如果您不需要或不想要随机序列的可重复性,我更喜欢Charles的random_device建议。或者您可以继续使用time。还有一种被称为soup-up seeder on steroids的东西叫做seed_seq。我已经实现了这个东西,但仍然不确定何时使用它。:-)而且为了增加乐趣,seed_seq本身也可以被种子化! - Howard Hinnant
1
从cppreference.com引用:“如果实现中没有可用的非确定性源(例如硬件设备),则std :: random_device可能是基于实现定义的伪随机数引擎实现的。在这种情况下,每个std :: random_device对象都可以生成相同的数字序列。这意味着,在某些系统上,使用默认构造的randomdevice将始终使用相同的数字种子mt19937等!” - Marti Nito
显示剩余3条评论

6

我将统一和简化已经提供的一些示例,总结如下:

// Good random seed, good engine
auto rnd1 = std::mt19937(std::random_device{}());

// Good random seed, default engine
auto rnd2 = std::default_random_engine(std::random_device{}());

// like rnd1, but force distribution to int32_t range
auto rnd3 = std::bind(std::uniform_int_distribution<int32_t>{}, std::mt19937(std::random_device{}()));

// like rnd3, but force distribution across negative numbers as well
auto rnd4 = std::bind(std::uniform_int_distribution<int32_t>{std::numeric_limits<int32_t>::min(),std::numeric_limits<int32_t>::max()}, std::mt19937(std::random_device{}()));

我进行了一些测试,以查看默认设置如何:

#include <random>
#include <functional>
#include <limits>
#include <iostream>

template<class Func>
void print_min_mean_max(Func f) {
   typedef decltype(f()) ret_t;
   ret_t min = std::numeric_limits<ret_t>::max(), max = std::numeric_limits<ret_t>::min();
   uint64_t total = 0, count = 10000000;
   for (uint64_t i = 0; i < count; ++i) {
      auto res = f();
      min = std::min(min,res);
      max = std::max(max,res);
      total += res;
   }
   std::cout << "min: " << min << " mean: " << (total/count) << " max: " << max << std::endl;
}

int main() {
   auto rnd1 = std::mt19937(std::random_device{}());
   auto rnd2 = std::default_random_engine(std::random_device{}());

   auto rnd3 = std::bind(std::uniform_int_distribution<int32_t>{}, std::mt19937(std::random_device{}()));
   auto rnd4 = std::bind(std::uniform_int_distribution<int32_t>{std::numeric_limits<int32_t>::min(),std::numeric_limits<int32_t>::max()}, std::mt19937(std::random_device{}()));

   print_min_mean_max(rnd1);
   print_min_mean_max(rnd2);
   print_min_mean_max(rnd3);
   print_min_mean_max(rnd4);
}

生成以下输出:
min: 234 mean: 2147328297 max: 4294966759
min: 349 mean: 1073305503 max: 2147483423
min: 601 mean: 1073779123 max: 2147483022
min: -2147481965 mean: 178496 max: 2147482978

从上面我们可以看出,mt19937和default_random_engine有不同的默认范围,因此建议使用uniform_int_distribution。

此外,默认的uniform_int_distribution是[0,max_int](非负数),即使使用带符号的整数类型也是如此。如果需要完整的范围,必须明确提供范围。

最后,在这种情况下,记住这一点很重要


2
值得注意的是,std::mt19937 有一个 64 位版本:std::mt19937_64,每次调用返回 64 位的随机数。auto rnd5 = std::mt19937_64(std::random_device{}()); // min: 4879020137534 mean: 1655417118684 max: 18446741225191893648 - Sean
顺便问一下,多个分布和std::bind()是否可以使用相同的随机数引擎?还是最好将每个分布绑定到新的引擎实例上?这样怎么样:std::mt19937_64 random = std::mt19937_64(std::random_device{}());``auto randomCardinality = std::bind(std::uniform_int_distribution<int>(1, 4), random); auto randomValue = std::bind(std::uniform_real_distribution<double>(-1.0, 1.0), random); - Xharlie

2
我在我的项目中使用了以下代码。'engine'和'distribution'可以是库提供的其中之一。
#include <random>
#include <functional>
#include <iostream>
...
std::uniform_int_distribution<unsigned int> unif;
std::random_device rd;
std::mt19937 engine(rd());
std::function<unsigned int()> rnd = std::bind(unif, engine);

std::cout << rnd() << '\n';

2

这里是随机生成指定范围内的双精度浮点数:

// For ints
// replace _real_ with _int_, 
// <double> with <int> and use integer constants

#include <random>
#include <iostream>
#include <ctime>
#include <algorithm>
#include <iterator>

int main()
{
    std::default_random_engine rng(std::random_device{}()); 
    std::uniform_real_distribution<double> dist(-100, 100);  //(min, max)

    //get one
    const double random_num = dist(rng);

    //or..
    //print 10 of them, for fun.
    std::generate_n( 
        std::ostream_iterator<double>(std::cout, "\n"), 
        10, 
        [&]{ return dist(rng);} ); 
    return 0;
}

2
如果您现有的代码在新标准之前是适当的,那么它将继续保持适当。新的随机数生成器是为需要更高质量的伪随机性的应用程序添加的,例如随机模拟。

0

随机数生成是一个困难的问题。没有真正随机的方法来做到这一点。如果你只是生成随机数来种子化游戏环境,那么你的方法应该是可以的。rand() 有几个缺点。

如果你需要随机性来生成加密密钥,那么你就很难了。在这种情况下,最好的方法是去操作系统中寻找机制。在 POSIX 上,这是 random()(或者如果你愿意的话,从 /dev/random 中读取)。在 Windows 上,你可以使用 CryptoAPI:

https://www.securecoding.cert.org/confluence/display/seccode/MSC30-C.+Do+not+use+the+rand%28%29+function+for+generating+pseudorandom+numbers


0
你可以使用RC4生成随机字节。这可能具有你想要的属性。它快速且相当简单易实现。当种子已知时,序列在所有实现中是可重复的,并且在种子未知时完全不可预测。http://en.wikipedia.org/wiki/RC4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接