提问人:kaisong 提问时间:9/1/2023 最后编辑:Peter Mortensenkaisong 更新时间:9/5/2023 访问量:121
从<随机>重复重新播种 PRNG 是不可重现的
Repeated reseeding of a PRNG from <random> isn't reproducible
问:
C++中有确定性随机数生成器吗?<random>
有问题的一点是,我的 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";
}
}
产生以下结果:
List[65472381]=0.972683
List[65472381]=-0.773812
List[65472381]=0.972683
List[65472381]=-0.773812
List[65472381]=0.972683
List[65472381]=-0.773812
...
我的困惑在于,尽管它之前每次都会重置为种子。0.972683 != -0.773812
65472381
g
我的处理器是 Zen 2,操作系统是 Windows 10 Pro 版本 22H2。编译器是 GCC (x86_64-w64-mingw32/12.2.0)。但是,通过在不同的虚拟机和编译器上在线测试代码,结果似乎在您的计算机上也可能是相同的。
我实际上寻求的是一种从时空 O(1) 中随机分布数的任意固定通用列表中获取第 i 个数的方法,该列表长度为 4,294,967,295,这意味着该列表的任何元素都不会被存储。
答:
每个生成器(除了可能的 std::random_device
)在C++上都是确定性的。
但是,发行版可以(并且确实)保持状态。在我的编译器上,调用生成器 4 次并使用它来生成 2 个数字。对于以下代码:std::normal_distribution
#include <iostream>
#include <random>
std::mt19937 g;
struct MyGenerator {
auto operator()() {
std::cout << "I'm called!\n";
return g();
}
static auto max() { return g.max(); }
static auto min() { return g.min(); }
};
int main() {
std::normal_distribution<double> d;
MyGenerator gen;
g.seed(65472381);
std::cout << "List[0] = " << d(gen) << "\n";
g.seed(65472381);
std::cout << "List[1] = " << d(gen) << "\n";
g.seed(65472381);
std::cout << "List[2] = " << d(gen) << "\n";
}
我得到以下输出:
List[0] = I'm called!
I'm called!
I'm called!
I'm called!
0.972683
List[1] = -0.773812
List[2] = I'm called!
I'm called!
I'm called!
I'm called!
0.972683
这解释了您看到的输出。每一秒调用 不会请求任何新的熵,而是依赖于先前生成的熵。d(g)
g
您可以在分布对象上调用 reset()
方法来重置它所持有的任何内部状态,并确保在生成器具有相同值的种子时获得一致的结果。
在 O(1) 时间内(可能)不可能获得第 i 个数。您可以在引擎上调用 discard()
方法来跳过它生成的一些熵,但它有 2 个问题:std::mt19937
- 不能保证它会比循环快(可能是也可能不是,不能保证)
- 你实际上不知道你的发行版需要多少熵,所以你不知道要丢弃多少才能达到它产生的第i个数,这都是你的标准库实现的内部行为,可以随时改变
评论
分发对象具有内部状态。因此,在重新设定随机数引擎的种子后,必须重置分布以清除其内部状态。对于标准库中的所有引擎和发行版都是如此。通常,在为引擎播种时,还应该重置分配。
#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";
}
}
输出如下:
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
Mersenne Twister的高效功能?discard
这个 Stack Overflow 问题的答案解释了 Mersenne Twister 有一个“快速跳跃”算法。这意味着高效的功能是可能的。不幸的是,C++ 标准并没有强制要求实现使用它。discard
您可能会发现您的系统是一个具有高效 .但是,您不能假设每个系统都会这样做。discard
如果您决定使用 ,请务必在之后重置分配。否则,不能保证分布生成的值是可重复的。discard
是便携的吗?std::mt19937
正如我在下面的评论中指出的那样,C++标准要求可移植。它必须在每个实现中生成相同的随机数序列。std::mt19937
我使用以下程序从 生成 10 个值。我使用最新版本的 MSVC 运行它。如果在系统上运行它,则应获得相同的输出。std::mt19937
#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";
}
}
这是输出。
Seed : 65472381
g() : 3522518833
g() : 1238868101
g() : 1353561095
g() : 3289615924
g() : 1455032182
g() : 573730142
g() : 700682001
g() : 2371867773
g() : 3721872455
g() : 2742745620
随机数分布不可移植
无论好坏,C++ 标准并不要求每个实现中的发行版都相同。一般来说,它们不便携。std::normal_distribution
评论
std::mt19937
std::normal_distribution
std::mt19937
std::normal_distribution
评论