これは「十分な」ランダムアルゴリズムですか。高速の場合はなぜ使用されないのですか?


171

というクラスを作成しました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);
}

これは、前のdoubleに「マジックナンバー」のdoubleを単純に乗算する非常に単純なアルゴリズムです。私はそれをかなり速く一緒に投げたので、私はおそらくそれをより良くすることができましたが、奇妙なことに、それはうまく機能しているようです。

これは、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単純な乗算と小数の切り取りで十分だと思われるのに、なぜそんなに多くのことをするのですか?

154
「かなりランダムに見える」; ヒストグラムを生成し、シーケンスでいくつかの自己相関を実行する必要があります...
Oliver Charlesworth 2013年

63
彼は、「かなりランダムに見える」は実際にはランダム性の客観的な尺度ではなく、実際の統計を取得する必要があることを意味します。
Matt H

23
@Doorknob:簡単に言えば、数値が0と1の間の「フラット」分布であるかどうかを調査し、時間の経過に応じて定期的/反復的なパターンがあるかどうかを確認する必要があります。
Oliver Charlesworth 2013年

22
new QuickRandom(0,5)またはを試してくださいnew QuickRandom(.5, 2)。これらは両方とも、番号に対して0を繰り返し出力します。
FrankieTheKneeMan 2013年

119
独自の乱数生成アルゴリズムを作成することは、独自の暗号化アルゴリズムを作成することに似ています。高度な資格を持つ人々による先行技術は非常に多いため、正しく理解するために時間を費やすことは無意味です。Javaライブラリ関数を使用しない理由はありません。何らかの理由で独自に作成したい場合は、Wikipediaにアクセスして、Mersenne Twisterなどのアルゴリズムを検索してください。
steveha 2013年

回答:


351

あなたのQuickRandom実装は本当に均一な分布ではありません。周波数は一般に、値が低いほど高くなり、Math.random()分布はより均一になります。ここだSSCCEれ表示されていることは:

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  :###################    

テストを繰り返すと、MR分布が安定している一方で、QR分布が初期シードに応じて大きく変動していることがわかります。目的の均一な分布に到達することもありますが、到達しないことはよくあります。これはより極端な例の1つで、グラフの境界を超えています。

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  :                                                  

17
数値データの+1-生の数値を見ると統計的に有意な差があることを意味しないため、誤解を招く可能性があります。
Maciej Piechotka 2013年

16
これらの結果は、に渡される初期シードによって大きく異なりますQuickRandom。時々、それは均一に近いです、時々、それはこれよりずっと悪いです。
PetrJaneček2013年

68
@ BlueRaja-DannyPflughoeft出力の品質が(内部定数ではなく)初期シード値に大きく依存するPRNGは、私には壊れているようです。
CVn 2013年

22
統計の最初のルール:データをプロットします。分析は的確ですが、ヒストグラムをプロットすると、これがはるかに速く表示されます。;
Konrad Rudolph

37
必須の引用:「ランダムな数字を生成する算術的な方法を検討する人は、もちろん、罪の状態にあります。」-ジョン・フォン・ノイマン(1951)「少なくとも100か所で上記の引用を見ていない人は、おそらくあまり年をとっていません。」-DV Pryor(1993)「乱数ジェネレータはランダムに選択すべきではありません。」-ドナルドクヌース(1986)
ハッピーグリーンキッドナップ2013年

133

あなたが説明しているのは、線形合同発生器と呼ばれる一種のランダム発生です。ジェネレーターは次のように動作します。

  • シード値と乗数から始めます。
  • 乱数を生成するには:
    • シードに乗数を掛けます。
    • シードをこの値に等しく設定します。
    • この値を返します。

このジェネレータには多くの優れたプロパティがありますが、優れたランダムソースとして重大な問題があります。上にリンクされているウィキペディアの記事は、長所と短所のいくつかを説明しています。つまり、適切なランダム値が必要な場合、これはおそらくあまり良い方法ではありません。

