Bu “yeterince iyi” rastgele bir algoritma mıdır; daha hızlıysa neden kullanılmıyor?


171

Ben denilen bir sınıf yaptım QuickRandomve onun işi hızla rastgele sayılar üretmektir. Gerçekten basit: Eski değeri alın, a ile çarpın doubleve ondalık kısmı alın.

İşte QuickRandomsınıfım bütünüyle:

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;
    }

}

Ve test etmek için yazdığım kod:

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);
}

Bu basit bir önceki çift ile bir "sihirli sayı" çift çarpma basit bir algoritma. Çok hızlı bir şekilde bir araya getirdim, bu yüzden muhtemelen daha iyi yapabilirdim, ama garip bir şekilde, iyi çalışıyor gibi görünüyor.

Bu mainyöntemdeki yorum satırlarının örnek çıktısıdır :

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

Hm. Oldukça rastgele. Aslında, bu bir oyunda rastgele bir sayı üreteci için işe yarayacaktır.

Yorumlanmamış kısmın örnek çıktısı:

5456313909
1427223941

Vaov! Neredeyse 4 kat daha hızlı performans gösterir Math.random.

Math.randomKullanılan bir yerde okuduğumu hatırlıyorumSystem.nanoTime() ve tonlarca çılgın modül ve bölme şeyler . Bu gerçekten gerekli mi? Algoritmam çok daha hızlı çalışıyor ve oldukça rastgele görünüyor.

İki sorum var:

  • Algoritmam "yeterince iyi" mi (örneğin, bir oyun için, gerçekten rastgele sayılar çok önemli değildir)?
  • Neden Math.randomsadece basit çarpma ve ondalık kesimi yeterli göründüğünde bu kadar çok şey yapar ?

154
"oldukça rastgele görünüyor"; bir histogram oluşturmalı ve sıranızda bazı otokorelasyon yapmalısınız ...
Oliver Charlesworth

63
"Oldukça rastgele görünüyor" demek, rastgele bir nesnel ölçü değildir ve bazı gerçek istatistikler elde etmeniz gerekir.
Matt H

23
@Doorknob: Layman'ın terimleriyle, sayılarınızın 0 ile 1 arasında "düz" bir dağılımı olup olmadığını araştırmalı ve zaman içinde periyodik / tekrarlayan kalıplar olup olmadığını görmelisiniz.
Oliver Charlesworth

22
new QuickRandom(0,5)Veya deneyin new QuickRandom(.5, 2). Bunların her ikisi de numaranız için tekrar tekrar 0 verir.
FrankieTheKneeMan

119
Kendi rasgele sayı üretme algoritmanızı yazmak, kendi şifreleme algoritmanızı yazmak gibidir. Hiper nitelikli insanlar tarafından o kadar çok önceki sanat var ki, zamanınızı doğru yapmaya çalışmak için zaman harcamak mantıklı değil. Java kitaplığı işlevlerini kullanmamanız için hiçbir neden yoktur ve gerçekten herhangi bir nedenle kendiniz yazmak istiyorsanız, Wikipedia'yı ziyaret edin ve orada Mersenne Twister gibi algoritmalara bakın.
steveha

Yanıtlar:


351

Sizin QuickRandomuygulaması gerçekten bir düzenli dağılımı vardır. Frekanslar genellikle daha düşük değerlerde daha yüksektir, Math.random()daha düzgün bir dağılıma sahiptir. İşte bunu gösteren bir 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));
        }
    }

}

Ortalama sonuç şuna benzer:

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

Testi tekrarlarsanız, MR dağılımı sabitken, QR dağılımının ilk tohumlara bağlı olarak büyük ölçüde değiştiğini göreceksiniz. Bazen istenen tekdüze dağılıma ulaşır, ancak çoğu zaman ulaşmaz. İşte en uç örneklerden biri, hatta grafiğin sınırlarının ötesinde:

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
Sayısal veriler için +1 - ham sayılara bakmak, yanıltıcı olabilir, ancak bu istatistiksel olarak anlamlı bir fark olduğu anlamına gelmez.
Maciej Piechotka

