C++ のシード乱数生成器
- C++ の乱数ジェネレーターのシードの概念
-
C++ で
srand()
関数を使用して乱数ジェネレーターをシードする -
C++ で
time()
関数を使用して乱数ジェネレーターをシードする - 回避するためのランダムジェネレータの間違いのシード
- まとめ
C++ などのプログラミング言語は、真にランダムな数値を生成しません。むしろ、コンピューターは数学関数を使用して疑似乱数を生成します。
この記事では、乱数を生成する際のシードの概念と、C++ で乱数を生成するために使用される関数にシードを提供する方法について説明します。
C++ の乱数ジェネレーターのシードの概念
C++ は、決定論的アルゴリズムを使用してランダムな数値のシーケンスを生成します。したがって、数列は純粋に確率的ではなく、疑似ランダムです。
この場合、シードはアルゴリズムの開始点として機能します。生成された最初の数がシードであるかのように考えるべきではありません。
むしろ、アルゴリズムはシードが定義する分布からランダムに数値を選択します。アルゴリズムに同じシードを指定すると、同じシーケンスの疑似乱数が生成されます。
ただし、ほとんどの場合、実行ごとに異なる疑似乱数のシーケンスを生成する必要がある場合があります。その場合、アルゴリズムを実行するたびに、アルゴリズムに異なるシードを提供する必要があります。
C++ で srand()
関数を使用して乱数ジェネレーターをシードする
srand()
関数は、符号なし整数を引数として受け入れます。引数を使用して、疑似乱数を生成するアルゴリズムをシードします。
構文:
void srand(unsigned int seed);
srand()
関数の引数として 1
を指定すると、疑似乱数ジェネレーターが初期値に初期化されます。ジェネレーターは、rand()
関数の最後の呼び出しと同じ結果を生成します。
ユーザーから入力として取得した任意の数を疑似乱数ジェネレーターにシードする例を見てみましょう。
サンプルコード:
#include <iostream>
using namespace std;
int main() {
unsigned int seed;
cout << "Enter seed value:\n";
cin >> seed;
srand(seed);
cout << "Successfully seeded the generator\n";
return 0;
}
出力:
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ g++ seed_example.cc
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ ./a.out
Enter seed value:
12
Successfully seeded the generator
C++ で time()
関数を使用して乱数ジェネレーターをシードする
以前のアプローチの問題は、ユーザーが同じ番号を複数回入力できることです。アルゴリズムが実行されるたびに異なるシードが提供されるようにする必要がある場合は、time()
関数を使用して疑似乱数ジェネレーターにシードを提供します。
C++ の time()
関数は、現在の UNIX タイムスタンプ、つまり 1970 年 1 月 1 日 UTC00:00 から経過した秒数を返します。
構文:
time_t time(time_t* timer);
この関数は、time_t
型のポインターとして引数を取ります。関数への null 以外の参照をパラメーターとして指定すると、タイプ time_t
のオブジェクトが現在のタイムスタンプを保持するパラメーターに設定されます。
タイプ time_t
は算術タイプのエイリアスであり、現在の UNIX タイムスタンプ値を保持できます。これは事実上、符号なし整数値です。
サンプルコード:
#include <iostream>
using namespace std;
int main() {
srand(time(NULL));
cout << "Successfully seeded the generator\n";
return 0;
}
コードは time()
関数の引数として NULL
を渡すことに注意してください。コードによると、何らかの理由でタイプ time_t
のオブジェクトは必要ありません。
出力:
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ g++ seed_example.cc
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ ./a.out
Successfully seeded the generator
回避するためのランダムジェネレータの間違いのシード
通常、コード内のループで一連の疑似乱数を生成します。ほとんどの人は、ループが実行されるたびにジェネレーターをシードするというよくある間違いを犯します。
このような場合、ジェネレーターは同じ番号をシードするたびに同じ番号のシーケンスを作成するため、番号が繰り返される可能性があります。
これは、time()
関数を使用してランダムジェネレーターをシードする場合でも当てはまります。これは、time()
関数が過去の固定日から経過した秒数を返すためです。
一方、ループははるかに高速に実行されるため、time()
関数を呼び出すたびに、1 秒が経過するまで同じ値が返され続けます。このようにして、ジェネレータに同じ値をシードすることになります。
サンプルコード:
#include <iostream>
using namespace std;
int main() {
for (int i = 1; i <= 10; i++) {
srand(time(NULL));
cout << rand() << " ";
}
cout << endl;
return 0;
}
出力:
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ g++ seed_example.cc
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ ./a.out
1524491454 1524491454 1524491454 1524491454 1524491454 1524491454 1524491454 1524491454 1524491454 1524491454
コードはループ内に乱数ジェネレーターをシードし、実行結果は同じ数の 10 回になります。この落とし穴を避けるために、ループの前に常に乱数ジェネレーターをシードする必要があります。
コードがループの外側にランダムジェネレーターをシードする例を見てみましょう。
サンプルコード:
#include <iostream>
using namespace std;
int main() {
srand(time(NULL));
for (int i = 1; i <= 10; i++) {
cout << rand() << " ";
}
cout << endl;
return 0;
}
出力:
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ g++ seed_example.cc
mohtashim@mohtashim:~/eclipse-workspace/Java2Blog$ ./a.out
213462937 1076978976 1207347426 8310730 1551061902 266528745 944000672 871831053 1678325834 868781842
コードの結果が異なる数列になることに注意してください。
まとめ
アルゴリズムの開始点として機能する疑似乱数ジェネレーターにシードを提供できますが、記事で説明されているように、落とし穴を回避するように注意する必要があります。そうしないと、望ましくない結果が生じる可能性があります。
シード後に疑似乱数のシーケンスを生成する rand()
関数の詳細については、ここを参照してください。