お役に立てれば!


@ louism-それ自体は、実際には「ランダム」ではありません。結果は確定的です。そうは言っても、私の回答を書いているときは、そのことについては考えていませんでした。おそらく誰かがその詳細を明確にすることができますか?
templatetypedef

2
浮動小数点演算エラーは実装設計です。私の知る限り、これらは特定のプラットフォームで一貫していますが、たとえば、異なる携帯電話間やPCアーキテクチャ間で異なる場合があります。一連の浮動小数点計算を連続して実行するときに追加の「ガードビット」が追加されることがありますが、これらのガードビットの有無により、計算結果が微妙に異なる場合があります。(ガードビットは64ビットダブルから80ビットへの拡張など)
Patashu 2013年

2
また、LCRNGの背後にある理論はすべて、整数を扱うことを前提としていることに注意してください。浮動小数点数をそれに投げると、同じ品質の結果得られません
duskwuff -inactive- 2013

1
@duskwuff、あなたは正しいです。ただし、浮動小数点ハードウェアが正しい規則に従っている場合、これは仮数サイズを法として行うことと同じであり、理論が適用されます。あなたがしていることに特別な注意が必要です。
フォンブランド2013年

113

内部状態が少なすぎるため、乱数関数は貧弱です-任意のステップで関数が出力する数値は、前の数値に完全に依存しています。たとえば、それmagicNumberが2(例として)であると仮定すると、シーケンスは次のようになります。

0.10 -> 0.20

同様のシーケンスによって強くミラーリングされています:

0.09 -> 0.18
0.11 -> 0.22

多くの場合、これによりゲームで顕著な相関関係が生成されます。たとえば、関数を連続して呼び出してオブジェクトのX座標とY座標を生成すると、オブジェクトは明確な対角線パターンを形成します。

乱数ジェネレーターがアプリケーションの速度を低下させていると確信する正当な理由がない限り(そしてこれはほとんどあり得ないことです)、自分で試して書く理由はありません。


36
実用的な答えの+1 ...これをシュートエムアップで使用し、壮大な複数のヘッドショットのために対角線に沿って敵をスポーンしますか?:D
wim

@wim:そのようなパターンが必要な場合は、PRNGは必要ありません。
Lie Ryan

109

これの本当の問題は、出力ヒストグラムが初期シードに大きく依存していることです。ほとんどの場合、出力はほぼ均一になりますが、多くの場合、出力は明らかに不均一になります。

触発悪いPHPの方法については、この記事rand()の関数があり、私が使用していくつかのランダムな行列イメージを作っQuickRandomしてSystem.Random。この実行は、as System.Randomがかなり均一である場合に、シードが悪影響を与える可能性があることを示しています(この場合、数値が小さい方が有利です)。

QuickRandom

System.Random

さらに悪い

この画像を取得して初期化するとQuickRandom、次のようnew QuickRandom(0.01, 1.03)になります。

コード

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace QuickRandomTest
{
    public class QuickRandom
    {
        private double prevNum;
        private readonly double magicNumber;

        private static readonly Random rand = new Random();

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

        public QuickRandom()
            : this(rand.NextDouble(), rand.NextDouble() * 10)
        {
        }

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

    class Program
    {
        static void Main(string[] args)
        {
            var rand = new Random();
            var qrand = new QuickRandom();
            int w = 600;
            int h = 600;
            CreateMatrix(w, h, rand.NextDouble).Save("System.Random.png", ImageFormat.Png);
            CreateMatrix(w, h, qrand.Random).Save("QuickRandom.png", ImageFormat.Png);
        }

        private static Image CreateMatrix(int width, int height, Func<double> f)
        {
            var bitmap = new Bitmap(width, height);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    var c = (int) (f()*255);
                    bitmap.SetPixel(x, y, Color.FromArgb(c,c,c));
                }
            }

            return bitmap;
        }
    }
}

