乱数とは、その名の通りランダムな数のことです。
要するに、
サイコロと同じだと考えてください。
ランダムな数が必要になるゲームでは乱数は欠かせません。
また、複雑な現象や統計的な性質の解析などを行う場合には、
乱数を使うことで手軽に実験を行うことができます。
しかし、皆さんもご存じのように、コンピュータは非常に正確な機械であり、
本質的にはランダムに数を作るということはできません。
そこで、計算によってランダムな数を得る、疑似乱数という手法が使われます。
【疑似乱数】
計算によってランダムな数値を得る方法。
本当のランダムではないが、現実的にはランダムだと考えて良い。
疑似乱数では、あくまでも計算によってランダムに見える数を作っています。
しかし、実際にかなりバラバラな数値を得ることができるので、
ほぼランダムな数であると考えて良いと思います。
疑似乱数にはさまざまな計算方法があるのですが、
C言語で用意されるのはほとんどが線形合同法です。
詳しい説明は省きますが、簡単に説明すれば、
X = 適当な数 * X(上位ケタの部分を切り捨てて増加を防ぐ)
をひたすら繰り返すことで毎回異なる値を得る計算です。
この方法は単純ですがそれほどランダムにはならず、
何回か組み合わせて使うと同じパターンになってしまいます。
しかし、ゲームなどの用途であれば十分ランダムな値になります。
疑似乱数がなんなのかがだいたいわかったところで、
さっそく疑似乱数を使ってみたいと思います。
C言語には、疑似乱数を作る
rand関数が用意されています。
なお、rand関数を使うには <stdlib.h> を #include する必要があります。
rand関数には、とくにパラメータなどを渡す必要はありません。
そのまま使うだけでランダムな値を計算します。
次のプログラムは、乱数を10回計算させた例です。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", rand());
}
return 0;
}
このプログラムの実行結果は、次の通りになるかもしれません。
130
10982
1090
11656
7117
17595
6415
22948
31126
9004
見ての通り、ランダムな値が得られています。
前項で、乱数を計算する方法はわかりましたが、
これは数があまりにもバラバラ過ぎて使いにくくなっています。
サイコロの1~6のように、
ある範囲の乱数を得ることはできないのでしょうか。
もし、得られる値の最大値がわかるなら、それを等分してやればよいことになります。
C言語では、rand関数で得られる最大値は RAND_MAX という定数の値でわかります。
したがって、rand関数で得られた値をRAND_MAXを等分した値で割れば良いわけですが、
そのための式を計算するのは結構面倒なので、
公式を紹介してしまいます。
最小値 + (int)( rand() * (最大値 - 最小値 + 1.0) / (1.0 + RAND_MAX) )
この公式の意味はわからなくてかまいません。
とにかく、この通りにすれば最小値~最大値の範囲の乱数を計算できます。
次のプログラムは、上の公式を計算するGetRandom関数を作り、
GetRandom関数を使用してサイコロを10回振る例です。
#include <stdio.h>
#include <stdlib.h>
int GetRandom(int min, int max);
int main(void)
{
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", GetRandom(1, 6));
}
return 0;
}
int GetRandom(int min, int max)
{
return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}
このプログラムの実行結果は、次の通りになるかもしれません。
見ての通り、1~6の範囲のランダムな数が得られています。
一般にはコンピュータを使うには数学が重要だと思われています。
本質的にはその通りなのですが、そうとも言えない部分があります。
なぜなら、コンピュータを使えば計算は自動的にやってくれるわけで、
公式さえ知っていれば、それを当てはめるだけで済んでしまうからです。
前項で作ったGetRandom関数を使えば、好きな乱数を計算できます。
しかし、実はまだ、考えなければならない問題が残されています。
初めに説明したように、疑似乱数は計算によるものです。
つまり、
同じ数を元に作った場合は同じ乱数になってしまうのです。
このことを確認するために、前項で作成したプログラムを2回実行してみます。
なんと、1回目と2回目でまったく同じ値が得られています。何回やっても同じです。
これでは、とてもではありませんがサイコロの代わりにはなりません。
この問題を解決するためには、乱数の計算に使う元の数を変える必要があります。
そのための関数として、
srand関数が用意されています。
ただし、srand関数を使って別の数値を入れたとしても、
実行される時に元の数が同じであれば同じ乱数になるので解決にはなりません。
もちろん、srand関数にrand関数を入れても、初めに作られる乱数が同じなので無意味です。
ユーザーに入力させる方法もありますが、手間がかかる不親切な手段です。
要するに、srand関数になんとかして完全にデタラメな数を入れたいのですが、
それにピッタリの方法が一つあります。それは、
現在時刻を入れる方法です。
秒単位の現在時刻をsrand関数に入れれば、毎回異なる元の数を乱数に使えます。
現在時刻を得る関数は
time関数で <time.h> を #include する必要があります。
srand関数とtime関数を次のように使えば、毎回異なる乱数を計算できます。
srand((unsigned int)time(NULL));
乱数発生の場合はtime関数の使い方は知らなくてかまわないのでこの通りにしてください。
なお、
unsigned int という型にキャストしていますが、これは符号なしの整数値です。
この処理はプログラムを開始するときに1回行えば十分です。
次のプログラムは、上記の処理を加えて毎回異なる乱数を計算する例です。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int GetRandom(int min, int max);
int main(void)
{
int i;
srand((unsigned int)time(NULL));
for (i = 0; i < 10; i++) {
printf("%d\n", GetRandom(1, 6));
}
return 0;
}
int GetRandom(int min, int max)
{
return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}
このプログラムを2回実行した結果は次の通りになるかも知れません。 ```
見事に異なる値が得られるのがわかります。 なお、ここではmain関数でsrand関数を使用していますが、
次のようにすればこれをGetRandom関数に含めることができます。
int GetRandom(int min, int max)
{
static int flag;
if (flag == 0) {
srand((unsigned int)time(NULL));
flag = 1;
}
return min + (int)(rand() * (max - min + 1.0) / (1.0 + RAND_MAX));
}
static変数を使うことで、初めの一回だけsrand関数を使うようにしています。