C++中mt19937的平台相关状态?

3
我希望能在C++程序中保存std::mt19937随机数生成器的状态,以便我能够在后期以相同的“随机性状态”恢复我的程序。我还想在不同的平台上(Linux和Mac)使用我的程序。
考虑以下最小示例,其中我仅将当前状态写入stdout:
#include <iostream>
#include <random>
using namespace std;

static mt19937 rng;

int main() {
  seed_seq seeder{1234};
  rng = mt19937(seeder);

  cout << "mt1: " << rng() << endl;
  cout << "mt2: " << rng() << endl;

  cout.imbue(locale("en_US.UTF-8"));
  cout << rng << endl;
}

我的问题是,这个程序在不同平台上编译后,状态输出结果不同。在Linux系统(g++ 7.1.0)上,我会得到以下输出:

mt1: 2684129121
mt2: 3957864051
3,598,990,873 2,041,003,246 [...]

在我的Mac上(Apple LLVM 8.1.0),我得到了以下信息:

mt1: 2684129121
mt2: 3957864051 
1,413,537,266 1,230,536,264 [...]

基本上我想了解为什么状态不同以及如何实现它们相同,这样我就可以在系统之间保存和加载状态。此问题与以下问题相关:C++ std::mt19937 and rng state save/load & portability。然而,该线程并未回答我的问题。它提到使用相同的语言环境,但似乎并不影响状态。

1
我不相信标准规定了 RNG 状态的确切细节。它们留给实现。您无法在实现之间可移植地传输它。 - Igor Tandetnik
1个回答

4

由于类的实现方式取决于编译器/平台,所以你无法通过STL来实现你想要的一致性。如果你想要一个一致的实现,我建议你考虑使用Boost。它有一个实现所有随机类的STL兼容版本。(它也比STL具有一些优势,例如允许使用boost::random::random_device作为种子序列)。你也可以制作自己的类实现,尽管Boost是一个已存在的跨平台实现,你可以使用它。

编辑:

我想重新审视这个答案,并更新一些更准确的信息。当我写原始答案时,我假设标准没有规定各个引擎的operator<<operator>>的工作方式,这就是为什么它不可移植。后来我花了一些时间阅读了<random>头文件的标准(草案)文档,并了解到这是错误的。标准指定了标准库中包含的所有生成器的状态的“文本表示”,iostream运算符是基于此定义的。问题实际上是libstdc++(GCC和有时Clang的标准库实现)在这方面没有正确地实现标准。有一个未解决的问题,但它不太可能在短时间内得到解决。

实现iostream运算符的挑战在于,mersenne_twister_engine的文本表示每次调用operator()都将所有值向前移动一位。这需要使用类似循环缓冲器的东西来存储状态。libstdc++试图通过计算state_size块中状态的更改来优化此过程。这种方法的问题在于引擎大部分时间并没有直接存储iostream运算符所需的所有值。为了解决这个问题,libstdc++选择输出其实际的内部状态(state_size个状态值,后跟状态中的“当前索引”),而不是标准要求的。

有许多方法可以使libstdc ++修复其实现,以正确地实现标准,其中几乎所有方法都被其他实现使用。它可以将用于引擎的存储量增加一倍,以保留始终需要iostream运算符所需的值,如Microsoft的STL。它可以删除批量状态更新,并改为每次调用operator()时更新一次,如LLVM的libc ++。它还可以实现“倒回”功能,在需要时计算iostream运算符所需的值,如Boost.Random(这将增加计算成本,不仅适用于iostream运算符,而且适用于operator == )。所有这些更改都有相关的权衡,并且任何此类更改可能都必须经过某种过渡计划,以避免在向后兼容性方面引起太多问题。因此,即使有许多方法可以修复它,但可能要等待一段时间才能修复。


谢谢。看来唯一的选择是使用另一个库。 - TriSSSe

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