1. ホーム
  2. java

[解決済み] これは「十分な」ランダムアルゴリズムなのか、もっと速いのになぜ使われないのか?

2022-04-23 08:45:29

質問

というクラスを作りました。 QuickRandom その仕事は、乱数を素早く生成することです。それは本当に簡単で、古い値を取って、それに double で、小数点以下を取る。

ここで、私の QuickRandom クラスの全体像を示しています。

public class QuickRandom {
    private double prevNum;
    private double magicNumber;

    public QuickRandom(double seed1, double seed2) {
        if (seed1 >= 1 || seed1 < 0) throw new IllegalArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
        prevNum = seed1;
        if (seed2 <= 1 || seed2 > 10) throw new IllegalArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
        magicNumber = seed2;
    }

    public QuickRandom() {
        this(Math.random(), Math.random() * 10);
    }

    public double random() {
        return prevNum = (prevNum*magicNumber)%1;
    }

}

そして、それをテストするために書いたコードがこちらです。

public static void main(String[] args) {
        QuickRandom qr = new QuickRandom();

        /*for (int i = 0; i < 20; i ++) {
            System.out.println(qr.random());
        }*/

        //Warm up
        for (int i = 0; i < 10000000; i ++) {
            Math.random();
            qr.random();
            System.nanoTime();
        }

        long oldTime;

        oldTime = System.nanoTime();
        for (int i = 0; i < 100000000; i ++) {
            Math.random();
        }
        System.out.println(System.nanoTime() - oldTime);

        oldTime = System.nanoTime();
        for (int i = 0; i < 100000000; i ++) {
            qr.random();
        }
        System.out.println(System.nanoTime() - oldTime);
}

これは非常に単純なアルゴリズムで、単純に前の2倍を"マジックナンバー"2倍で乗算します。かなり急いで作ったので、もっといいものができるかもしれませんが、不思議なことにうまくいっているようです。

のコメントアウトされた行の出力例です。 main メソッドを使用します。

0.612201846732229
0.5823974655091941
0.31062451498865684
0.8324473610354004
0.5907187526770246
0.38650264675748947
0.5243464344127049
0.7812828761272188
0.12417247811074805
0.1322738256858378
0.20614642573072284
0.8797579436677381
0.022122999476108518
0.2017298328387873
0.8394849894162446
0.6548917685640614
0.971667953190428
0.8602096647696964
0.8438709031160894
0.694884972852229

ふむ。かなりランダムですね。実際、ゲームの乱数発生装置には使えそうですね。

以下は、コメントアウトしていない部分の出力例です。

5456313909
1427223941

すごい! と比べて約4倍高速に動作します。 Math.random .

どこかで読んだ記憶があるのですが Math.random 中古 System.nanoTime() とか、とんでもないモジュレーションやディビジョンの数々。それは本当に必要なのでしょうか?私のアルゴリズムはかなり速く実行され、かなりランダムなようです。

2つほど質問があります。

  • 私のアルゴリズムは十分に良いものでしょうか? 本当に 乱数はあまり重要ではない)?
  • なぜ Math.random 単純な掛け算と小数の切り捨てで十分だと思うのですが、ここまでやるのですか?

解き方は?

あなたの QuickRandom の実装は、実際には一様な分布ではありません。一般に、頻度は低い値で高くなり、一方 Math.random() はより均一な分布をしています。以下は エスシーシーイー ということを表しています。

package com.stackoverflow.q14491966;

import java.util.Arrays;

public class Test {

    public static void main(String[] args) throws Exception {
        QuickRandom qr = new QuickRandom();
        int[] frequencies = new int[10];
        for (int i = 0; i < 100000; i++) {
            frequencies[(int) (qr.random() * 10)]++;
        }
        printDistribution("QR", frequencies);

        frequencies = new int[10];
        for (int i = 0; i < 100000; i++) {
            frequencies[(int) (Math.random() * 10)]++;
        }
        printDistribution("MR", frequencies);
    }

    public static void printDistribution(String name, int[] frequencies) {
        System.out.printf("%n%s distribution |8000     |9000     |10000    |11000    |12000%n", name);
        for (int i = 0; i < 10; i++) {
            char[] bar = "                                                  ".toCharArray(); // 50 chars.
            Arrays.fill(bar, 0, Math.max(0, Math.min(50, frequencies[i] / 100 - 80)), '#');
            System.out.printf("0.%dxxx: %6d  :%s%n", i, frequencies[i], new String(bar));
        }
    }

}

平均的な結果はこのようになります。

QR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  11376  :#################################                 
0.1xxx:  11178  :###############################                   
0.2xxx:  11312  :#################################                 
0.3xxx:  10809  :############################                      
0.4xxx:  10242  :######################                            
0.5xxx:   8860  :########                                          
0.6xxx:   9004  :##########                                        
0.7xxx:   8987  :#########                                         
0.8xxx:   9075  :##########                                        
0.9xxx:   9157  :###########                                       

MR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  10097  :####################                              
0.1xxx:   9901  :###################                               
0.2xxx:  10018  :####################                              
0.3xxx:   9956  :###################                               
0.4xxx:   9974  :###################                               
0.5xxx:  10007  :####################                              
0.6xxx:  10136  :#####################                             
0.7xxx:   9937  :###################                               
0.8xxx:  10029  :####################                              
0.9xxx:   9945  :###################    

テストを繰り返すと、QR分布は初期シードによって大きく変化するのに対して、MR分布は安定していることがわかります。時には望ましい一様分布になることもありますが、そうでないことの方が多いのです。以下は、より極端な例で、グラフの境界を越えていることさえあります。

QR distribution |8000     |9000     |10000    |11000    |12000
0.0xxx:  41788  :##################################################
0.1xxx:  17495  :##################################################
0.2xxx:  10285  :######################                            
0.3xxx:   7273  :                                                  
0.4xxx:   5643  :                                                  
0.5xxx:   4608  :                                                  
0.6xxx:   3907  :                                                  
0.7xxx:   3350  :                                                  
0.8xxx:   2999  :                                                  
0.9xxx:   2652  :