16
Bu sonuçlar, geçen tohumlar ile büyük ölçüde değişir QuickRandom. Bazen, üniformaya yakın, bazen bundan daha kötü.
Petr Janeček

68
@ BlueRaja-DannyPflughoeft Çıktının kalitesinin başlangıçtaki tohum değerlerine (iç sabitlerin aksine) büyük ölçüde bağlı olduğu herhangi bir PRNG bana kırık geliyor.
CVn

22
İstatistiğin ilk kuralı: verileri çizin . Analiziniz yerinde, ancak bir histogram çizmek bunu çok daha hızlı gösteriyor. ;-) (Ve R'de iki satır var)
Konrad Rudolph

37
Zorunlu alıntılar: “Rasgele rakamlar üretmek için aritmetik yöntemleri düşünen herkes elbette günah halindedir.” - John von Neumann (1951) “Yukarıdaki teklifi en az 100 yerde görmemiş olan biri muhtemelen çok eski değildir.” - DV Pryor (1993) “Rastgele sayı üreteçleri rastgele seçilmemelidir.” - Donald Knuth (1986)
Happy Green Kid Naps

133

Açıkladığınız şey doğrusal bir doğuştan üreteç olarak adlandırılan bir tür rastgele üretecidir . Jeneratör aşağıdaki gibi çalışır:

  • Bir tohum değeri ve çarpanı ile başlayın.
  • Rasgele bir sayı oluşturmak için:
    • Tohumu çarpanla çarpın.
    • Tohumu bu değere eşit olarak ayarlayın.
    • Bu değeri döndür.

Bu jeneratör birçok güzel özelliğe sahiptir, ancak iyi bir rastgele kaynak olarak önemli problemlere sahiptir. Yukarıda bağlantılı Wikipedia makalesinde bazı güçlü ve zayıf yönler açıklanmaktadır. Kısacası, iyi rastgele değerlere ihtiyacınız varsa, bu muhtemelen çok iyi bir yaklaşım değildir.

Bu yardımcı olur umarım!


@ louism- Gerçekten "rastgele" değil, kendi başına. Sonuçlar belirleyici olacaktır. Bununla birlikte, cevabımı yazarken bunu düşünmedim; belki birisi bu detayı açıklığa kavuşturabilir?
templatetypedef

2
Kayan nokta aritmetik hataları uygulama olarak tasarlanmıştır. Bildiğim kadarıyla, belirli bir platform için tutarlılar, ancak örneğin farklı cep telefonları ve PC mimarileri arasında farklılık gösterebilir. Arka arkaya bir dizi kayan nokta hesaplaması yaparken bazen fazladan 'koruma bitleri' eklenmesine rağmen, bu koruma bitlerinin varlığı veya yokluğu bir hesaplamayı net bir şekilde farklılaştırabilir. (koruma bitleri, örneğin, 64 bit
çiftin

2
Ayrıca, LCRNG'lerin arkasındaki teorinin tamsayılarla çalıştığınızı varsaydığını unutmayın! Kayan nokta sayılarını atmak aynı kalitede sonuç vermeyecektir .
duskwuff -inaktif-

1
@duskwuff, haklısın. Ancak kayan nokta donanımı aklı başında kurallara uyuyorsa, bunu yapmak mantis boyutunu modulo yapmakla aynıdır ve teori geçerlidir. Yaptığınız işte ekstra özen göstermeniz yeterli.
vonbrand

113

Rastgele sayı işleviniz, çok az dahili duruma sahip olduğu için zayıf - herhangi bir adımda işlev tarafından verilen sayı tamamen önceki sayıya bağlıdır. Örneğin, magicNumberbunun 2 olduğunu varsayarsak (örnek olarak), o zaman sıra:

0.10 -> 0.20

benzer dizilerle güçlü bir şekilde yansıtılır:

0.09 -> 0.18
0.11 -> 0.22

Çoğu durumda, bu, oyununuzda gözle görülür korelasyonlar yaratacaktır - örneğin, nesneler için X ve Y koordinatları oluşturmak üzere işlevinize ardışık çağrılar yaparsanız, nesneler net köşegen desenler oluşturur.

Rastgele sayı üretecinin başvurunuzu yavaşlattığına inanmak için iyi bir nedeniniz olmadıkça (ve bu ÇOK olası değildir), kendi yazmayı denemek için iyi bir neden yoktur.


36
Pratik bir cevap için +1 ... bunu bir çekim em'de kullanın ve destansı çoklu kafa vuruşları için çapraz çizgiler boyunca düşmanları yumurtlayın? : D
wim

@ wim: Bu tür kalıpları istiyorsanız PRNG'ye ihtiyacınız yoktur.
Yalan Ryan

109

Bununla ilgili asıl sorun, çıktı histogramının başlangıç ​​tohumuna çok fazla bağımlı olmasıdır - çoğu zaman neredeyse tekdüze bir çıktı ile sonuçlanacaktır, ancak çoğu zaman belirgin olarak tekdüze bir çıktıya sahip olacaktır.

Php rand()işlevinin ne kadar kötü olduğu hakkında bu makaleden esinlenerek , QuickRandomve kullanarak bazı rastgele matris görüntüleri yaptım System.Random. Bu çalışma, tohumun bazen System.Randomoldukça düzgün olduğu (bu durumda daha düşük sayıları tercih ederek) nasıl kötü bir etkiye sahip olabileceğini gösterir .

QuickRandom

System.Random

Daha da kötüsü

Biz initialise Eğer QuickRandomolarak new QuickRandom(0.01, 1.03)bu görüntüyü elde edersiniz:

Kod

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
Güzel kod. Evet, bu harika. Bunu bazen yapardım, ölçülebilir bir önlem almak zordur, ancak diziye bakmak için başka iyi bir yoldur. Ve eğer genişlik * yükseklikten daha uzun dizilere bakmak isterseniz, piksel başına bir piksel ile bir sonraki görüntüyü xorveya yapabilirsiniz. QuickRandom resminin bir deniz yosunu halısı gibi dokulu olması nedeniyle estetik açıdan çok daha hoş olduğunu düşünüyorum.
Cris Stringfellow

Estetik açıdan hoş olan kısım, her bir sıra boyunca ilerledikçe (ve daha sonra tekrar başlangıca) sekansın , magicNumberbenzerliği olmayan bir sayı ürettiği için sekansın nasıl artma eğiliminde prevNumolduğudur. Tohumları kullanırsak, new QuickRandom(0.01, 1.03)bunu i.imgur.com/Q1Yunbe.png alırız !
Callum Rogers

Evet, harika analiz. Mod 1'i, sarma gerçekleşmeden önce açıkça bir sabitle çarptığından, açıkladığınız artış olacaktır. 1 milyar ile çarpıp daha sonra mod 256 renk paletini azaltarak daha az önemli ondalık yerleşimleri alırsak bu önlenebilir gibi görünüyor.
Cris Stringfellow

Bu çıktı görüntülerini oluşturmak için ne kullandığınızı söyleyebilir misiniz? Matlab?
uday

@ uDaY: C # ve koduna bir göz atın System.Drawing.Bitmap.
Callum Rogers

37

Rastgele sayı üretecinizle ilgili bir sorun, 'gizli durum' olmamasıdır - son çağrıda hangi rastgele sayıyı döndürdüğümü biliyorsanız, zamanın sonuna kadar göndereceğiniz her rastgele sayıyı biliyorum, çünkü sadece bir tane var olası bir sonraki sonuç, vb.

Dikkate alınması gereken başka bir şey, rastgele sayı üretecinizin 'periyodudur'. Açıkçası, bir çiftin mantis kısmına eşit olan sınırlı bir durum büyüklüğüyle, döngüden önce en fazla 2 ^ 52 değerine geri dönebilir. Ama bu en iyi durumda - dönem 1, 2, 3, 4 döngüleri olmadığını kanıtlayabilir misiniz? Varsa, RNG'niz bu durumlarda korkunç, dejenere davranışlara sahip olacaktır.

Ayrıca, rastgele sayı oluşturma işleminizin tüm başlangıç ​​noktaları için eşit bir dağılımı olacak mı? Başlamazsa, RNG'niz başlangıç ​​tohumuna bağlı olarak önyargılı veya daha kötü yönde farklı eğilimli olacaktır.

Tüm bu soruları cevaplayabilirseniz, harika. Eğer yapamıyorsanız, o zaman neden çoğu insanın tekerleği yeniden icat etmediğini ve kanıtlanmış bir rastgele sayı üreteci kullanmadığını biliyorsunuz;)

(Bu arada, iyi bir atasözü: En hızlı kod çalışmayan koddur. Dünyadaki en hızlı rasgele () yapabilirsiniz, ancak çok rasgele değilse iyi değildir)


8
Bütün tohumlar için bu jeneratörle en az bir önemsiz döngü vardır: 0 -> 0. Tohuma bağlı olarak, başkaları da olabilir. (A, 3.0 tohumuyla Mesela, 0.5 -> 0.5, 0.25 -> 0.75 -> 0.25, 0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2, vs.)
-inactive- duskwuff

36

PRNG'ler geliştirirken her zaman yaptığım yaygın bir test:

  1. Çıktıyı char değerlerine dönüştürme
  2. Karakter değerini bir dosyaya yazma
  3. Dosyayı sıkıştır

Bu, yaklaşık 1 ila 20 megabaytlık diziler için "yeterince iyi" PRNG'ler olan fikirleri hızlı bir şekilde tekrarlamamı sağlar. Ayrıca, sadece gözle denetlemekten daha iyi bir yukarıdan aşağı resim verdi, çünkü yarım kelimelik bir durum içeren herhangi bir "yeterince iyi" PRNG, gözlerinizi döngü noktasını görme yeteneğini hızla aşabilir.

Gerçekten seçici olsaydım, iyi algoritmaları alıp DIEHARD / NIST testlerini çalıştırabilir, daha fazla bilgi edinebilir ve sonra geri dönüp biraz daha ince ayar yapabilirim.

Sıklık testinin avantajı, bir frekans analizinin aksine, önemsiz bir şekilde, iyi bir dağılım oluşturmak kolaydır: 0 - 255 arasındaki tüm değerleri içeren 256 uzunluklu bir blok çıktısı alın ve bunu 100.000 kez yapın. Ancak bu dizinin uzunluğu 256 olan bir döngü vardır.

Çarpık bir dağılım, hatta küçük bir farkla bile, özellikle çalışmak için diziyi yeterli (örneğin 1 megabayt) verirseniz, bir sıkıştırma algoritması tarafından alınmalıdır. Bazı karakterler veya bigramlar veya n-gramlar daha sık ortaya çıkarsa, bir sıkıştırma algoritması bu dağıtım eğimini daha kısa kod sözcükleriyle sık karşılaşılan durumları destekleyen kodlara kodlayabilir ve bir sıkıştırma deltası elde edebilirsiniz.

Çoğu sıkıştırma algoritması hızlı olduğu ve herhangi bir uygulama gerektirmediği için (işletim sistemlerinin sadece etrafta yattığı için), sıkıştırma testi, geliştirmekte olduğunuz bir PRNG için başarılı / başarısız olarak derecelendirmek için çok yararlı bir testtir.

Deneylerinizde iyi şanslar!

Oh, bu testi yukarıdaki kodunuzda, kodunuzun aşağıdaki küçük modunu kullanarak gerçekleştirdim:

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));
        }
    }
}

