ホームページ目次前の話次の話

JavaのRandomクラスは定数発生器

2007年8月14日

昨日は、朝早くから北関東群馬まで出掛けて、饂飩を食べて帰ってきた。 饂飩だけでなく、舞茸もしっかり食べてきた。あまりにも沢山だったので、 夕食は簡単に済ませた。

途中まで電車で行き、自動車に乗り換えたところで寝込んでしまったら、 いつのまにか大きな川を渡っていた。どうやら利根川だったようだ。 そしてまた寝込んでいたら、山の中に連れていかれた。 もうこれ以上は車でいけないというところで、 山道を分け入り、何とか目的地を探しあて、用事を済ませてから昼食に饂飩を食べた。 この場所では、饂飩以外を食べたことはないし、饂飩屋以外何もない所である。

ということで、貴重な夏休みの1日は終った。 山歩きと、昼食以外はほとんど寝ていたような気がするが、 十分な睡眠が取れたので良かった。

さて、気分よく仕事、それも久々にJavaのプログラムということで、取り掛かった。 とりあえず手間のかかる部分はダミーでごまかしておいて、全体の動きを確認しようと思った。

今回は、乱数を使って、品質の良いばらばらのデータを作ることであった。 ただし、乱数の種は、毎回、ユーザが入力することになっており、 同じ種の場合には同じデータを生成しなければいけないという条件があった。 乱数を使ったプログラムは、実はテストがなかなか難しい。 いかにバラバラなデータが生成できるかの保障をしないといけない。

乱数を利用する仕事ではよくある条件で、とくに問題ないと思っていた。 それで、とりあえず、与えられる種(seed)をそのまま new Rand(seed) として 乱数系列を作ることにし、ホイホイとプログラムを書いてテストを始めた。

とりあえず、種として1を入れてそれなりに動作することを確認した。 次に種を2にして動作確認した。ちょっと似ている結果になったようだが、偶然だろう。 次に種を3にして動作確認した。またまた似ている結果になってしまった。 4、5と種を変えても、わずかに違うデータが生成されるだけである。 種が少し違うと、少し違うデータが生成できてしまうように思えた。 これでは、絶対に使い物にならない。なぜだろうか? 2

今回のプログラムの利用者は、少ししか違わない値を種として利用する可能性が非常に高い。 だから、わざわざ種を、1、2、3、4、と少しずつ変えてテストした訳である。 正しく動いて当たり前と思い込まなくて良かった。 運用が始まってからこんなことが発覚したら大問題で、 システムをナタで叩き潰さなくてはならない。

でも、この動きの原因は何だろうか。 最近、Javaのプログラムをあまり書いていないので、ミスってしまったのだろうか。 Randomクラスの使い方を間違えたのだろか、色々悩んで次のような検証プログラムを組んでみた。

import java.util.Random;
                                                                                
public class RandomTest {
    public static void main(String[] argv) {
        Random  r;
        for( int i=1; i<=10; ++i ) {
            r = new Random(i);
            double v = r.nextDouble();
            System.out.println("i="+i+ "  " + v );
        }
    }
}
そして実行結果は、
fuji$ java RandomTest
i=1  0.7308781907032909
i=2  0.7311469360199058
i=3  0.731057369148862
i=4  0.7306094602878371
i=5  0.730519863614471
i=6  0.7307886238322471
i=7  0.7306990420600421
i=8  0.7302511331990172
i=9  0.7301615514268123
i=10  0.7304302967434272
fuji$ 
となったのである。

0.0〜1.0の一様乱数が出てくると期待していたのだが、 0.730ばかりが出てきて、まったく定数ではないか、 せいぜい定数にノイズが乗っている程度ではないかと呆れてしまった。

ユーザが入力する種を元に、最初に取りだした乱数で、生成するデータの最も重要な 特性を決定していたのだが、これではまったく同じ特性を持つデータばかりが作られる訳である。 nextDouble(), nextFloat() はさっぱりダメなようだ。nextInt()を使うように改め、 ユーザが与えるであろう値をそのまま種とすると似た乱数しか生えないようなので、 ユーザの与える種をおもいっきり左シフトしてから使うことにした。

すぐに済むと思った作業が、阿呆な乱数の影響で、とんでもない時間がかかってしまった。 nextDouble(), nextFloat() など、実際に使われているのだろうか。 乱数の検定をするまでもなく、ボツ以外考えられないような性質を持っていたのだが、 Javaのように広く使われていてもそんな馬鹿なことがあるのだろうか。

何か悪夢でも見ているんだろうか?

美しいパズルとは

ナンプレ問題
自動生成


これで、今日から
貴方もパズル作家


稲葉のパズル研究室

Cパズル
プログラミング
〜再帰編〜


ホームページ目次前の話次の話