一様分布や正規分布に従った乱数を発生させます。
定式化が困難な数学、統計、ORなどの問題では、乱数を用いた実験(シミュレーション)により、近似的な解を得ることが広く用いられます。
多くのコンピュータ言語が乱数発生の関数を標準関数としてもっています。Javascriptでは、Math.rondom()があり、0~1の乱数を発生します。
ここでは、Math.rondom()を用いて、いろいろな確率分布に従う乱数を発生する関数を作成しました。それらのコードは、このページのソースを見てください。
●連続
function xRandomUni(xmin, xmax) {
return xmin + (xmax - xmin)*Math.random(); ・・・A
}
一様分布は、左図のようにxmin~xmaxの区間での発生確率が等しい(一様)な分布です。Math.random()は0~1の一様分布に従う乱数ですから、0→xmin、1→xmaxに広げる操作をします。右図のようになりますので、Aの式になります。
ここでは、表作成の都合から、7.5~8.5なら8とする(四捨五入する)ことにして表示しています。10等分したので、通常の区間では確率は1/10=0.1になり、両端(5と15)はその半分になっています。
(ここでは10,000回も行ったので、キレイな形になりましたが、回数が少ないと偶然により、乱れた形になります。回数を100, 200, 500, 1000と変えて確認してください。このように、シミュレーションで信頼性を高めるには、多数回にする必要があります。)
●整数
function iRandomUni(imin, imax) {
return imin + Math.floor( (imax - imin + 1)*Math.random() ); ・・・B
}
求める値が人数だというような場合、整数であることが重要な場合もあります。
整数にするには、Bの式を用います。上述のように四捨五入するのでは、両端の値が半分になってしまいます。両端の左右に0.5広げて(1を加える)必要があります。連続のときは10等分したのに、整数のときは5と15を含む11等分するので、各点の確率は0.1よりも少なくなります。
三角分布とは、左図のように3点を与えた三角形の確率密度になる分布です。
費用や期間などを想定するとき、楽観値(xmin)と悲観値(xmin)以外に、最もありそうな値として最頻値(xmod)を想定することがよくあります。そのため、シミュレーションモデルでは、三角分布に従う乱数が使われる機会が多いのです。
三角分布の累積確率は右図のように、最頻値の左側と右側で異なる式になります。ここでF(x)がMath.randam()で与えられるので、 その値により、用いる式を決定し、式を変形してxを求めればよいのです。
しかし、これは煩雑です。ここで「2つの0~1の一様乱数uとvの差は、最小値0、最大値1、最頻値1/2の三角分布に従う」という理論があります。
それに最頻値0≦c≦1を加味することにより、0~1の三角分布の乱数x01は、
x01 = (1-c)*(u,vの小さいほう) + c*(u,vの小さいほう) ・・・A
となります。それをxmin~xmaxに広げれば(B)よいのです。
実際に、多数の組で実験すると、三角分布になっていることがわかります。
function xRandomTri(xmin, xmax, xmod) {
var c = (xmod-xmin)/(xmax-xmin);
var u = Math.random();
var v = Math.random();
if (u > v) var x01 = (1-c)*v + c*u; ┬─ A
else x01 = (1-c)*u + c*v; ┘
return xmin + (xmax - xmin)*x01; ・・・ B
}
function xRandomNormal(average, deviation) {
var z01 = 0;
for (var i=1; i<=12; i++) { ┐
z01 = z01 + Math.random(); ├─ A
} │
z01 = z01 - 6; ┘
return (average + z01) + (deviation * z01); ・・・B
}
一様分布に従う0~1の乱数は,平均 μ = 1/2 ,分散 σ2 = 1/12 です。中心極限定理より、この一様乱数12個の合計値の分布は、μ = 6 ,σ2= 1 の正規分布に近似できます。σ2= 1 より、標準偏差σ=1になります。すなわち、
「12個のMath.random()の合計-6」は「平均0、標準偏差1」の分布になる →Aのz01
になります。
これに与件の平均(average)と標準偏差(deviation)を加味する(B)と、平均=average、標準偏差=deviationの正規分布乱数になります。→参照:「正規分布」
整数化するとき、両端の確率は非常に小さい値になるので、連続で得たBの値を単純に四捨五入すればよいのです。
function iRandomNormal(average, deviation) {
var z01 = 0;
for (var i=1; i<=12; i++) {
z01 =z01 + Math.random();
}
z01 = z01 - 6;
return Math.round(average + z01 + deviation*z01) ・・・C;
}
シミュレーションなどを行うとき、あまりにも小さい(大きい)値があると、極端な結果になることがあります。また、例えば男性成人の身長が正規分布になるといっても、1m未満や2.5m以上の人がいるとは考えにくいでしょう。それで実務的には、最小値~最大値を設けるのが一般的です。
求める方法は単純で、範囲内の値になる(D)まで、繰り返し通常の正規分布の計算をさせるだけです。
function xRandomNormalMinmax(xmin, xmax, average, deviation) {
var ok = 0; // 範囲内なら1、範囲外なら0
while (ok==0) {
var z01 = 0; ┐
for (var i=1; i<=12; i++) { │
z01 =z01 + Math.random(); ├─(通常の)
} │ 正規分布
z01 = z01 - 6; │
var x = (average + z01) + (deviation * z01); ┘
if ( (x >= xmin) && (x <= xmax) ) ok = 1; ・・・D
}
return x; ・・・E
}
整数の場合も同様ですが、「範囲内」の指定が両端を0.5広げ(D')て、結果のxを四捨五入します(E')
function iRandomNormalMinmax(imin, imax, average, deviation) {
var ok = 0;
while (ok==0) {
var z01 = 0;
for (var i=1; i<=12; i++) {
z01 =z01 + Math.random();
}
z01 = z01 - 6;
var x = (average + z01) + (deviation * z01);
if ( (x >= imin-0.499) && (x <= imax+0.499) ) ok = 1; ・・・D'
}
return Math.round(x); ・・・E'
}
単位時間中にある事象が発生するのは、他の事象に関係なく独立だとすれば、単位時間中に事象が発生する平均回数をλとするとき、その事象の発生間隔がt単位時間である確率密度P(t)は、
P(t)=λe-λt
の指数分布に従います。
そして、0~tの累積確率は、
P(≦t)=1-e-λt
になります。→参照:「ポアソン分布と指数分布」
ここでP(≦t)=Math.random()として変形すると
t = (-1/λ)loge(1- Math.random())
となります。
このtが、平均λの指数分布に従う乱数になります。
function xRandomExp(lambda) {
return -1/lambda*Math.log(1- Math.random());
}
指数乱数は、「待ち行列」のシミュレーションによく用いられます。
事象発生の平均回数がλ[回/時間]とは、ある事象が発生してから次の事象が発生するまでの平均間隔時間が1/λ[時間]だということです。例えば、客が来てから次の客が来るまでの間隔が平均5分だとすれば、λ=1/5になります。ここで得られた値xが、ある客が到着してから、次の客が到着するまでの間隔時間になります。
整数化するための定式化は私には理論的な説明ができません。それでやや強引な方法を採りました。
平均到着間隔(1/λ)を0.5だけ大きくして計算し(A)、結果を四捨五入する(B)ことにしました。
1/λ'=1/λ+0.5 ∴λ'=λ/(1+0.5λ
function iRandomExp(lambda) {
var lambda1 = lambda/(1+0.5*lambda); ・・・A
return Math.round( -1/lambda1*Math.log(1- Math.random()) ); ・・・B
}
指数分布の結果は(名称のように)0近辺(同時に客がくる)が多くなり、裾野が長い(次の客がなかなか来ない)のが特徴です。そのため、現実とは異なるシミュレーション結果になるので、範囲を与えることがあります。
その調整方法として、範囲内の値になるまで、通常の(範囲指定のない)指数分布の計算を繰り返すことにしました。
連続
function xRandomExpMinmax(xmin, xmax, lambda) {
var ok = 0;
while (ok==0) { ┐
var x = -1/lambda*Math.log(1- Math.random()); ├─(通常の)
if ( (x>=xmin) && (x<=xmax) ) ok = 1; │ 指数分布
} ┘
return x;
}
整数のときも同様に、指数分布(整数)を繰り返します。
function iRandomExpMinmax(imin, imax, lambda) {
var lambda1 = lambda/(1+0.5*lambda);
var ok = 0;
while (ok==0) { ┐
var x = -1/lambda1*Math.log(1- Math.random()); ├─(通常の)
if ( (x>=imin-0.499) && (x<=imax+0.499) ) ok = 1; │ 指数分布
} ┘
return Math.round(x);
}