Sonuçlar:

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

Çıktı dosyası hiç sıkıştırılamazsa bir PRNG iyi düşünecektim. Dürüst olmak gerekirse, PRNG'nizin bu kadar iyi yapacağını düşünmedim, ~ 20 Megs'de sadece% 16, bu kadar basit bir yapı için oldukça etkileyici. Ama yine de başarısız olduğunu düşünüyorum.


2
Hayal edip etmemek, yıllar önce rasgele jeneratörlerimi test ettiğimde aynı fikre sahibim.
Aristos

1
@Alexandre C. ve Aristos ve aidan. Sana inanıyorum.
Cris Stringfellow

33

Uygulayabileceğiniz en hızlı rastgele jeneratör şudur:

resim açıklamasını buraya girin

XD, şakalar, burada söylenen her şeyin yanı sıra, rastgele dizileri test etmenin "zor bir görev" olduğunu söylemeye katkıda bulunmak istiyorum [1] ve sözde rastgele sayıların belirli özelliklerini kontrol eden birkaç test var, bir birçoğu burada: http://www.random.org/analysis/#2005

Rastgele üreteç "kalitesini" değerlendirmenin basit bir yolu eski Chi Square testidir.

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);
}

Alıntı yapma [1]

Test² testi fikri üretilen sayıların makul bir şekilde yayılıp yayılmadığını kontrol etmektir. Oluşturduğumuz Eğer N den az pozitif sayılar r , o zaman yaklaşık almak için beklediğiniz N / r her değerin sayılarla. Ama --- ve bu, işin özüdür --- tüm değerlerin meydana gelme sıklıkları tam olarak aynı olmamalıdır: bu rastgele olmaz!

