C++で乱数の範囲を指定する場合は、主に整数の乱数と小数の乱数で考え方が分かれます。
整数の乱数では、指定した最小値から最大値までの値をランダムに生成します。
小数の乱数では、指定した範囲内の実数値をランダムに生成します。
現在のC++では、乱数生成には <random> を使う方法が一般的です。
古い方法として rand() を使う書き方もありますが、乱数の品質や偏りの問題があるため、新しくコードを書く場合は <random> を使う方が適しています。
整数の乱数の範囲指定
最小値と最大値を指定する
整数の乱数では、最小値と最大値を指定して、その範囲内の値を生成します。
たとえば、1から6までの乱数を生成する場合、出力される値は1、2、3、4、5、6のいずれかです。
整数の範囲指定では、基本的に最小値と最大値の両方が範囲に含まれます。
そのため、1から10までを指定した場合は、1も10も出る可能性があります。
最大値も含まれる
整数の乱数では、最大値が含まれる点に注意が必要です。
1から100までの乱数を指定した場合、100も生成されます。
0から9までの乱数を指定した場合、生成される値は0、1、2、3、4、5、6、7、8、9です。
10は含まれません。
サイコロのような乱数
サイコロの目を表す乱数では、1から6までの範囲を指定します。
この場合、1から6までの整数が均等に選ばれるようにします。
ゲームやシミュレーションでは、このような整数の範囲指定がよく使われます。
小数の乱数の範囲指定
小数には専用の分布を使う
小数の乱数を生成する場合は、整数用とは別の分布を使います。
整数ではなく、0.25や3.14のような小数値を扱いたい場合に使います。
たとえば、0.0から1.0までの範囲を指定すると、0.0以上1.0未満の小数が生成されます。
小数では上限を含まないことが多い
小数の乱数では、整数の乱数とは異なり、基本的に上限は含まれないと考えます。
0.0から1.0までの範囲を指定した場合、0.0は含まれますが、1.0は通常含まれません。
そのため、小数の範囲は次のようなイメージになります。
0.0以上、1.0未満。
実数値を扱う場面
小数の乱数は、確率、座標、割合、速度、重み付けなどを扱う場面でよく使われます。
ゲームでキャラクターの移動量をランダムにしたい場合や、シミュレーションで連続的な値を扱いたい場合に適しています。
rand()を使った古い範囲指定
以前からある乱数生成方法
C++では、古い方法として rand() を使った乱数生成があります。
この方法では、生成された乱数に対して余りを取ることで範囲を調整します。
たとえば、10で割った余りを使うと、0から9までの整数を作ることができます。
最小値から最大値までに調整する考え方
rand() を使って任意の範囲を作る場合は、まず0から必要な個数分の範囲を作り、そこに最小値を足します。
たとえば、10から20までの範囲を作る場合、まず0から10までの値を作り、そこに10を足します。
これにより、最終的に10から20までの値になります。
rand()には偏りが出る可能性がある
rand() と余りを使った範囲指定では、値に偏りが出る可能性があります。
乱数の元になる値の個数が、指定した範囲の個数で割り切れない場合、一部の値が他の値より出やすくなることがあります。
この偏りは、簡単な学習用コードでは大きな問題にならないこともあります。
しかし、正確なランダム性が必要な場面では避けた方がよいです。
<random>を使うメリット
範囲指定が明確
<random> を使うと、どの範囲の乱数を生成したいのかを明確に指定できます。
整数用の分布、小数用の分布、確率判定用の分布など、目的に応じた仕組みが用意されています。
そのため、乱数の意味が分かりやすくなります。
乱数の品質が高い
<random> で使われる乱数エンジンは、古い rand() よりも扱いやすく、品質面でも優れています。
特に、std::mt19937 はよく使われる疑似乱数生成器です。
ゲーム、シミュレーション、テストデータ生成などで広く利用できます。
分布を使い分けられる
<random> では、整数の一様分布、小数の一様分布、確率判定などを目的に応じて使い分けられます。
単に乱数を作るだけでなく、「どのようなルールで値を出すか」を指定できる点が特徴です。
乱数エンジンと分布の役割
乱数エンジン
乱数エンジンは、乱数の元になる値を生成する役割を持ちます。
代表的なものに std::mt19937 があります。
これはメルセンヌ・ツイスタと呼ばれる疑似乱数生成器です。
乱数エンジンだけでは、1から6のような具体的な範囲指定は行いません。
分布
分布は、乱数エンジンが作った値を指定した範囲やルールに合わせて変換する役割を持ちます。
整数の範囲を指定する場合は整数用の分布を使います。
小数の範囲を指定する場合は小数用の分布を使います。
確率で真偽を判定したい場合は、確率判定用の分布を使います。
エンジンと分布を組み合わせる
C++の乱数生成では、乱数エンジンと分布を組み合わせて使います。
乱数エンジンが乱数の元を作り、分布がそれを指定した範囲に変換します。
この仕組みにより、乱数の範囲や性質を明確に制御できます。
シードの考え方
シードとは
シードとは、乱数列を作るための初期値です。
疑似乱数は、完全に無秩序な値を毎回作っているわけではありません。
初期値をもとに、決まった手順で乱数列を生成します。
そのため、同じシードを使うと、同じ乱数列が再現されます。
毎回同じ乱数にする場合
テストやデバッグでは、毎回同じ乱数列を出したいことがあります。
この場合は、固定のシードを使います。
固定シードを使うと、プログラムを何度実行しても同じ順番で乱数が生成されます。
ゲームのデバッグやシミュレーションの検証では、再現性があるため便利です。
毎回異なる乱数にする場合
毎回異なる乱数列を使いたい場合は、環境から得られる値をシードとして使います。
一般的には std::random_device が使われます。
ただし、std::random_device は環境によって挙動が異なる場合があります。
多くの環境では実行ごとに異なる値を得られますが、実装によっては決定的な値を返す可能性もあります。
std::mt19937の注意点
一般用途では使いやすい
std::mt19937 は、C++でよく使われる疑似乱数生成器です。
ゲーム、シミュレーション、サンプルデータ生成、簡単なランダム処理などに向いています。
扱いやすく、再現性も管理しやすい点が特徴です。
暗号用途には使わない
std::mt19937 は、暗号学的に安全な乱数生成器ではありません。
パスワード、認証トークン、APIキー、セッションID、暗号鍵などの生成には使うべきではありません。
セキュリティが関係する場面では、OSや暗号ライブラリが提供する安全な乱数生成機能を使う必要があります。
関数化する場合の注意点
乱数エンジンを毎回作らない
乱数を生成する関数を作る場合、呼び出すたびに乱数エンジンを作り直すのは避けた方がよいです。
乱数エンジンは一度作成して使い回す方が自然です。
毎回作り直すと効率が悪くなり、乱数列の扱いも不自然になる場合があります。
最小値と最大値の順序を確認する
範囲指定では、最小値が最大値以下である必要があります。
最小値が最大値より大きい指定は不正です。
たとえば、10から1までのような指定は避ける必要があります。
汎用的な関数を作る場合は、最小値と最大値の大小関係を確認する処理を入れると安全です。
マルチスレッドでは注意する
乱数エンジンを共有する場合、複数のスレッドから同時に使うと問題が起きる可能性があります。
単一スレッドのプログラムでは大きな問題にならないことが多いですが、マルチスレッド環境では注意が必要です。
スレッドごとに乱数エンジンを持たせる方法もあります。
配列やリストからランダムに選ぶ場合
インデックスの範囲を指定する
配列やリストからランダムに要素を選ぶ場合は、要素そのものではなくインデックスをランダムに選びます。
要素数が3つの場合、使えるインデックスは0、1、2です。
インデックスは0から始まるため、最大値は要素数そのものではなく、要素数から1を引いた値になります。
範囲外アクセスに注意する
インデックスの最大値を間違えると、存在しない要素にアクセスしてしまう可能性があります。
たとえば、要素数が3つの配列に対して、3番目のインデックスを指定すると範囲外になります。
ランダムに要素を選ぶ場合は、0から要素数マイナス1までの範囲を指定する必要があります。
空の配列には注意する
配列やリストが空の場合、ランダムに選べる要素がありません。
空の状態でランダム選択を行うと、範囲指定が不正になります。
実用的な処理では、要素が空でないことを確認してからランダム選択を行う必要があります。
確率判定で使う乱数
一定確率で処理を実行する
乱数は、一定確率で処理を実行する場面でも使われます。
たとえば、20%の確率で成功、30%の確率でアイテムを入手、5%の確率でクリティカル発生といった処理です。
このような場合、整数の乱数で判定する方法と、確率判定用の分布を使う方法があります。
整数で確率を表す方法
1から100までの乱数を作り、特定の範囲に入った場合だけ成功とする方法があります。
たとえば、1から100までのうち1から20までを成功にすれば、約20%の確率になります。
この方法は直感的で分かりやすいです。
確率判定用の分布を使う方法
C++には、指定した確率で真偽値を返す分布があります。
20%の確率なら0.2、30%の確率なら0.3のように指定します。
確率そのものを直接表せるため、意味が明確です。
よくある間違い
整数の最大値を含まないと思ってしまう
整数の範囲指定では、最大値も含まれます。
1から10までを指定した場合、10も出ます。
1から9までにしたい場合は、最大値を9にする必要があります。
rand()の余りで1から最大値になると思ってしまう
10で割った余りは、0から9までです。
1から10までにはなりません。
1から10までにしたい場合は、0から9までの値を作ったあとに1を足す必要があります。
小数の上限を整数と同じように考える
小数の範囲指定では、上限は含まれないと考えるのが基本です。
0.0から1.0までを指定した場合、1.0は通常含まれません。
整数の範囲指定とは扱いが異なります。
乱数エンジンを毎回初期化してしまう
乱数を生成するたびに乱数エンジンを初期化すると、効率が悪くなります。
乱数エンジンは一度作成し、継続して使う方が適しています。
セキュリティ用途に使ってしまう
一般的な疑似乱数生成器は、暗号用途には向いていません。
パスワードや認証トークンの生成に使うと、安全性に問題が出る可能性があります。
セキュリティ用途では、暗号学的に安全な乱数生成機能を使う必要があります。
実用上の使い分け
学習用ならrand()でも理解しやすい
乱数の基本的な仕組みを学ぶだけなら、rand() を使った例は理解しやすいです。
余りを使って範囲を調整する考え方も、乱数の基礎を学ぶ上では役立ちます。
ただし、新しく実用的なコードを書く場合は、rand() を積極的に使う理由はあまりありません。
通常の開発では<random>を使う
ゲーム、ツール、シミュレーション、テストデータ作成などでは、基本的に <random> を使うのが適しています。
範囲指定が明確で、整数や小数、確率判定なども扱いやすくなります。
乱数の再現性も管理しやすいため、デバッグにも向いています。
セキュリティ用途では専用の方法を使う
認証や暗号に関係する乱数には、一般的な疑似乱数生成器を使うべきではありません。
セキュリティが必要な場面では、OSや暗号ライブラリが提供する安全な乱数生成機能を使います。
C++の乱数範囲指定の要点
整数は両端を含む
整数の乱数では、最小値と最大値の両方が含まれます。
1から6までを指定すれば、1も6も出る可能性があります。
小数は上限を含まない
小数の乱数では、基本的に最小値以上、最大値未満の範囲になります。
0.0から1.0までを指定した場合、0.0以上1.0未満と考えます。
新しいコードでは<random>を使う
現在のC++では、乱数生成には <random> を使う方法が適しています。
rand() は現在でも使えますが、乱数の品質や偏りの問題があるため、実用的なコードでは避けるのが無難です。
乱数エンジンと分布を分けて考える
C++の乱数生成では、乱数エンジンと分布を分けて考えます。
乱数エンジンは乱数の元を作り、分布は範囲や確率のルールを決めます。
この2つを組み合わせることで、目的に合った乱数を生成できます。
以上、C++での乱数の範囲指定についてでした。
最後までお読みいただき、ありがとうございました。
