VC++.NET2003, Managed C++です。
過去ログを参考に乱数の出力をできるようになりました。しかし問題が出てきたので皆
さんの助けをお願いしたいと思います。
実際には次のようにやりました。
for(int i = 0......)
{
//int countを更新
System::Random* r = new System::Random((int)DateTime::Now.Ticks);
int j = r->Next(0,count);
}
こうするとどうやら、ループの実行のたびに(i=0, i=1, i=2...)乱数jを違う値にするこ
とはできないようです。理由は(勘です!)for内の実行と、その次のforの実行には時
間差がほとんどないので、DateTime::Now.Ticksの値が変化しないんではないかと…
もしそうならと思い、System::Random((int)DateTime::Now.Ticks + i * 100)と、ルー
プのインデックスを使ってシード値を変えてみたんですけど結果は同じ。やはり違う理
由なんでしょうかね?どなたかわかりますか?
MC++は知らないので、勘ですが、
System::Random* のインスタンスの初期化は、最初の1回だけ行ない、
2回目以降は、そのインスタンスを使いまわすことで乱数を使用できるのでは?
#using <mscorlib.dll>
using namespace System;
int main( void )
{
for ( int i = 0; i < 100; i++ )
{
Random* r = new Random( ( int )( DateTime::Now.Ticks + i * 100 ) );
int j = r->Next( 0, 20 );
Console::WriteLine( j.ToString() );
}
return 0;
}
で、ちゃんと違う数字が出ますよ。
RAPT さんの案に一票。
コードは Blue さんのをちょっといじって、こんな感じに。
#using <mscorlib.dll>
using namespace System;
int main( void )
{
Random * r = new Random( ( int )( DateTime::Now.Ticks ) );
for ( int i = 0; i < 100; i++ )
{
int j = r->Next( 0, 20 );
Console::WriteLine( j.ToString() );
}
return 0;
}
ちなみに
> 理由は(勘です!)for内の実行と、その次のforの実行には時
> 間差がほとんどないので、DateTime::Now.Ticksの値が変化しないんではないかと…
は、MSDNの Random コンストラクタ におもいっきり、
<msdn>
解説
アプリケーションで複数の異なるシーケンスが必要な場合は、異なるシード値を使用し
て、このコンストラクタを複数回呼び出します。一意のシード値を生成する方法の 1 つ
に、シード値を時刻依存させる方法があります。たとえば、シード値の生成をシステム
時計によって行います。
しかし、アプリケーションが高速なコンピュータ上で実行された場合には、このコンス
トラクタが複数回呼び出される間にシステム時計の値が変化せず、 Random の複数の異
なるインスタンスに対して同じシード値が生成される可能性があります。このような場
合には、呼び出しのたびにシード値を変えるためのアルゴリズムを適用します。
</msdn>
とあります。
http://www.microsoft.com/japan/msdn/library/default.asp?
url=/japan/msdn/library/ja/cpref/html/frlrfSystemRandomClassTopic.asp
結果をどのようにして確認しているかが問題なようですけど。
Blueさん失礼しました。ギエ~ほんとに書いてある!…俺なさけないです…
初期化を一回だけにしたらできました。ありがとうございます。
ついでに質問していいですか?なぜ初期化を一回にすると上手くいったのかちょっとわ
からないです。ちゃんと理解したいです。
まず、初期化しますよね。
Random * r = new Random( ( int )( DateTime::Now.Ticks ) );
で、この r はいったい何者ですか?
自分はてっきりrが「この乱数をだす」という情報を持っている、つまり、初期化の時点
でどういう乱数を出すかが決まってしまう、と思っていました。
Random * r = new Random( ( int )( DateTime::Now.Ticks ) );←この時点で決まる
for ( int i = 0; i < 100; i++ )
{
int j = r->Next( 0, 20 );←これはよっていつも同じ値になるような…?
Console::WriteLine( j.ToString() );
}
だから初めは毎回新しいインスタンスを必要だろうと、ループの中に入れました。
もしrが「乱数をだせ!」という命令だけを持っているとしたら、毎回初期化しても上手
くいくはずですよね。
> 毎回初期化しても上手くいくはずですよね。
> Blue [E-Mail] 2005/12/04(日) 18:54:41
のコードでは毎回違う値がでますよ。
コンストラクタに指定した値(今回はシステム時間)を元にランダムな値を
生成します。
当然同じ値から生成すると、結果も同じになります。
MC++は私もやった事がないので正確な内容ではないかもしれませんが、
Random* rは、乱数を発生するクラスのインスタンスですよね。
言語で用意されている乱数と言うのは確か乱数テーブルと言うテーブルから出力してるだけと
言う話を聞いたことがあります。
私は初期化の時に引き渡す種はこのテーブルを初期化する為なのかなと理解しています。
同じ値で初期化すると初期化されたテーブルも同じ物になるので最初に出てくる値も同じ
物になる。
一度だけ初期化してそれから連続で取り出す場合は一度初期化したテーブルから順に取り
出すので
ちゃんと違う数字が出てくる。
そういう事ではないでしょうか。
訂正。
誤)
Random* rは、乱数を発生するクラスのインスタンスですよね。
正)
Random* rは、乱数を発生するクラスのインスタンスへのポインタですよね。
擬似乱数、初期シード、乱数系列
これらの意味が正しく理解できていれば、何の疑問も無いはずです。
因みに、VC++ の C標準ライブラリの rand() は、
乱数 = ((現在のシード値 * 214013L + 2531011L) >> 16) & 0x7fff)
次回のシード値 = 現在のシード値 * 214013L + 2531011L
という単純な計算をしているだけです(線形合同法)。
初期シード値が同じなら同じ乱数系列(同じ順序で同じ乱数が出てくる)
になります。
なるほど、単純に計算しているだけでしたか。
私的には完全に納得です。
C の標準ライブラリに喩えれば、Random のインスタンスが乱数系列で、コンストラクタ
が srand、Next メソッドが rand に相当します。
> で、この r はいったい何者ですか?
> 自分はてっきりrが「この乱数をだす」という情報を持っている、
> つまり、初期化の時点でどういう乱数を出すかが決まってしまう、
> と思っていました。
その通りです。
> int j = r->Next( 0, 20 );←これはよっていつも同じ値になるような…?
それだと、「乱数」って言いませんよね。
初期化した時点で、「次に Next を呼んだ時に出てくる値」が決定します。
そして、Next を呼ぶたびに、「その次に Next を呼んだときに出てくる値」は変わりま
す。
コンピュータで生成できる乱数は、完全にランダムな数ではありません。「擬似乱数」
と呼ばれる、「実は規則性があるんだけど、その規則性を読み取れない程度にはランダ
ムな数」です。
毎回出す乱数は一定の計算によって求めていますので、初期値と計算式があれば、出て
くる乱数を予測することは(理論的には)可能です。
この「出てくる乱数」の集合を「乱数系列」と言います。
Random クラスの初期化の時点では、「どのような乱数系列を使うか」が決定されます。
そして、Next を呼び出すごとに、その列から次の乱数を取ってきて返すという仕組みに
なっています。