WEBサービス創造記

WEBサービスを作ったり保守したりしてる人のメモブログです。

指定した範囲内で乱数を発生させる

      2015/05/17

この記事では乱数の発生処理自体はC言語で行い、それをObjective-Cのメソッドとして利用します。

乱数について

乱数とはランダムな数のことです。
乱数を発生させるというのは、正確な計算を行ってその結果を出力するというコンピュータの本質から外れた行為なので、実はコンピュータは本質的な乱数を発生させることはできせん。

コンピュータが返す乱数は、コンピュータがなんらかの計算を行ってランダムな数値を作り、その結果を返したものになります。つまりランダムに見える数値を計算によって返しているだけです。
これを「疑似乱数」と呼びます。

プログラミング言語で使用する乱数は基本的にはこの疑似乱数のようです。

乱数発生のアルゴリズム

C言語では疑似乱数を発生させるrandという組み込み関数が用意されています。
以下のソースをコンパイルして実行すると、ランダムな数値が3回出力されます。

※rand関数を利用するためにはstdlib.hというヘッダファイルを読み込まなければいけません。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i;
	
	for (i = 0;i < 3;i++) {
		printf("乱数は %d です。\n", rand());
	}
		
	return 0;
}
&#91;/c&#93;

&#91;shell&#93;
$ ./a.out
乱数は 16807 です。
乱数は 282475249 です。
乱数は 1622650073 です。
&#91;/shell&#93;

<h3>任意の範囲の乱数を発生させる</h3>

<p>rand関数を使って乱数を発生させることができることはわかりました。<br>
しかし、上記のようにrand関数を利用すると、サイコロのように1〜6までの間の数をランダムに出したいと思ったときに乱数の範囲を絞ることができません。</p>

<p>
rand関数は0〜定数RAND_MAXで定義された数値までいずれかの数を返します。<br/>
RAND_MAX定数の値はコンパイラによって変化します。<br>
RAND_MAX定数の値は下記のコードで確認できます。</p>

[c]
printf("RAND_MAXの値は %d です。¥n",RAND_MAX);

PHPやRubyのようなLL言語のrand関数は引数に乱数の範囲を指定できるようですが、C言語のrand関数はそのような仕組みではないため、いちいち上記のrand関数の振る舞いを利用して範囲を絞るコードを考えなければいけないようです。
いくつかObjective-C(C言語)の入門サイトを見て回ったところ、下記のコードのようにrand関数で得た数値を任意の数字で割り、そのあまりを出現させるというのが常套手段のようです。

printf("目は %d\n", rand() % 6 + 1);

この例はサイコロのように1〜6の間でランダムに数字を出します。
% は、あまりを出力する演算子なので、6でランダムな数値を割ると、あまりは0〜5のいずれかになります。これに+1をつけることによって、最終的に1〜6のいずれかを出現させます。

以下は、1〜6の数値を3回出力するソースコードです。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i;
	
	for (i = 0;i < 3;i++) {
		printf("乱数は %d です。\n", rand() % 6 + 1);
	}
		
	return 0;
}
&#91;/c&#93;

<h3>乱数の初期化</h3>

<p>上記のソースコードを何度か実行してみてください。<br>
どうですか?同じ数字しか出ませんよね。<br>
これは乱数を初期化していないために起こる問題です。</p>

<p>冒頭でコンピュータが出力する乱数は、計算に基づいて出力する擬似的な乱数であると書きました。<br>
つまり、同じ数を元にして計算された乱数は同じ値にしかならないということです。<br>
逆に言えば、この乱数の計算の元となる数だけ毎回変えれば、この問題は回避できるということになります。</p>

<p>乱数の初期化に対応したソースコードは以下の通りです。</p>

[c]
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
	int value, i;
    srand((unsigned int)time(NULL));

	for(i = 0; i < 3; i++)
	{
		value = rand() % 6 + 1;
		printf("%d\n", value);
	}

	return 0;
}
&#91;/c&#93;

<p>このコードでは毎回違った値の乱数が出力されます。<br>
以下、簡単なアルゴリズムの解説です。</p>

<p>rand関数で得られる値を初期化するにはsrandという関数を利用します。<br>
randはsrandに与えられた数値をもとに乱数を発生させています。<br>
rand関数は、srandに渡された値が1なら乱数の初期設定を1として、2なら初期設定を2として疑似乱数を算出します。</p>

<p>しかし、srand関数に渡す値が同じになると、先ほどの例のように同じ数しか出力されないということが起こりえます。<br>
srandに渡す数値が常に一意になるようにするには、time関数の習性を利用します。</p>

<p>time関数は、UNIX時間と呼ばれる1970年1月1日0時0分0秒からの経過時間を返します。<br>
この習性を利用すると、srand関数には毎回常に違った数値を与えることができます。<br>
time関数はtime.hというヘッダファイルを読み込まないと使えないので、これも忘れないようにします。</p>

<h2>Onjective-Cで乱数を取得するメソッド</h2>

<p>以上の乱数発生のアルゴリズムを利用して実装したメソッドがこれです。</p>

[c]
-(int)getRandamInt:(int)min max:(int)max {
    static int initFlag;
    if (initFlag == 0) {
        srand((unsigned int)time(NULL));
        initFlag = 1;
    }
    return min + (int)(rand()*(max-min+1.0)/(1.0+RAND_MAX));
}

参考資料

 - C言語