C言語でプログラムを作成する際、ゲームの挙動をランダムにしたり、テストデータを生成したりするために「乱数」は欠かせない要素です。
標準ライブラリにはrand関数が用意されていますが、正しく扱わないと毎回同じ数値が出力されるといったトラブルが発生します。
本記事では、初心者の方が迷いやすいrand関数の基本的な使い方から、実用的な範囲指定の方法まで、モダンなプログラミング手法に基づいて詳しく解説します。
rand関数の基本仕様と仕組み
C言語における乱数生成は、標準ライブラリであるstdlib.hを使用します。
乱数を生成するための中心的な存在が、rand関数です。
この関数は、呼び出すたびに0からRAND_MAXまでの範囲の整数を返します。
RAND_MAXは環境によって異なりますが、多くのシステムでは32767以上の値として定義されています。
まずは、最もシンプルなrand関数の使用例を確認してみましょう。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 乱数を3回生成して表示する
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
41
18467
6334
上記のコードを実行すると、実行環境に応じた数値が表示されます。
しかし、このプログラムには大きな問題があります。
それは、プログラムを何度再実行しても、常に同じ数値の組み合わせが出力されるという点です。
これはrand関数が「疑似乱数」と呼ばれる仕組みで動作しているためです。
疑似乱数とは何か
疑似乱数とは、特定の計算式に基づいて生成される数値の列を指します。
計算式が同じであれば、生成される結果も必ず同じ順序になります。
この計算の起点となる値を「シード(種)」と呼びます。
C言語のrand関数は、デフォルトではシード値が「1」に固定されています。
そのため、プログラムを起動するたびに同じシードから計算が始まり、結果として同じ乱数列が得られてしまうのです。
srand関数によるシードの初期化
実行のたびに異なる乱数を得るためには、シード値を毎回変更する必要があります。
このシード値を設定するために使用するのが、srand関数です。
srand関数に渡す値を実行のたびに変えることで、生成される乱数のパターンを変化させることができます。
time関数を利用した動的な初期化
最も一般的な手法は、現在の時刻をシード値として利用することです。
時刻は刻一刻と変化するため、プログラムを実行するたびに異なるシードを供給できます。
時刻を取得するには、time.hヘッダーに含まれるtime関数を使用します。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
// 現在時刻をシード値として設定
srand((unsigned int)time(NULL));
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
time(NULL)は、1970年1月1日からの経過秒数を返します。
これにより、秒単位で実行タイミングが異なれば、出力される乱数も変化するようになります。
なお、srand関数はプログラムの開始時に一度だけ呼び出すのが鉄則です。
ループの中で何度も呼び出すと、実行速度が速すぎる場合に同じ時刻(同じ秒数)がシードとして設定され、すべての乱数が同じ値になってしまう可能性があるため注意してください。
特定の範囲で乱数を取得する計算式
rand関数が返す値は非常に広範囲ですが、実際の開発では「1から6まで(サイコロ)」や「0から100まで」といった特定の範囲の数値が必要になることがほとんどです。
ここでは、剰余演算子(%)を活用した範囲指定の方法を解説します。
0からN-1までの乱数を得る
特定の数Nで割った余りは、必ず「0」から「N-1」の範囲に収まります。
例えば、0から9までの乱数が欲しい場合は、rand() % 10と記述します。
int num = rand() % 10; // 0, 1, 2, ..., 9 のいずれか
1からNまでの乱数を得る
サイコロのように1から始まる数値が欲しい場合は、先ほどの式に「1」を加算します。
rand() % 6は0から5を返すため、これに1を足すことで1から6の範囲になります。
int dice = (rand() % 6) + 1; // 1, 2, 3, 4, 5, 6 のいずれか
最小値minから最大値maxまでの範囲指定
より汎用的な範囲指定を行うための公式を覚えておきましょう。
「min以上max以下」の整数を得るための計算式は以下の通りです。
rand() % (max – min + 1) + min
この式を分解すると、まず(max - min + 1)によって「求めたい範囲の個数」を算出しています。
その余りに最小値minを足すことで、開始位置をずらしています。
例えば、10から20の範囲で乱数が欲しい場合は、以下のようになります。
int min = 10;
int max = 20;
int result = rand() % (max - min + 1) + min;
応用的な乱数生成テクニック
整数の範囲指定だけでなく、実務では浮動小数点数(小数)や、より精度の高い処理が求められる場面があります。
0.0から1.0までの小数の乱数を生成する
小数の乱数が必要な場合は、rand関数の結果をRAND_MAXで割ります。
ただし、整数同士の割り算にならないよう、キャスト(型変換)を行う必要があります。
double r = (double)rand() / RAND_MAX;
この計算により、0.0以上1.0以下の実数が得られます。
これを応用すれば、「0.0から5.5まで」といった任意の小数の範囲も計算可能です。
乱数の精度と分布に関する注意点
rand() % Nという手法は非常に手軽ですが、数学的な厳密さを求められる場合には弱点があります。
RAND_MAXがNの倍数でない場合、特定の値が出現しやすくなるという「偏り」が生じます。
一般的なアプリケーションや簡易的なゲーム制作では問題になりませんが、統計学的なシミュレーションや暗号化処理には向きません。
より高度な乱数が必要な場合は、最新のC言語規格やプラットフォーム固有のAPI(Windowsのrand_sなど)を検討する必要があります。
よくあるエラーとトラブルシューティング
rand関数を使用する際、初心者が陥りやすいミスとその対策をまとめました。
| 現象 | 原因 | 解決策 |
|---|---|---|
| 毎回同じ数値が出る | srandによる初期化が行われていない | プログラムの冒頭でsrand((unsigned)time(NULL))を呼ぶ |
| 数値が偏る | ループ内で何度もsrandを呼んでいる | srandの呼び出しはメイン処理の最初1回のみにする |
| RAND_MAXを超える値が必要 | rand関数の仕様上の限界 | 複数のrandの結果を組み合わせるか、別のライブラリを使用する |
特に「ループ内でのsrand呼び出し」は非常に多いミスです。
コンピュータの処理速度は非常に速いため、1秒以内に何度もtime(NULL)を呼び出すと、すべて同じ戻り値(シード)になってしまいます。
その結果、生成される乱数もすべて同じ値になるという現象が発生します。
実践:乱数を使った簡単な数当てゲーム
これまでに解説した知識を組み合わせて、実践的なプログラムを作成してみましょう。
コンピュータがランダムに決めた1から100までの数字を当てるゲームです。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int target, answer;
// シードの初期化
srand((unsigned int)time(NULL));
// 1から100までの乱数を生成
target = rand() % 100 + 1;
printf("1から100までの数字を当ててください。\n");
do {
printf("数字を入力: ");
scanf("%d", &answer);
if (answer > target) {
printf("もっと小さいです。\n");
} else if (answer < target) {
printf("もっと大きいです。\n");
}
} while (answer != target);
printf("正解です!ターゲットは%dでした。\n", target);
return 0;
}
このプログラムでは、rand() % 100 + 1を使用して適切な範囲の正解値を作成しています。
また、プログラム実行時に一度だけsrandを呼び出すことで、遊ぶたびに答えが変わるようになっています。
まとめ
C言語における乱数生成は、rand関数とsrand関数の組み合わせが基本となります。
単にrandを呼び出すだけでは真のランダム性は得られず、time関数などを利用して適切にシードを初期化することが不可欠です。
また、剰余演算子%を用いた範囲指定の公式rand() % (max - min + 1) + minは、あらゆる場面で応用できる非常に便利なテクニックです。
ただし、rand関数は疑似乱数であるため、統計的な厳密さやセキュリティが求められる用途には限界があることも理解しておきましょう。
まずは本記事で紹介した基本をマスターし、さまざまなプログラムで動きのあるロジックを実装してみてください。
