C++11中的随机数生成:如何生成,它是如何工作的?

117

我最近了解到了一种在C++11中生成随机数的新方法,但是我无法理解我读到的关于它的文章(什么是那个引擎,类似于分布的数学术语,“产生的所有整数都是等可能的”)。

那么,有人可以解释一下:

  • 它们究竟是什么?
  • 它们的含义是什么?
  • 如何生成它们?
  • 它们是如何工作的?
  • 等等

你可以把这些信息放在一个关于生成随机数的常见问题解答中。


6
如果您不知道“分布”是什么,就问关于随机数生成器(RNG)的问题就好像在不知道表达式是什么的情况下询问表达式解析器一样。C++11中的RNG库旨在面向那些了解一些统计学知识且需要比rand生成的平坦分布更高级的人群,您应该快速查看维基百科上的一些基本统计和RNG概念,否则很难向您解释<random>的合理性以及其各种组件的使用方法。 - Matteo Italia
30
很少。孩子可以理解骰子产生随机数的概念,即使不理解分布是什么。 - Benjamin Lindley
4
@Benjamin:他的理解就止步于此,这只是第一步(引擎),甚至没有理解为什么生成平坦分布很重要。在不理解分布和其他统计学概念的情况下,整个库都是一个谜团。 - Matteo Italia
2个回答

159

这个问题太广泛了,无法给出完整的答案,但让我挑选一些有趣的点:

为什么要“等可能性”

假设您有一个简单的随机数生成器,生成0、1、…、10这些数字时每个数字的概率相等(把它看作是经典的rand())。现在您想要的是一个在0、1、2范围内的随机数,且每个数字出现的概率相等。您的第一反应可能是使用 rand() % 3。但是请稍等,余数为0和1出现的频率比余数为2的频率更高,因此这种方法是不正确的!

这就是为什么我们需要适当的分布,将一组均匀分布的随机整数转换成我们想要的分布,例如上面例子中的 Uniform[0,2]。最好交给好的库去处理!

引擎

因此,在所有随机性的核心都是一个良好的伪随机数生成器,它生成一系列数字,这些数字在某个区间上服从均匀分布,并且理想情况下具有非常长的周期。标准实现的 rand() 并不经常是最好的,因此有多个选择很好。线性同余法和梅森旋转算法是两个很好的选择(LG实际上通常也被 rand()使用);同样,最好让库来处理。

它是如何工作的

很简单:首先,设置一个引擎并对其进行种子化。种子完全确定了“随机”数字的整个序列,因此a)每次使用不同的种子(例如从/ dev / urandom 中获取),b)如果您希望重新创建一系列随机选择,则需要存储种子。

#include <random>

typedef std::mt19937 MyRNG;  // the Mersenne Twister with a popular choice of parameters
uint32_t seed_val;           // populate somehow

MyRNG rng;                   // e.g. keep one global instance (per thread)

void initialize()
{
  rng.seed(seed_val);
}

现在我们可以创建分布:

std::uniform_int_distribution<uint32_t> uint_dist;         // by default range [0, MAX]
std::uniform_int_distribution<uint32_t> uint_dist10(0,10); // range [0,10]
std::normal_distribution<double> normal_dist(mean, stddeviation);  // N(mean, stddeviation)

...使用引擎生成随机数!

while (true)
{
  std::cout << uint_dist(rng) << " "
            << uint_dist10(rng) << " "
            << normal_dist(rng) << std::endl;

}

并发性

更重要的一点是,与传统的 rand() 相比,更倾向于使用 <random> 的原因在于,现在很清楚和明显如何使随机数生成成为线程安全的:要么为每个线程提供其自己的、线程本地的 engine,种子为线程本地的种子; 要么同步访问 engine 对象。

其他

  • Codeguru 上有一篇有趣的文章,讲述了 TR1 随机数。
  • Wikipedia 提供了一个很好的总结(感谢 @Justin)。
  • 理论上,每个 engine 应该 typedef 一个 result_type,这是用于种子的正确整数类型。 我想我曾经有过一个有 bug 的实现,强制我在 x64 上将 std::mt19937 的种子强制为 uint32_t,最终应该修复这个问题,你可以说 MyRNG::result_type seed_val,从而使 engine 可以非常容易地替换。

2
再次,Kerrek比我更快地给出了一个比我正在思考的更好的答案。+1 - Justin ᚅᚔᚈᚄᚒᚔ
15
针对“populate somehow”这部分,我认为值得提及的是std::random_device而非/dev/urandom - Cubbi
2
可以在此处找到有关std::random_device的示例:http://en.cppreference.com/w/cpp/numeric/random/random_device。 - W.K.S
请查看这个有关主题的演示文稿链接 - Pedru
1
维基百科文章中的代码有漏洞。random和random2是相同的。从代码片段的注释中可以清楚地看出,作者不理解如何使用<random>中的功能。 - user515430
显示剩余3条评论

5
一个随机数生成器是一个方程,它会给你一个新的数字,而这个数字通常要么是由你提供的第一个数字,要么是从系统时间等类似来源中获取。
每次你请求一个新的数字时,它都会使用前一个数字来执行这个方程。
如果一个随机数生成器倾向于产生与其他数字相比更多的相同数字,那么它就不被认为是非常好的。例如,如果你想要一个1到5之间的随机数,并且你有以下数字分布:
1: 1% 2: 80% 3: 5% 4: 5% 5: 9%
那么2就比其他数字更容易被产生出来。如果所有数字都是平等的,那么每次获得每个数字的概率都是20%。换句话说,上述数字分布非常不均匀,因为2更受青睐。所有数字都是20%的数字分布是均匀的。
通常,如果你想要一个真正的随机数,你会从天气或其他自然来源中获取数据,而不是从随机数生成器中获取。

8
大多数随机数生成器可以产生良好的均匀分布,但它们并非真正随机,因为它们是经过计算得出的,因此如果给定足够的序列数字,你可以猜测下一个数字(这一事实使它们不适用于需要真正随机数的安全性问题)。但对于游戏等领域,它们仍然可以使用。 - Martin York
7
我非常确定OP正在询问有关C++ <random>头文件中提供的特定设施的信息。这个答案甚至没有涉及编程,更不用说C++了。 - Benjamin Lindley
1
@Martin:安全性并不一定需要真正随机数的来源。例如,AES在计数器模式下可以很好地完成任务,即使它是确定性的。它需要合理数量的密钥熵,但不需要任何真正的随机性。 - Jerry Coffin
@Benjamin Lindley:没事了,我重新读了一遍,意识到我错了。 - N_A

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