<ランダム>はLinuxでは同じ数を生成しますが、Windowsでは生成しません


90

以下のコードは、間隔[1,100]の5つの疑似乱数のリストを生成することを目的としています。default_random_enginewith をシードしtime(0)ます。これは、システム時間をUNIX時間で返します。Microsoft Visual Studio 2013を使用してWindows 7でこのプログラムをコンパイルして実行すると、期待どおりに動作します(以下を参照)。しかし、g ++コンパイラーを備えたArch Linuxでそうすると、奇妙な動作をします。

Linuxでは、5つの数値が毎回生成されます。最後の4つの数値は実行ごとに異なります(よくあることです)、最初の数値は同じままです。

WindowsおよびLinuxでの5回の実行の出力例:

      | Windows:       | Linux:        
---------------------------------------
Run 1 | 54,01,91,73,68 | 25,38,40,42,21
Run 2 | 46,24,16,93,82 | 25,78,66,80,81
Run 3 | 86,36,33,63,05 | 25,17,93,17,40
Run 4 | 75,79,66,23,84 | 25,70,95,01,54
Run 5 | 64,36,32,44,85 | 25,09,22,38,13

謎に加えて、その最初の数はLinuxでは定期的に1ずつ増加します。上記の出力を取得した後、約30分待って、最初の数値が変更され、常に26として生成されていることを再度確認しました。定期的に1ずつ増加し続け、現在は32になっています。の値が変化しますtime(0)

最初の数値が実行間でめったに変化しないのはなぜですか?その後、変化すると、1ずつ増加しますか?

コード。5つの数値とシステム時刻をきちんと出力します。

#include <iostream>
#include <random>
#include <time.h>

using namespace std;

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    time_t system_time = time(0);    

    default_random_engine e(system_time);
    uniform_int_distribution<int> u(lower_bound, upper_bound);

    cout << '#' << '\t' << "system time" << endl
         << "-------------------" << endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);
        cout << secret << '\t' << system_time << endl;
    }   

    system("pause");
    return 0;
}

3
sizeof(time_t)対は何sizeof(default_random_engine::result_type)ですか?
Mark Ransom

3
default_random_engineこれらの2つのプラットフォームでは完全に異なることに注意してください。
TC

1
ランダムなBTWでもかまいません。
Alec Teal 2015

5
すべてのプログラマーは、時間は良い乱数生成シードであると考えるフェーズを通過しますか?
OldFart 2015

6
@OldFartはい、それは学界と呼ばれています。
ケーシー

回答:


141

これが起こっていることです:

  • default_random_enginelibstdc ++(GCCの標準ライブラリ)minstd_rand0では、単純な線形合同エンジンである:

    typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
  • このエンジンが乱数を生成する方法は、x i + 1 =(16807x i + 0)mod 2147483647です。

  • したがって、シードが1異なる場合、ほとんどの場合、最初に生成された数は16807異なります。

  • このジェネレータの範囲は[1、2147483646]です。libstdc ++が[1、100 uniform_int_distribution]の範囲の整数にマップする方法は、基本的にこれですn。数値を生成します。数が2147483600以下の場合は、を返し(n - 1) / 21474836 + 1ます。それ以外の場合は、新しい番号で再試行してください。

    ほとんどの場合、2つの nこの手順では、16807だけ異なるのが[1、100]で同じ数にです。実際、生成された数は、約21474836/16807 = 1278秒または21.3分ごとに1ずつ増えると予想されます。これは、観測結果とかなり一致しています。

MSVCのがdefault_random_engineあるmt19937。この問題を持っていません、。


36
このような恐ろしいデフォルトを選択するために、GCCの標準ライブラリの開発者が何を所有していたのでしょうか。
CodesInChaos

13
@CodesInChaosそれが関連しているかどうかはわかりませんが、MacOS / iOSツールチェーンも同じ恐ろしいランダムエンジンを使用しているため、rand()%7は常に0を返します
phuclv

7
@LưuVĩnhPhúc修正しないことrand()はいくぶん理解できます(それは絶望的なレガシーがらくたです)。何か新しいものにたわごと層のPRNGを使用することは許されません。標準では「比較的カジュアルで、専門家ではない、または軽量な使用のために、少なくとも許容できるエンジン動作を提供する」必要があるため、これを標準違反と見なすこともできます。これは、rand % 7例のような些細なユースケースでも破滅的に失敗するため、この実装では提供されません。
CodesInChaos

