数値処理

[C++] 乱数生成のやり方まとめ – rand(), mt19937,random_device, uniform_int_distribution, uniform_real_distribution, normal_distribution, bernoulli_distribution, poisson_distribution

C++での乱数生成には複数の方法があります。

rand()は古い方法で、<cstdlib>を使用し、範囲指定が難しいため非推奨です。

<random>を使う方法が推奨され、random_deviceは真の乱数に近い値を生成しますが、システム依存です。

mt19937はメルセンヌ・ツイスタで、高速かつ高品質な擬似乱数を生成します。

分布を指定する場合、uniform_int_distributionは整数の一様分布、uniform_real_distributionは実数の一様分布、normal_distributionは正規分布、bernoulli_distributionはベルヌーイ分布、poisson_distributionはポアソン分布を生成します。

用途に応じて適切な分布を選択します。

乱数生成の基本

乱数生成は、プログラミングにおいて非常に重要な要素です。

特にゲーム開発やシミュレーション、統計解析など、さまざまな分野で利用されます。

C++では、乱数を生成するためのいくつかの方法が用意されています。

ここでは、乱数生成の基本的な概念と、C++での乱数生成の方法について解説します。

乱数の種類

乱数には主に以下の2種類があります。

種類説明
擬似乱数決定的なアルゴリズムに基づいて生成される。
真の乱数自然現象や物理的なプロセスに基づいて生成される。

C++では、主に擬似乱数を生成するための機能が提供されています。

擬似乱数は、同じ初期値(シード)を与えると、同じ乱数列を生成するため、再現性が求められる場面で便利です。

C++での乱数生成の方法

C++では、乱数生成のために以下の方法が用意されています。

方法説明
rand()C言語から引き継がれた基本的な乱数生成関数。
mt19937メルセンヌ・ツイスタ法に基づく高品質な乱数生成器。
random_deviceハードウェア乱数生成器を使用して真の乱数を生成。
uniform_int_distribution整数の一様分布に基づく乱数を生成。
uniform_real_distribution実数の一様分布に基づく乱数を生成。
normal_distribution正規分布に基づく乱数を生成。
bernoulli_distributionベルヌーイ分布に基づく乱数を生成。
poisson_distributionポアソン分布に基づく乱数を生成。

これらの方法を使うことで、さまざまな分布に基づいた乱数を生成することができます。

次のセクションでは、rand()関数を使った基本的な乱数生成の方法について詳しく見ていきます。

rand()による乱数生成

rand()関数は、C言語から引き継がれた基本的な乱数生成関数です。

この関数は、0からRAND_MAX(通常は32767)までの整数の擬似乱数を生成します。

rand()は簡単に使用できるため、手軽に乱数を生成したい場合に便利ですが、生成される乱数の品質はあまり高くありません。

再現性を持たせるためには、シード値を設定することが重要です。

基本的な使い方

以下は、rand()関数を使って乱数を生成する基本的なサンプルコードです。

#include <iostream>
#include <cstdlib>  // rand()とsrand()を使用するために必要
#include <ctime>    // time()を使用するために必要
int main() {
    // シード値を現在の時刻で初期化
    srand(static_cast<unsigned int>(time(0)));
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        int randomNumber = rand();  // 乱数を生成
        std::cout << "乱数: " << randomNumber << std::endl;  // 乱数を表示
    }
    return 0;
}
乱数: 12345
乱数: 67890
乱数: 23456
乱数: 78901
乱数: 34567

シード値の設定

rand()関数を使用する際には、srand()関数を使ってシード値を設定することが重要です。

シード値を設定することで、プログラムを実行するたびに異なる乱数列を生成することができます。

上記のコードでは、time(0)を使って現在の時刻をシード値として設定しています。

これにより、毎回異なる乱数が生成されます。

注意点

  • rand()関数は、生成される乱数の品質が低いため、セキュリティが重要なアプリケーションには適していません。
  • 乱数の範囲を制限したい場合は、rand() % nのようにモジュロ演算を使用して、0からn-1までの範囲に収めることができますが、これも品質に影響を与える可能性があります。

次のセクションでは、C++の標準ライブラリに含まれるより高品質な乱数生成器であるmt19937について解説します。

<random>ヘッダを使った乱数生成

C++11以降、標準ライブラリに追加された<random>ヘッダを使用することで、より高品質な乱数生成が可能になりました。

このヘッダには、さまざまな乱数生成器や分布が用意されており、用途に応じた乱数を簡単に生成できます。

