15. 乱数
15.1 概要
ランダムな値をとる数列を乱数といい, 例えば, 無作為にサンプルを選ぶような場合に使われます. 数値計算の分野で乱数が必要になる例としては, モンテカルロ法, 確率的現象のシミュレーションなどがあげられます.
乱数をコンピュータで生成するとは, あるアルゴリズムに基づいたプログラムを使って乱数を得るということなので, 完全にはランダムではなく何らかの規則性を持つことになります. そのためこれを疑似乱数とよぶこともあります.
疑似乱数の利点としては, 必要なときにいつでも生成することができ, 結果を検証したいときに完全に再現することができることがあげられます.
単に疑似乱数というと一般的には一様分布に従う乱数のことを指します. それ以外の, 例えば, 正規分布に従う疑似乱数は一様分布に従う乱数から作り出すことができます.
Excel では標準で疑似乱数を生成することができ, VBA では Rnd 関数と Randomize 文, ワークシート関数では RAND, RANDARRAY と RANDBETWEEN 関数が提供されています.
15.2 乱数の生成方法
15.2.1 線形合同法
古くから広く使われている代表的な疑似乱数生成法です.
\(0\) と \(m\) の間の整数の適切な初期値 \(X_0\) を選び, 次の漸化式により整数列を生成します.
\[
X_{i+1} = (aX_i + c) \space mod \space m
\]
ただし, \(0 \leq a \lt m, 0 \leq c \lt m\) とします.
パラメータ \(a\), \(c\) および \(m\) を適切に選ぶと乱数列が得られます. 乱数列を繰り返す周期や統計的性質 (ランダム性) はこれらのパラメータの選び方によって決まります.
同じ初期値 \(X_0\) (これを「シード」とよびます) からは同じ乱数列が得られます. 従って, 最大でも \(m\) の周期で同じ数列を繰り返すことになります. そのため \(m\) はできるだけ大きい値を選んだ方がよいことになります. 加えて, 漸化式中の \(mod \space m\) の計算をオーバーフローを利用して速く行うために, ワード長 32 ビットのコンピュータであれば \(m = 2^{32}\) とすることがよくあります.
実数の乱数が必要な場合には, 得られた整数乱数を浮動小数演算で \(m\) で割るなどして実数に変換します.
VBA 関数 Rnd では m = 224, a = 16598013, c = 12820163 が使われていて, 周期は 224 となります. 関数値としては 224 で割った [0, 1) の単精度浮動小数 (Single 型) を返します.
線形合同法の欠点としては, m を 2 の累乗にしたときに下位桁がランダムでないことがあげられます. 例えば, 得られる乱数は, 奇数のみ, 偶数のみ, または, 奇数と偶数を順に繰り返します (最下位ビットが 1 か 0 に固定, または, 1 と 0 を交互に繰り返す).
しかし, 上位桁のランダム性は良いので, 例えば上位 16 ビットだけを使うなどすれば良いランダム性が得られます. また, 実数乱数に変換して使う場合には下位ビットの周期性は問題になりません.
15.2.2 メルセンヌツイスター (MT)
日本で開発され 1998 年の論文で発表された比較的新しい方法です. 超長周期 (219937 – 1) で統計的性質が良く, しかも非常に高速な方法です. これは, Python や C++11 などの標準乱数生成関数として採用されていることでも知られています. また, Excel のワークシート関数 RAND にもこの方法が使われています.
さらに詳細な疑似乱数の生成方法について知りたい方は以下を参照してください.
15.3 Excel VBA における乱数の生成方法
15.3.1 Rnd 関数を使った乱数の生成方法
Excel VBA では Rnd 関数を使えば乱数を生成することができます.
Rnd 関数は引数の値によって動作が変わります.
- 引数を省略 (Rnd()) または引数が正 (Rnd(1), Rnd(2) など): 新しい乱数を生成します
- 引数が 0 (Rnd(0)): 新しい乱数を生成せずに最後の値を繰り返します
- 引数が負 (Rnd(-1), Rnd(-2) など): 引数の値をもとにシードを設定し直してから新しい乱数を生成します
従って, あとで結果の検証のために再現できるようにするには, 最初に引数を負の値にして Rnd を呼び出し, その後は引数なしで呼び出すようにします. あとで同じ負の値を指定してやり直すと同じ結果を再現することができます.
シードを設定せずに (1 度も負の引数で呼び出すことなく) Rnd 関数で新しい乱数を生成すると常に同じ結果が得られます. すなわち, Excel を開始した直後の最初の呼び出しでは 0.7055475 を返し, その後に続く乱数も決まった値が返されます.
Excel VBA には Randomize という命令があり, これを引数なしで使用すると毎回異なった適当な値にシードを設定し直します. すなわち, Rnd を使って乱数を生成する前に Randomize を実行しておくと, Rnd 関数で新しい乱数を生成したときにどんな値になるかわからなくなります.
なお, Randomize 10 というように数値を指定して呼び出すとあとで同じ結果を再現できそうですが, そうはなりません. これは最後に生成した乱数の値と指定した数値の両方を使用してシードを設定し直すためです.
15.3.2 XLPack を使った乱数の生成方法
一般的には Rnd 関数を使用すれば問題ありませんが, 倍精度浮動小数が必要な場合や特に長周期ですぐれたランダム性を必要とする場合のためにメルセンヌツイスターの VBA 関数 InitGenrand, GenrandInt32, GenrandInt31, GenrandReal53 を提供しています.
InitGenrand は引数 (シード) の値により初期化を行います. このシード値を使用すると, あとで同じ結果を再現することができます.
GenrandInt32, GenrandInt31, GenrandReal53 はそれぞれ符号付き 32 ビット整数, 符号なし 31 ビット整数, 区間 [0, 1) における 53 ビット実数の疑似乱数を生成します. これらの関数を使用する前には初期化を行っておくのが原則ですが, 初期化せずに呼び出した場合にはシードを 5489 として自動的に初期化が行われます.
例題
はじめに InitGenrand による初期化を行い GenrandInt32 を呼び出して 32 ビット整数乱数を 10 個生成する.
VBA プログラム例を以下に示します.
Sub Start()
Dim Seed As Long, I As Long
Seed = 13
Call InitGenrand(Seed)
For I = 1 To 10
Debug.Print GenrandInt32()
Next
End Sub
次のような結果が得られました.
-954760878 -1686456144 1020231754 -603726320 -754717978 -459635870 -147106060 769458329 -117677332 -1036873798
メルセンヌツイスターは高速に乱数を生成しますが, この場合, VBA からの関数呼び出しのオーバーヘッドが大きいため乱数の生成速度が遅く, 大量の乱数を必要とする使い方には適しません.