2
素敵なコード。はい、それはクールです。私も時々それをしていました、それから定量化可能な測定を得るのは難しいですが、それはシーケンスを見る別の良い方法です。また、width * heightよりも長いシーケンスを確認したい場合は、この1ピクセル/ピクセルで次の画像をXORできます。QuickRandomの画像は、海藻のカーペットのように織り目加工されているため、見た目ははるかに美しいと思います。
Cris Stringfellow 2013年

見た目が良い部分は、各行に沿って(そして最初に戻って)いくと、シーケンスが増加する傾向にあります。これは、magicNumber乗算によってに類似した数が生成され、prevNumランダム性の欠如を示します。シードを使用する場合は、i.imgur.com / Q1Yunbe.pngnew QuickRandom(0.01, 1.03)を取得します
Callum Rogers 2013

はい、すばらしい分析です。折り返しが発生する前にmod 1に定数を明確に乗算するだけなので、説明するように増加します。このように見えるのは、たとえば10億を掛けて256のカラーパレットを減らすなど、重要度の低い小数点以下の桁数を取った場合です。
Cris Stringfellow 2013年

これらの出力画像を生成するために何を使用しましたか?Matlab?
2013年

@uDaY:コード、C#とを見てくださいSystem.Drawing.Bitmap
Callum Rogers 2013

37

乱数ジェネレーターの1つの問題は、「隠された状態」がないことです。前回の呼び出しで返された乱数がわかっている場合、1つしかないため、最後まで送信するすべての乱数がわかります。可能な次の結果など。

考慮すべきもう1つのことは、乱数ジェネレーターの「期間」です。明らかに、有限の状態サイズでは、doubleの仮数部に等しいため、ループする前に最大で2 ^ 52の値しか返すことができません。しかし、それが最良のケースです-期間1、2、3、4のループがないことを証明できますか?存在する場合、RNGはそのような場合にひどい縮退した動作をします。

さらに、乱数生成は、すべての開始点に対して均一な分布を持ちますか?そうでない場合は、RNGにバイアスがかかります。さらに悪いことに、開始シードに応じてさまざまな方法でバイアスがかかります。

これらの質問すべてに答えられるなら、素晴らしい。できない場合は、ほとんどの人がホイールを再発明せず、実績のある乱数ジェネレータを使用しない理由を理解しています;)

(ちなみに、良い格言は:最速のコードは実行されないコードです。世界で最速のrandom()を作成することはできますが、あまりランダムでないと良くありません)


8
このジェネレータには、すべてのシードの少なくとも1つの簡単なループがあります0 -> 0。種によっては、他にもたくさんあるかもしれません。(例えば、3.0の種子と、0.5 -> 0.50.25 -> 0.75 -> 0.250.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2、など)
-inactive- duskwuff

36

PRNGを開発するときに私がいつも行った1つの一般的なテストは、

  1. 出力をchar値に変換する
  2. chars値をファイルに書き込む
  3. ファイルを圧縮する

これにより、約1〜20メガバイトのシーケンスに対して「十分な」PRNGであるアイデアをすばやく反復できます。また、目視で検査するよりも上から見下ろした画像も得られます。状態が半端な「十分な」PRNGは、サイクルポイントを見る目の能力をすぐに超えてしまう可能性があるためです。

私が本当にうるさい場合は、優れたアルゴリズムを使用してDIEHARD / NISTテストを実行し、より多くの洞察を得てから、戻ってさらに調整を加えます。

周波数分析とは対照的に、圧縮テストの利点は、良い分布を簡単に作成できることです。値0〜255のすべての文字を含む長さ256のブロックを出力し、これを100,000回行うだけです。しかし、このシーケンスには長さ256のサイクルがあります。

たとえ少しでも差があっても、特に操作するのに十分な(たとえば1メガバイト)シーケンスを指定する場合は、圧縮アルゴリズムによって歪んだ分布を検出する必要があります。一部の文字、バイグラム、またはNグラムがより頻繁に出現する場合、圧縮アルゴリズムはこの分布スキューを、短いコードワードでの頻繁な出現を優先するコードにエンコードでき、圧縮のデルタを取得します。

