我应该使用从std::random_device种子化的随机引擎还是每次都使用std::random_device?

23

我有一个包含两个随机数源的类。

std::random_device rd;
std::mt19937 random_engine;
我使用调用std::random_device来种下std::mt19937。如果我想产生一个数,我不在乎它是否可重复,那应该调用rd()还是random_engine()呢?
在我的特定情况下,我相信两个都可以正常工作,因为这将在一些网络编码中被调用,其中性能并不占主导地位,而结果也不是特别敏感。然而,我对何时使用硬件熵和何时使用伪随机数感兴趣的一些“经验法则”。
目前,我只使用std::random_device来种植我的std::mt19937引擎,以及我需要的任何随机数生成器,我使用std::mt19937引擎。
编辑:这里是关于我在这个例子中使用的确切说明:
这是一个游戏程序。这个特定的游戏允许用户在与对手开始回合之前自定义他们的“团队”。设置一个战斗的一部分涉及将一个团队发送到服务器。我的程序有几个团队,并使用随机数确定要加载哪个团队。每次新的战斗都会调用std::random_device来种子伪随机数生成器。我记录战斗的初始状态,其中包括我随机选择的这个团队和初始种子。
我在这个问题中询问的特定随机数是用于随机团队选择(在这种情况下,不让对手提前知道我将使用哪个团队是有利的,但不是关键任务),但我真正寻求的是一个经验法则。如果我不需要数字的可重复性,总是使用std::random_device可以吗?或者使用熵的风险比它能够收集的更快吗?
6个回答

11
据我所知,标准做法是用一个不由计算机计算(而是来自某个外部、不可预测的源)的数字来初始化随机数生成器。这应该是你的rd()函数的情况。从那时起,你需要调用伪随机数生成器(PRNG)来获得每一个伪随机数。
如果你担心数字不够随机,则应选择另一个PRNG。熵是一种稀缺且珍贵的资源,应该像对待宝贵物品一样对待。尽管你现在可能不需要那么多随机数,但将来可能会需要,或者其他应用程序可能需要它们。你希望当应用程序要求熵时,它始终可用。
对于你的应用程序来说,听起来mersenne twister完全可以满足你的需求。没有人玩你的游戏会觉得加载的队伍不是随机的。

9

如果您需要用于模拟或游戏的随机性,那么您现在做的还可以。只需调用一次随机设备,并使用随机种子生成的伪随机数生成器进行其他所有操作。作为奖励,您应将种子值存储在日志文件中,以便稍后重播伪随机序列:

auto const seed = std::random_device()();
// save "seed" to log file
std::mt19937 random_engine(seed);

(对于多线程,使用主线程中的PRNG生成种子,以供生成子线程中的PRNG。)
如果您需要大量用于加密目的的真随机性,则PRNG从来不是一个好主意,因为长序列的输出包含的随机性比真正的随机性少得多,即您可以从小的子集中预测所有输出。如果您需要真正的随机性,应该从某些不可预测的源(例如热传感器、用户键盘/鼠标活动等)收集它。Unix的/dev/random可能是这样的“真随机性”源,但它可能填充得不太快。

通常情况下,你不能从一个伪随机数生成器中使用一小组随机数来推断信息。对于许多情况是可以的,但并非全部。 - Johan Lundberg
@JohanLundberg:好的,说得对——我想这取决于你所说的“小”是什么意思 :-) - Kerrek SB
@KerrekSB,我认为你代码的第一行应该是 auto const seed = std::random_device()();,而不是 std::random_engine()() - Ela782

8
如果您不使用它进行加密,那么重复使用由random_engine种子生成的mt19937是可以的。对于本答案的其余部分,我假设您正在使用随机数来加密网络编码。简而言之,mt19937不适用于该用途。

http://en.wikipedia.org/wiki/Mersenne_twister#Disadvantages

有潜在风险,您可能会逐渐泄露信息(可能是间接的),以至于攻击者可以开始预测随机数。至少从理论上讲,但这就是问题所在。来自维基百科。

...因为这个数字是从中产生未来迭代的状态向量的大小)允许人们预测所有未来的迭代。

防止随机数生成信息泄露给用户的简单方法是使用单向哈希函数,但实际上还有更多内容。您应该使用专为此目的设计的随机数生成器:

http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator

在这里可以找到各种示例(带有代码)http://xlinux.nist.gov/dads/HTML/pseudorandomNumberGen.html


3

1
标准并不要求random_device具有加密安全性。因此,在Windows上使用Visual C++实现时,它是以这种方式实现的,但在其他平台上可能(并且确实)不是。我们可以认为不将其加密安全是有点愚蠢的,但是... - user7610

2
答案取决于平台。我记得在使用Visual C++ 2010时,std::random_device只是以某种未经记录的方式种子化的mt19937。
当然,你应该意识到,基于随机数生成器的任何临时加密方案都很容易被攻破。

-2

假设这不是为了加密目的,关于随机数生成最重要的是要考虑你希望产生的随机数的分布方式和期望的范围。

通常库中的标准随机数生成器被设计为给出均匀分布。因此,随机数将在0到某个RAND_MAX(例如在32位机器上为2^31-1)之间变化。

现在需要记住的事情是,伪随机数生成器大多数都是为生成随机数而非随机位而设计的。这两者之间的差别微妙。如果你需要0到8之间的数字,大多数程序员会说 rand() % 8,但这种方法是错误的,因为该算法用于随机化32位,而你仅使用了底部的3位。这样做是不好的,因为它不能给出均匀分布(假设这就是你想要的)。

你应该使用 8 * (rand() + 1) / (RAND_MAX),这样会给你一个在0到8之间均匀随机的数字。

现在有了硬件随机数生成器,您可以获得产生的随机位。如果确实是这种情况,那么每个位都是独立生成的。那么它更像是一组相同的独立随机位。这里的建模会有所不同。要记住这一点,特别是在模拟中分布变得重要。

2
关于低/高位的问题,这对于旧的LCG随机数生成器是正确的,但对于现代的随机数生成器可能不相关。这不是Mersenne Twister的问题,而OP正在使用它。 - interjay
1
我正在使用C++11标准库功能std::uniform_int_distribution来生成我的分布,所以我已经涵盖了这部分内容。 - David Stone
2
此外,您的公式 8 * (rand() + 1) / (RAND_MAX) 是不正确的(它会导致整数溢出。即使没有这种情况,返回8的可能性也非常小)。而且,在使用C++11随机数库时,RAND_MAX并不是限制,只有在使用rand()时才是。 - interjay
好的,我在示例中使用了rand()而不是C++11的随机数生成器。因此,在那里RAND_MAX适用。但我同意他所说的是关于C ++随机数生成器的。 - av501

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