Beklenen sıklığa göre ölçeklendirilmiş, her bir değerin ortaya çıkma sıklıklarının karelerinin toplamını hesaplıyoruz ve daha sonra sekansın boyutunu alt özetliyoruz. Bu sayı, "stat² istatistiği", matematiksel olarak şu şekilde ifade edilebilir:

chi kare formülü

Χ² istatistiği r'ye yakınsa , sayılar rasgele olur; eğer çok uzakta ise, o zaman değildir. "Kapat" ve "uzak" kavramları daha kesin olarak tanımlanabilir: istatistiğin rastgele dizilerin özellikleriyle tam olarak nasıl ilişkilendirildiğini anlatan tablolar mevcuttur. Gerçekleştirdiğimiz basit test için istatistik 2√r içinde olmalıdır.

Bu teori ve aşağıdaki kodu kullanarak:

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);
    }
}

Aşağıdaki sonucu aldım:

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

Hangi QuickRandom için, uzaklardan olan r (dış r ± 2 * sqrt(r))

Bununla birlikte, QuickRandom hızlı olabilir, ancak (başka cevaplarda belirtildiği gibi) rastgele bir sayı üreticisi olarak iyi değildir


[1] SEDGEWICK ROBERT, C Algoritmaları , Addinson Wesley Publishing Company, 1990, sayfa 516 - 518