ほとんどの圧縮アルゴリズムは高速であり、実装が不要なため(OSにはただ横になっているため)、圧縮テストは、開発中のPRNGの合否をすばやく評価するのに非常に役立ちます。

あなたの実験で頑張ってください!

ああ、私はあなたのコードの次の小さなmodを使用して、上記のrngでこのテストを実行しました:

import java.io.*;

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) throws Exception {
        QuickRandom qr = new QuickRandom();
        FileOutputStream fout = new FileOutputStream("qr20M.bin");

        for (int i = 0; i < 20000000; i ++) {
            fout.write((char)(qr.random()*256));
        }
    }
}

結果は:

Cris-Mac-Book-2:rt cris$ zip -9 qr20M.zip qr20M.bin2
adding: qr20M.bin2 (deflated 16%)
Cris-Mac-Book-2:rt cris$ ls -al
total 104400
drwxr-xr-x   8 cris  staff       272 Jan 25 05:09 .
drwxr-xr-x+ 48 cris  staff      1632 Jan 25 05:04 ..
-rw-r--r--   1 cris  staff      1243 Jan 25 04:54 QuickRandom.class
-rw-r--r--   1 cris  staff       883 Jan 25 05:04 QuickRandom.java
-rw-r--r--   1 cris  staff  16717260 Jan 25 04:55 qr20M.bin.gz
-rw-r--r--   1 cris  staff  20000000 Jan 25 05:07 qr20M.bin2
-rw-r--r--   1 cris  staff  16717402 Jan 25 05:09 qr20M.zip

出力ファイルをまったく圧縮できなかった場合は、PRNGを検討します。正直なところ、私はあなたのPRNGがうまくいくとは思いませんでした。20メガ以下の16%だけが、このような単純な構造にかなり印象的です。しかし、私はそれでも失敗だと考えています。


2
それをイメージするかどうかに関係なく、ランダムジェネレーターをテストするとき、私は数年前のzipと同じ考えを持っています。
アリストス2013年

1
@Alexandre C.、Aristos、エイダンに感謝します。私はあなたを信じています。
Cris Stringfellow 2013年

33

実装できる最速のランダムジェネレーターは次のとおりです。

ここに画像の説明を入力してください

XD、冗談は別として、ここで述べたすべてのほかに、ランダムシーケンスのテストは「困難な作業」[1]であり、疑似乱数の特定のプロパティをチェックするいくつかのテストがあることを挙げて、貢献できます。それらの多くはここにあります:http : //www.random.org/analysis/#2005

ランダムジェネレータの「品質」を評価する1つの簡単な方法は、古いカイ二乗検定です。

static double chisquare(int numberCount, int maxRandomNumber) {
    long[] f = new long[maxRandomNumber];
    for (long i = 0; i < numberCount; i++) {
        f[randomint(maxRandomNumber)]++;
    }

    long t = 0;
    for (int i = 0; i < maxRandomNumber; i++) {
        t += f[i] * f[i];
    }
    return (((double) maxRandomNumber * t) / numberCount) - (double) (numberCount);
}

引用[1]

test検定のアイデアは、生成された数値が合理的に広がっているかどうかを確認することです。rより小さいN個の正の数を生成する場合、各値について約N / r個の数が得られると予想されます。しかし、---これが問題の本質です---すべての値の出現頻度は完全に同じであってはなりません。それはランダムではありません!

単純に、各値の出現頻度の2乗の合計を計算し、期待頻度でスケーリングした後、シーケンスのサイズを差し引きます。この数値、「χ統計量」は、数学的に次のように表すことができます。

カイ二乗式

