乱数を発生させて計算やシミュレーションに利用することは, プログラミング技術の中で重要なもののひとつです。 ここでは主に Ruby と Java に実装されている乱数関数(メソッド)を 中心にして簡単な解説を行います。
簡単に言うと,「ある確率分布に従うランダムな数の列」が乱数である。たとえば 離散型一様分布に従う代表的な乱数発生器であるサイコロ(こんな立派なものだったのだ!)は, 1,2,3,4,5,6 という数の集合からランダムに,かつ等しい確率で取り出してくれるわけである。 コインの裏表を0,1に対応付けた場合には,コイントスも乱数発生のための行為ということになる。
連続一様分布に従う乱数もよく使われる。この場合にはふつう$0 \le x \lt 1$の区間 の実数(コンピュータ上では浮動小数点数)を生成し,どの $x$も等しい確率で 出現するようになっている。
しかし,乱数は「でたらめ」な数の列であるので,それを生成させるアルゴリズムの実現は とても難しい。アルゴリズムは原理的に規則性をもたらすものであるから,「予測のできない」 数を出力するのは本来無理なのだ。
そういう制約の中で,性質のよい乱数を作り出すアルゴリズムはいくつか作られている。 「性質がよい」というのは,単に次が予測できないというだけではなく,その確率分布のもつ 統計的な性質を満たしているということであり,たとえば平均値や分散が多数回の試行で 正しく収束することが必要な性質である。
以上のようにして,適切なアルゴリズムを使って作られる乱数を, 擬似乱数という。この先,乱数と呼ぶ時にはこの擬似乱数を指す。
Rubyにはメルセンヌ・ツイスタのアルゴリズムにもとづいた Random クラスが実装されていて, 高品質の乱数を高速に発生させることができる。より詳しい説明は, リファレンスマニュアルの該当箇所を参照のこと。
r1 = Random.new()
20.times do
puts r1.rand()
end
上の最初の行は,Random クラスのインスタンス r1 を生成している。 このとき,new(100) のように整数の引数を与えると,生成する乱数の列は その値によって決定される。これを乱数の種まきという。 上の例のように引数を省略すると,その時ごとに異なる種まきが行われる。
r1 = Random.new(100)
20.times{puts r1.rand()}
Random クラスのインスタンス名 rand() に引数として整数を与えると, 0からその整数の一つ前までの一様乱数を生成する.
r1 = Random.new(100)
20.times do
puts r1.rand(10) # 0から9の乱数を発生
end
確率モデルのシミュレーションなど,標準正規分布などさまざまの確率分布に 従う乱数が必要になることがある。 Java では nextGaussian メソッドがそのために用意されている。 Rubyでは実装されてい ないので,メソッドを定義して使うことにする。
# rand.rb
# Random クラスに新しいメソッドを追加する。
# このファイルは他から reruire './rand' して利用する。
class Random
include Math
# ボックス―ミューラー法をよる正規分布乱数発生
# Box-Muller method
def normal_rand(mu = 0,sigma = 1.0)
a, b = self.rand(), self.rand()
(sqrt(-2*log(rand()))*sin(2*PI*rand()) * sigma) + mu
end
# ポアソン分布に従う乱数を発生する
def poisson_rand(mu)
lambda = Math.exp(-mu)
k = 0
p = 1.0
while p >= lambda
p *= self.rand()
k += 1
end
return k - 1
end
end
# 上でメソッドを定義。実際の使い方の例は下の通り。
# randtest.rb
# 正規分布,ポアソン分布乱数発生のテスト
require './rand'
rnd = Random.new()
# 正規分布に従う乱数
mu = 100
sigma = 10
10.times do
puts rnd.normal_rand(mu,sigma)
end
# ポアソン分布に従う乱数
mu = 3.5
10.times do
print rnd.poisson_rand(mu), " "
end
puts
Java ではさまざまのデータ型の乱数を発生させることができる。
Java 2 Platform Standard Edition 7 API ドキュメント
の中の
Java™ Platform, Standard Edition 7 API 仕様
から,Random クラスの記述を参考にしてほしい。
Java における乱数の種まきは2通りの方法が用意されていて,ひとつは Random クラスのインスタンスを生成するときに種を与えるやり方と, もうひとつは setSeed() メソッドを用いるやり方である。 途中で種まきをやり直す必要があるときには後者のやり方を併用する。
なお,インスタンス生成時に種の引数を与えなければ,時刻データから 毎回異なる種が供給される。モンテカルロ法などでプログラムのテストを 終えて本番の計算を行う時には,このようにして乱数列が再現しないようにする。
Java の場合,もっと簡便に範囲 [0,1] の連続一様乱数を double 精度で出力させるには, Math.random() を用いるとよい。ただし,これはライブラリの読み込みを必要としないが, 乱数の種を与えることができない。したがって,ちゃんとしたシミュレーションには 用いるべきではない。
import java.util.Random;
public class RandomTest1{
public static void main(String s[]) {
Random rand = new Random(10); // インスタンス生成時に種をまく。
for(int i=0;i<20;i++)
System.out.println(rand.nextDouble());
}
}
import java.util.Random;
public class RandomTest2{
public static void main(String s[]) {
//インスタンス生成時に777という種をまく
Random rand = new Random(777);
for(int i=0;i<5;i++)
System.out.println(rand.nextDouble());
System.out.println("-------");
rand.setSeed(777); // ここで同じ種をまきなおす
for(int i=0;i<5;i++)
System.out.println(rand.nextDouble());
System.out.println("-------");
}
}
//import java.util.Random; ライブラリは不要
public class RandomTest3{
public static void main(String s[]) {
//Random rand = new Random(10); 初期化は不要
for(int i=0;i<6;i++)
System.out.println(Math.random());
}
}
上のソースで(rand.nextDouble()のところを 変更することで,さまざまな乱数系列を発生できる。
nextInt, nextfloat, nextDouble, nextBoolean, nextLong, nextByte, nextGaussian