web-dev-qa-db-ja.com

srand()—なぜ一度だけ呼び出すのですか?

この質問は、この質問のコメントに関するものです srandを初期化する推奨方法? 最初のコメントは、srand()は、アプリケーション。なぜそうですか?

71
Lipika Deka

それはあなたが達成しようとしているものに依存します。

ランダム化は、開始値、つまりシードを持つ関数として実行されます。

したがって、同じシードに対して、常に同じ値のシーケンスを取得します。

ランダムな値が必要になるたびにシードを設定しようとして、シードが同じ数である場合、常に同じ「ランダムな」値を取得します。

シードは通常time(NULL)のように現在の時間(秒)から取得されるため、乱数を取得する前に常にシードを設定すると、srandを呼び出す限り同じ数を取得できます/ Randコンボを複数回同じ秒で

この問題を回避するために、srandはアプリケーションごとに1回だけ設定されます。これは、2つのアプリケーションインスタンスが同じ秒で初期化されるのが疑わしいため、各インスタンスに異なる乱数シーケンスが設定されるためです。

ただし、アプリ(特に短いアプリ、コマンドラインツールなど)を1秒間に何回も実行する可能性がわずかにある場合は、他の方法を選択する必要があります。シード(異なるアプリケーションインスタンスの同じシーケンスで問題ない場合)。しかし、私が言ったように、それはあなたのアプリケーションの使用状況に依存します。

また、精度をマイクロ秒(同じシードの可能性を最小限に抑える)に上げようとする場合があります(sys/time.h):

struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
97
Kornelije Petak

乱数は実際には擬似乱数です。シードが最初に設定され、Randの各呼び出しから乱数が取得され、内部状態が変更され、この新しい状態が次のRand呼び出しで使用されて別の番号が取得されます。これらの「乱数」の生成には特定の式が使用されるため、Randを呼び出すたびに特定の値のシードを設定すると、呼び出しから同じ番号が返されます。たとえば、srand (1234); Rand ();は同じ値を返します。シード値で初期状態を一度初期化すると、srandで内部状態を設定しないため、十分な乱数が生成されるため、数値がランダムになる可能性が高くなります。

通常、シード値を初期化するときは、time (NULL)戻り値の秒値を使用します。 srand (time (NULL));がループしているとしましょう。その後、ループは1秒間に複数回繰り返すことができるため、ループ内の2番目のRand呼び出しでループ内でループが繰り返される回数は、同じ「乱数」を返しますが、これは望ましくありません。プログラムの開始時に一度初期化すると、シードが1回設定され、Randが呼び出されるたびに新しい番号が生成され、内部状態が変更されるため、次の呼び出しRandは十分にランダムです。

たとえば、 http://linux.die.net/man/3/Rand からのこのコード:

static unsigned long next = 1;
/* Rand_MAX assumed to be 32767 */
int myrand(void) {
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
    next = seed;
}

内部状態nextはグローバルとして宣言されます。各myrand呼び出しは、内部状態を変更して更新し、乱数を返します。 myrandの呼び出しごとに異なるnext値があるため、メソッドは呼び出しごとに異なる番号を返します。

mysrand実装を見てください。 nextに渡すシード値を設定するだけです。したがって、nextを呼び出す前に毎回Rand値を同じ値に設定すると、同じランダムな値が返されます。これは、同じ式が適用されるためです。ランダムに。

ただし、ニーズに応じて、シードを特定の値に設定して、たとえばベンチマークなどで実行ごとに同じ「ランダムシーケンス」を生成できます。

22
phoxis

短い答え:srand()の呼び出しはnot乱数ジェネレーターの「サイコロを転がす」のようなものです。カードのデッキをシャッフルするようなものでもありません。どちらかといえば、それはカードのデッキを切り取るようなものです。

このように考えてください。 Rand()は大きなカードデッキからの取引であり、コールするたびに、デッキの一番上から次のカードを選び、値を与え、そのカードを一番下に戻すだけです。デッキ。 (はい、それはしばらくすると「ランダム」シーケンスが繰り返されることを意味します。非常に大きなデッキですが、通常は4,294,967,296カードです。)

さらに、プログラムを実行するたびに、最新のカードパックがゲームショップから購入され、andすべての最新のカードパックには常に同じシーケンス。したがって、特別なことをしない限り、プログラムを実行するたびに、Rand()からまったく同じ「乱数」が返されます。

さて、「さて、どうやってデッキをシャッフルしますか」と言うかもしれません。そして、少なくともRandsrandに関する限り、答えはデッキをシャッフルする方法がないということです。

では、srandは何をするのでしょうか?ここで構築しているアナロジーに基づいて、srand(n)を呼び出すことは、基本的に「上からデッキnカードをカットする」と言っているようなものです。ちょっと待ってください。実際には別の新しいデッキから始めて、nカードを上からカットします

したがって、srand(n)Rand()srand(n)Rand()、...を同じn everyで呼び出すと、あまりランダムではないシーケンスを取得するだけでなく、実際に毎回Rand()から同じ番号を取得します。 (おそらく、あなたがsrandに渡したのと同じ番号ではなく、Randから戻ってきた同じ番号です。)

したがって、できることはデッキをカットすることですonce、つまり、プログラムの開始時にsrand()を1回呼び出す、合理的にランダムなnを使用して、プログラムを実行するたびに大きなデッキの異なるランダムな場所から開始します。 Rand()を使用すると、それが本当にできる最善の方法です。

[P.S.はい、実生活では、新しいカードのデッキを購入するとき、それは通常、ランダムな順番ではなく順番になっています。ここでの類推が機能するために、私はあなたがゲームショップから購入する各デッキが一見ランダムな順序であると想像していますが、同じショップから購入するカードの他のすべてのデッキとまったく同じ一見ランダムな順序です。ブリッジトーナメントで同じようにシャッフルされたカードのデッキのようなもの。]

8
Steve Summit

その理由は、srand()がランダムジェネレーターの初期状態を設定し、ジェネレーターが生成するすべての値は、その状態に自分で触れない限り「十分にランダム」だからです。

たとえば、次のことができます。

_int getRandomValue()
{
    srand(time(0));
    return Rand();
}
_

そして、その関数を繰り返し呼び出してtime()が隣接する呼び出しで同じ値を返すようにすると、同じ値が生成されます-これは設計によるものです。

7
sharptooth

同じ秒で実行されるアプリケーションインスタンスの異なるシードを生成するためにsrand()を使用するためのより簡単なソリューションは、次のとおりです。

srand(time(NULL)-getpid());

このメソッドは、スレッドがいつ開始されたかを推測する方法がなく、pidも異なるため、シードをランダムに非常に近づけます。

2
achoora

srandは、疑似乱数ジェネレーターにシードします。複数回呼び出すと、RNGが再シードされます。そして、同じ引数で呼び出すと、同じシーケンスを再開します。

それを証明するために、あなたが次のような簡単なことをしたら:

#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
        srand(0);
        printf("%d\n", Rand());
    }
}

同じ番号が100回印刷されます。

2
Foo Bah

1\Rand()が実行されるたびに、次のRand()の新しいシードが設定されます。

2\srand()が複数回実行される場合、2つの実行が1秒で発生する場合(時間(NULL)は変わらない)、次のRand()は直前のRand()と同じになりますsrand()。

0
imoc