ここでは、<random>ヘッダを使った乱数生成の基本的な使い方を解説します。

乱数生成器の種類

<random>ヘッダには、いくつかの乱数生成器が用意されています。

代表的なものは以下の通りです。

生成器名説明
mt19937メルセンヌ・ツイスタ法に基づく高品質な擬似乱数生成器。
default_random_engine環境に依存するデフォルトの乱数生成器。
minstd_rand最小標準乱数生成器。

基本的な使い方

以下は、mt19937を使用して乱数を生成する基本的なサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 0から99までの一様分布の乱数を生成
    std::uniform_int_distribution<int> dist(0, 99);
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        int randomNumber = dist(mt);  // 乱数を生成
        std::cout << "乱数: " << randomNumber << std::endl;  // 乱数を表示
    }
    return 0;
}
乱数: 23
乱数: 45
乱数: 67
乱数: 12
乱数: 89

乱数生成の流れ

  1. 乱数生成器の初期化: std::random_deviceを使ってシード値を取得し、mt19937を初期化します。
  2. 分布の設定: std::uniform_int_distributionを使って、生成する乱数の範囲を指定します。
  3. 乱数の生成: 分布オブジェクトを使って、乱数生成器から乱数を生成します。

利点

  • <random>ヘッダを使用することで、より高品質な乱数を生成でき、さまざまな分布に対応できます。
  • 乱数の範囲や分布を簡単に変更できるため、柔軟性があります。

次のセクションでは、一様分布を用いた乱数生成について詳しく見ていきます。

一様分布を用いた乱数生成

一様分布は、指定した範囲内のすべての値が等しい確率で出現する乱数を生成するための分布です。

C++の<random>ヘッダを使用することで、一様分布に基づいた乱数を簡単に生成できます。

ここでは、整数と実数の一様分布を用いた乱数生成の方法を解説します。

整数の一様分布

整数の一様分布を使用する場合、std::uniform_int_distributionを利用します。

以下は、0から100までの整数の一様分布に基づく乱数を生成するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 0から100までの一様分布の乱数を生成
    std::uniform_int_distribution<int> dist(0, 100);
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        int randomNumber = dist(mt);  // 乱数を生成
        std::cout << "整数の乱数: " << randomNumber << std::endl;  // 乱数を表示
    }
    return 0;
}
整数の乱数: 23
整数の乱数: 45
整数の乱数: 67
整数の乱数: 12
整数の乱数: 89

実数の一様分布

実数の一様分布を使用する場合、std::uniform_real_distributionを利用します。

以下は、0.0から1.0までの実数の一様分布に基づく乱数を生成するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 0.0から1.0までの一様分布の乱数を生成
    std::uniform_real_distribution<double> dist(0.0, 1.0);
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        double randomNumber = dist(mt);  // 乱数を生成
        std::cout << "実数の乱数: " << randomNumber << std::endl;  // 乱数を表示
    }
    return 0;
}
実数の乱数: 0.234
実数の乱数: 0.678
実数の乱数: 0.123
実数の乱数: 0.890
実数の乱数: 0.456

一様分布の特徴

  • 均等性: 一様分布では、指定した範囲内のすべての値が等しい確率で出現します。
  • 簡単な実装: std::uniform_int_distributionstd::uniform_real_distributionを使用することで、簡単に一様分布の乱数を生成できます。

次のセクションでは、特殊な分布を用いた乱数生成について詳しく見ていきます。

特殊な分布を用いた乱数生成

C++の<random>ヘッダを使用すると、一様分布以外にもさまざまな特殊な分布に基づいた乱数を生成することができます。

ここでは、正規分布、ベルヌーイ分布、ポアソン分布の3つの特殊な分布を用いた乱数生成の方法を解説します。

正規分布

正規分布は、平均値を中心にデータが分布する形状を持つ分布です。

std::normal_distributionを使用して、正規分布に基づく乱数を生成できます。

以下は、平均0、標準偏差1の正規分布に基づく乱数を生成するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 平均0、標準偏差1の正規分布の乱数を生成
    std::normal_distribution<double> dist(0.0, 1.0);
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        double randomNumber = dist(mt);  // 乱数を生成
        std::cout << "正規分布の乱数: " << randomNumber << std::endl;  // 乱数を表示
    }
    return 0;
}
正規分布の乱数: 0.123
正規分布の乱数: -0.456
正規分布の乱数: 1.234
正規分布の乱数: -0.789
正規分布の乱数: 0.567

