C++ のシード乱数生成器

Mohd Mohtashim Nawaz 2023年12月11日
  1. C++ の乱数ジェネレーターのシードの概念
  2. C++ で srand() 関数を使用して乱数ジェネレーターをシードする
  3. C++ で time() 関数を使用して乱数ジェネレーターをシードする
  4. 回避するためのランダムジェネレータの間違いのシード
  5. まとめ
C++ のシード乱数生成器

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() 関数の詳細については、ここを参照してください。

関連記事 - C++ Random