χ²統計がrに近い場合、数値はランダムです。それが遠すぎる場合、そうではありません。「近い」と「遠い」の概念はより正確に定義できます。統計とランダムシーケンスのプロパティとの関係を正確に示すテーブルが存在します。実行している簡単なテストでは、統計は2√r以内である必要があります

この理論と次のコードを使用します。

abstract class RandomFunction {
    public abstract int randomint(int range); 
}

public class test {
    static QuickRandom qr = new QuickRandom();

    static double chisquare(int numberCount, int maxRandomNumber, RandomFunction function) {
        long[] f = new long[maxRandomNumber];
        for (long i = 0; i < numberCount; i++) {
            f[function.randomint(maxRandomNumber)]++;
        }

        long t = 0;
        for (int i = 0; i < maxRandomNumber; i++) {
            t += f[i] * f[i];
        }
        return (((double) maxRandomNumber * t) / numberCount) - (double) (numberCount);
    }

    public static void main(String[] args) {
        final int ITERATION_COUNT = 1000;
        final int N = 5000000;
        final int R = 100000;

        double total = 0.0;
        RandomFunction qrRandomInt = new RandomFunction() {
            @Override
            public int randomint(int range) {
                return (int) (qr.random() * range);
            }
        }; 
        for (int i = 0; i < ITERATION_COUNT; i++) {
            total += chisquare(N, R, qrRandomInt);
        }
        System.out.printf("Ave Chi2 for QR: %f \n", total / ITERATION_COUNT);        

        total = 0.0;
        RandomFunction mathRandomInt = new RandomFunction() {
            @Override
            public int randomint(int range) {
                return (int) (Math.random() * range);
            }
        };         
        for (int i = 0; i < ITERATION_COUNT; i++) {
            total += chisquare(N, R, mathRandomInt);
        }
        System.out.printf("Ave Chi2 for Math.random: %f \n", total / ITERATION_COUNT);
    }
}

次の結果が得られました。

Ave Chi2 for QR: 108965,078640
Ave Chi2 for Math.random: 99988,629040

