WisdomSoft - for your serial experiences.

9.10 乱数

C 言語の標準関数を用いて適当な数値を取得する方法を解説します。

9.10.1 ランダムな値を得る

多くのゲームは、ユーザーが想定できないような結果を得る必要があります。または、自然科学や社会科学などのシミュレーションを行うプログラムにおいても、一定の予期しない変動を用意しなければなりません。通常のビジネスアプリケーションでは必要ありませんが、プログラムの種類によっては、あえて不定の結果を算出しなければならないことがあるのです。

これを実現するには、乱数を得なければなりません。乱数を用いるための関数は stdlib.h ヘッダファイルで宣言されています。乱数を用いれば、ゲームプログラミングにはもちろん、反復処理と乱数を使って問題の処理を行うモンテカルロ法と呼ばれる数学手法を使ったプログラムなど、様々な用途が考えられます。

乱数を得るには rand() 関数を使います。

rand() 関数
int rand( void );

rand() 関数は、0 から RAND_MAX の範囲内で int 型疑似乱数を返します。RAND_MAX は stdlib.h ヘッダファイルで定義されている rand() 関数が返す最大値を表す定数です。

この関数が返す値は、完全な乱数ではなく、特定の計算式で算出した適当な値です。通常、コンピュータは乱数を発生させるようなハードウェアを持っていません。そこで、乱数を取得するために基本となる値を計算し、乱数列を生成するのです。rand() 関数は生成された擬似乱数を得るための関数なのです。

コード
#include <stdio.h>
#include <stdlib.h>

int main() {
	int iCount;

	printf("///乱数の最大値 = %d///\n" , RAND_MAX);

	for(iCount = 0 ; iCount < 10 ; iCount++)
		printf("乱数列 %d = %d\n" , iCount , rand());

	return 0;
}
実行結果
コード1 実行結果

コード1を実行すると RAND_MAX 定数の値と、rand() 関数で取得した擬似乱数を表示します。

標準では、stdlib.h で定義されているマクロ RAND_MAX は 32767 以上であることを保証しています。表示されている擬似乱数を見ると、確かに rand() 関数は 0 ~ 32767 以内の適当な値を返していることが確認できます。

しかし、コード1をもう一度実行すると、まったく同じ結果が得られます。乱数列は確かに適当な値ですが、プログラムを実行するたびに同じ乱数列が返されるようでは、プログラムの実行結果が常に同じになるためランダム処理としては使えません。なぜ、rand() 関数は同じ乱数列を返すのでしょうか。

実は、擬似乱数を発生させるためには"種"をまかなければなりません。乱数列は基本となる値を使って生成されます。使われる計算式は常に同じなので、基本となる値が同一であれば、同じ乱数列が生成されてしまいます。コード1が常に同じ結果になるのは、乱数を生成するための基準値が同じだったからです。この基準値をシードと呼びます。乱数を生成するためのシードを設定するには srand() 関数を使います。

srand() 関数
void srand( unsigned int seed );

seed には乱数を生成するためのシード値を指定します。乱数は seed で指定された値を初期値として乱数を生成します。seed に 1 を指定すると乱数が初期化されます。srand() を呼び出す前に rand() が呼び出された場合は seed を 1 として乱数列が生成されます。

プログラムを実行するたびに、予期できない適当な値を取得するためには、常に異なるシードを与える必要があります。乱数の生成に最も使われる初期値は時間です。time() 関数を使って取得した値は常に異なる値となるため、開発者も予期することができない適当な値を動的に得ることができるでしょう。

コード2
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main() {
	int iCount;

	srand(time(NULL));
	for(iCount = 0 ; iCount < 10 ; iCount++)
		printf("乱数列 %d = %d\n" , iCount , rand());

	return 0;
}
実行結果
コード2 実行結果

コード2は rand() 関数を呼び出す前に srand() 関数を用いて乱数の初期値を指定しています。time() 関数を使って現在の時刻値をシードに指定しているため、このプログラムは実行するたびに異なる初期値を乱数の生成に使います。今度は、実行するたびに適当な値が得られるでしょう。

しかし、この状態では特定の範囲内の乱数を得ることができません。通常は、32767 までの広い範囲の乱数を用いることはありません。逆に、場合によっては 32767 以上の広い範囲の乱数を得たい時もあるでしょう。これを実現するには、rand() 関数で得られた結果を計算して解決します。指定範囲の乱数を得る最も簡単な方法は、乱数と最大値を除算した余りから値を得る方法です。0 ~ 9 の乱数が必要な場合は、rand() % 10 を計算することで得ることができるでしょう。

ですが、この方法はあまり推奨されません。より最適な方法として、0.0 から 1.0 までの浮動小数型擬似乱数を使う手法が好まれます。0 ~ 1 までの浮動小数擬似乱数を得ることができれば、これに最大値を乗算することで特定範囲の乱数を得ることができます。この方法であれば、乱数の最大値に縛られることはありません。0.0 から 1.0 までの乱数を得るには次のようなマクロを記述すると良いでしょう。

#define random() ((double)rand() / (RAND_MAX + 1))

random() マクロ関数は 0.0 から 1.0 までの乱数を返します。

コード3
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#undef random
#define random() ((double)rand() / (RAND_MAX + 1))

int main() {
	int iCount;
	srand(time(NULL));
	for(iCount = 0 ; iCount < 10 ; iCount++)
		printf("乱数列 %d = %g\n" , iCount , random());
	return 0;
}
実行結果
コード3 実行結果

コード3は、0.0 から 1.0 までの double 型の乱数を返す random() マクロ関数を定義しています。因みに、互換性のために一部の処理系ではマクロ関数 randam() がすでに定義されている可能性があります。そこで #undef ディレクティブを使って random() が重複しないことを保証しています。余計な手間が嫌いであれば、名前が衝突しないように別の名前でマクロ関数を定義してもかまいません。