C++のmt19937は環境非依存だがdistributionは環境依存

  • 18
    Like
  • 2
    Comment
More than 1 year has passed since last update.

常に同じ乱数を使いたいという状況がある。例えば、テストケースをランダムに生成してテストしているときに「123番目の結果がおかしい」と共有できると便利だし、論文の実験プログラムなどは再現性が重要なので同じプログラムを他の人のところで動かしたら厳密に同じ結果が出てほしい。

Cのrand()はこの条件を満たさない。srand()に同じ値を与えて初期化すれば、環境が同じならば常に同じ値が返ってくるが、別の環境では値が異なる。

#include <iostream>
#include <cstdlib>
using namespace std;

int main()
{
    srand(12345678);
    for (int i=0; i<8; i++)
        cout<<rand()<<endl;
}

をVisual Studio 2013でコンパイルして実行すると、

11188
29462
10945
26170
10388
28330
29356
10952

となるが、Linux上のgccやclangでコンパイルすると、

1077711487
1795553069
1809916401
67351997
301842236
286692564
1716894219
648068293

となる。乱数の値どころか、値の範囲まで異なる。これは困る。

C++では便利な<random>が追加された。その中にはmt19937というメルセンヌ・ツイスタの実装がある。アルゴリズムも決まっているし各種パラメタも決まっているのでどの環境でも同じ値が返ってくる。

このプログラム

#include <iostream>
#include <random>
using namespace std;

int main()
{
    mt19937 rand(12345678);
    for (int i=0; i<8; i++)
        cout<<rand()<<endl;
}

は、Visual Studioでもgccでもclangでも

1055721139
3422054626
2561641375
1376353668
1540998321
825546192
1627406507
1797302575

を出力する。素晴らしい。

実際に乱数を使うときには、特定の範囲の乱数が欲しい。単なる一様分布であっても自分で書くとなると大変。0以上n未満の一様分布なら%nで良さそうなものだけど、nが大きくなると誤差が出てくる。例えば、mt19937の場合、最大値が0xffffffffなので、n=0xffffffffとすると0だけ他の値の2倍出やすくなる。<random>には種々の分布に従った乱数を得られるクラスが用意されているので、車輪の再発明をせずにこれを使えば良い。例えば、一様分布uniform_int_distributionで1から6の値を得るには次のように書く。

#include <iostream>
#include <random>
using namespace std;

int main()
{
    mt19937 rand(12345678);
    uniform_int_distribution<int> dist(1,6);

    for (int i=0; i<8; i++)
        cout<<dist(rand);
    cout<<endl;
}

ところが、残念なことに、この実行結果は環境によって異なる。

Visual Studio 2013

25214162

gcc 6.0

25423233

clang 3.9

43521421

C++の仕様では決まっていないらしい。以前に江添さんにちらっと訊いてみたところ、仕様にするほど決まり切った方法が無いのでは?とのことだった。悲しい。

再現性が重要なプログラムでは分布は自前で用意する必要がある。