从 <random> 重复重新播种 PRNG 是不可重现的

kai*_*ong 6 c++ random-seed mt19937

<random>C++中有确定性随机数生成器吗?

有问题的一点是我的 Windows 机器上的以下代码:

#include<iostream>
#include<random>
int main(){
  std::mt19937 g;
  std::normal_distribution<double> d;
  for(int i=0;i<100;++i){
    g.seed(65472381);
    std::cout << "List[65472381] = " << d(g) << "\n";
  }
}
Run Code Online (Sandbox Code Playgroud)

产生以下结果:

List[65472381]=0.972683
List[65472381]=-0.773812
List[65472381]=0.972683
List[65472381]=-0.773812
List[65472381]=0.972683
List[65472381]=-0.773812
...
Run Code Online (Sandbox Code Playgroud)

我的困惑在于,尽管使用了之前每次0.972683 != -0.773812重置的种子。65472381g

我的处理器是Zen 2,操作系统是 Windows 10 Pro,版本 22H2。编译器是GCC(x86_64-w64-mingw32/12.2.0)。但通过在不同的虚拟机和编译器上在线测试代码,结果似乎在您的计算机上也可能是相同的。

真正寻求的是一种从任意固定通用列表中获取第 i 个数字的方法,该通用列表长度为 4,294,967,295,随机分布的数字在 SPACETIME O(1) 中,这意味着该列表中没有任何元素被存储。

tbx*_*are 12

分发对象具有内部状态。因此,在重新设置随机数引擎种子后,必须重置分布以清除其内部状态。对于标准库中的所有引擎和发行版都是如此。一般来说,当您为引擎播种时,您还应该重置发行版。

#include<iostream>
#include<random>

int main() {
    std::mt19937 g;
    std::normal_distribution<double> d;
    for (int i = 0; i < 10; ++i) {
        g.seed(65472381);
        d.reset();
        std::cout << "List[65472381] = " << d(g) << "\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

这是输出:

List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
List[65472381] = -0.773812
Run Code Online (Sandbox Code Playgroud)
Mersenne Twister的高效discard功能?

哪些 C++ 随机数引擎具有 O(1) 丢弃函数?

此 Stack Overflow 问题的答案解释了 Mersenne Twister 有一个“快速跳转”算法。这意味着高效的discard功能是可能的。不幸的是,C++ 标准并不强制要求实现使用它。

您可能会发现您的系统具有高效的discard. 但是,您不能假设每个系统都会这样做。

如果您决定使用discard,请务必随后重置发行版。否则,不能保证分布生成的值是可重复的。

std::mt19937便携式的吗?

正如我在下面的评论中指出的,C++ 标准要求std::mt19937可移植。它必须在每次实现时生成相同的随机数序列。

我使用以下程序从 生成 10 个值std::mt19937。我用最新版本的 MSVC 运行它。如果您在您的系统上运行它,您应该得到相同的输出。

#include<iostream>
#include<random>
int main() {
    std::mt19937 g;
    unsigned seed{ 65472381u };
    g.seed(seed);
    std::cout << "Seed : " << seed << "\n\n";
    for (int i = 0; i < 10; ++i) {
        std::cout << "g() : " << g() << "\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

这是输出。

Seed : 65472381

g() : 3522518833
g() : 1238868101
g() : 1353561095
g() : 3289615924
g() : 1455032182
g() : 573730142
g() : 700682001
g() : 2371867773
g() : 3721872455
g() : 2742745620
Run Code Online (Sandbox Code Playgroud)
随机数分布不可移植

无论好坏,C++ 标准并不要求std::normal_distribution每个实现中的分布都相同。一般来说,它们不便于携带。

  • C++ 标准要求“std::mt19937”在每个实现中都具有相同的行为。不幸的是,它对任何随机数分布都没有这样的保证,包括“std::normal_distribution”。因此,“std::mt19937”是可移植的,但“std::normal_distribution”则不是。 (2认同)