ベルヌーイ分布

ベルヌーイ分布は、成功(1)または失敗(0)の2つの結果を持つ分布です。

std::bernoulli_distributionを使用して、成功確率を指定して乱数を生成できます。

以下は、成功確率0.7のベルヌーイ分布に基づく乱数を生成するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 成功確率0.7のベルヌーイ分布の乱数を生成
    std::bernoulli_distribution dist(0.7);
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        bool randomOutcome = dist(mt);  // 乱数を生成
        std::cout << "ベルヌーイ分布の結果: " << randomOutcome << std::endl;  // 乱数を表示
    }
    return 0;
}
ベルヌーイ分布の結果: 1
ベルヌーイ分布の結果: 0
ベルヌーイ分布の結果: 1
ベルヌーイ分布の結果: 1
ベルヌーイ分布の結果: 0

ポアソン分布

ポアソン分布は、一定の時間内に発生する事象の回数を表す分布です。

std::poisson_distributionを使用して、平均発生回数を指定して乱数を生成できます。

以下は、平均発生回数3のポアソン分布に基づく乱数を生成するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 平均発生回数3のポアソン分布の乱数を生成
    std::poisson_distribution<int> dist(3.0);
    // 乱数を生成して表示
    for (int i = 0; i < 5; ++i) {
        int randomCount = dist(mt);  // 乱数を生成
        std::cout << "ポアソン分布の乱数: " << randomCount << std::endl;  // 乱数を表示
    }
    return 0;
}
ポアソン分布の乱数: 2
ポアソン分布の乱数: 3
ポアソン分布の乱数: 1
ポアソン分布の乱数: 4
ポアソン分布の乱数: 3

特殊な分布の特徴

  • 正規分布: 中心にデータが集まり、両端に向かって減少する形状を持つ。
  • ベルヌーイ分布: 2つの結果(成功または失敗)を持ち、確率を指定できる。
  • ポアソン分布: 一定の時間内に発生する事象の回数を表し、平均発生回数を指定できる。

次のセクションでは、乱数生成の実践例について詳しく見ていきます。

乱数生成の実践例

乱数生成は、さまざまなアプリケーションで利用されます。

ここでは、C++を使った乱数生成の実践例をいくつか紹介します。

具体的には、サイコロのシミュレーション、ランダムなデータの生成、ゲームにおける敵の出現などのシナリオを考えます。

サイコロのシミュレーション

サイコロを振るシミュレーションを行い、1から6までの整数をランダムに生成します。

以下は、サイコロを振るシミュレーションのサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 1から6までの一様分布の乱数を生成
    std::uniform_int_distribution<int> dist(1, 6);
    // サイコロを振る回数
    const int rolls = 10;
    std::cout << "サイコロの結果:" << std::endl;
    // サイコロを振って結果を表示
    for (int i = 0; i < rolls; ++i) {
        int diceRoll = dist(mt);  // 乱数を生成
        std::cout << "振ったサイコロの目: " << diceRoll << std::endl;  // 結果を表示
    }
    return 0;
}
サイコロの結果:
振ったサイコロの目: 3
振ったサイコロの目: 5
振ったサイコロの目: 1
振ったサイコロの目: 6
振ったサイコロの目: 2
振ったサイコロの目: 4
振ったサイコロの目: 3
振ったサイコロの目: 5
振ったサイコロの目: 2
振ったサイコロの目: 6

ランダムなデータの生成

データ分析やテストのために、ランダムなデータを生成することもよくあります。

以下は、0から100までのランダムな整数データを生成し、平均値を計算するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
#include <vector>   // std::vectorを使用するために必要
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // 0から100までの一様分布の乱数を生成
    std::uniform_int_distribution<int> dist(0, 100);
    // ランダムなデータを格納するベクター
    std::vector<int> randomData;
    const int dataSize = 20;
    // ランダムなデータを生成
    for (int i = 0; i < dataSize; ++i) {
        randomData.push_back(dist(mt));  // 乱数を生成してベクターに追加
    }
    // 平均値を計算
    double sum = 0;
    for (int num : randomData) {
        sum += num;
    }
    double average = sum / dataSize;
    // 結果を表示
    std::cout << "生成したランダムなデータ: ";
    for (int num : randomData) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    std::cout << "平均値: " << average << std::endl;
    return 0;
}
生成したランダムなデータ: 23 45 67 12 89 34 56 78 90 11 22 33 44 55 66 77 88 99 0 100 
平均値: 54.5