これは、QuickRandomの場合、rから遠く離れています (の外 r ± 2 * sqrt(r)

そうは言っても、QuickRandomは高速である可能性がありますが、(別の回答で述べられているように)乱数ジェネレーターとしては良くありません


[1] SEDGEWICK ROBERT、Cのアルゴリズム、Addinson Wesley Publishing Company、1990年、ページ516〜518


9
素晴らしいウォブサイトであるxkcdの+1 (ああ、そしてすばらしい答えです):P
tckmn 2013年

1
ありがとう、そしてはいxkcdラック!XD
2013年

理論は問題ありませんが、実行は不十分です。コードは整数オーバーフローの影響を受けやすくなっています。Javaではすべてint[]がゼロに初期化されるため、この部分は必要ありません。floatへのキャストは、doubleで作業する場合は意味がありません。最後に、メソッド名random1とrandom2を呼び出すのはとても面白いです。
bestss 2013年

@bestsss観測ありがとうございます!私はCコードから直接翻訳を行い、あまり注意を払わなかった=(。私はいくつかの変更を行い、回答を更新しました。追加の提案を
いただければ幸いです

14

私は一緒に入れ、迅速モックアップあなたのアルゴリズムの結果を評価するために、JavaScriptでを。0から99までの100,000のランダムな整数を生成し、各整数のインスタンスを追跡します。

最初に気づくのは、高い数値よりも低い数値を取得する可能性が高いということです。あなたが見る、このときに最もseed1高く、seed2低いです。いくつかの例で、私はたった3つの数を得ました。

せいぜい、あなたのアルゴリズムはいくらかの改良を必要とします。


8

Math.Random()関数がオペレーティングシステムを呼び出して時刻を取得する場合、それを関数と比較することはできません。あなたの関数はPRNGですが、その関数は実際の乱数を求めています。リンゴとオレンジ。

PRNGは高速である可能性がありますが、繰り返す前に長い期間を達成するのに十分な状態情報がありません(そのロジックは、多くの状態情報で可能な期間を達成するほど高度なものではありません)。

期間は、PRNGが繰り返される前のシーケンスの長さです。これは、PRNGマシンが過去の状態と同一の状態に状態遷移するとすぐに発生します。そこから、その状態で始まった遷移が繰り返されます。PRNGのもう1つの問題は、固有のシーケンスの数が少ないことと、繰り返される特定のシーケンスの縮退収束です。望ましくないパターンが存在する場合もあります。たとえば、数値が10進数で出力される場合、PRNGはかなりランダムに見えますが、2進数で値を調べると、各呼び出しでビット4が単に0と1の間で切り替わっていることがわかります。おっとっと!

Mersenne Twisterと他のアルゴリズムを見てください。期間の長さとCPUサイクルのバランスをとる方法があります。(メルセンヌツイスターで使用される)1つの基本的なアプローチは、状態ベクトル内を循環することです。つまり、数値が生成されている場合、それは状態全体に基づくのではなく、数ビットの操作の対象となる状態配列からの数ワードに基づくだけです。ただし、各ステップで、アルゴリズムは配列内を移動し、一度にコンテンツを少しずつスクランブルします。


5
最初の段落を除いて、私はほとんど同意します。組み込みのランダム呼び出し(およびUnixライクなシステムでは/ dev / random)もPRNGです。シードが予測しにくいものであっても、乱数を生成するものをアルゴリズムでPRNGと呼びます。放射性崩壊、大気ノイズなどを使用する「真の」乱数ジェネレータがいくつかありますが、これらは多くの場合、比較的少ないビット/秒を生成します。
Matt Krause 2013年

Linuxボックスで/dev/randomは、PRNGではなく、デバイスドライバーから取得される実際のランダム性のソースです。十分なビットが利用できない場合、ブロックします。姉妹デバイス/dev/urandomもブロックしませんが、ランダムビットが使用可能になると更新されるため、PRNGではありません。
Kaz

Math.Random()関数がオペレーティングシステムを呼び出して時刻を取得する場合、これはまったく正しくありません。(私が知っているJavaフレーバー/バージョンのいずれかで)
bestsss 2013年

@bestsssこれは元の質問からです:Math.randomがSystem.nanoTime()を使用したことをどこかで読んだことを覚えています。あなたの知識はそこにまたはあなたの答えに追加する価値があるかもしれません。ifで条件付きで使用しました。:)
Kaz

Kaz、両方のnanoTime()+ counter / hashはjava.util.Random、oracle / OpenJDKのデフォルトシードに使用されます。それはシードのためだけであり、それは標準的なLCGです。実際には、OPジェネレーターはシードに2つの乱数を使用しますjava.util.Random。これは問題ありません。System.currentTimeMillis()JDK1.4-のデフォルトのシードだった
bestsss

7

たくさんの疑似乱数ジェネレータが世の中にあります。たとえば、KnuthのranarrayMersenneツイスター、またはLFSRジェネレーターを探します。Knuthの記念碑的な「半数的アルゴリズム」は領域を分析し、いくつかの線形合同ジェネレーター(実装が簡単、高速)を提案します。

しかし、私は、java.util.RandomまたはMath.randomに固執することをお勧めします。それらは高速で、少なくとも時々の使用(つまり、ゲームなど)には問題ありません。ディストリビューションに偏執しているだけの場合(一部のモンテカルロプログラムまたは遺伝的アルゴリズム)、それらの実装を確認し(ソースはどこかで入手可能です)、オペレーティングシステムまたはrandom.orgから本当に乱数をシードします。セキュリティが重要な一部のアプリケーションでこれが必要な場合は、自分で掘り下げる必要があります。そしてその場合のように、ビットが欠けているいくつかの色付きの正方形がここで噴出するのを信じてはいけないので、私は今黙ります。


7

あなたが単一アクセスしない限り、思い付いた乱数生成のパフォーマンスがどのユースケースのために問題になることはほとんどありませんRandom(ので、複数のスレッドからインスタンスをRandomありますsynchronized)。

しかし、それが本当に当てはまり、大量の乱数を高速に必要とする場合、ソリューションの信頼性は非常に高くなります。時々それは良い結果を与える、時々それは恐ろしい結果を与える(初期設定に基づいて)。

Randomクラスが提供するのと同じ数値が必要な場合は、より速く、そこで同期を取り除くことができます。

public class QuickRandom {

    private long seed;

    private static final long MULTIPLIER = 0x5DEECE66DL;
    private static final long ADDEND = 0xBL;
    private static final long MASK = (1L << 48) - 1;

    public QuickRandom() {
        this((8682522807148012L * 181783497276652981L) ^ System.nanoTime());
    }

    public QuickRandom(long seed) {
        this.seed = (seed ^ MULTIPLIER) & MASK;
    }

    public double nextDouble() {
        return (((long)(next(26)) << 27) + next(27)) / (double)(1L << 53);
    }

    private int next(int bits) {
        seed = (seed * MULTIPLIER + ADDEND) & MASK;
        return (int)(seed >>> (48 - bits));
    }

}

java.util.Randomコードを取得して同期を削除するだけで、Oracle HotSpot JVM 7u9のオリジナルと比較して2倍のパフォーマンスが得られます。それでもまだは遅いですが、QuickRandomより一貫した結果が得られます。正確には、同じseed値とシングルスレッドのアプリケーションの場合、元のクラスと同じ擬似乱数Randomが得られます。


このコードは、GNU GPL v2の下でライセンスされているjava.util.RandomOpenJDK 7uの最新版基づいています。


10か月後に編集

同期していないRandomインスタンスを取得するために上記のコードを使用する必要がないことを発見しました。JDKにも1つあります。

Java 7のThreadLocalRandomクラスを見てください。その中のコードは上のコードとほとんど同じです。このクラスは、Random乱数をすばやく生成するのに適したローカルスレッド分離バージョンです。私が考えられる唯一の欠点は、seed手動で設定できないことです。

使用例:

Random random = ThreadLocalRandom.current();

2
@Hmmを編集します。怠惰ではないときに、QR、Math.random、およびThreadLocalRandomを比較し:)ます。
tckmn 2013年