9
İnanılmaz bir XKCD için 1 wobsite P: (oh ve büyük cevap)
tckmn

1
Teşekkürler ve evet xkcd rafları! XD
Higuaro

Teori iyidir, ancak yürütme kötüdür: kod tamsayı taşmasına karşı hassastır. Java'da hepsi int[]sıfırlanır, bu nedenle bu parçaya gerek yoktur. Çiftler için çalışırken yüzdürme yapmak anlamsızdır. Son: random1 ve random2 yöntemlerini çağırmak oldukça komik.
Ocak'ta bestsss

@bestsss Gözlemler için teşekkürler! C kodundan doğrudan bir çeviri yaptım ve buna çok dikkat etmedim = (. Bazı değişiklikler yaptım ve cevabı güncelledim. Herhangi bir ek öneriyi takdir ediyorum
higuaro

14

Sonuçları değerlendirmek için JavaScript'te algoritmanızın hızlı bir modelini bir araya getirdim . 0 - 99 arasında 100.000 rastgele tamsayı üretir ve her tamsayı örneğini izler.

Fark ettiğim ilk şey, yüksek bir sayıdan daha düşük bir sayı alma olasılığınızın daha yüksek olmasıdır. Bunu en seed1çok yüksek ve seed2düşük olduğunda görürsünüz . Birkaç örnekte, sadece 3 sayı aldım.

En iyi ihtimalle, algoritmanızın biraz rafine edilmesi gerekir.


8

Math.Random()İşlev, günün saatini almak için işletim sistemini çağırırsa , işlevi işlevinizle karşılaştıramazsınız. İşleviniz bir PRNG iken, bu işlev gerçek rastgele sayılar için çabalamaktadır. Elmalar ve Portakallar.

PRNG'niz hızlı olabilir, ancak tekrarlanmadan önce uzun bir süre elde etmek için yeterli durum bilgisine sahip değildir (ve mantığı, bu kadar çok devlet bilgisiyle mümkün olan dönemleri bile başaracak kadar sofistike değildir).

Dönem, PRNG'niz kendini tekrarlamaya başlamadan önce dizinin uzunluğudur. Bu, PRNG makinesi geçmiş durumlarla aynı olan bir duruma geçiş yapar yapmaz olur. Oradan, bu durumda başlayan geçişleri tekrarlayacak. PRNG'lerle ilgili bir başka sorun, düşük sayıda benzersiz sekansın yanı sıra tekrar eden belirli bir sekansta dejenere yakınlaşma olabilir. İstenmeyen desenler de olabilir. Örneğin, sayılar ondalık olarak yazdırıldığında bir PRNG'nin oldukça rastgele göründüğünü varsayalım, ancak ikili değerlerin incelenmesi, bit 4'ün her çağrıda 0 ile 1 arasında değiştiğini gösterir. Hata!

Mersenne Twister ve diğer algoritmalara bir göz atın. Periyot uzunluğu ve CPU döngüleri arasında bir denge kurmanın yolları vardır. Temel bir yaklaşım (Mersenne Twister'da kullanılır) durum vektöründe dolaşmaktır. Yani, bir sayı üretilirken, tüm duruma değil, sadece durum dizisinden birkaç bit işleme tabi birkaç kelimeye dayanır. Ancak her adımda, algoritma dizide de dolaşır ve içeriği her seferinde biraz karıştırır.


5
İlk paragrafınız dışında çoğunlukla katılıyorum. Yerleşik rastgele çağrılar (ve Unix benzeri sistemlerde / dev / random) de PRNG'lerdir. Tohum tahmin edilmesi zor bir şey olsa bile, rastgele sayılar üreten bir PRNG rastgele bir şey çağırır. Radyoaktif bozunma, atmosferik gürültü, vb. Kullanan birkaç "gerçek" rasgele sayı üreteci vardır, ancak bunlar genellikle nispeten az bit / saniye üretir.
Matt Krause

Linux kutularında, /dev/randombir PRNG değil, aygıt sürücülerinden elde edilen gerçek bir rastgelelik kaynağıdır. Yeterli bit olmadığında engeller. Kardeş cihaz /dev/urandomda engellemez, ancak kullanılabilir olduklarında rastgele bitlerle güncellendiğinden hala tam olarak bir PRNG değildir.
Kaz

Math.Random () işlevi, günün saatini almak için işletim sistemini çağırırsa - bu kesinlikle yanlıştır. (bildiğim java lezzetlerinin / versiyonlarının herhangi birinde)
bestsss

@bestsss Bu orijinal sorudan: Math.random'un System.nanoTime () kullandığı bir yerde okuduğumu hatırlıyorum . Bilginiz oraya veya cevabınıza eklemeye değer olabilir. Bir if ile koşullu olarak kullandım . :)
Kaz

Kaz, hem nanoTime()+ counter / hash hem de java.util.Randomoracle / OpenJDK'nın varsayılan tohumu için kullanılır . Bu sadece tohum için standart bir LCG'dir. Aslında OP jeneratörü tohum için 2 rastgele sayı alır, bu tamam - bu yüzden fark yok java.util.Random. System.currentTimeMillis()1.4
'deki

7

Dışarıda çok sayıda sahte rasgele sayı üreteci var. Örneğin Knuth'un ranarray'ı , Mersenne twister veya LFSR jeneratörlerini arayın. Knuth'un anıtsal "Seminumerical algoritmaları" alanı analiz eder ve bazı lineer kongruratif jeneratörler (uygulaması basit, hızlı) önerir.

Ama ben sadece java.util.Randomya da Math.random, hızlı ve en azından ara sıra kullanım için (yani, oyunlar ve benzeri) için sopa öneririz . Dağıtım konusunda sadece paranoyaksanız (bazı Monte Carlo programı veya genetik bir algoritma), uygulamalarına bakın (kaynak bir yerde mevcuttur) ve işletim sisteminizden veya random.org'dan gerçekten rastgele bir sayı ile tohumlayın . Güvenliğin kritik olduğu bazı uygulamalar için bu gerekliyse, kendinizi kazmanız gerekir. Ve bu durumda olduğu gibi, burada bit uçları olan bazı renkli meydanlara inanmamalısınız, şimdi susacağım.


7

Size tek erişen sürece ile geldi rasgele sayı üretme performansı herhangi bir kullanım örneği için bir sorun olacağını pek olası değildir Random(çünkü birden çok iş parçacığı örneği Randomolan synchronized).

Ancak, bu gerçekten durum buysa ve çok sayıda rastgele sayıya hızlı ihtiyacınız varsa, çözümünüz çok güvenilmezdir. Bazen iyi sonuçlar verir, bazen korkunç sonuçlar verir (başlangıç ​​ayarlarına göre).

RandomSınıfın size verdiği aynı sayıları istiyorsanız, sadece daha hızlı, oradaki senkronizasyondan kurtulabilirsiniz:

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));
    }

}

