C言語でランダムな値を生成する方法【初心者向け】
はじめに
プログラミングの世界では、ランダムな(でたらめな、予測できない)値が必要になる場面がたくさんあります。例えば、
- ゲーム:敵の出現位置を決めたり、カードをシャッフルしたり、サイコロを振ったり。
- シミュレーション:複雑な現象をモデル化し、予測不可能な要素を取り入れたり。
- データ処理:ランダムなサンプルを選んだり、テストデータを生成したり。
- 暗号:秘密の鍵を生成したり(ただし、標準ライブラリの機能は暗号には通常使いません)。
このように、ランダムな値は様々なアプリケーションで重要な役割を果たします。
しかし、コンピュータは基本的に非常に論理的で、与えられた指示通りに正確に動作する機械です。どのようにすれば、そんなコンピュータに「ランダムな値」を生み出させることができるのでしょうか?
実は、C言語を含む多くのプログラミング言語で「ランダムな値」として扱われるのは、厳密には「真のランダムな値」ではありません。これは「擬似乱数」と呼ばれるものです。
この記事では、C言語の標準ライブラリを使って、この擬似乱数を生成する基本的な方法を、初心者の方にも分かりやすく、ステップバイステップで解説していきます。擬似乱数とは何かから始まり、具体的な関数の使い方、特定の範囲の値を生成する方法、そしてよくある注意点まで、詳しく見ていきましょう。
この記事を読めば、C言語で必要なランダムな値を自在に生成できるようになります。
さあ、一緒にC言語のランダムな世界へ踏み出しましょう!
擬似乱数とは? なぜ「擬似」なのか?
コンピュータは、決められた手順(アルゴリズム)に従って計算を行います。全く予測不可能な、真の意味での「ランダム」な値を生成するには、例えばサイコロを振る、空気中の分子の動きを測定するなど、物理的な現象を利用する必要があります。これはコンピュータ単独では難しいことです。
そこでコンピュータの世界では、「擬似乱数 (Pseudo-Random Number)」が使われます。擬似乱数とは、ある初期値(これを「シード (seed)」と呼びます)を与えると、あらかじめ決められた計算式(アルゴリズム)に従って、あたかもランダムであるかのように見える数列を生成するものです。
この数列は、計算によって生成されるため、シード値と計算アルゴリズムが分かっていれば、次にどんな値が出てくるかを予測することができます。また、同じシード値を使えば、いつでも全く同じ数列が生成されます。真の乱数では、同じ条件で何度繰り返しても同じ結果にはなりません。この予測可能性が「擬似」と呼ばれる理由です。
しかし、適切なシード値を使い、高品質なアルゴリズムで生成された擬似乱数は、多くの用途(ゲームやシミュレーションなど)において、真の乱数と区別がつかないほどランダムに見えます。
C言語の標準ライブラリで提供されている乱数生成機能も、この擬似乱数を生成するものです。
ポイント:
- コンピュータは真の乱数を直接生成できない。
- C言語で使うのは「擬似乱数」。
- 擬似乱数は「シード値」と「計算アルゴリズム」から生成される。
- 同じシード値からは常に同じ擬似乱数の並び(シーケンス)が生成される。
この「同じシード値からは常に同じシーケンスが生成される」という性質を理解しておくことが、C言語でランダムな値を扱う上で非常に重要になります。
C言語での基本的なランダム生成: rand()
と srand()
C言語で擬似乱数を生成するために、標準ライブラリには以下の二つの関数が用意されています。これらは <stdlib.h>
というヘッダーファイルに含まれています。
rand()
: 擬似乱数を一つ生成する関数。srand()
: 擬似乱数生成器のシード値を設定する関数。
この二つの関数を組み合わせて使います。
rand()
関数の使い方
rand()
関数は引数を取らず、呼び出すたびに擬似乱数を一つ返します。
“`c
include // rand() 関数を使うために必要
int main() {
int random_value = rand(); // 乱数を一つ生成
// 生成された乱数を使う処理
return 0;
}
“`
rand()
が返す値は int
型の整数です。この値は、0
から RAND_MAX
までの範囲のいずれかになります。RAND_MAX
は <stdlib.h>
で定義されている定数で、その環境で rand()
が生成できる最大値を表します。RAND_MAX
の値はシステムによって異なりますが、最低でも 32767 であることが標準で定められています。多くの現代的なシステムでは、それよりもずっと大きい値(例えば 2147483647 など)になっています。
試しに、rand()
を数回呼び出して、どんな値が返ってくるか見てみましょう。
“`c
include // printf 関数を使うために必要
include // rand(), RAND_MAX を使うために必要
int main() {
printf(“最初の乱数: %d\n”, rand());
printf(“次の乱数: %d\n”, rand());
printf(“さらに次の乱数: %d\n”, rand());
printf(“RAND_MAX の値: %d\n”, RAND_MAX); // RAND_MAX の値を確認
return 0;
}
“`
このプログラムを実行してみてください。おそらく、実行するたびに異なる値が表示される…と期待するかもしれませんが、実はそうはなりません。
注意:srand()
を呼ばずに rand()
を使うと…
先ほどのプログラムを何度か実行してみると、驚くことに、実行するたびに全く同じ乱数の並びが表示されるはずです。
(例)
最初の乱数: 1804289383
次の乱数: 846930886
さらに次の乱数: 1681692777
RAND_MAX の値: 2147483647
なぜ毎回同じ値になるのでしょうか?
それは、擬似乱数生成器が内部で使うシード値が、プログラムの実行開始時に常に同じ初期値(通常は 1)で設定されているからです。前述の通り、同じシード値からは常に同じ擬似乱数のシーケンスが生成されます。
これでは、ゲームのキャラクターが毎回同じ動きをしたり、シミュレーションの結果が毎回同じになったりして、困ってしまいますね。ここで必要になるのが、もう一つの関数 srand()
です。
srand()
関数の使い方
srand()
関数は、擬似乱数生成器が使用するシード値を設定するために使います。
“`c
include // srand() 関数を使うために必要
int main() {
unsigned int seed = 123; // 例としてシード値に 123 を使う
srand(seed); // シード値を設定
// これ以降の rand() の呼び出しは、設定したシード値に基づいたシーケンスを生成する
return 0;
}
“`
srand()
は unsigned int
型の引数を一つ取ります。この引数として指定した値が、擬似乱数生成器の新たなシード値となります。
では、srand()
を使ってシード値を設定してから rand()
を呼び出すように、先ほどのプログラムを修正してみましょう。
“`c
include
include
include // time() 関数を使う可能性があるため(後述)
int main() {
unsigned int seed_value = 123; // 例として固定のシード値 123 を使う
srand(seed_value); // シード値を設定
printf("シード値 %u を設定しました。\n", seed_value);
printf("最初の乱数: %d\n", rand());
printf("次の乱数: %d\n", rand());
printf("さらに次の乱数: %d\n", rand());
// 別のシード値で試してみる(通常は一度だけ設定すればOK)
unsigned int another_seed = 456;
srand(another_seed); // 新しいシード値を設定
printf("\nシード値 %u を再設定しました。\n", another_seed);
printf("再設定後の最初の乱数: %d\n", rand()); // ここから新しいシーケンス
printf("再設定後の次の乱数: %d\n", rand());
return 0;
}
“`
このプログラムを実行してみてください。
“`
(実行例1)
シード値 123 を設定しました。
最初の乱数: 1445865041
次の乱数: 1808217201
さらに次の乱数: 279936431
シード値 456 を再設定しました。
再設定後の最初の乱数: 568033608
再設定後の次の乱数: 1601500443
(実行例2 – 実行例1と同じ環境で同じシード値を使った場合)
シード値 123 を設定しました。
最初の乱数: 1445865041 <– 実行例1と同じ
次の乱数: 1808217201 <– 実行例1と同じ
さらに次の乱数: 279936431 <– 実行例1と同じ
シード値 456 を再設定しました。
再設定後の最初の乱数: 568033608 <– 実行例1と同じ
再設定後の次の乱数: 1601500443 <– 実行例1と同じ
“`
この例からわかるように、srand(123)
と設定した後の rand()
の出力は、プログラムを何度実行しても常に同じになります。同様に、srand(456)
と設定した後の rand()
の出力も、常に同じになりますが、srand(123)
の場合とは異なるシーケンスになります。
つまり、srand()
を使うことで、擬似乱数の「始まり」を制御できるわけです。
srand()
はいつ呼ぶべきか?
通常、srand()
はプログラムの実行開始時に一度だけ呼び出せば十分です。乱数が必要になるたびに srand()
を何度も呼び出してしまうと、シード値が頻繁に変わりすぎて、かえってランダム性が損なわれたり、期待通りの結果が得られなくなったりすることがあります。
じゃあ、毎回違う乱数のシーケンスを得るためには、どうすれば良いのでしょうか? それには、「実行するたびに変わる値」をシードとして使えば良いのです。最も一般的なのが、現在の時刻をシードとして使う方法です。
時間を使ったシード値の設定
プログラムを実行するたびに異なる乱数列を得るために、多くの場合はプログラムを実行した「時刻」をシードとして使います。なぜなら、プログラムを開始する時刻は、通常、実行するたびに少しずつでも変化するからです。
C言語で現在の時刻を取得するには、<time.h>
ヘッダーファイルに含まれる time()
関数を使います。
time()
関数の使い方
time()
関数は、通常、協定世界時 (UTC) の1970年1月1日0時0分0秒からの経過秒数を返します。この値は time_t
という型で表現されます。
“`c
include // time() 関数を使うために必要
int main() {
time_t current_time = time(NULL); // 現在の時刻を取得
// current_time の値は、プログラムを実行するたびに(少なくとも1秒経過すれば)変化する
return 0;
}
“`
time(NULL)
のように引数に NULL
を指定するのが一般的です。これで現在のカレンダー時刻が time_t
型で返されます。
この time(NULL)
が返す値を、srand()
のシード値として使います。srand()
は unsigned int
型の引数を取るので、time_t
型の値を unsigned int
型にキャスト(型変換)して渡します。
“`c
include // srand() を使うために必要
include // time() を使うために必要
int main() {
// 現在の時刻をシードとして設定
// time(NULL) が返す time_t 型を unsigned int 型にキャスト
srand((unsigned int)time(NULL));
// これ以降の rand() は、実行するたびに異なるシーケンスを生成する
printf("乱数生成器を時刻で初期化しました。\n");
printf("最初の乱数: %d\n", rand());
printf("次の乱数: %d\n", rand());
printf("さらに次の乱数: %d\n", rand());
return 0;
}
“`
このプログラムをコンパイルして、何度か実行してみてください。今度は、実行するたびに異なる乱数の並びが表示されるはずです。
“`
(実行例1)
乱数生成器を時刻で初期化しました。
最初の乱数: 1723351062
次の乱数: 1775177973
さらに次の乱数: 1192614188
(実行例2 – しばらく時間を置いてから実行)
乱数生成器を時刻で初期化しました。
最初の乱数: 1633597880 <– 実行例1とは異なる
次の乱数: 1598911499 <– 実行例1とは異なる
さらに次の乱数: 1410003795 <– 実行例1とは異なる
“`
これで、実行するたびに「違う」ランダムな値を得られるようになりました!
注意点:
time(NULL)
が返す値は「秒単位」です。もし、同じプログラムを1秒以内に複数回起動した場合、time(NULL)
は同じ値を返すため、結果として同じ乱数列が生成されてしまいます。多くのアプリケーションではこれで十分ですが、もしミリ秒単位などで異なる乱数列が必要な場合は、より高精度な時刻情報や、OS固有の乱数生成機能などを利用する必要があります(C言語標準ライブラリの範囲外です)。srand((unsigned int)time(NULL));
は、通常、プログラムのmain
関数の先頭など、プログラム起動時に一度だけ実行します。プログラムの実行中に何度も呼び出す必要はありませんし、呼び出すと前述のようにかえって問題が生じることがあります。
これで、C言語で基本的な擬似乱数を生成し、実行ごとに異なる結果を得るための準備が整いました。
特定の範囲のランダム値を生成する
rand()
関数は 0
から RAND_MAX
までの範囲の整数を生成します。しかし、サイコロの目のように「1から6まで」とか、パーセンテージのように「0から100まで」といった、特定の範囲の乱数が必要になることがほとんどです。
rand()
が返す値を利用して、特定の範囲の乱数を生成する方法を学びましょう。ここでは、主に整数と浮動小数点数の二つのケースを考えます。
特定の範囲の整数を生成する
0 から N-1 までの範囲
rand()
が返す値を、目的の範囲の「サイズ」で割った余り(剰余)を取ることで、0 から「サイズ – 1」までの範囲の値を生成できます。これには、C言語の剰余演算子 %
を使います。
rand() % N
この式は、rand()
が返した値を N
で割った余りを計算します。剰余演算子の性質により、結果は 0
から N-1
までのいずれかの値になります。
例:0 から 99 までの乱数
100種類の値(0, 1, 2, …, 99)が必要なので、サイズは 100 です。
c
int random_0_to_99 = rand() % 100;
これにより、random_0_to_99
には 0 から 99 のいずれかの整数が格納されます。
1 から N までの範囲
サイコロの目のように「1から6まで」といった、「1から始まる」範囲が必要な場合、上記の「0 から N-1」の範囲の乱数を生成してから、それに 1 を足せば良いだけです。
例:1 から 6 までの乱数(サイコロの目)
6種類の値(1, 2, 3, 4, 5, 6)が必要なので、サイズは 6 です。まず 0 から 5 までの乱数 (rand() % 6
) を生成し、それに 1 を足します。
c
int dice_roll = rand() % 6 + 1;
これにより、dice_roll
には 1 から 6 のいずれかの整数が格納されます。
一般的な範囲 [min, max] の整数を生成する
min
から max
までの任意の範囲の整数を生成したい場合、上記の考え方を応用できます。
目的の範囲 [min, max]
に含まれる整数の個数(サイズ)は max - min + 1
です。
例えば、[10, 20] の範囲なら、個数は 20 – 10 + 1 = 11 個です (10, 11, …, 20)。
まず、0 から サイズ - 1
までの乱数を生成します。つまり、0
から (max - min + 1) - 1
すなわち max - min
までの乱数を生成します。これは rand() % (max - min + 1)
で得られます。
この値は 0 から max - min
の範囲を動きます。この範囲を min
から max
までにずらすには、生成された値に min
を足せば良いのです。
したがって、範囲 [min, max]
の整数を生成する一般的な式は以下のようになります。
rand() % (max - min + 1) + min
例:10 から 20 までの乱数
min = 10
, max = 20
です。サイズは 20 - 10 + 1 = 11
です。
c
int random_10_to_20 = rand() % (20 - 10 + 1) + 10; // rand() % 11 + 10 と同じ
これにより、random_10_to_20
には 10 から 20 のいずれかの整数が格納されます。
この公式 rand() % (max - min + 1) + min
を覚えておくと、様々な範囲の整数乱数を簡単に生成できます。
注意点:rand() % N
の偏りについて
rand() % N
という方法はシンプルで一般的ですが、厳密には小さな偏りが発生する可能性があります。これは、rand()
が返す最大値 RAND_MAX
が、求めたいサイズ N
のちょうど倍数になっていない場合に起こります。
例えば、RAND_MAX
が 32767 で、1 から 3 までの乱数(サイズ 3)を rand() % 3 + 1
で生成する場合を考えます。
rand()
は 0 から 32767 までの値を返します。
rand() % 3
の結果は 0, 1, 2 のいずれかになります。
理想的には、0, 1, 2 がそれぞれ等しい確率で出現してほしいところです。
32767 を 3 で割ると 10922 余り 1 です (32767 = 3 * 10922 + 1)。
つまり、rand()
の結果のうち、
* 3 で割ると余り 0 になる値は 0, 3, 6, …, 32766 の 10923 個。
* 3 で割ると余り 1 になる値は 1, 4, 7, …, 32767 の 10923 個。
* 3 で割ると余り 2 になる値は 2, 5, 8, …, 32765 の 10922 個。
したがって、rand() % 3
の結果は、0 と 1 が 2 より少しだけ出やすくなります。この偏りは、RAND_MAX
に比べて N
が非常に小さい場合に顕著になります。
ただし、多くの環境では RAND_MAX
が非常に大きく、目的の範囲 N
が小さい場合、この偏りは無視できるほど小さいです。一般的なゲームやシミュレーションであれば、rand() % (max - min + 1) + min
の方法で問題ありません。
より厳密に均等な分布が求められる場合は、rand()
の結果をそのまま使うのではなく、例えば (double)rand() / (RAND_MAX + 1.0)
のように正規化してから範囲にマッピングしたり、特定の範囲外の値が出た場合はもう一度生成し直すなどの工夫が必要になります。あるいは、後述するC++11以降の <random>
のような、より高度な乱数生成器を使用することを検討します。
初心者向けの記事としては、まず基本的な %
演算子を使った方法をしっかりと理解することが重要です。偏りの話は、知識として知っておくと良いでしょう。
特定の範囲の浮動小数点数を生成する
整数だけでなく、0.0 から 1.0 の間のような、特定の範囲の浮動小数点数(小数)の乱数が必要な場合もあります。
0.0 から 1.0 までの範囲
rand()
が返す値は 0 から RAND_MAX
までの整数です。これを RAND_MAX
で割ることで、0.0 から 1.0 の間の浮動小数点数を得ることができます。
rand() / RAND_MAX
ただし、ここで注意が必要です。C言語では、整数同士の割り算は小数点以下が切り捨てられます。例えば、5 / 2
は 2
になります。rand()
と RAND_MAX
はどちらも整数型なので、そのまま割ってしまうと結果は 0 か 1 になってしまい、期待通りになりません。
これを防ぐためには、どちらか一方を浮動小数点型(double
または float
)にキャスト(型変換)してから割り算を行います。
(double)rand() / RAND_MAX
または
rand() / (double)RAND_MAX
一般的には、rand()
の戻り値を double
にキャストする方法がよく使われます。
c
double random_0_to_1 = (double)rand() / RAND_MAX;
これで、random_0_to_1
には 0.0 から 1.0 までの間の浮動小数点数が格納されます。(ただし、RAND_MAX
で割っているので、正確には 0.0 から 1.0 未満 となる可能性が高いです。なぜなら rand()
の最大値は RAND_MAX
であり、(double)RAND_MAX / RAND_MAX
は 1.0 になりますが、ほとんどの場合 rand()
の値は RAND_MAX
より小さいためです。厳密には 0.0 から 1.0 以下 の値を取りうると考えられます。)
より正確には 0.0 から 1.0 未満 の範囲 (つまり [0.0, 1.0) ) の乱数が必要な場合は、RAND_MAX
ではなく RAND_MAX + 1.0
で割る方法も考えられます。
(double)rand() / (RAND_MAX + 1.0)
しかし、多くの用途では (double)rand() / RAND_MAX
で十分です。初心者の方は、まずはこちらの方法を覚えておきましょう。
特定の範囲 [min, max] の浮動小数点数を生成する
0.0 から 1.0 の間の乱数を使って、任意の範囲 [min, max]
の浮動小数点数乱数を生成することもできます。
まず、0.0 から 1.0 の乱数 ((double)rand() / RAND_MAX
) を生成します。
この乱数は 0.0 から 1.0 の範囲を動きます。
これを目的の範囲の幅 max - min
倍します。これで 0.0 から max - min
の範囲の乱数になります。
(double)rand() / RAND_MAX * (max - min)
最後に、この値に min
を足すことで、範囲が min
から max
にずれます。
min + (double)rand() / RAND_MAX * (max - min)
したがって、範囲 [min, max]
の浮動小数点数を生成する一般的な式は以下のようになります。
min + (double)rand() / RAND_MAX * (max - min)
例:0.0 から 100.0 までの乱数
min = 0.0
, max = 100.0
です。
c
double random_0_to_100 = 0.0 + (double)rand() / RAND_MAX * (100.0 - 0.0); // (double)rand() / RAND_MAX * 100.0 と同じ
例:50.0 から 75.0 までの乱数
min = 50.0
, max = 75.0
です。
c
double random_50_to_75 = 50.0 + (double)rand() / RAND_MAX * (75.0 - 50.0); // 50.0 + (double)rand() / RAND_MAX * 25.0 と同じ
これらの式を使えば、必要な範囲の浮動小数点数乱数を生成できます。浮動小数点数の計算になるため、小数点以下の精度は double
型の精度に依存します。
よくある落とし穴と注意点
C言語の rand()
と srand()
はシンプルで使いやすいですが、いくつか注意すべき点があります。
-
srand()
はプログラム実行時に一度だけ呼ぶ
これはすでに説明しましたが、非常に重要なので繰り返します。乱数が必要になるたびにsrand(time(NULL));
のように呼び出してしまうと、短い時間内に同じシード値が何度も設定される可能性が高くなり、結果として生成される乱数が単調になったり、期待通りのランダム性が得られなくなったりします。main
関数の先頭など、プログラムの起動処理の一部として一度だけ呼び出すのが正しい使い方です。悪い例:
c
// こんな使い方はしない!
for (int i = 0; i < 10; ++i) {
srand((unsigned int)time(NULL)); // ループ内で毎回呼ばない
printf("%d\n", rand());
}
このコードは、同じ秒内にループが回った場合、常に同じ乱数を10回出力します。良い例:
c
// srand はループの外で、プログラム開始時に一度だけ
srand((unsigned int)time(NULL));
for (int i = 0; i < 10; ++i) {
printf("%d\n", rand()); // rand() は何度呼んでもOK
}
このようにすることで、毎回異なる乱数のシーケンスから値が取り出されます。 -
time(NULL)
の精度
time(NULL)
が返す時刻は秒単位です。非常に短い間隔(1秒未満)でプログラムを複数起動したり、プログラム内で短い時間内に大量の乱数を生成しつつ途中でsrand(time(NULL))
を呼び出したりすると、同じシード値が使われる可能性があります。一般的な用途では問題になりにくいですが、厳密なランダム性が求められる場合や、同じ秒内に複数のプロセスで異なる乱数列を使いたい場合などは、より高精度な時刻情報(例えばgettimeofday
関数など、OS依存の関数)や、シードの生成に他の要素(プロセスIDなど)も組み合わせることを検討する必要があります。 -
RAND_MAX
は環境によって異なる
RAND_MAX
は<stdlib.h>
で定義されていますが、その具体的な値はC言語の規格によって最低値(32767)だけが定められており、使用するコンパイラやOSによって異なります。RAND_MAX
が小さい環境では、rand()
の返す値の種類が少なくなり、乱数の質が低くなる可能性があります。また、rand() % N
の偏りの問題もより顕著になります。プログラムを書く際は、特定のRAND_MAX
の値に依存するようなマジックナンバーを使わず、必ず<stdlib.h>
のRAND_MAX
定数を使用するようにしましょう。 -
rand() % N
の偏り
前述したとおり、rand() % N
は厳密には偏りがある可能性があります。ほとんどのアプリケーションでは問題になりませんが、統計的なシミュレーションや暗号など、高い品質の乱数が必要な場合は、この方法だけでは不十分です。 -
暗号論的に安全な乱数には不適
C言語のrand()
で生成される擬似乱数は、シード値が分かれば次に生成される値を予測できてしまいます。また、アルゴリズムによっては短い周期で同じパターンを繰り返したり、統計的な偏りがあったりします。そのため、セキュリティが重要なアプリケーション(例えば、暗号化キーの生成、認証トークンの生成など)には絶対に使用してはいけません。暗号論的に安全な乱数が必要な場合は、OSが提供する機能(例えば、Linuxの/dev/urandom
や WindowsのCryptGenRandom
など)や、専用の暗号ライブラリが提供する乱数生成機能を使用する必要があります。C言語標準ライブラリのrand()
は、あくまで「見た目ランダムであれば十分」な用途(ゲームなど)に限定して使いましょう。
これらの注意点を理解しておくことで、rand()
と srand()
をより適切に、安全に利用することができます。
応用例:簡単なコード
ここまで学んだことを活かして、いくつかの簡単な応用例を見てみましょう。
例1:サイコロを10回振る
1から6までのランダムな整数を10回生成します。
“`c
include
include
include
int main() {
// プログラム開始時に一度だけ乱数生成器を初期化
srand((unsigned int)time(NULL));
printf("サイコロを10回振ります。\n");
for (int i = 0; i < 10; ++i) {
// 1 から 6 の乱数を生成
int dice_roll = rand() % 6 + 1;
printf("%d回目の目: %d\n", i + 1, dice_roll);
}
return 0;
}
“`
実行するたびに、異なる10個のサイコロの目が表示されるはずです。
例2:簡単なじゃんけんゲーム
コンピュータとユーザーがじゃんけんをします。コンピュータの手を乱数で決めます。
“`c
include
include
include
int main() {
// プログラム開始時に一度だけ乱数生成器を初期化
srand((unsigned int)time(NULL));
int user_hand; // ユーザーの手 (0:グー, 1:チョキ, 2:パー)
int computer_hand; // コンピュータの手
printf("じゃんけんゲーム開始!\n");
printf("出す手を選んでください。\n");
printf("0: グー, 1: チョキ, 2: パー\n");
printf("あなたの手: ");
scanf("%d", &user_hand);
// ユーザーの入力が不正な場合は終了
if (user_hand < 0 || user_hand > 2) {
printf("不正な入力です。終了します。\n");
return 1;
}
// コンピュータの手を 0 から 2 の乱数で決める
computer_hand = rand() % 3; // 0, 1, 2 のいずれか
// 手を表示
printf("あなたの手: ");
switch (user_hand) {
case 0: printf("グー\n"); break;
case 1: printf("チョキ\n"); break;
case 2: printf("パー\n"); break;
}
printf("コンピュータの手: ");
switch (computer_hand) {
case 0: printf("グー\n"); break;
case 1: printf("チョキ\n"); break;
case 2: printf("パー\n"); break;
}
// 勝敗判定
// 引き分け: user_hand == computer_hand
// ユーザーの勝ち: (user_hand == 0 && computer_hand == 1) || (user_hand == 1 && computer_hand == 2) || (user_hand == 2 && computer_hand == 0)
// それ以外はコンピュータの勝ち
if (user_hand == computer_hand) {
printf("結果: 引き分けです!\n");
} else if ((user_hand == 0 && computer_hand == 1) ||
(user_hand == 1 && computer_hand == 2) ||
(user_hand == 2 && computer_hand == 0)) {
printf("結果: あなたの勝ちです!\n");
} else {
printf("結果: コンピュータの勝ちです!\n");
}
return 0;
}
“`
このプログラムでは、rand() % 3
を使って 0, 1, 2 のいずれかの値をランダムに生成し、コンピュータの手としています。実行するたびにコンピュータの手が変わるので、飽きずに遊べる(かもしれない)じゃんけんゲームができました。
例3:配列の要素をランダムにシャッフルする
乱数は、配列やリストの要素をランダムな順序に並べ替える(シャッフルする)ためにもよく使われます。ここでは、簡単なフィッシャー-イェーツアルゴリズムの考え方を使ったシャッフルを行います。
考え方:
1. 配列の最後の要素から順番に見ていく。
2. 現在の要素から配列の先頭(または現在の要素自身)までの範囲で、ランダムな位置を選ぶ。
3. 現在の要素とランダムな位置の要素を交換する。
4. これを配列の先頭の要素まで繰り返す。
“`c
include
include
include
// 整数配列をシャッフルする関数
void shuffle_array(int arr[], int size) {
// 配列サイズが1以下の場合はシャッフル不要
if (size <= 1) {
return;
}
// 配列の末尾から先頭に向かってループ
for (int i = size - 1; i > 0; --i) {
// 0 から i までの範囲でランダムなインデックスを生成
// (max - min + 1) + min の公式で min=0, max=i なので (i - 0 + 1) + 0 => i + 1
int random_index = rand() % (i + 1);
// 現在の要素 arr[i] とランダムな位置 arr[random_index] の要素を交換
int temp = arr[i];
arr[i] = arr[random_index];
arr[random_index] = temp;
}
}
int main() {
// プログラム開始時に一度だけ乱数生成器を初期化
srand((unsigned int)time(NULL));
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(numbers) / sizeof(numbers[0]); // 配列の要素数を計算
printf("元の配列: ");
for (int i = 0; i < size; ++i) {
printf("%d ", numbers[i]);
}
printf("\n");
// 配列をシャッフル
shuffle_array(numbers, size);
printf("シャッフル後の配列: ");
for (int i = 0; i < size; ++i) {
printf("%d ", numbers[i]);
}
printf("\n");
// もう一度シャッフル
shuffle_array(numbers, size);
printf("もう一度シャッフル後の配列: ");
for (int i = 0; i < size; ++i) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
“`
この例では、rand() % (i + 1)
を使って、ループ変数 i
の値に応じた範囲 [0, i]
のインデックスをランダムに生成しています。そして、現在の要素 arr[i]
とランダムに選ばれた要素 arr[random_index]
を交換しています。これにより、配列の要素がランダムな順序に並べ替えられます。実行するたびに異なるシャッフル結果が得られるはずです。
これらの応用例から、rand()
と srand()
が様々な場面で役立つことがお分かりいただけたかと思います。
より高度な乱数生成(発展的な内容)
C言語の標準ライブラリで提供される rand()
関数は、手軽に使えて多くの用途には十分ですが、いくつか限界もあります。
- 乱数の質: 生成される擬似乱数の質は、使用されているアルゴリズムに依存します。多くのシステムで使われているアルゴリズムは、統計的な偏りがあったり、比較的短い周期で同じパターンを繰り返したりすることがあります。より高品質な乱数(例えば、より長い周期、より均等な分布)が必要な場合は、他の方法を検討する必要があります。
- シード値の管理:
srand()
とtime(NULL)
を使う方法は一般的ですが、同じ秒内に複数のプロセスで異なる乱数列が必要な場合など、より複雑な状況ではシード値の適切な管理が必要になります。 - 分布の指定:
rand()
は一様分布(どの値も同じ確率で出現する)の乱数を生成しますが、正規分布や指数分布など、特定種類の統計的な分布に従う乱数が必要な場合もあります。
これらの課題に対応するために、より高度な乱数生成の仕組みが存在します。
C++11 以降の <random>
ライブラリ
もしC++を使う場合は、C++11規格以降で導入された <random>
ライブラリが非常に強力です。このライブラリでは、
- 多様な乱数エンジン: メルセンヌ・ツイスター (
std::mt19937
など) のような、より高品質な擬似乱数生成アルゴリズムを利用できます。 - 多様な分布: 一様分布はもちろん、正規分布 (
std::normal_distribution
)、指数分布 (std::exponential_distribution
) など、様々な確率分布に従う乱数を生成できます。 - シードの柔軟な指定: より細かくシード値を制御したり、複数のシード値から状態を初期化したりできます。
C++を使う機会がある場合は、<random>
ライブラリの利用を検討すると良いでしょう。
OS固有の乱数生成機能
前述したように、暗号などセキュリティが重要な場面では、C言語標準ライブラリの rand()
は使えません。このような場合は、OSが提供する、より強力で予測困難な乱数生成機能を利用します。
- Linux/macOS など Unix系OS:
/dev/random
や/dev/urandom
というデバイスファイルから乱数を読み出す方法があります。/dev/random
はエントロピー(システムの物理的なノイズ)を利用するため真の乱数に近いですが、エントロピーが不足すると読み出しがブロックされることがあります。/dev/urandom
は既存のエントロピーから擬似乱数を生成するためブロックされませんが、/dev/random
よりは予測可能性が高まります(ただし、通常の擬似乱数よりはるかに強力です)。 - Windows:
CryptGenRandom
関数などのCryptoAPIが提供する機能を利用します。
これらの機能はOSに依存するため、C言語の標準ライブラリ関数ではありません。利用するには、それぞれのOSのAPIを呼び出すコードを書く必要があります。
まとめとして
初心者の方がC言語で乱数を使う場合、まずは <stdlib.h>
の rand()
と srand()
の使い方をマスターすれば十分です。ゲームや練習用プログラムでは、これらの関数で生成される擬似乱数で全く問題ありません。より高度な応用や厳密な要件がある場合に、ここで紹介した発展的な内容を深掘りしていくと良いでしょう。
まとめ
この記事では、C言語でランダムな値を生成する方法について、初心者向けに詳しく解説しました。
重要なポイントを振り返りましょう。
- コンピュータは真の乱数を生成できず、C言語標準ライブラリでは擬似乱数を使用します。
- 擬似乱数生成器はシード値に基づいて数列を生成します。
- 擬似乱数を生成するには、
<stdlib.h>
ヘッダーファイルが必要です。 rand()
関数は、シード値に基づいた擬似乱数(0からRAND_MAX
までの整数)を生成して返します。srand()
関数は、擬似乱数生成器のシード値を設定します。- 実行するたびに異なる乱数列を得るためには、
srand()
のシード値として現在の時刻を使うのが一般的です。これには<time.h>
のtime(NULL)
関数を使用し、srand((unsigned int)time(NULL));
のように呼び出します。 srand()
関数は、プログラムの実行開始時に一度だけ呼び出すようにします。- 特定の範囲
[min, max]
の整数を生成するには、rand() % (max - min + 1) + min
の式が利用できます。 - 特定の範囲
[min, max]
の浮動小数点数を生成するには、min + (double)rand() / RAND_MAX * (max - min)
の式が利用できます。 rand() % N
にはわずかな偏りがあることや、rand()
関数は暗号などのセキュリティに関わる用途には使えないことに注意が必要です。
これらの基本的な関数と概念を理解すれば、C言語で様々なランダム要素をプログラムに取り入れることができるようになります。
ぜひ、実際にコードを書いて、rand()
と srand()
の動きを確認してみてください。サイコロゲームを作ってみたり、ランダムな数字を生成して何か面白いプログラムを作ってみたりするのも良い練習になります。
C言語でのプログラミング学習を進める中で、乱数はきっとあなたのプログラムをより面白く、多様なものにしてくれるでしょう。
これで、C言語でランダムな値を生成する方法に関する詳細な説明は終わりです。
もし、さらに乱数の質にこだわる必要が出てきたり、特定の分布に従う乱数が必要になったりした場合は、C++の <random>
ライブラリやOS固有の機能について調べてみることをお勧めします。しかし、まずはこの記事で紹介した基本的な方法をしっかりと身につけてください。
あなたのC言語での乱数プログラミングが成功することを願っています!
参考文献/参考資料:
- C言語標準ライブラリ関数リファレンス (rand, srand, time)
- ISO/IEC 9899 (C言語の規格書)
- 各コンパイラのドキュメント
以上で約5000語の詳細な解説記事となります。初心者向けに、各概念やコード例、注意点を丁寧に説明することを心がけました。
はい、承知いたしました。C言語でランダムな値を生成する方法について、初心者向けに約5000語の詳細な解説記事を作成します。以下に記事の内容を直接表示します。
C言語でランダムな値を生成する方法【初心者向け】
はじめに
プログラミングの世界では、予測できない、あるいはデタラメな値、つまり「ランダムな値」が必要になる場面が頻繁にあります。ゲームで敵がどこに出現するか、カードゲームで山札をどう並べるか、シミュレーションで現実世界の不確実性をどう表現するか、といった多くの状況でランダムな値は不可欠な要素となります。
例えば、あなたがC言語で
- サイコロを振るプログラムを作りたい
- トランプの山札をシャッフルしたい
- 簡単なゲームで敵の出現位置をランダムに決めたい
- 何かの抽選プログラムを作りたい
このようなことを考えたとき、どのようにしてコンピュータに「ランダムな数字」を選ばせることができるのでしょうか?
コンピュータは、私たちが与えた命令(プログラム)に従って正確に動作する機械です。同じ入力に対しては、常に同じ出力が得られるのがコンピュータの基本的な性質です。このような仕組みで、どうやって予測できないランダムな値を生み出すのでしょう?
実は、C言語を含むほとんどのプログラミング言語で使われる「ランダムな値」は、厳密には「真のランダムな値」ではありません。これは「擬似乱数(ぎじらんすう)」と呼ばれるものです。擬似乱数は、ある規則(アルゴリズム)に基づいて計算によって生成される、あたかもランダムであるかのように見える数列です。
この記事では、C言語の標準ライブラリが提供する機能を使って、この擬似乱数を生成する最も基本的で一般的な方法を、初心者の方にも分かりやすく、丁寧に解説していきます。
この記事を読むことで、あなたは以下のことを学べます:
- 擬似乱数とは何か、なぜコンピュータは真の乱数を生成しにくいのか
- C言語で擬似乱数を生成するための基本的な関数
rand()
の使い方 - 毎回異なる乱数列を得るために必要な
srand()
関数の使い方 - プログラムを実行するたびに異なる結果を得るための「シード値」の設定方法
- 特定の範囲(例: 1から6のサイコロの目)のランダムな値を生成する方法
- 乱数生成におけるいくつかの注意点や落とし穴
さあ、C言語の世界で「ランダム」を実現するための第一歩を踏み出しましょう!
擬似乱数とは? なぜ「擬似」なのかを理解する
コンピュータは、決められた手順(アルゴリズム)と入力データに基づいて、常に同じ結果を出力する設計になっています。これは、プログラムが予測可能で信頼できるようにするために非常に重要です。しかし、この性質は「予測できないランダムな値」を生成することとは矛盾します。
真のランダムな値は、物理的な現象(例えば、放射性崩壊、大気ノイズ、サイコロの出目など)に由来する、全く予測不可能なものです。コンピュータ単体では、このような物理現象を直接利用することは難しいです。
そこで登場するのが「擬似乱数」です。擬似乱数(Pseudo-Random Number)は、「偽物の」あるいは「見せかけの」乱数という意味です。これは、ある初期値(シード、seed)を与えられ、あらかじめ決められた計算式(アルゴリズム)に従って計算を繰り返すことで生成される数列です。
擬似乱数生成器(Pseudo-Random Number Generator, PRNG)と呼ばれる仕組みが、この計算を行います。PRNGは、内部に現在の「状態」を持っており、rand()
関数が呼び出されるたびに、この状態を使って次の乱数値を計算し、同時に自身の状態を更新します。この最初の「状態」を決定するのがシード値です。
擬似乱数の性質:
- 決定論的である: シード値とアルゴリズムが同じであれば、生成される数列は常に全く同じになります。例えば、シード値 100 から始まる擬似乱数列が [5, 12, 3, 8, …] だとすると、次にシード値 100 を使って生成した場合も必ず [5, 12, 3, 8, …] となります。
- 周期性がある: 計算によって生成されるため、いつかは同じ状態に戻り、そこからは同じ数列が繰り返されます。周期が非常に長いアルゴリズムであれば、実用上は繰り返しを気にする必要はありません。
- 真の乱数ではない: 上記の性質から、シード値や過去の値を分析すれば、将来生成される値を予測することが可能です。そのため、暗号化キーの生成など、高いセキュリティが必要な場面には使用できません。
「擬似」乱数と呼ばれるのは、この「決定論的であること」と「周期性があること」、そして「予測可能であること」によるものです。しかし、適切なアルゴリズムとシード値を使えば、多くの一般的なアプリケーション(ゲーム、シミュレーションなど)では、統計的に偏りが少なく、十分ランダムに見える数列を得ることができます。
重要なポイント:
- C言語の
rand()
は擬似乱数を生成する。 - 擬似乱数はシード値と計算アルゴリズムによって決まる。
- 同じシード値からは常に同じ乱数列が生成される。
この「同じシード値からは常に同じ乱数列が生成される」という性質が、C言語で毎回異なるランダムな結果を得るために、シード値をどう設定するかが重要になる理由です。
C言語での基本的な乱数生成:rand()
関数
C言語の標準ライブラリには、乱数を生成するための基本的な関数が用意されています。これらは <stdlib.h>
ヘッダーファイルに含まれています。
最も基本的な関数が rand()
です。
rand()
関数の使い方
rand()
関数は引数を取らず、呼び出されるたびに一つ擬似乱数(整数)を返します。
“`c
include // rand() 関数を使うために必要
int main() {
// rand() を呼び出すと、擬似乱数が一つ生成される
int random_number = rand();
// 生成された random_number を使った処理...
return 0;
}
“`
rand()
が返す値は int
型の整数です。この整数は、0
から RAND_MAX
までの範囲に含まれるいずれかの値になります。
RAND_MAX
とは?
RAND_MAX
は <stdlib.h>
ヘッダーファイルで定義されているマクロ(定数)です。これは、rand()
関数が生成できる最大の擬似乱数値を表します。C言語の標準では、RAND_MAX
の値は少なくとも 32767 であることが保証されていますが、多くの現代的なシステムではもっと大きな値(例えば 2147483647、これは 2^31 – 1 です)になっています。
試しに、rand()
関数を数回呼び出して、その結果を表示するプログラムを書いてみましょう。
“`c
include // printf 関数を使うために必要
include // rand(), RAND_MAX を使うために必要
int main() {
printf(“1回目の rand() の結果: %d\n”, rand());
printf(“2回目の rand() の結果: %d\n”, rand());
printf(“3回目の rand() の結果: %d\n”, rand());
printf(“RAND_MAX の値は %d です。\n”, RAND_MAX); // あなたの環境での RAND_MAX を表示
return 0;
}
“`
このプログラムをコンパイルして実行してみてください。そして、同じプログラムをもう一度実行してみてください。
どうでしたか?
おそらく、プログラムを何度実行しても、表示される乱数の値は全く同じ並びになったのではないでしょうか?
(例:実行するたびに以下の同じ結果が表示される)
1回目の rand() の結果: 1804289383
2回目の rand() の結果: 846930886
3回目の rand() の結果: 1681692777
RAND_MAX の値は 2147483647 です。
これはなぜでしょうか? 前のセクションで説明したシード値が関係しています。
rand()
関数は擬似乱数生成器から値を取得しますが、この擬似乱数生成器がどのシード値から始まるかは、特に何も設定しない場合、プログラムの起動時に常に同じ初期値(通常は 1)で初期化されるとC言語の標準で定められています。
つまり、デフォルトでは毎回シード値 1 から始まる同じ乱数列が使われるため、プログラムを何度実行しても rand()
を呼び出す順番が同じなら、常に同じ値が得られるのです。
これでは、ゲームの敵が毎回同じ動きをしたり、サイコロの目が毎回同じ順序で出たりしてしまい、困りますね。ここで必要になるのが、次に説明する srand()
関数です。
シード値を設定する:srand()
関数
rand()
関数が毎回同じ乱数列を生成してしまう問題を解決し、実行するたびに異なる乱数列を得るためには、擬似乱数生成器のシード値を変更する必要があります。シード値を設定するために使うのが srand()
関数です。
srand()
関数の使い方
srand()
関数は、擬似乱数生成器が次に rand()
関数を呼び出したときに使用するシード値を設定します。
“`c
include // srand() 関数を使うために必要
int main() {
// 例としてシード値に 12345 を設定
unsigned int seed_value = 12345;
srand(seed_value);
// これ以降の rand() の呼び出しは、設定したシード値に基づいた乱数列を生成する
return 0;
}
“`
srand()
関数は unsigned int
型の引数を一つ取ります。この引数に指定した値が、新しいシード値として設定されます。
では、srand()
を使ってシード値を設定してから rand()
を呼び出すように、先ほどのプログラムを修正してみましょう。
“`c
include
include // rand(), srand() を使うために必要
int main() {
unsigned int my_seed = 123; // 固定のシード値 123 を使う例
// srand() でシード値を設定
// この呼び出しはプログラム実行中に一度だけ行うのが基本
srand(my_seed);
printf("シード値 %u を設定しました。\n", my_seed);
printf("1回目の rand() の結果: %d\n", rand());
printf("2回目の rand() の結果: %d\n", rand());
printf("3回目の rand() の結果: %d\n", rand());
// 別の固定シード値でも試してみる
unsigned int another_seed = 456;
srand(another_seed); // 新しいシード値を設定(通常は一度だけだが、ここでは動作確認用)
printf("\nシード値 %u に再設定しました。\n", another_seed);
printf("再設定後 1回目の rand() の結果: %d\n", rand()); // ここから新しい乱数列が始まる
printf("再設定後 2回目の rand() の結果: %d\n", rand());
return 0;
}
“`
このプログラムをコンパイルして実行してみてください。そして、同じプログラムをもう一度実行してみてください。
どうでしたか?
“`
(実行例1)
シード値 123 を設定しました。
1回目の rand() の結果: 1445865041
2回目の rand() の結果: 1808217201
3回目の rand() の結果: 279936431
シード値 456 に再設定しました。
再設定後 1回目の rand() の結果: 568033608
再設定後 2回目の rand() の結果: 1601500443
(実行例2 – 実行例1と同じ環境で実行)
シード値 123 を設定しました。
1回目の rand() の結果: 1445865041 <– 実行例1と同じ
2回目の rand() の結果: 1808217201 <– 実行例1と同じ
3回目の rand() の結果: 279936431 <– 実行例1と同じ
シード値 456 に再設定しました。
再設定後 1回目の rand() の結果: 568033608 <– 実行例1と同じ
再設定後 2回目の rand() の結果: 1601500443 <– 実行例1と同じ
“`
この結果からわかるように、srand()
に同じ固定値を指定した場合、その後に続く rand()
の呼び出しは毎回同じ乱数列を生成します。srand(123)
と srand(456)
では、それぞれ異なる乱数列が生成されています。
これで、シード値を変更すれば異なる乱数列が得られることが分かりました。しかし、これでもまだ、プログラムを実行するたびに「異なる」乱数を生成するという目的は達成できていません。なぜなら、シード値 123
や 456
を固定で使っている限り、何度実行しても結果は同じになるからです。
プログラムを実行するたびに異なる乱数列を得るためには、実行するたびに変化する値をシード値として srand()
に渡す必要があります。最も一般的な方法が、プログラムを実行した「現在の時刻」をシードとして使うことです。
時間を使ったシード値の設定で毎回異なる乱数列を得る
プログラムを起動する時刻は、通常、実行するたびに少しずつ変化します。この変化する時刻の値をシードとして使えば、プログラムを実行するたびに異なるシード値が設定され、結果として異なる乱数列が得られることになります。
C言語で現在の時刻を取得するには、<time.h>
ヘッダーファイルに含まれる time()
関数を使います。
time()
関数の使い方
time()
関数は、通常、協定世界時 (UTC) の1970年1月1日0時0分0秒からの経過秒数を返します。この値は time_t
という型で表現されます。
“`c
include // time() 関数を使うために必要
int main() {
// time(NULL) を呼び出すと、現在の時刻(エポックからの経過秒数)が取得できる
time_t current_time = time(NULL);
// current_time の値は、プログラムを実行するたびに(少なくとも1秒経過すれば)変化する
return 0;
}
“`
time(NULL)
のように引数に NULL
を指定するのが最も一般的です。これにより、現在のカレンダー時刻が time_t
型の値として返されます。
この time(NULL)
が返す値を、srand()
関数のシード値として使います。srand()
関数は unsigned int
型の引数を取るので、time_t
型の値を unsigned int
型にキャスト(型変換)して渡す必要があります。キャストとは、ある型の値を別の型に変換することです。
“`c
include // srand() 関数を使うために必要
include // time() 関数を使うために必要
int main() {
// 現在の時刻をシード値として設定する
// time(NULL) の戻り値 time_t を unsigned int にキャストして srand に渡す
srand((unsigned int)time(NULL));
// これ以降の rand() の呼び出しは、プログラムを実行するたびに異なる時刻をシードとするため、
// 異なる乱数列を生成するようになる
printf("乱数生成器を時刻で初期化しました。\n");
printf("1回目の rand() の結果: %d\n", rand());
printf("2回目の rand() の結果: %d\n", rand());
printf("3回目の rand() の結果: %d\n", rand());
return 0;
}
“`
このプログラムをコンパイルして、何度か実行してみてください。今度は、実行するたびに全く異なる乱数の並びが表示されるはずです。
“`
(実行例1)
乱数生成器を時刻で初期化しました。
1回目の rand() の結果: 1723351062
2回目の rand() の結果: 1775177973
3回目の rand() の結果: 1192614188
(実行例2 – しばらく時間を置いてから実行)
乱数生成器を時刻で初期化しました。
1回目の rand() の結果: 1633597880 <– 実行例1とは異なる
2回目の rand() の結果: 1598911499 <– 実行例1とは異なる
3回目の rand() の結果: 1410003795 <– 実行例1とは異なる
“`
これは、プログラムを実行するたびに time(NULL)
が異なる値を返し、その異なる値がシードとして srand()
に渡されたため、毎回異なる乱数列から値が生成された結果です。
srand()
はいつ呼び出すべきか?
srand()
関数は、プログラムの実行開始時に一度だけ呼び出すのが一般的です。通常、main
関数の最初の部分などで呼び出します。乱数が必要になるたびに srand(time(NULL))
を呼び出してしまうと、短い時間内に同じ秒数が取得されて同じシード値が何度も設定されたり、乱数列の途中でシードがリセットされてかえってランダム性が損なわれたりすることがあります。
注意点:time(NULL)
の精度
time(NULL)
が返す値は秒単位の精度です。もし、同じプログラムを1秒間に満たない非常に短い間隔で複数回起動した場合(例えば、シェルスクリプトなどから高速に連続実行した場合)、time(NULL)
は同じ値を返すため、結果として同じ乱数列が生成されてしまいます。多くの一般的なアプリケーションでは問題になりにくいですが、もしミリ秒単位などで異なる乱数列が絶対に必要だったり、同じ秒内に複数のプロセスで異なる乱数列を使いたい場合などは、time(NULL)
よりも高精度な時刻情報(例えば、OS固有の gettimeofday
関数など)を利用したり、シードの生成にプロセスIDなどを組み合わせたりするなどの工夫が必要になります(これらの方法はC言語標準ライブラリの範囲外です)。
まずは、srand((unsigned int)time(NULL));
をプログラム開始時に一度だけ呼び出す方法をしっかりと覚えましょう。これで、実行するたびに異なるランダムな結果を得るという目的は達成できます。
特定の範囲のランダム値を生成する
rand()
関数は 0
から RAND_MAX
までの非常に広い範囲の整数を生成します。しかし、多くの場合は「1から6まで」のサイコロの目や、「0から99まで」の確率など、特定の狭い範囲の乱数が必要になります。
rand()
が返す値を利用して、目的の範囲の乱数を生成する方法を学びましょう。ここでは、特定の範囲の整数と浮動小数点数を生成する方法を説明します。
特定の範囲の整数を生成する
rand()
が返す値を、目的の範囲の「サイズ」で割った余り(剰余)を取ることで、0 から「サイズ – 1」までの範囲の値を生成できます。これには、C言語の剰余演算子 %
を使います。
rand() % N
この式は、rand()
が返した整数値を N
で割った余りを計算します。剰余演算子の性質により、結果は 0
から N-1
までのいずれかの値になります。
例1:0 から 99 までの乱数
100種類の値(0, 1, 2, …, 99)が必要なので、範囲のサイズは 100 です。
c
// 0 から 99 までの乱数を生成
int random_0_to_99 = rand() % 100;
これにより、変数 random_0_to_99
には 0 から 99 のいずれかの整数がランダムに格納されます。
例2:1 から 6 までの乱数(サイコロの目)
6種類の値(1, 2, 3, 4, 5, 6)が必要なので、範囲のサイズは 6 です。
まず、rand() % 6
で 0 から 5 までの乱数を生成します。
次に、この 0 から 5 の範囲を 1 から 6 にずらすために、結果に 1 を足します。
c
// 1 から 6 までの乱数(サイコロの目)を生成
int dice_roll = rand() % 6 + 1;
これにより、変数 dice_roll
には 1 から 6 のいずれかの整数がランダムに格納されます。
一般的な範囲 [min, max] の整数を生成する
min
から max
までの任意の整数範囲 [min, max]
の乱数を生成したい場合、上記の考え方を応用できます。
範囲 [min, max]
に含まれる整数の個数、つまり範囲のサイズは max - min + 1
で計算できます。
例えば、範囲が [10, 20] の場合、含まれる整数は 10, 11, …, 20 であり、個数は 20 – 10 + 1 = 11 個です。
まず、0 から サイズ - 1
までの乱数を生成します。つまり、0
から (max - min + 1) - 1
すなわち max - min
までの乱数を生成します。これは、rand() % (max - min + 1)
という式で得られます。
この rand() % (max - min + 1)
の結果は、0, 1, 2, …, max - min
の範囲のいずれかの値になります。
この範囲を min
から max
までにずらすには、生成された値に min
を足せば良いのです。
したがって、範囲 [min, max]
の整数を生成する一般的な式は以下のようになります。
rand() % (max - min + 1) + min
例:10 から 20 までの乱数
min = 10
, max = 20
です。範囲のサイズは 20 - 10 + 1 = 11
です。
c
// 10 から 20 までの乱数を生成
int random_10_to_20 = rand() % (20 - 10 + 1) + 10; // rand() % 11 + 10 と同じ
これにより、変数 random_10_to_20
には 10 から 20 のいずれかの整数がランダムに格納されます。
この公式 rand() % (max - min + 1) + min
を覚えておけば、様々な範囲の整数乱数を簡単に生成できるようになります。
rand() % N
の偏りについて(補足)
rand() % N
という方法は非常に手軽ですが、厳密には、RAND_MAX
の値が N
のちょうど倍数でない場合、わずかながら特定の余りの値が出やすくなるという偏りが発生する可能性があります。
例えば、RAND_MAX
が 32767 で、1 から 3 までの乱数(サイズ 3)を rand() % 3 + 1
で生成する場合を考えます。
rand()
は 0 から 32767 の値を返します。
32767 / 3 = 10922 余り 1
です。
これは、0から32767までの整数のうち、
* 3で割って余りが0になる数 (0, 3, …, 32766) は 10923個
* 3で割って余りが1になる数 (1, 4, …, 32767) は 10923個
* 3で割って余りが2になる数 (2, 5, …, 32765) は 10922個
が存在することを意味します。
つまり、rand() % 3
の結果は、0と1が2よりもわずかに多く出現する可能性があります。
この偏りは、RAND_MAX
に比べて N
が非常に小さい場合に顕著になりますが、ほとんどのシステムでは RAND_MAX
が十分に大きいため、一般的なゲームやシミュレーションにおいては無視できるレベルです。もし統計的な厳密さが必要な場合は、より高度な乱数生成方法を検討する必要がありますが、初心者の方はまず基本的な %
演算子を使った方法をしっかりと理解すれば十分です。
特定の範囲の浮動小数点数を生成する
整数だけでなく、0.0 から 1.0 の間のような、特定の範囲の浮動小数点数(小数)の乱数が必要な場合もあります。
0.0 から 1.0 までの範囲
rand()
が返す値は 0 から RAND_MAX
までの整数です。これを RAND_MAX
で割ることで、0.0 から 1.0 の間の浮動小数点数を得ることができます。
rand() / RAND_MAX
ただし、ここでも型に注意が必要です。C言語では、整数同士の割り算は小数点以下が切り捨てられます。例えば、5 / 2
は小数点以下が切り捨てられて 2
になります。rand()
と RAND_MAX
はどちらも整数型なので、そのまま割ってしまうと結果は 0 か 1 にしかならず、期待通りになりません。
これを避けるためには、割り算を行う前に、少なくとも一方のオペランド(被演算子)を浮動小数点型(double
または float
)にキャスト(型変換)する必要があります。
(double)rand() / RAND_MAX
または
rand() / (double)RAND_MAX
一般的には、rand()
の戻り値を double
にキャストする方法がよく使われます。
c
// 0.0 から 1.0 までの乱数を生成
double random_0_to_1 = (double)rand() / RAND_MAX;
これで、変数 random_0_to_1
には 0.0 から 1.0 までの間の浮動小数点数が格納されます。
(注: 厳密には、(double)rand() / RAND_MAX
の結果は 0.0 から 1.0 以下 の範囲の値を取りうる可能性があります。rand()
の最大値は RAND_MAX
なので、(double)RAND_MAX / RAND_MAX
は 1.0 になります。もし厳密に 0.0 から 1.0 未満 の範囲 (つまり [0.0, 1.0) ) の乱数が必要な場合は、RAND_MAX + 1.0
で割る方法なども考えられますが、通常は (double)rand() / RAND_MAX
で十分です。)
特定の範囲 [min, max] の浮動小数点数を生成する
0.0 から 1.0 の間の乱数 ((double)rand() / RAND_MAX
) を利用して、任意の範囲 [min, max]
の浮動小数点数乱数を生成することもできます。
まず、0.0 から 1.0 の乱数 ((double)rand() / RAND_MAX
) を生成します。
この乱数は 0.0 から 1.0 の範囲を動きます。
次に、これを目的の範囲の幅 max - min
倍します。これにより、0.0 から max - min
の範囲の乱数になります。
((double)rand() / RAND_MAX) * (max - min)
最後に、この値に範囲の開始値 min
を足すことで、範囲が min
から max
までにずれます。
したがって、範囲 [min, max]
の浮動小数点数を生成する一般的な式は以下のようになります。
min + (double)rand() / RAND_MAX * (max - min)
例:0.0 から 100.0 までの乱数
min = 0.0
, max = 100.0
です。
c
// 0.0 から 100.0 までの乱数を生成
double random_0_to_100 = 0.0 + (double)rand() / RAND_MAX * (100.0 - 0.0); // (double)rand() / RAND_MAX * 100.0 と同じ
例:50.0 から 75.0 までの乱数
min = 50.0
, max = 75.0
です。
c
// 50.0 から 75.0 までの乱数を生成
double random_50_to_75 = 50.0 + (double)rand() / RAND_MAX * (75.0 - 50.0); // 50.0 + (double)rand() / RAND_MAX * 25.0 と同じ
これらの式を使えば、必要な範囲の浮動小数点数乱数を生成できます。浮動小数点数の計算精度は double
型に依存します。
これらの範囲指定の方法をマスターすれば、C言語の rand()
と srand()
を使って、様々な場面で必要なランダムな値を生成できるようになります。
よくある落とし穴と注意点
C言語の rand()
と srand()
は非常に基本的な関数であり、手軽に使える一方で、いくつか注意すべき重要な点があります。これらの注意点を理解しておかないと、予期しない結果になったり、セキュリティ上の問題を引き起こしたりする可能性があります。
-
srand()
はプログラムの開始時に一度だけ呼ぶ
これはすでに説明しましたが、非常に重要なポイントです。プログラムが実行を開始したとき、例えばmain
関数の先頭などで、srand((unsigned int)time(NULL));
のように一度だけ呼び出します。乱数が必要になるたびに、またはループの中で何度もsrand()
を呼び出してはいけません。
繰り返しになりますが、その理由は以下の通りです。time(NULL)
は秒単位の精度しかないため、短い時間内に何度も呼び出しても同じシード値が設定される可能性が高いです。srand()
を呼び出すと、擬似乱数生成器の内部状態がリセットされ、新しいシード値から乱数列が再生成されます。これにより、乱数列の連続性が失われ、本来得られるはずだった多様な乱数が得られなくなる可能性があります。- 頻繁な
srand()
の呼び出しは、プログラムのパフォーマンスにも影響を与える可能性があります。
常に「初期化は一度だけ」という原則を守りましょう。
“`c
include
include
include
int main() {
// ** これが正しい使い方 ****
srand((unsigned int)time(NULL));printf("ランダムな数字を3つ表示:\n"); for (int i = 0; i < 3; ++i) { printf("%d\n", rand()); // rand() は何度呼んでもOK } // ****** これは悪い使い方 ****** // for (int i = 0; i < 3; ++i) { // srand((unsigned int)time(NULL)); // ループの中でsrandを呼んではいけない // printf("(悪い例)ランダムな数字?: %d\n", rand()); // } // 短時間で実行すると、上の3つの数字が同じになる可能性が高い return 0;
}
“` -
time(NULL)
の精度とシード値の衝突
time(NULL)
が返す時刻は秒単位です。もし、全く同じ秒にプログラムが複数起動された場合、それらのプログラムはすべて同じシード値で乱数生成器を初期化することになります。結果として、すべてのプログラムが全く同じ乱数列を生成してしまいます。これは、複数のユーザーが同時にゲームを始めたときに、皆同じ敵のパターンに遭遇する、といった問題につながる可能性があります。
これを避けるためには、シード値の生成にtime(NULL)
以外の要素(例えば、プロセスID、ユニークなカウンター、あるいはシステムが提供するより高精度なタイマーなど)も組み合わせるなどの工夫が必要になる場合があります。しかし、これはC言語の標準ライブラリの範囲を超え、OSに依存する技術になることが多いです。 -
RAND_MAX
の値は環境によって異なる
RAND_MAX
はC言語の規格で最低値(32767)のみが保証されており、実際の値はコンパイラや実行環境によって異なります。プログラムを書く際は、RAND_MAX
の具体的な値に依存するような処理は避け、必ず<stdlib.h>
のRAND_MAX
定数を利用するようにしましょう。例えば、0から1の浮動小数点数を生成する際に(double)rand() / 32767.0
のようにマジックナンバーを書くのではなく、(double)rand() / RAND_MAX
と書くべきです。 -
rand() % N
の偏り(再掲)
前述したように、rand() % N
の方法はシンプルですが、RAND_MAX
がN
の倍数でない場合にわずかな統計的な偏りが発生する可能性があります。この偏りは、RAND_MAX
に比べてN
が非常に小さい場合に顕著になりやすいです。多くの用途では問題になりませんが、モンテカルロ法のような厳密なシミュレーションや統計的な分析を行う場合は、この偏りが結果に影響を与える可能性があるため注意が必要です。より高品質な乱数が必要な場合は、後述するような他の方法を検討します。 -
セキュリティに関わる用途には絶対に使わない
C言語のrand()
で生成される擬似乱数は、シード値が分かれば簡単に予測できてしまいます。また、生成アルゴリズムによっては、短い乱数値のシーケンスから次の値を予測することも比較的容易な場合があります。そのため、暗号化キーの生成、セキュリティトークンの生成、パスワードの生成など、セキュリティが非常に重要な場面には、絶対にrand()
関数を使用してはいけません。これらの用途には、OSが提供する暗号論的に安全な乱数生成機能や、専門の暗号ライブラリが提供する機能を利用する必要があります。rand()
関数は、あくまで「見た目ランダムであれば十分」な、セキュリティに関わらない用途(ゲームのキャラクターの動き、練習用のデータ生成など)に限定して使いましょう。
これらの注意点を理解し、特に srand()
を呼び出すタイミングと、rand()
を使うべきではない場面を把握しておくことが、C言語で乱数を安全かつ効果的に使うために非常に重要です。
応用例:簡単なコード
ここまで学んだ rand()
と srand()
、そして範囲指定の方法を使って、いくつかの簡単な応用例を見てみましょう。
例1:サイコロを10回振るシミュレーション
1から6までのランダムな整数を10回生成します。
“`c
include
include // rand(), srand() のために必要
include // time() のために必要
int main() {
// プログラム開始時に一度だけ、現在の時刻を使って乱数生成器を初期化
srand((unsigned int)time(NULL));
printf("サイコロを10回振ります。\n");
for (int i = 0; i < 10; ++i) {
// 1 から 6 の乱数を生成 (rand() % 6 は 0-5, +1 して 1-6 に)
int dice_roll = rand() % 6 + 1;
printf("%d回目の目: %d\n", i + 1, dice_roll);
}
return 0;
}
“`
このプログラムを実行するたびに、異なるサイコロの目の並び(乱数列)が表示されるはずです。例えば、1回目は [3, 1, 6, …] と表示され、2回目は [5, 4, 2, …] と表示される、といった具合です。
例2:簡単なじゃんけんゲーム(コンピュータ対戦)
コンピュータの手を乱数で決めて、ユーザーとじゃんけんをします。
“`c
include
include // rand(), srand() のために必要
include // time() のために必要
int main() {
// プログラム開始時に一度だけ乱数生成器を初期化
srand((unsigned int)time(NULL));
int user_hand; // ユーザーの手 (0:グー, 1:チョキ, 2:パー)
int computer_hand; // コンピュータの手
printf("じゃんけんゲーム開始!\n");
printf("出す手を選んでください。\n");
printf("0: グー, 1: チョキ, 2: パー\n");
printf("あなたの手: ");
// ユーザーからの入力を受け取る
// scanf はセキュリティ上のリスク(バッファオーバーフローなど)があるため
// 実用的なプログラムでは他の安全な入力方法を検討しますが、
// ここでは簡単な例として使用します。
if (scanf("%d", &user_hand) != 1) {
printf("入力エラー。\n");
return 1; // 入力に失敗したら終了
}
// ユーザーの入力が 0, 1, 2 の範囲外か確認
if (user_hand < 0 || user_hand > 2) {
printf("不正な入力です(0, 1, 2のいずれかを入力してください)。終了します。\n");
return 1;
}
// コンピュータの手を 0 から 2 の範囲の乱数で決める
// rand() % 3 は 0, 1, 2 のいずれかを生成
computer_hand = rand() % 3;
// ユーザーとコンピュータの手を表示
printf("あなたの手: ");
switch (user_hand) {
case 0: printf("グー\n"); break;
case 1: printf("チョキ\n"); break;
case 2: printf("パー\n"); break;
}
printf("コンピュータの手: ");
switch (computer_hand) {
case 0: printf("グー\n"); break;
case 1: printf("チョキ\n"); break;
case 2: printf("パー\n"); break;
}
// 勝敗判定ロジック
// 引き分け: user_hand == computer_hand
// ユーザーの勝ち: グー(0) vs チョキ(1), チョキ(1) vs パー(2), パー(2) vs グー(0)
// それ以外はコンピュータの勝ち
if (user_hand == computer_hand) {
printf("結果: 引き分けです!\n");
} else if ((user_hand == 0 && computer_hand == 1) ||
(user_hand == 1 && computer_hand == 2) ||
(user_hand == 2 && computer_hand == 0)) {
printf("結果: あなたの勝ちです!\n");
} else {
printf("結果: コンピュータの勝ちです!\n");
}
return 0;
}
“`
このプログラムでは、rand() % 3
を使ってコンピュータの手(0=グー, 1=チョキ, 2=パー)をランダムに決定しています。実行するたびにコンピュータの手が変わるため、じゃんけんの結果も毎回ランダムになります。
例3:配列の要素をランダムにシャッフルする
乱数は、配列などのコレクションの要素をランダムな順序に並べ替える(シャッフルする)ためにも非常によく使われます。ここでは、フィッシャー-イェーツ(Fisher–Yates)シャッフルと呼ばれる効率的なアルゴリズムを簡単に実装してみましょう。
考え方:
1. 配列の最後の要素から順番に、配列の先頭に向かってループします。
2. ループの各ステップ i
では、0
から現在の要素のインデックス i
までの範囲 [0, i]
から、ランダムなインデックス j
を選びます。
3. 配列の i
番目の要素と j
番目の要素を交換します。
4. これを配列の先頭(インデックス 0)まで繰り返します。
“`c
include
include // rand(), srand() のために必要
include // time() のために必要
// 整数配列をランダムにシャッフルする関数 (フィッシャー-イェーツアルゴリズム)
void shuffle_array(int arr[], int size) {
// 配列サイズが1以下の場合はシャッフル不要
if (size <= 1) {
return;
}
// 後ろから前に向かってループ
for (int i = size - 1; i > 0; --i) {
// 0 から i (現在の要素のインデックス) までの範囲 [0, i] でランダムなインデックス j を生成
// 範囲 [min, max] の乱数は rand() % (max - min + 1) + min
// ここでは min=0, max=i なので、(i - 0 + 1) + 0 = i + 1 となり、
// rand() % (i + 1) で 0 から i までの乱数が得られる
int random_index = rand() % (i + 1);
// 現在の要素 arr[i] とランダムに選ばれた位置 arr[random_index] の要素を交換
int temp = arr[i]; // arr[i] の値を一時保存
arr[i] = arr[random_index]; // arr[i] に arr[random_index] の値をコピー
arr[random_index] = temp; // arr[random_index] に一時保存しておいた元の arr[i] の値をコピー
}
}
// 配列の内容を表示するヘルパー関数
void print_array(const int arr[], int size) {
for (int i = 0; i < size; ++i) {
printf(“%d “, arr[i]);
}
printf(“\n”);
}
int main() {
// プログラム開始時に一度だけ乱数生成器を初期化
srand((unsigned int)time(NULL));
// シャッフルしたい整数の配列
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 配列の要素数を計算 (sizeof(配列全体) / sizeof(配列の1要素))
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("元の配列: ");
print_array(numbers, size);
// 配列をシャッフル
shuffle_array(numbers, size);
printf("シャッフル後の配列: ");
print_array(numbers, size);
// もう一度シャッフルしてみる
shuffle_array(numbers, size);
printf("もう一度シャッフル後の配列: ");
print_array(numbers, size);
return 0;
}
“`
この例では、rand() % (i + 1)
を使って、ループ変数 i
(配列の末尾から先頭に向かって減っていくインデックス)に応じた範囲 [0, i]
のインデックスをランダムに生成しています。そして、現在の要素 arr[i]
とランダムに選ばれた要素 arr[random_index]
を交換しています。これを繰り返すことで、配列の要素がランダムな順序に並べ替えられます。実行するたびに異なるシャッフル結果が得られるはずです。
これらの応用例から、rand()
と srand()
、そして範囲指定の方法が、C言語で様々なランダムな動作を実現するために非常に役立つことがお分かりいただけたかと思います。
より高度な乱数生成(発展的な内容)
C言語の標準ライブラリの rand()
と srand()
は、基本的な擬似乱数を生成するためのシンプルで手軽な機能です。しかし、これらの関数には限界もあります。
- 乱数の質:
rand()
が使用する擬似乱数アルゴリズムは、C言語の規格で具体的に定められているわけではありません。そのため、コンパイラやシステムによっては比較的単純なアルゴリズムが使われており、周期が短かったり、統計的な偏りが見られたりすることがあります。より高品質な、例えば周期が非常に長く、統計的な偏りが少ない擬似乱数が必要な場合、rand()
では不十分なことがあります。 - 分布の指定:
rand()
は基本的に一様分布(どの値も同じ確率で出現する)の整数乱数(またはそれを基にした一様分布の浮動小数点数)を生成します。しかし、例えば統計的なシミュレーションでは、正規分布(いわゆるガウス分布)や指数分布など、特定種類の確率分布に従う乱数が必要になることがあります。rand()
を使ってこれらの分布の乱数を生成することは可能ですが、それなりの数学的知識と実装が必要になります。
これらの限界を克服し、より高品質で柔軟な乱数生成を行うための方法はいくつか存在します。
C++11 以降の <random>
ライブラリ
もしC++を使う機会があるなら、C++11規格以降で導入された <random>
ライブラリの利用を強くお勧めします。このライブラリは、C言語の rand()
/srand()
よりもはるかに強力で柔軟な乱数生成機能を提供します。
<random>
ライブラリの主な特徴:
- 乱数エンジン: メルセンヌ・ツイスター(
std::mt19937
など)のような、より高品質で周期の長い擬似乱数生成アルゴリズムが複数提供されています。これらのエンジンは、C言語のrand()
よりも優れた統計的性質を持ちます。 - 乱数分布: 一様分布だけでなく、正規分布 (
std::normal_distribution
)、指数分布 (std::exponential_distribution
)、二項分布など、様々な確率分布に従う乱数を生成するためのクラスが提供されています。 - シード管理: エンジンの状態をシード値で初期化する方法がより柔軟で、複数のシード値から複雑な初期状態を生成することも可能です。
<random>
ライブラリは、乱数エンジンと分布オブジェクトを組み合わせて使用するため、最初は少し複雑に感じるかもしれませんが、高品質な乱数が必要な場合には非常に有用です。
OS固有の乱数生成機能(暗号論的に安全な乱数など)
前述したように、暗号化キーの生成や認証など、セキュリティが極めて重要な場面で必要なのは、予測が非常に困難な暗号論的に安全な乱数(Cryptographically Secure Pseudo-Random Number Generator, CSPRNG)です。C言語の rand()
はこのような用途には全く適していません。
暗号論的に安全な乱数が必要な場合は、通常、オペレーティングシステム(OS)が提供する専用の機能を利用します。
- Linux/macOS など Unix系OS:
/dev/random
や/dev/urandom
という特殊なデバイスファイルから乱数を読み出す方法があります。/dev/random
はシステムの物理的なノイズ(キーボード入力のタイミング、ディスクアクセス、ネットワークアクティビティなど)からエントロピー(非予測性)を収集して乱数を生成するため、真の乱数に近い性質を持ちますが、エントロピーが不足すると乱数生成ができなくなり、読み出しがブロックされることがあります。/dev/urandom
は、既存のエントロピープールを使って擬似乱数を生成します。こちらはブロックされることはありませんが、エントロピープールが枯渇した場合は(これは非常に稀ですが)理論的には予測可能性が高まります。多くの場合、/dev/urandom
が推奨されます。 - Windows: Microsoft CryptoAPIやCNG (Cryptography API: Next Generation) が提供する
CryptGenRandom
関数などの機能を利用します。
これらの機能はOSに依存するため、C言語の標準ライブラリ関数ではありません。利用するには、それぞれのOSのAPIを呼び出すコードを書く必要があります。
まとめとして
初心者の方がC言語で乱数を使う場合、まずは <stdlib.h>
の rand()
と srand()
の基本的な使い方をしっかりと理解し、サイコロやカードシャッフルといった身近な応用から試してみるのが良いでしょう。これらの関数は、多くの一般的な用途には十分な機能を提供します。
もし、あなたがより高度な統計シミュレーションを行いたい場合や、セキュリティに関わる乱数が必要になった場合には、C++の <random>
ライブラリやOS固有の乱数生成機能といった、より発展的な内容を学ぶ必要が出てきます。しかし、焦る必要はありません。まずは基本からしっかりとマスターすることが大切です。
まとめ
この記事では、C言語でランダムな値を生成するための最も一般的で基本的な方法を、初心者の方にも分かりやすく解説しました。
重要なポイントをもう一度おさらいしておきましょう。
- C言語の
rand()
関数は、擬似乱数を生成します。これは計算によって生成される、あたかもランダムに見える値の並びです。 - 擬似乱数生成器はシード値に基づいて動作します。同じシード値からは常に同じ乱数列が生成されます。
rand()
関数を使うには、<stdlib.h>
ヘッダーファイルが必要です。rand()
は0
からRAND_MAX
までの範囲の整数を返します。- 実行するたびに異なる乱数列を得るためには、擬似乱数生成器のシード値を変更する必要があります。
- シード値を設定するためには、
srand()
関数を使います。srand()
の引数にはunsigned int
型の値を指定します。 - プログラムを実行するたびに異なるシード値を得るために、現在の時刻をシードとして利用するのが最も一般的です。これには
<time.h>
ヘッダーファイルのtime(NULL)
関数を使います。コードでは通常srand((unsigned int)time(NULL));
のように記述します。 srand()
関数は、プログラムの実行開始時(例えばmain
関数の先頭)に一度だけ呼び出すのが正しい使い方です。乱数が必要になるたびに何度も呼び出してはいけません。- 特定の範囲
[min, max]
の整数乱数を生成するには、rand() % (max - min + 1) + min
の式が利用できます。 - 特定の範囲
[min, max]
の浮動小数点数乱数を生成するには、min + (double)rand() / RAND_MAX * (max - min)
の式が利用できます。ここで(double)
によるキャストを忘れないようにしましょう。 rand() % N
の方法にはわずかな偏りがある可能性があること、そしてrand()
関数は暗号などのセキュリティが重要な用途には絶対に使ってはいけないことを覚えておいてください。
これらの基本的な関数とその使い方、そして重要な注意点を理解すれば、C言語で様々なプログラムに「ランダムな要素」を取り入れることができるようになります。
ぜひ、この記事で学んだ知識を活かして、実際にC言語で乱数を使ったプログラムを作成してみてください。サイコロを振る、簡単なゲームを作る、シミュレーションを試すなど、実践を通じて理解を深めることが、プログラミング学習では非常に重要です。
C言語での乱数生成は、あなたのプログラムをより面白く、多様なものにしてくれるはずです。
これで、C言語でランダムな値を生成する方法に関する詳細な解説は終わりです。
プログラミングの学習は、一つずつ着実に知識を積み上げていくことが大切です。この記事が、あなたのC言語学習の一助となれば幸いです。
頑張ってください!
参考文献/参考資料:
- C言語 標準ライブラリ関数 (rand, srand, time) のオンラインリファレンス
- ISO/IEC 9899 (C言語の国際標準規格)
- 各コンパイラのドキュメントやヘルプ