1.最上位の16ビットは使用されているビットに影響を与えないため、マスクを削除することで速度をさらに上げることができます。2.これらのビットを使用して、1つの減算を保存し、より良いジェネレーターを取得できます(状態が大きくなります。製品の最も重要なビットが最もうまく分散されますが、いくつかの評価が必要になります)。3. Sunの人たちは、Knuthによる古風なRNGを実装し、同期を追加しました。:(
maaartinus 2017年

3

「ランダム」とは、数値を取得することだけではありません... 疑似ランダムです。

疑似ランダムがあなたの目的に十分適している場合は、確かにそれははるかに高速です(そしてXOR + Bitshiftはあなたが持っているものよりも高速になります)

ロルフ

編集:

さて、この答えに早すぎるので、コードが高速である本当の理由に答えましょう。

Math.Random()のJavaDocから

このメソッドは適切に同期され、複数のスレッドで正しく使用できます。ただし、多くのスレッドが疑似乱数を高速で生成する必要がある場合は、各スレッドが独自の疑似乱数ジェネレーターを持つことで競合を減らすことができます。

これが、コードが高速になる理由です。


3
ハードウェアノイズジェネレーターやOSのI / Oへの直接線を含まないほとんどすべてのものは、疑似ランダムになります。本物のランダム性は、アルゴリズムだけでは生成できません。どこかからのノイズが必要です。(一部のOSのRNGは、マウスの移動方法や入力などを測定することで入力を取得します。マイクロ秒からナノ秒のスケールで測定されるため、非常に予測不可能です。)
cHao

@OliCharlesworth:実際、私が知る限り、真のランダム値は大気ノイズを使用して検出されます。
Jeroen Vannevel 2013年

@me ...急いで答えるのはばかげています。Math.randomは疑似ランダムであり、同期されます。
rolfl 2013年

@rolfl:同期Math.random()が遅い理由を十分に説明できます。Random毎回同期するか新しいものを作成する必要があり、どちらもパフォーマンス面で非常に魅力的ではありません。パフォーマンスを気にしているのなら、自分new Randomで作成してそれを使用するだけです。:P
cHao

@JeroenVannevelの放射性崩壊もランダムです。
RxS 2013年

3

java.util.Randomはそれほど変わらない、Knuthによって記述された基本的なLCG。ただし、主な2つの主な利点/違いがあります。

  • スレッドセーフ-各更新は、単純な書き込みよりもコストが高く、ブランチが必要なCASです(完全に予測されたシングルスレッドであっても)。CPUによっては、大きな違いが生じる可能性があります。
  • 公開されていない内部状態-これは、重要なものにとって非常に重要です。乱数が予測可能でないことを望みます。

その下には、java.util.Randomで「ランダムな」整数を生成するメインルーチンがあります。


  protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
          oldseed = seed.get();
          nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

AtomicLongと非公開の状態(つまり、のすべてのビットを使用long)を削除すると、2倍の乗算/剰余よりもパフォーマンスが向上します。

最後の注意:Math.random単純なテスト以外には使用しないでください。競合が発生しやすく、2つ以上のスレッドで同時にそれを呼び出すと、パフォーマンスが低下します。そのほとんど知られていない歴史的特徴の1つは、JavaでのCASの導入です-悪名高いベンチマークを打ち破るため(最初はIBMが組み込み関数を介して、次にSunが「JavaからのCAS」を作成)


0

これは私のゲームで使用するランダム関数です。これはかなり高速で、十分な(十分な)分布を持っています。

public class FastRandom {

    public static int randSeed;

      public static final int random()
      {
        // this makes a 'nod' to being potentially called from multiple threads
        int seed = randSeed;

        seed    *= 1103515245;
        seed    += 12345;
        randSeed = seed;
        return seed;
      }

      public static final int random(int range)
      {
        return ((random()>>>15) * range) >>> 17;
      }

      public static final boolean randomBoolean()
      {
         return random() > 0;
      }

       public static final float randomFloat()
       {
         return (random()>>>8) * (1.f/(1<<24));
       }

       public static final double randomDouble() {
           return (random()>>>8) * (1.0/(1<<24));
       }
}

1
これは質問に対する答えを提供しません。批評したり、著者に説明を求めたりするには、投稿の下にコメントを残してください。
ジョンウィレムセ2014年

元のアルゴリズムでは十分でないことがすでに確立されていると思いますか?おそらく、何が十分に良いかの例は、それを改善する方法についてのインスピレーションにつながる可能性がありますか?
Terje 2014年

はい、たぶん、しかしそれは質問にまったく答えません、そしてあなたのアルゴリズムをサポートするデータは実際には「十分」ではありません。一般に、乱数アルゴリズムと密接に関連する暗号化アルゴリズムは、プログラミング言語でそれらを実装した専門家によるアルゴリズムほど優れていません。したがって、あなたがあなたの主張を支持し、なぜそれが質問のアルゴリズムよりも優れているのかを詳しく説明できれば、少なくとも尋ねられた質問に答えるでしょう。
ジョンウィレムセ2014年

まあ...プログラミング言語でそれらを実装した専門家は「完全な」配布を目指していますが、ゲームではそれが必要になることはありません。速度と「十分な」配布が必要です。このコードはこれを提供します。ここで不適切な場合は、答えを削除します。問題ありません。
Terje

マルチスレッディングに関しては、ローカル変数の使用法は何もしないvolatileので、コンパイラーはローカル変数を自由に削除(または導入)できます。
maaartinus 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.