Basitçe java.util.Randomkodu aldım ve Oracle HotSpot JVM 7u9'umdaki orijinalle karşılaştırıldığında iki kat daha fazla performansla sonuçlanan senkronizasyonu kaldırdım. Hala sizden daha yavaş QuickRandom, ama çok daha tutarlı sonuçlar veriyor. Kesin olmak gerekirse, aynı seeddeğerler ve tek iş parçacıklı uygulamalar için, orijinal sınıfla aynı sahte rasgele sayılar verir Random.


Bu kod, GNU GPL v2 altında lisanslanan java.util.RandomOpenJDK 7u'daki akıma dayanmaktadır .


10 ay sonra DÜZENLE :

Sadece senkronize olmayan bir Randomörnek almak için yukarıdaki kodumu kullanmak zorunda olmadığını keşfettim . JDK'da da bir tane var!

Java 7'nin ThreadLocalRandomsınıfına bakın. İçindeki kod neredeyse yukarıdaki kod ile aynı. Sınıf, Randomhızlı bir şekilde rastgele sayılar üretmek için uygun olan yerel-iş parçacıklı izole bir versiyondur. Düşünebildiğim tek dezavantajı, seedmanuel olarak ayarlayamamanızdır.

Örnek kullanım:

Random random = ThreadLocalRandom.current();

