Javaの乱数

リンデマン グーズ

最近、乱数について興味があって、いろいろ調べてたら、java.util.Randomはひとつめの値が乱数としては使い物にならないくらい偏りがあるらしい。
試しにこんなプログラムを書いてみます。

public class RandomRange {
    public static void main(String[] args){
        Random r = new Random();
        double min = Double.MAX_VALUE;
        double max = Double.MIN_VALUE;
        for(int i = 0; i < 1000; ++i){
            r.setSeed(i);
            double x = r.nextDouble();
            if(x > max) max = x;
            if(x < min) min = x;
        }
        System.out.printf("min=%.4f max=%.4f%n", min, max);
    }
}


結果はこうなりました。

min=0.6753 max=0.7670


0.6から0.7くらいに値が固まってることがわかります。種の値を5000-10000にしても0.5以上になりました。(追記:なんか勘違いしてました。1万から1万5千で0.3-0.8でした)
これは乱数としてはバグだと思うのですが、Javaでは乱数の算出式が仕様で決められているので、治ることはありません。
ということで、マジメに乱数を使うときは、最初の値は捨てたほうがいいみたいです。
ただ、マジメに乱数を使うときには、そもそもjava.util.Randomを使わないほうがいいようで。


で、気づくと、java.security.SecureRandomというクラスがありました。暗号用に、系列が推測できない乱数です、これ使って乱数オブジェクトをこんな感じに確保します。

Random r = new SecureRandom();


そうすると、こんな値がでました。

min=0.0002 max=0.9978


ちゃんとばらついてます。けれども、SecureRandomは、同じ種を与えても乱数系列が違うので、それはそれで統計用には使えません。


なので、統計用などでまともに乱数を使うには、やはりメルセンヌ・ツイスターを使いましょうということになるようです。
今回は、ここの一番下のものを使ってみました。
Mersenne Twister in JAVA


ソースの乱数オブジェクト生成部分をこんな感じに変更します。

Random r = new MTRandom();


そうすると、こんな感じになりました。

min=0.0010 max=0.9991


ちゃんと全域にばらついています。