在C++中使用随机
Use of random in C++
这些代码片段在"随机性"方面是等价的吗?
1)
std::vector<int> counts(20);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 19);
for (int i = 0; i < 10000; ++i) {
++counts[dis(gen)];
}
2)
std::vector<int> counts(20);
std::random_device rd;
std::mt19937 gen(rd());
for (int i = 0; i < 10000; ++i) {
std::uniform_int_distribution<> dis(0, 19);
++counts[dis(gen)];
}
3)
std::vector<int> counts(20);
std::random_device rd;
for (int i = 0; i < 10000; ++i) {
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 19);
++counts[dis(gen)];
}
4)
std::vector<int> counts(20);
for (int i = 0; i < 10000; ++i) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 19);
++counts[dis(gen)];
}
在 std::random_device 的文档中,据说多个 std::random_device 对象可能会生成相同的数字序列,所以代码 4 是坏的,不是吗?
而对于其他代码?
如果我需要为多个不相关的东西生成随机值,我是否需要创建不同的生成器,或者我可以保持相同吗?
1)
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> disInt(0, 10);
std::uniform_float_distribution<> disFloat(0, 1.0f);
// Use for one stuff
disInt(gen);
// Use same gen for another unrelated stuff
disFloat(gen);
2)
std::random_device rd1, rd2;
std::mt19937 gen1(rd1()), gen2(rd2());
std::uniform_int_distribution<> disInt(0, 10);
// Use for one stuff
disInt(gen1);
// Use another gen for another unrelated stuff
disFloat(gen2);
随机生成器的目的是保持算法的状态,以便基于特定的随机种子生成可重复的伪随机数字序列。
随机设备的要点是为随机生成器提供随机种子。
如果您尝试为每个随机值设定新的生成器种子,则不再执行随机生成器算法提供的随机性。相反,您将生成器偏向于依赖于随机设备本身的随机性。
因此,不建议使用示例 #3 和 #4。
生成随机序列的正确方法是示例 #1:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 19);
for (int i = 0; i < 10000; ++i) {
int foo = dis(gen);
}
示例 #2 也是正确的,但是在循环中构造uniform_int_distribution
有点毫无意义。 当然,通过编译器优化,它并没有真正的伤害,为了清楚起见,有时最好将发行版保持在使用它的位置附近。
如果我需要为多个不相关的东西生成随机值,我是否需要创建不同的生成器,或者我可以保持相同吗?
如果你愿意,欢迎你为不相关的随机序列使用多个生成器——这实际上是他们的主要吸引力之一。 如果在生成其他序列时未使用特定序列的生成器(最明显的是从序列中提取数字时),则可以保留特定序列的伪随机算法的随机性保证。
这对于可重复性也很有用:例如,当您实际具有特定的种子值(而不是从随机设备中提取它)时,将该种子用于一个特定序列会给出可重复的结果,而不管同时使用任何其他序列。
另一个主要好处是,通过使用单独的生成器,您可以获得适用于其他对象的相同线程安全保证。 换句话说,这意味着如果你想同时生成多个伪随机序列,你可以在没有锁的情况下这样做,前提是每个线程都在单独的生成器上运行。
正如您正确提到的,std::random_device
可能总是返回相同的序列。这尤其发生在 MinGW 上,在使用std::random_device
的任何程序的多次运行中,行为都是完全确定的。
std::mt19937
和std::uniform_int_distribution
的行为是确定性的,因为它们的输入。因此,在 MinGW 上,所有四个片段的随机性同样糟糕,每个片段将始终返回相同的序列(尽管每个片段可能是一个不同的序列)。
如果您担心这一点,请使用std::chrono::high_resolution_clock
来初始化std::mt19937
,代替或与std::random_device
结合使用。
random_device
用法
前两个循环是完全等效的,因为uniform_int_distribution
类型是无状态的(所有此类分布也是如此)。 第二个可能稍微慢一些:我看到一个多余的堆栈存储,-O3
带有 GCC 或 Clang。
对于基于/dev/random
之类的常见random_device
实现,后两个循环也是等价的:random_device
只是一个句柄,并且访问同一个熵池,而不考虑干预破坏和重新初始化。 但是,实现可以保留一个种子不需要的位,并将其用于另一个种子。 这当然更难测试,因为状态是故意不可重现的。 (Majk 是正确的,random_device
的确定性实现使最后一个循环仅生成单个值。
重复使用random_device
前两者通常是首选:从单个种子生成许多随机数是 PRNG 的重点,放弃它可能会在常见安装中迅速耗尽熵池。 根据实现,您的进程可能会阻止(广泛)等待更多熵,或者可能会回退到操作系统提供的 PRNG。 无论哪种情况,你都剥夺了其他过程的真正熵。
无论如何,在这里使用mt19937
并没有增加太多:random_device
已经可以直接与uniform_int_distribution
一起使用,但有一个小缺点,即(很少)多次轮询random_device
以获得超过 20 个值的均匀分布(因为这不是 2 的幂)。
不同的流
将一个生成器用于各种分布(交错或无)是完全合理的。 在某些情况下,您需要使用单独的线程,通常具有多个线程或想要控制种子设定。 作为后者的一个例子,您可以根据具有特定种子的 PRNG 定义某种过程内容生成。 如果在生成过程中需要(或在未来版本中需要)其他随机数,则为它们使用单独的生成器允许内容生成器在用于任何此类额外随机数用法时具有相同的功能。
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- 为什么 Serial.println(<char[]>);返回随机字符?
- 字符串-C++后显示的随机字符
- 循环中的随机函数
- 在c++构造函数中使用随机字符串生成器
- 使用std::mt19937从字符串中返回一个随机单词
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- 如何在C++中高效地构造随机骰子
- 在类中使用随机生成器时出现性能问题
- 在将数字随机生成为数组期间从内存输出随机数的数组
- 将字符随机转换为大写的函数
- 为什么 vector 的随机访问迭代器给出与指针不同的内存地址?
- 如何生成一个随机的 n 位数,其中 n 是任意的
- 将随机生成的数字添加到数组 + 对这些数组求平均值
- 如何使用要传递给 mt19937 的可选随机种子参数设计函数
- 在C++中随机生成 20 个非重复数字
- GCC:随机构建导致执行期间分段错误
- 如何使用 SML 随机生成八进制元组
- 当我尝试使用它时,Scanf 会抛出一个随机异常(scanf_s 也是如此)
- 将正态随机变量与任意 RHO(corrcoef) 相关联