このページではC言語で乱数を扱う方法を解説していきたいと思います。
乱数を扱うことで、プログラム内でランダムな処理(不確定な処理)を行うことができるようになります。
特にゲームなんかを作ろうと思うとランダムな処理は必須になります。例えば下記のようなところでランダムな処理が用いられています。
- RPG ゲームで出現する敵をランダムに決定する
- ジャンケンゲームでコンピュータが出す手をランダムに決定する
- すごろくゲームでサイコロの出目をランダムに決定する
出現する敵やコンピュータが出す手が決まっていたら面白く無いもんね!
ゲーム好きだから乱数に興味出てきたよ!
プログラムのテストなんかでも使えるよ!
ランダムな値を関数の引数に指定してきちんと動作しているかをテストするとか
他にも乱数はパスワードの自動生成なんかでも使用されます。
いろいろ使いどころは多いですので、是非この機会にC言語での乱数の扱い方について学んで行ってください!
乱数とは「法則性のない数値」です
一方、コンピュータやプログラミングで扱う乱数は法則性に則って生成される数値です
これらの乱数は「擬似乱数」と呼ばれます
今回扱う乱数ももちろん「擬似乱数」になります
Contents
乱数を生成する(rand
関数)
では乱数を生成する方法について解説していきます。
rand
関数
C言語で乱数を生成するためには rand
関数を使用します。
rand
関数の定義は下記のようになります。
#include <stdlib.h>
int rand(void);
rand
関数はC言語の標準関数であり、利用するためには stdlib.h を include
する必要があります。
rand
関数を実行すれば、生成された乱数を返却値として1つ取得することができます。
例えば下記のように rand
関数を実行すれば「乱数を1つ取得して表示する」が10回繰り返されることになります。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
for (i = 0; i < 10; i++) {
int r = rand();
printf("%d\n", r);
}
return 0;
}
実行すると私の場合は下記のように表示されました。ランダムな数字が10個生成されていることが確認できると思います。
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
rand
関数で生成される乱数は 0
〜 RAND_MAX
の範囲の値となります。
RAND_MAX
は stdlib.h を include
すれば使えるマクロ値です(環境によって定義値は異なると思います)。
スポンサーリンク
特定の範囲の乱数を生成する
次は特定の範囲の乱数を生成するためのテクニックを紹介していきます。
でも僕が作りたいのはサイコロゲームなんだよね…
生成される乱数の範囲は 1 から 6 で十分なんだけど…
rand
関数で生成される乱数の範囲は前述の通り 0
〜 RAND_MAX
の範囲なんだ
だけど、生成された乱数に対して「剰余演算」や「その他四則演算」を行うことで、特定の範囲の乱数のみを扱うことができるよ!
rand
関数で生成される乱数は前述の通り 0
〜 RAND_MAX
の範囲です。
ですが、特定の範囲の乱数のみを扱いたいケースは多々あります。
例えばジャンケンゲームであればグーを表す 0
、チョキを表す 1
、パーを表す 2
の 0
〜 2
の範囲の乱数のみを扱いたいですし、サイコロゲームであれば 1
〜 6
の範囲の乱数を扱いたいですよね?
こういった特定の範囲の乱数のみを扱いたい場合は「rand
関数で生成された乱数に対して演算を行う」ことが有効です。
生成する乱数のパターン数を絞る
一番使うのが剰余算(%
)で乱数のパターン数を絞る方法です。
剰余算は除数で割った時の余りを計算することができます。
ですので、rand
関数で生成した乱数に対して除数で剰余算を行うことで、rand
関数で生成した乱数を 0
〜 除数 - 1
の範囲の値に変換することができます。つまり扱う乱数のパターン数を絞ることができます。
例えば 0
〜 2
の範囲の乱数のみを扱いたいのであれば下記のように演算を行えば良いです。
int x;
x = rand() % 3;
これにより x
に格納される値は必ず 0
〜 2
の値になります。乱数を剰余算しているだけなので、0
〜 2
の値もランダムな値となります。
こんな感じで剰余算を利用することで扱う乱数のパターン数を絞ることができます。
特定の範囲のみの乱数を生成する
剰余算で乱数のパターン数を絞った上で、足し算を行うことで特定の範囲の乱数のみを扱うことができます。
例えばサイコロのように1
〜 6
の乱数のみを扱いたい場合は、rand
関数で生成した乱数をまず 6
を除数として剰余算を計算します。
int x, y;
x = rand() % 6;
これにより扱う乱数(上記の例では x
に格納される値)が 0
〜 5
の6パターンに絞られます。
さらに上記に対して + 1
を行えば、扱う乱数が 1
〜 6
のみになります。
例えば下記のように記述すれば、y
には 1
〜 6
の乱数が格納されることになります。
int x, y;
x = rand() % 6;
y = x + 1;
応用例1(奇数のみの乱数を生成する)
こんな感じで rand
関数で生成した乱数に対して演算を行うことで、扱いたい範囲の乱数のみを扱うことができるようになります。
例えば下記のように演算を行えば、0
〜 NUM
未満の奇数の整数のみを乱数として扱うことができます。
これは 2 をかけた整数に +1
した結果は必ず奇数になるためです。
int x;
x = 2 * (rand() % (NUM / 2)) + 1
応用例2(小数の乱数のみを生成する)
また、下記のように演算を行えば、0
〜 1
の小数を乱数として扱うことができます。
rand
関数で生成される乱数は 0
〜 RAND_MAX
なので、生成された乱数を RAND_MAX
で割ってやれば 0
〜 1
の小数の乱数になります(キャスト (double)
が必要なところに注意です)。
double x;
x = (double)rand() / RAND_MAX;
応用例3(文字をランダムに生成する)
また、下記のように演算を行えば A
〜 Z
の文字(英字の大文字)をランダムに生成することができます。
char c;
c = rand() % 26 + 'A';
アルファベットは 26 文字なので、剰余算により 0
〜 25
の乱数にパターンを絞り、それに 'A'
を足すことで A
〜 Z
の文字に置き換えています。
同様に a
〜 z
の文字(英字の小文字)をランダムに生成するためには下記のように演算を行えば良いです。
char c;
c = rand() % 26 + 'a';
rand
関数の注意点(rand
関数だけだと毎回同じ乱数になる)
rand
関数はプログラム内で実行するたびに新たに乱数を生成することができます。
この乱数を生成する時の動作は、イメージ的には下記のように予め作成された乱数の配列から rand
関数を実行するたびに前方から乱数を順番に取得する感じです。
この乱数の並びを「乱数の発生系列」と呼びます。
ここで注意点です。
実は rand
関数を実行するだけだと、プログラム内で生成される「乱数の発生系列」は毎回同じものになってしまいます!!
なので、rand
関数を実行するたびに毎回乱数を生成することができますが、一度プログラムを終了して再度実行した場合は再度同じ乱数の発生系列から乱数が取得されることになります。
つまり、同じ順序で乱数が生成されることになってしまいます。
例えば前述でも紹介した下記プログラムで10回 rand
関数で生成した乱数を表示すると、
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
for (i = 0; i < 10; i++) {
int r = rand();
printf("%d\n", r);
}
return 0;
}
1度目のプログラム実行で表示される乱数が例えば下記のような並びであった時、
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
2度目以降のプログラム実行で表示される乱数も同じに並びになります。
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
例えばジャンケンゲームであれば、対戦相手のコンピュータが毎回同じ順序でグーチョキパーの手を選ぶことになります。
毎回同じ乱数が生成されてしまったらゲームとして面白くないですよね…。
実は、Java や Python 等の他の言語においては、生成される乱数が毎回同じにならないよう、プログラムを実行するたびに乱数の発生系列が自動的に変わるようになっています。
その一方で、C言語の場合は rand
関数を実行する前に “前準備” をしないと毎回同じ乱数の発生系列になってしまいます。この辺りが上記のような言語と異なりますので、特に他の言語を学ばれた後にC言語で乱数を使用する場合などは注意が必要です。
で、その前準備を行うために実行するのが、次に紹介する srand
関数になります。
この srand
関数を利用することで「乱数の発生系列」を変更することができます。
乱数の発生系列を変更する(srand
関数)
では、続いて乱数の発生系列を変更する srand
関数について解説していきます。
スポンサーリンク
srand
関数
C言語で乱数の発生系列を変更するためには srand
関数を使用します。
srand
関数の定義は下記のようになります。
#include <stdlib.h>
void srand(unsigned int seed);
srand
関数はC言語の標準関数であり、利用するためには stdlib.h を include
する必要があります。
引数の seed
はまさに「乱数の発生系列」を生み出す「種」になります。
この seed
の値を変更してやることで(異なる種を植えることで)、srand
関数実行時に異なる「乱数の発生系列」を生み出す(利用する)ことができます。
逆に同じ seed
を設定すると、同じ「乱数の発生系列」が生み出されることになります。
つまり、プログラム実行毎に異なる seed
を設定して srand
関数を実行してやれば、毎回異なる「乱数の発生系列」が生成されます。
したがって、その後 rand
関数で乱数を生成すれば、取得先の乱数の発生系列が異なることになるため、毎回同じ順序の乱数が生成されることを防ぐことができます。
例えば下記の例などは分かりやすいと思います。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
unsigned int seed;
printf("seed = ");
scanf("%u", &seed);
srand(seed);
for (i = 0; i < 10; i++) {
int r = rand();
printf("%d\n", r);
}
return 0;
}
scanf
関数で指定した値によって異なる乱数の発生系列から10個分の乱数が表示される様子が確認できると思います。
同じ値を指定した場合は毎回同じ結果が表示されることになります。
環境によってはもしかしたら異なるかもしれませんが、srand
関数を実行せずに rand
関数を実行した場合は、srand(1)
を実行した時に生成される乱数の発生系列に基づいて乱数が生成されるようです。
srand
関数は「乱数の発生系列」そのものを変更する関数ですので、基本的にはプログラム起動時に一度実行するだけで良いです。rand
関数実行するたびに srand
関数も実行する必要があるというわけではありません。
seed
(種)の設定方法
ここまでの解説の通り、srand
関数に設定する seed
を毎回異なる値に設定してやれば、毎回異なる乱数の発生系列より乱数を生成することができるようになります。
ではどのようにして srand
関数に設定する seed
を毎回異なる値に設定すれば良いでしょうか?次はここについて解説していきたいと思います。
簡単じゃん!
seed
も乱数で設定しちゃえばいい!
seed
を異なる値に設定して srand
関数を実行してやる必要があるよ?
この seed
はどうやって設定するんだい?
ぐぬぬ…
確かに…
seed
を毎回異なる値に設定するために、seed
を rand
関数で生成した乱数に設定してやるのが一番簡単な考え方だと思います。
ですが、毎回 rand
関数で異なる乱数を生成するためには、rand
関数を実行する前に srand
関数に毎回異なる seed
を設定して実行してやる必要があります。つまり rand
関数で毎回異なるために seed
を設定するのに、また別の seed
が必要になるということです。
こう考えると、rand
関数で生成した乱数により毎回異なる seed
を設定するのは無理そうだとすぐにイメージがつくと思います。
なので別の手段で seed
を設定する必要があります。
この seed
の設定に一番良く使用されるのが「時刻」です。
時刻は瞬間瞬間で変化しますよね?なので、この時刻の情報を seed
に設定してやれば、毎回異なる seed
を設定することができ、毎回異なる乱数の発生系列を理論的には生み出すことができることになります。
で、この時刻を取得するのに使用されるのが time
関数です。
time
関数では(処理系によって異なる場合もありますが)、1970/1/1 00:00:00(GMT) から time
関数実行した瞬間までの経過時間を返却することができます。
time
関数の定義は下記のようになります。利用するためには time.h を include
する必要があります。
#include <time.h>
time_t time(time_t *timer);
time_t
型は(これも処理系により異なるかもしれませんが)、一般に long
型を typedef
して名前を再定義したものになります。つまり単なる数値ですね。
ですので、time
関数の返却値を seed
として srand
関数に指定してやれば、毎回異なる乱数の発生系列を生成することができます。下記はその例です。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
int i;
unsigned int seed;
seed = (unsigned int)time(NULL);
srand(seed);
for (i = 0; i < 10; i++) {
int r = rand();
printf("%d\n", r);
}
return 0;
}
ただし、まだ注意点があります。それは「time
関数の返却値は秒単位」であることです。
つまり「同じ秒に実行された場合は同じ乱数の発生系列」になってしまうということです。これは覚えておいた方が良い事実だと思います。
しかし、趣味や授業の範囲では上記のような time
関数で seed
を設定して乱数を生成するので十分だと思います(正直これ以上 seed
を細かく設定する方法を私も知りません…)。
まとめ
このページではC言語で乱数を扱う方法について解説しました。
要点をまとめておくと下記になります。
rand
関数を実行することで乱数を生成することが可能- 特定の範囲のみの乱数を扱いたい場合は
rand
関数の返却値に対して剰余算等の演算を行えば良い srand
関数を実行することで乱数の発生系列を変更することが可能srand
関数の引数seed
にtime
関数の返却値を設定することで、毎回異なる乱数の発生系列を利用ことが可能
特にC言語においては毎回異なる乱数の発生系列を利用するのに srand
関数の利用が必要なので注意しましょう!
ちなみにですが、rand
関数により生成される乱数は重複する場合があります。もし重複なしの乱数を生成したい場合は、下記ページの解説が役に立つと思いますので、こちらも是非読んでみてください!