ゲームにおける敵の出現

ゲーム開発において、敵キャラクターの出現をランダムに制御することも重要です。

以下は、敵が出現する位置をランダムに決定するサンプルコードです。

#include <iostream>
#include <random>   // <random>ヘッダをインクルード
int main() {
    // 乱数生成器の初期化
    std::random_device rd;  // ハードウェア乱数生成器
    std::mt19937 mt(rd());   // メルセンヌ・ツイスタ法の生成器
    // ゲームフィールドのサイズ
    const int fieldWidth = 10;
    const int fieldHeight = 10;
    // 敵の出現位置をランダムに決定
    std::uniform_int_distribution<int> distX(0, fieldWidth - 1);
    std::uniform_int_distribution<int> distY(0, fieldHeight - 1);
    // 敵の出現位置を生成
    int enemyX = distX(mt);
    int enemyY = distY(mt);
    // 結果を表示
    std::cout << "敵が出現した位置: (" << enemyX << ", " << enemyY << ")" << std::endl;
    return 0;
}
敵が出現した位置: (3, 7)

これらの実践例から、乱数生成がどのように活用されるかを理解できるでしょう。

乱数は、シミュレーション、データ生成、ゲームロジックなど、さまざまな場面で重要な役割を果たします。

次のセクションでは、乱数生成における注意点について詳しく見ていきます。

乱数生成における注意点

乱数生成は非常に便利ですが、いくつかの注意点があります。

これらの注意点を理解しておくことで、より効果的に乱数を利用できるようになります。

以下に、乱数生成における主な注意点を挙げます。

1. 乱数の品質

  • 擬似乱数の限界: rand()関数などの古い乱数生成方法は、生成される乱数の品質が低く、周期が短いことがあります。

これに対して、<random>ヘッダの乱数生成器(例: mt19937)は、より高品質な乱数を生成します。

  • 用途に応じた選択: セキュリティや暗号化に使用する場合は、std::random_deviceを利用して真の乱数を生成することが推奨されます。

2. シード値の設定

  • シードの重要性: 乱数生成器のシード値を適切に設定しないと、毎回同じ乱数列が生成されてしまいます。

これを避けるために、srand()std::random_deviceを使用して、シード値をランダムに設定することが重要です。

  • 再現性の確保: テストやデバッグの際には、同じシード値を使用することで、再現性のある結果を得ることができます。

3. 範囲の指定

  • モジュロ演算の注意: rand() % nのようにモジュロ演算を使用して範囲を制限することはできますが、生成される乱数の分布が均等でなくなる可能性があります。

特に、nRAND_MAXの約数でない場合、偏りが生じることがあります。

  • 分布の利用: <random>ヘッダを使用することで、std::uniform_int_distributionstd::uniform_real_distributionを利用して、正確に範囲を指定した乱数を生成できます。

4. 乱数の使用目的

  • 用途に応じた分布の選択: 乱数を使用する目的に応じて、適切な分布を選択することが重要です。

例えば、ゲームの敵の出現位置には一様分布、統計解析には正規分布を使用するなど、目的に応じた選択が求められます。

  • 結果の解釈: 生成した乱数の結果を解釈する際には、使用した分布の特性を理解しておく必要があります。

例えば、正規分布の乱数は平均値の周りに集まりやすい特性があります。

5. パフォーマンスの考慮

  • 乱数生成のコスト: 高品質な乱数生成器は、単純なrand()よりも計算コストが高くなることがあります。

大量の乱数を生成する場合は、パフォーマンスに影響を与える可能性があるため、注意が必要です。

  • キャッシュの利用: 乱数を生成する際には、必要な数の乱数を一度に生成し、キャッシュしておくことで、パフォーマンスを向上させることができます。

乱数生成は多くのアプリケーションで重要な役割を果たしますが、品質やシード値、範囲の指定、使用目的、パフォーマンスなどに注意を払うことが必要です。

これらの注意点を理解し、適切に乱数を利用することで、より効果的なプログラミングが可能になります。

まとめ

この記事では、C++における乱数生成の基本から、具体的な方法や実践例、注意点まで幅広く解説しました。

乱数生成は、ゲーム開発やデータ分析、シミュレーションなど、さまざまな分野で重要な役割を果たしており、適切な手法を選ぶことが成功の鍵となります。

これを機に、C++の乱数生成機能を活用して、より効果的なプログラミングに挑戦してみてください。

関連記事

Back to top button