2
@Edit Hmm, çok tembel olmadığım zaman QR, Math.random ve ThreadLocalRandom'u karşılaştırabilirim :)Bu ilginç, teşekkürler!
tckmn

1. En yüksek 16 bit kullanılan bitleri etkilemediğinden maskeyi düşürerek biraz daha hız kazanabilirsiniz. 2. Bu bitleri kullanabilir, bir çıkarma kaydedebilir ve daha iyi bir jeneratör elde edebilirsiniz (daha büyük durum; bir ürünün en önemli bitleri en güzel şekilde dağıtılır, ancak bazı değerlendirmeler gerekir). 3. Sun adamları Knuth tarafından basit bir arkaik RNG uyguladılar ve senkronizasyon eklediler. :(
maaartinus

3

'Rastgele' sadece sayıları almaktan ibaret değildir .... sahip olduğunuz şey sahte rastgele

Sözde rastgele amaçlarınız için yeterince iyiyse, o zaman emin olun, çok daha hızlı (ve XOR + Bitshift sahip olduğunuzdan daha hızlı olacaktır)

Rolf

Düzenle:

Tamam, bu cevapta çok aceleci olduktan sonra, kodunuzun daha hızlı olmasının gerçek nedenini cevaplayayım:

Math.Random () için JavaDoc'tan

Bu yöntem, birden fazla iş parçacığının doğru kullanımına izin vermek için uygun şekilde senkronize edilir. Bununla birlikte, birçok iş parçacığının büyük bir oranda psödondom sayıları üretmesi gerekiyorsa, her bir iş parçacığının kendi sahte sözde sayı üretecine sahip olması için çekişmeyi azaltabilir.

Bu nedenle kodunuz daha hızlıdır.


3
Bir donanım gürültü jeneratörü veya işletim sisteminin I / O malzemelerine doğrudan bir hat içermeyen hemen hemen her şey sahte rastgele olacaktır. Gerçek rastgelelik sadece bir algoritma ile üretilemez; bir yerden gürültü lazım. (Bazı OSE'lerde RNG'ler gibi şeyler ölçerek kendi girdi almak nasıl / sen nanosaniye mikrosaniye bir ölçekte Ölçülen vb fare, tip şeyler, son derece öngörülemeyen olabilir taşıdığınızda.)
Chao

@OliCharlesworth: Gerçekten, bildiğim kadarıyla tek gerçek rastgele değerler atmosferik gürültü kullanılarak bulunur.
Jeroen Vannevel

@ ben ... aceleyle cevaplamak için aptalca. Math.random yalancı ve aynı zamanda senkronize .
rolfl

@rolfl: Senkronizasyon neden Math.random()daha yavaş olduğunu çok iyi açıklayabilir . RandomHer seferinde senkronize etmek veya yeni bir tane oluşturmak zorunda kalacaktı ve ikisi de çok çekici bir performans sergilemedi. Performansı önemseseydim, kendiminkini yaratırım new Randomve bunu kullanırdım. : P
cHao

@JeroenVannevel radyoaktif bozunumu da rastgele.
RxS

3

Knava tarafından tarif edilen temel bir LCG olan rasgele çok farklı değildir. Bununla birlikte, ana 2 ana avantajı / farklılığı vardır:

  • iş parçacığı için güvenli - her güncelleme, basit bir yazma işleminden daha pahalı ve bir dala ihtiyacı olan bir CAS'dur (tek bir iş parçacıklı olarak mükemmel bir şekilde tahmin edilse bile). CPU'ya bağlı olarak önemli bir fark olabilir.
  • açıklanmamış iç devlet - bu önemsiz olmayan her şey için çok önemlidir. Rastgele sayıların tahmin edilemez olmasını istiyorsunuz.

Aşağıda java.util.Random'da ana rutin üreten 'rastgele' tamsayılar var.


  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'u ve açıklanmamış sateri (yani, tüm bitlerini kullanarak) kaldırırsanız long, çift çarpma / modulodan daha fazla performans elde edersiniz.

Son not: Math.randomBasit testlerden başka bir şey için kullanılmamalıdır, çekişmeye eğilimlidir ve aynı anda çağıran birkaç iş parçanız bile performans düşerse. Bilinen bir tarihi özelliği, java'da CAS'ın piyasaya sürülmesidir.


0

Bu, oyunlarım için kullandığım rastgele işlev. Oldukça hızlı ve iyi (yeterli) bir dağılımı var.

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
Bu soruya bir cevap vermez. Bir yazardan eleştiri veya açıklama istemek için gönderilerinin altına bir yorum bırakın.
John Willemse

Bence orijinal algoritmanın yeterince iyi olmadığı belirlendi mi? Belki de yeterince iyi olanın bir örneği, onu nasıl geliştireceğiniz konusunda ilham verebilir mi?
Terje

Evet, belki, ama soruya hiç cevap vermiyor ve algoritmanızı destekleyen hiçbir veri aslında "yeterince iyi" değil. Genellikle, rasgele sayı algoritmaları ve yakından ilişkili şifreleme algoritmaları, bunları bir programlama dilinde uygulayan uzmanlarınki kadar iyi değildir. Bu nedenle, talebinizi destekleyebiliyor ve neden Soru'daki algoritmadan daha iyi olduğunu açıklayabilseydiniz, en azından sorulan bir soruyu cevaplarsınız.
John Willemse

Peki ... Onları bir programlama dilinde uygulayan uzmanlar "mükemmel" dağıtım hedeflerken, oyunda buna asla ihtiyacınız yok. Hız ve "yeterince iyi" dağıtım istiyorsunuz. Bu kod bunu sunar. Burada uygun değilse, cevabı sileceğim, sorun değil.
Terje

Çok iş parçacığına ilişkin olarak, yerel değişkeni kullanmanız bir işlem gerektirmez volatile, çünkü derleyici dilsiz yerel değişkenleri ortadan kaldırmak (veya tanıtmak) serbesttir.
maaartinus
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.