指定した範囲の乱数を作成したい(実戦編)

アルゴリズム
スポンサーリンク

はじめに

みなさん、こんにちは。
さて、前回前々回にわたって、
指定した範囲の乱数の作成の仕方を書きました。
改めて奥が深いですね。

読み忘れた方のためにリンクしますっ(^o^)
指定した範囲の乱数を作成したい(前編)
指定した範囲の乱数を作成したい(後編)
指定した範囲の乱数を作成したい(実戦編)←いまここ

実例を考えて乱数の落とし穴を考える

はじめに

これまでの話では、
小さなより精度が低い乱数発生器を使用していたため、
実用上は、そんなに関係ないんじゃないかな。
と思う方も多いかもしれません。

今回の話はかなり短いですが、
実際の実用例を考えて、
乱数の落とし穴にはまっていきたいと思います。(^o^)丿

ネットワークゲームのレアアイテム取得の確率計算

あなたは、ネットワークゲームを設計しています。
モンスターを倒すと、レアアイテムを落とします。
確率は、0.01%刻みで設定できるようにしたいです。
言語は、VC++、またはHSPを使用することとします。

ほら、ちょっと本当にありそうな雰囲気になってきましたよね。
さて、どのように設計するでしょうか、
VC++の乱数発生器だったら、みんな使ってるし大丈夫だろう。
乱数作成の高速化も考えて、 x = rand() % 10000 こんな感じにして、
で、if(x < 1000) で 10% みたいに計算しちゃおう。

では、実際にコードを書いてみましょう。
とりあえず、HSPが好きなので、HSPで書きます。
まず、 HSP には rnd(n) という関数があるのですが、
これはCで rand() % n という文章にすぎません。

#define multiplier 214013
#define addend 2531011

x = 1

goto *start

#defcfunc rnd2 int s
	x = x * multiplier + addend
	return (((x >> 16) & 0x7FFF) \ s)

*start

mes "HSPの rnd(n) は、余りを使用して乱数を切り出している。"
mes "HSPの rnd -> " + rnd(100) + " は"
mes "上記の rnd2 -> " + rnd2(100) + " と同じ。"

上記をふまえて、
乱数の値がどのように分布するか度数分布表を作ってみましょう。

size = 100000
dim bin, 10

repeat size
	x = rnd(10000)
	bin(int(x / 1000))++	
loop

repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

実行すると、

範囲 回数
[ 0, 1000) 12401
[1000, 2000) 12176
[2000, 3000) 11618
[3000, 4000) 9079
[4000, 5000) 9088
[5000, 6000) 9107
[6000, 7000) 9150
[7000, 8000) 9048
[8000, 9000) 9316
[9000,10000) 9017

つまり、10%を作るつもりが、12%になってしまいます!!
さらに、if(rnd(10000) < 3000) で 30.00% みたいに計算しちゃおうと思うと 36%になります。

棒グラフを作ると分かりやすい。
histogram

おわりに

そんなこんなで、
今回は、ちょっと身近に感じそうな話を作ってみました。
みなさん、乱数には十分に気を付けましょう!

ちなみに、HSPの機能の中でより正しい乱数を作りたい場合は、
これまでの話をふまえると下のような感じ。(色々と追加しました。)

#module

#defcfunc rnd15 int s
	// 15ビットの実数の乱数を作成
	return int(((double(rnd(0x8000))) / 0x8000) * s)

#defcfunc rnd30 int s
	// 30ビットの実数の乱数を作成
	return int(((32768.0 * rnd(0x8000) + rnd(0x8000)) / 0x40000000) * s)

#defcfunc rnd52 int s, local a
	// 52ビットの実数の乱数を作成
	a = double(rnd(0x8000))
	repeat 3
		a *= 32768
		a += rnd(0x8000)
	loop
	return int(a / 1152921504606846976.0 * s)

#defcfunc rnd4 int s, local a, local b
	// 繰り返して範囲外は捨てる
	repeat
		a = rnd(0x8000)
		b = a \ s
		if(a - b + s <= 0x8000) {
			break
		}
	loop
	return b

#global

font "MS ゴシック", 12
pos 5, 5

// 作成する乱数の範囲によって、
// 乱数のビットを大きいものを選ぶか、
// 上記の rnd4() を利用するといいです。

randomize
size = 1000000

mes "通常版"
dim bin, 10
repeat size
	x = rnd(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "工夫したもの"
dim bin, 10
repeat size
	x = rnd15(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "誤差が少ない"
dim bin, 10
repeat size
	x = rnd30(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

pos 200, 5

mes "最も誤差が少ない"
dim bin, 10
repeat size
	x = rnd52(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

mes "正しい乱数"
dim bin, 10
repeat size
	x = rnd4(10000)
	bin(int(x / 1000))++	
loop
repeat 10
	mes strf("[%5d ,%5d) ... %5d回", (cnt * 1000), ((cnt + 1) * 1000), bin(cnt))
loop

上記の「正しい乱数」のアルゴリズムについては、
下で紹介していますが、前編から読むとより勉強になるかもしれません。
指定した範囲の乱数を作成したい(後編)

コメント

タイトルとURLをコピーしました