2
@CodesInChaosなぜ修正rand()が正確にある程度理解できないのですか?それは誰もそれをすることを考えていなかったかもしれないからだけですか?
user253751 2015

2
@immibis APIが非常に壊れているため、すべての問題を修正する独立した置き換えを使用したほうがよいでしょう。1)アルゴリズムを置き換えることは重大な変更になるため、古いプログラムには互換性スイッチが必要になるでしょう。2)のシードsrandが小さすぎて、固有のシードを簡単に生成できません。3)実装が定義した上限を備えた整数を返します。これは、呼び出し元が何らかの方法で望ましい範囲の数に減らす必要があります。適切に行われると、適切なAPIを使用した置換を作成するよりも多くの作業が必要ですrand()4)グローバルな可変状態を使用します。
CodesInChaos

30

std::default_random_engine実装定義されています。std::mt19937またはstd::mt19937_64代わりに使用します。

さらにstd::timectime関数はあまり正確ではありません。<chrono>代わりにヘッダーで定義されたタイプを使用してください:

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();

    std::mt19937 e;
    e.seed(static_cast<unsigned int>(t)); //Seed engine with timed value.
    std::uniform_int_distribution<int> u(lower_bound, upper_bound);

    std::cout << '#' << '\t' << "system time" << std::endl
    << "-------------------" << std::endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);

        std::cout << secret << '\t' << t << std::endl;
    }   

    system("pause");
    return 0;
}

3
疑似ランダム変数ジェネレータをシードするときは、より正確な時間を使用することが望ましいですか?おそらくこれはナイーブですが、エントロピーを導入する場合は不正確さがほとんど望ましいかもしれないと感じています。(正確性が低く、結果として潜在的なシードが実質的に少なくなるという意味でない限り)
Nat

15
std::random_deviceランダムジェネレーターのシードには、current_timeの代わりに使用することをお勧めします。ランダムに関するcppreferenceの例を確認してください。
Aleksander Fular 2015

5
誰かにシードを推測させたくない(したがってシーケンスを再現したくない)場合、精度を下げることはランダム性を高めることと同じではありません。極端に行きましょう:シードを翌日(または年?)に丸めます->推測は簡単です。フェムト秒の精度を使用する->多くの推測を行う...
linac

2
@ChemicalEngineerの粒度ctimeは1秒です。std::chrono実装の粒度はユーザー定義であり、デフォルトではstd::high_resolution_clock(Visual Studioではtypedefですstd::steady_clock)ナノ秒ですが、はるかに小さい測定値を選択できるため、はるかに正確です。
ケーシー

2
@linac暗号化プロパティが必要な場合は、適切なprngを使用します(この回答では使用されていません)。もちろん、約束された精度に関係なく、時間ベースのシードも問題外です。
クトゥルフ

-2

Linuxでは、ランダム関数は確率論的な意味でのランダム関数ではなく、疑似乱数ジェネレーターです。これはシードでソルト処理され、そのシードに基づいて、生成される数値は疑似ランダムで均一に分散されます。Linuxの方法には、母集団からの情報を使用する特定の実験の設計において、入力情報の既知の微調整による実験の繰り返しを測定できるという利点があります。最終的なプログラムが実際のテストの準備ができたら、ソルト(シード)は、ユーザーにマウスを動かして、マウスの動きをいくつかのキーストロークと混合し、マイクロ秒カウントのダッシュを追加して、最後の電源投入。

Windowsの乱数シードは、マウス、キーボード、ネットワーク、時刻のコレクションから取得されます。再現性はありません。しかし、上記のように実験の計画に関与している場合、この塩の値は既知のシードにリセットされる可能性があります。

Linuxには2つの乱数ジェネレーターがあります。1つは、デフォルトがモジュロ32ビットで、もう1つはモジュロ64ビットです。どちらを選択するかは、テストまたは実際の使用のために消費したい精度のニーズと計算時間に依存します。


5
なぜシード生成アルゴリズムについて話しているのかわかりません。OPは明らかにシステム時刻をシードとして使用します。また、いくつかの参照を追加できますかcollection of mouse, keyboard, network and time of day numbers
デフォルトのロケール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.