テストアプリケーションの最も基本的な問題は、1回srand
呼び出してからrand
1回呼び出して終了することです。
srand
関数の全体の要点は、疑似乱数のシーケンスをランダムシードで初期化することです。
つまり、同じ値をsrand
2つの異なるアプリケーションに(同じsrand
/ rand
実装で)渡すと、両方のアプリケーションでまったく同じrand()
値のシーケンスが読み取られます。
ただし、例のアプリケーションでは、疑似ランダムシーケンスは1つの要素(現在のsecond
精度の時間に等しいシードから生成された疑似ランダムシーケンスの最初の要素)のみで構成されています。次に、出力で何を期待しますか?
明らかに同じアプリケーションを同じ秒で実行した場合、同じシード値を使用しているため、結果はもちろん同じです(Martin Yorkは既に質問へのコメントで言及しています)。
実際には、srand(seed)
一度呼び出してからrand()
何度も呼び出して、そのシーケンスを分析する必要があります-ランダムに見えるはずです。
編集:
わかりました。どうやら言葉による説明だけでは不十分です(おそらく言語の壁か何か... :))。
OK。srand()/rand()/time()
質問で使用されたのと同じ関数に基づく昔ながらのCコードの例:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ THATプログラムの単一の実行からのシーケンスがランダムに見えるようになっています。
EDIT2:
CまたはC ++標準ライブラリを使用する場合、現時点では実際にはランダムなデータを決定的に生成する単一の標準関数またはクラスがないことを理解することが重要です(標準によって保証されています)。この問題に対処する唯一の標準ツールはstd :: random_deviceですが、残念ながら実際のランダム性は保証されていません。
アプリケーションの性質に応じて、本当に本当にランダムな(予測できない)データが本当に必要かどうかを判断する必要があります。真のランダム性が最も確実に必要となる注目すべきケースは、情報セキュリティです。たとえば、対称キー、非対称秘密キー、ソルト値、セキュリティトークンなどを生成します。
ただし、セキュリティグレードの乱数は、別の記事に値する別の業界です。
ほとんどの場合、疑似乱数ジェネレーターで十分です-科学シミュレーションやゲームなど。場合によっては、一貫して定義された疑似ランダムシーケンスが必要になることもあります。たとえば、ゲームでは、実行時にまったく同じマップを生成して、大量のデータを保存しないようにすることができます。
元の質問と繰り返し発生する同一/類似の質問(およびそれらに対する多くの誤った「答え」さえ)は、何よりもまず、乱数を疑似乱数から区別し、疑似乱数シーケンスが何であるかを理解することが重要であることを示しています。そもそもAND疑似乱数ジェネレータは、真の乱数ジェネレータと同じように使用されていないことを理解するために。
直感的に乱数を要求した場合-返される結果は以前に返された値に依存してはならず、誰かが以前に何かを要求したかどうかに依存したり、いつ、どのプロセス、どのコンピューター、どのジェネレーター、どのジェネレーターに依存したりしてはなりません。それが要求された銀河。それが結局のところ「ランダム」という言葉の意味です-予測不可能であり、何からも独立しています-そうでなければ、もうランダムではありませんよね?この直感では、可能なコンテキストでそのような乱数を取得するためにキャストする魔法の呪文をWebで検索するのは自然なことです。
^^^このような直感的な期待は非常に間違っており、疑似乱数ジェネレータを含むすべてのケースで有害です -真の乱数には妥当ですが。
「乱数」という意味のある概念は存在しますが、「疑似乱数」というものはありません。擬似乱数ジェネレータは、実際に擬似乱数生成シーケンスを。
専門家がPRNGの品質について話すとき、彼らは実際には生成されたシーケンス(およびその注目のサブシーケンス)の統計的特性について話します。たとえば、2つの高品質のPRNGを両方同時に使用して組み合わせると、結果として悪いシーケンスが生成される可能性があります-それらがそれぞれ別々に良いシーケンスを生成するにもかかわらず(これらの2つの良いシーケンスは単純に相互に関連しているため、うまく組み合わせられない可能性があります)。
疑似ランダムシーケンスは、実際には常に決定論的です(そのアルゴリズムと初期パラメーターによって事前に決定されます)。つまり、実際にはランダムではありません。
具体的にrand()
/ srand(s)
機能の対は、非スレッドセーフ特異ごとのプロセスを提供する(!)実装定義されたアルゴリズムを用いて生成された擬似乱数列。関数rand()
は範囲内の値を生成します[0, RAND_MAX]
。
C11標準からの引用:
このsrand
関数は、引き数を、以降のの呼び出しで返される疑似乱数の新しいシーケンスのシードとして使用しますrand
。srand
次に同じシード値で呼び出された場合
、一連の疑似乱数が繰り返されます。場合rand
への呼び出しの前に呼び出されsrand
行われている、同じシーケンスをするときのように生成されなければならないsrand
最初の1のシード値と呼ばれています。
多くの人々は、それrand()
が0
からの範囲の一連の半独立した均一に分布した数を生成することを合理的に期待していRAND_MAX
ます。まあそれは間違いなく(そうでなければ役に立たない)すべきですが、残念ながら標準はそれを必要としません- 「生成されたランダムシーケンスの品質に関して保証がない」と明記する明示的な免責事項さえあります。いくつかの歴史的な例ではrand
/ srand
実装は確かに非常に悪い品質でした。最近の実装ではそれで十分ですが、信頼は壊れており、簡単に回復することはできません。その非スレッドセーフの性質に加えて、マルチスレッドアプリケーションでの安全な使用は、トリッキーで制限されています(まだ可能です-1つの専用スレッドから使用するだけでもかまいません)。
新しいクラステンプレートstd :: mersenne_twister_engine <>(およびその便利なtypedefs-std::mt19937
/ std::mt19937_64
と適切なテンプレートパラメータの組み合わせ)は、C ++ 11標準で定義されたオブジェクトごとの疑似乱数ジェネレータを提供します。同じテンプレートパラメーターと同じ初期化パラメーターを使用すると、C ++ 11準拠の標準ライブラリで構築されたアプリケーションのコンピューターで、異なるオブジェクトがまったく同じオブジェクトごとの出力シーケンスを生成します。このクラスの利点は、予測可能な高品質の出力シーケンスと、実装全体での完全な一貫性です。
また、C ++ 11標準-std :: linear_congruential_engine <>(srand/rand
一部のC標準ライブラリ実装で公正品質アルゴリズムとして歴史的に使用されていました)およびstd :: subtract_with_carry_engine <>で定義されたPRNGエンジンがさらにあります。また、完全に定義されたパラメータ依存のオブジェクトごとの出力シーケンスも生成します。
上記の廃止されたCコードの現代のC ++ 11の例の置き換え:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
std :: uniform_int_distribution <>を使用する以前のコードのバージョン
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}