はじめに
みなさん、こんにちは。
さて、前回、前々回にわたって、
指定した範囲の乱数の作成の仕方を書きました。
改めて奥が深いですね。
読み忘れた方のためにリンクしますっ(^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%になります。
棒グラフを作ると分かりやすい。
おわりに
そんなこんなで、
今回は、ちょっと身近に感じそうな話を作ってみました。
みなさん、乱数には十分に気を付けましょう!
ちなみに、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
上記の「正しい乱数」のアルゴリズムについては、
下で紹介していますが、前編から読むとより勉強になるかもしれません。
指定した範囲の乱数を作成したい(後編)
コメント