在C++中使用随机

Use of random in C++

本文关键字:随机 C++      更新时间:2023-10-16

这些代码片段在"随机性"方面是等价的吗?

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::mt19937std::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 定义某种过程内容生成。 如果在生成过程中需要(或在未来版本中需要)其他随机数,则为它们使用单独的生成器允许内容生成器在用于任何此类额外随机数用法时具有相同的功能。