이것이“충분한”랜덤 알고리즘입니까? 왜 더 빠르면 사용되지 않습니까?


171

나는이라는 클래스를 만들었고 QuickRandom그 임무는 임의의 숫자를 빠르게 생성하는 것입니다. 정말 간단합니다. 이전 값에 a를 곱하고 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에 "magic number"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()합니다. 정말 필요한가요? 내 알고리즘은 훨씬 빠르게 수행되며 꽤 무작위로 보입니다.

두 가지 질문이 있습니다.

  • 내 알고리즘이 "충분히"(예를 들어, 실제로 임의의 숫자가 그렇게 중요하지 않은 게임 )입니까?
  • Math.random단순한 곱셈과 십진수를 잘라내는 것으로 충분할 때 왜 그렇게 많은 일을합니까?

154
"거의 무작위로 보인다"; 히스토그램을 생성하고 시퀀스에서 자기 상관을 실행해야합니다.
Oliver Charlesworth

63
"임의로 보이는 것"은 실제로 객관성을 나타내는 객관적인 측정 값이 아니며 실제 통계를 가져와야 함을 의미합니다.
Matt H

23
@Doorknob : 일반인의 관점에서, 숫자가 0과 1 사이의 "평평한"분포를 갖는지 조사하고 시간이 지남에 따라 주기적 / 반복적 인 패턴이 있는지 확인해야합니다.
Oliver Charlesworth

22
new QuickRandom(0,5)또는을 시도하십시오 new QuickRandom(.5, 2). 그것들은 당신의 숫자에 대해 반복적으로 0을 출력합니다.
FrankieTheKneeMan

119
자신의 난수 생성 알고리즘을 작성하는 것은 자체 암호화 알고리즘을 작성하는 것과 같습니다. 자격을 갖춘 사람들에 의해 너무나 많은 선행 기술이 있기 때문에 시간을 제대로 맞추려고하는 것은 무의미합니다. Java 라이브러리 함수를 사용하지 않을 이유가 없으며 어떤 이유로 든 직접 작성하려면 Wikipedia를 방문하여 Mersenne Twister와 같은 알고리즘을 찾으십시오.
steveha

답변:


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

테스트를 반복하면 초기 시드에 따라 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  :                                                  

17
수치 데이터의 경우 +1-원수를 찾는 것이 통계적으로 유의미한 차이가 있음을 의미하지 않기 때문에 오해의 소지가 있습니다.
Maciej Piechotka

16
이 결과는에 전달 된 초기 씨앗에 따라 크게 다릅니다 QuickRandom. 때로는 균일에 가깝고 때로는 이것보다 훨씬 나쁩니다.
Petr Janeček 2012 년

68
@ BlueRaja-DannyPflughoeft 출력의 품질이 초기 시드 값 (내부 상수가 아닌)에 크게 의존하는 모든 PRNG는 나에게 깨진 것처럼 보입니다.
CVn

22
통계의 첫 번째 규칙 : 데이터를 플로팅합니다 . 분석은 정확하지만 히스토그램을 플로팅하면 훨씬 빠릅니다. ;-) (그리고 그것은 R의 두 줄입니다)
Konrad Rudolph

37
필수 인용문 :“무작위 숫자를 생성하는 산술적 방법을 고려하는 사람은 물론 죄의 상태에 있습니다.” -John von Neumann (1951)“100여 곳에서 위의 인용문을 보지 못한 사람은 아마 그리 오래되지 않았습니다.” -DV Pryor (1993)“임의의 숫자 생성기는 임의로 선택해서는 안됩니다.” -Donald Knuth (1986)
Happy Green Kid Naps

133

당신이 설명하는 것은 선형 합동 발생기 라고 불리는 임의의 생성기 입니다. 발전기는 다음과 같이 작동합니다.

  • 시드 값과 승수로 시작하십시오.
  • 난수를 생성하려면
    • 시드에 승수를 곱하십시오.
    • 시드를이 값과 동일하게 설정하십시오.
    • 이 값을 돌려줍니다.

이 생성기는 많은 훌륭한 속성을 가지고 있지만 좋은 임의 소스로 심각한 문제가 있습니다. 위에 링크 된 Wikipedia 기사는 몇 가지 장단점을 설명합니다. 간단히 말해서, 임의의 좋은 값이 필요한 경우에는이 방법이 적합하지 않을 수 있습니다.

도움이 되었기를 바랍니다!


@ louism- 그것은 실제로 "무작위"가 아닙니다. 결과는 결정적입니다. 나는 대답을 쓸 때 그것에 대해 생각하지 않았다. 아마도 누군가가 그 세부 사항을 분명히 할 수 있습니까?
templatetypedef

2
부동 소수점 산술 오류는 구현 설계되었습니다. 내가 아는 한, 그들은 특정 플랫폼에 대해 일관성이 있지만 다른 휴대 전화와 PC 아키텍처 사이에서 다를 수 있습니다. 행에 일련의 부동 소수점 계산을 수행 할 때 추가 '가드 비트'가 추가되는 경우도 있지만 이러한 가드 비트의 존재 여부에 따라 계산 결과가 미묘하게 달라질 수 있습니다. (가드 비트는 예를 들어 64 비트를 80 비트로 확장하여 80 비트로 확장)
Patashu

2
또한 LCRNG의 이론은 모두 정수로 작업한다고 가정합니다! 그것을 부동 소수점 숫자를 던지는 것 없는 결과의 동일한 품질을 얻을 수 있습니다.
duskwuff -inactive- 2012

1
@ duskwuff, 당신이 맞아요. 그러나 부동 소수점 하드웨어가 정상 규칙을 따르는 경우, 이는 가수 크기를 모듈로하는 것과 동일하며 이론이 적용됩니다. 하고있는 일에 특별한주의가 필요합니다.
vonbrand

113

내부 숫자가 너무 적기 때문에 난수 함수는 좋지 않습니다. 주어진 단계에서 함수가 출력하는 숫자는 이전 숫자에 전적으로 의존합니다. 예를 들어, 그것이 magicNumber2 라고 가정하면 (예를 들어) 시퀀스는 다음과 같습니다.

0.10 -> 0.20

비슷한 시퀀스로 강력하게 미러링됩니다.

0.09 -> 0.18
0.11 -> 0.22

많은 경우, 이것은 게임에서 눈에 띄는 상관 관계를 생성합니다. 예를 들어 객체에 대한 X 및 Y 좌표를 생성하기 위해 함수를 연속적으로 호출하면 객체가 명확한 대각선 패턴을 형성합니다.

난수 생성기가 응용 프로그램 속도를 늦추고 있다고 믿을만한 충분한 이유가 없다면 (아마도 그럴 가능성은 낮음), 직접 시도해보아야 할 이유는 없습니다.


36
실용 답변 +1 ...이를 사용하여 쏴 버려서 서사시 여러 헤드 샷을 위해 대각선을 따라 적을 스폰합니까? : D
wim

@ wim : 그러한 패턴을 원한다면 PRNG가 필요하지 않습니다.
거짓말 라이언

109

이것의 실제 문제는 출력 히스토그램이 초기 시드에 크게 좌우된다는 것입니다. 많은 시간이 거의 균일 한 출력으로 끝나지만 많은 시간이 분명히 불균일 한 출력을 갖게됩니다.

PHP의 rand()기능이 얼마나 나쁜지에 대한이 기사에서 영감을 얻어 QuickRandomand를 사용하여 임의의 매트릭스 이미지를 만들었습니다 System.Random. 이 실행은 씨앗이 때때로 System.Random꽤 균일 한 경우에 나쁜 영향을 줄 수있는 방법 (이 경우 낮은 숫자를 선호)을 보여줍니다 .

QuickRandom

System.Random

심지어 더 나쁘다

우리가 초기화 경우 QuickRandomnew 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
좋은 코드입니다. 예, 멋지네요. 예전에도 그렇게했는데 정량적으로 측정하기가 어렵지만 시퀀스를 보는 또 다른 좋은 방법입니다. 너비 * 높이보다 긴 시퀀스를 보려면 원하는 픽셀 당 하나의 픽셀로 다음 이미지를 정렬 할 수 있습니다. 나는 QuickRandom 그림이 해초 카펫처럼 질감되어 있기 때문에 훨씬 미적으로 즐겁다 고 생각합니다.
Cris Stringfellow

미적으로 유쾌한 부분은 magicNumber곱셈이와 비슷한 숫자를 생성하여 prevNum임의성이 결여됨을 나타내므로 각 행을 따라 가다가 다시 시작으로 돌아갈 때 순서가 증가하는 방식 입니다. 씨앗 new QuickRandom(0.01, 1.03)을 사용하면 i.imgur.com/Q1Yunbe.png를 얻을 수 있습니다 !
Callum Rogers

예, 훌륭한 분석입니다. 래핑이 발생하기 전에 mod 1에 상수를 명확하게 곱하기 때문에 설명이 증가합니다. 10 억을 곱한 다음 mod를 256 색 팔레트로 줄여서 소수점 이하 자릿수를 줄이면 피할 수 있습니다.
Cris Stringfellow

출력 이미지를 생성하기 위해 무엇을 사용했는지 말해 주실 수 있습니까? MATLAB?
uday

@uDaY : 코드 C # 및을 살펴보십시오 System.Drawing.Bitmap.
Callum Rogers

37

난수 생성기의 한 가지 문제는 '숨겨진 상태'가 없다는 것입니다. 마지막 호출에서 어떤 난수를 반환했는지 알면 시간이 끝날 때까지 보낼 수있는 모든 난수는 하나만 알고 있습니다. 가능한 다음 결과 등.

고려해야 할 또 다른 사항은 난수 생성기의 '기간'입니다. 분명히 유한 상태 크기에서 double의 가수 부분과 동일하게 반복하기 전에 최대 2 ^ 52 값만 반환 할 수 있습니다. 그러나 그것은 가장 좋은 경우입니다-1, 2, 3, 4주기의 루프가 없음을 증명할 수 있습니까? 있다면, RNG는 그러한 경우에 끔찍하고 퇴보적인 행동을 할 것입니다.

또한 난수 생성에 모든 시작점에 대해 균일 한 분포가 있습니까? 그렇지 않은 경우 RNG가 시작 시드에 따라 다른 방식으로 바이어스됩니다.

이 모든 질문에 대답 할 수 있다면 굉장합니다. 당신이 할 수 없다면, 당신은 왜 대부분의 사람들이 바퀴를 다시 발명하지 않고 입증 된 난수 생성기를 사용하는지 알고 있습니다.)

(어쨌든 좋은 속담은 다음과 같습니다. 가장 빠른 코드는 실행되지 않는 코드입니다. 세계에서 가장 빠른 random ()을 만들 수는 있지만 매우 무작위가 아닌 경우 좋지 않습니다)


8
모든 시드에 대해이 생성기에는 최소한 하나의 간단한 루프가 0 -> 0있습니다. 씨앗에 따라 다른 많은 것들이있을 수 있습니다. (3.0의 종자와 인스턴스를 들어 0.5 -> 0.5, 0.25 -> 0.75 -> 0.25, 0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2, 등)
-inactive- duskwuff

36

PRNG를 개발할 때 항상 한 가지 일반적인 테스트는 다음과 같습니다.

  1. 출력을 char 값으로 변환
  2. 문자 값을 파일에 쓰기
  3. 압축 파일

이를 통해 약 1 ~ 20 메가 바이트의 시퀀스에 대해 "충분한"PRNG 아이디어를 신속하게 반복 할 수있었습니다. 또한 절반 단어의 상태를 가진 "충분히 충분한"PRNG는 눈의 사이클 포인트를 보는 눈 능력을 빠르게 초과 할 수 있기 때문에 눈으로 검사하는 것보다 더 나은 하향식 사진을 제공했습니다.

내가 정말 까다 롭다면 좋은 알고리즘을 사용하고 DIEHARD / NIST 테스트를 실행하여 더 많은 통찰력을 얻은 다음 다시 돌아가서 더 조정할 수 있습니다.

빈도 분석과 달리 압축 테스트의 장점은 사소하게 좋은 분포를 구성하는 것이 쉽다는 것입니다. 값이 0-255 인 모든 문자를 포함하는 256 길이 블록을 출력하고이 작업을 10 만 번 수행하면됩니다. 그러나이 시퀀스의 길이는 256입니다.

작은 마진으로도 왜곡 된 분포는 압축 알고리즘에 의해 선택되어야합니다. 특히 작업에 충분한 시퀀스 (예 : 1MB)를 제공하는 경우 압축 알고리즘을 사용해야합니다. 일부 문자 또는 bigram 또는 n-gram이 더 자주 발생하는 경우 압축 알고리즘은이 분포 왜곡을 더 짧은 코드 단어로 자주 발생하는 코드로 인코딩하여 압축 델타를 얻을 수 있습니다.

대부분의 압축 알고리즘은 빠르며 구현이 필요하지 않기 때문에 (OS는 그냥 누워 있기 때문에) 압축 테스트는 개발중인 PRNG에 대한 합격 / 불합격을 빠르게 평가하는 데 매우 유용합니다.

실험에 행운을 빕니다!

오, 나는 다음과 같은 작은 코드를 사용하여 위의 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 Megs의 16 %만이 간단한 구조로 매우 인상적입니다. 그러나 나는 여전히 그것이 실패라고 생각합니다.


2
이미징 여부에 관계없이 몇 년 전 랜덤 생성기를 테스트 할 때 지퍼와 동일한 아이디어가 있습니다.
Aristos

1
@Alexandre C.와 Aristos와 aidan에게 감사드립니다. 당신을 믿어요
Cris Stringfellow 5

33

가장 빠른 랜덤 생성기는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

XD, 농담, 여기에 언급 된 모든 것 외에도, 난수 시퀀스 테스트는 "어려운 작업"[1]이라는 인용에 기여하고 싶습니다. 의사 난수의 특정 속성을 확인하는 여러 테스트가 있습니다. 여기에 많은 것들이 있습니다 : http://www.random.org/analysis/#2005

랜덤 발생기 "품질"을 평가하는 간단한 방법 중 하나는 기존 Chi Square 테스트입니다.

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]

χ² 테스트의 아이디어는 생성 된 숫자가 합리적으로 퍼져 있는지 여부를 확인하는 것입니다. r 보다 작은 N 개의 양수 를 생성하면 각 값의 N / r 개의 숫자 를 얻을 것으로 예상됩니다 . 그러나 이것은 문제의 본질입니다. 모든 값의 발생 빈도는 정확히 동일해서는 안됩니다. 그것은 무작위가 아닙니다!

각 값의 발생 빈도의 제곱의 합을 예상 빈도로 스케일링 한 다음 시퀀스 크기를 뺍니다. 이 숫자 "χ² 통계"는 수학적으로 다음과 같이 표현 될 수 있습니다.

치 제곱 공식

χ² 통계량이 r에 가까우 면 숫자는 임의입니다. 너무 멀면 그렇지 않습니다. "close"와 "far away"의 개념을보다 정확하게 정의 할 수 있습니다. 통계가 랜덤 시퀀스의 속성과 어떻게 관련되어 있는지 정확히 알려주는 테이블이 존재합니다. 우리가 수행하는 간단한 테스트의 경우 통계량은 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
놀라운 wobsite 인 xkcd에 대해 +1 (아, 그리고 큰 대답) : P
tckmn

1
고마워, 그리고 네 xkcd 랙! XD
higuaro 2012 년

이론은 훌륭하지만 실행이 좋지 않습니다. 코드는 정수 오버플로에 취약합니다. Java에서는 모두 int[]0으로 초기화 되므로이 부분이 필요하지 않습니다. 두 배로 작업 할 때 플로팅 캐스팅은 의미가 없습니다. 마지막으로 메소드 이름 random1과 random2를 호출하는 것은 매우 재미있다.
bestsss

@bestsss 관찰 해 주셔서 감사합니다! 나는 (C 코드에서 직접 번역을 제작하고 =에 많은 관심을 지불하지 않았다 나는 약간 수정 한과 답을 업데이트 나 추가 제안 감사하겠습니다..
higuaro

14

내가 함께 넣어 알고리즘의 빠른 모형을 결과를 평가하기 위해 자바 스크립트. 0-99에서 100,000 개의 임의의 정수를 생성하고 각 정수의 인스턴스를 추적합니다.

가장 먼저 눈에 띄는 것은 높은 숫자보다 낮은 숫자를 얻을 가능성이 높다는 것입니다. seed1높고 seed2낮을 때 가장 많이 볼 수 있습니다 . 몇 가지 경우에, 나는 단지 3 개의 숫자를 얻었다.

기껏해야 알고리즘에는 약간의 정제가 필요합니다.


8

은 IF Math.Random()함수는 하루의 시간을 얻을 수있는 운영 체제를 호출, 당신은 당신의 함수에 비교할 수 없습니다. 귀하의 함수는 PRNG이며, 그 함수는 실제 난수를 위해 노력하고 있습니다. 사과와 오렌지.

PRNG는 빠를 수 있지만 반복하기 전에 오랜 기간 동안 달성 할 수있는 상태 정보가 충분하지 않습니다 (그리고 그 상태 정보로 가능한 기간을 달성 할만큼 논리가 정교하지 않습니다).

주기는 PRNG가 자체 반복되기 전의 시퀀스 길이입니다. 이것은 PRNG 기계가 과거 상태와 동일한 상태로 상태를 전환하자마자 발생합니다. 거기서부터 그 상태에서 시작된 전환을 반복합니다. PRNG의 또 다른 문제점은 적은 수의 고유 한 서열 일 수 있고 반복되는 특정 서열에 대한 수렴을 저하시킬 수있다. 바람직하지 않은 패턴이있을 수도 있습니다. 예를 들어, 숫자가 10 진수로 인쇄 될 때 PRNG가 상당히 임의적으로 보이지만 이진수로 값을 검사하면 비트 4가 각 호출에서 단순히 0과 1 사이에서 전환되는 것으로 나타납니다. 죄송합니다!

Mersenne Twister 및 기타 알고리즘을 살펴보십시오. 주기 길이와 CPU주기간에 균형을 맞추는 방법이 있습니다. Mersenne Twister에서 사용되는 기본 접근 방법 중 하나는 상태 벡터를 순환하는 것입니다. 다시 말해서, 숫자가 생성 될 때, 그것은 전체 상태에 기초하지 않고, 비트 배열에 따라 상태 어레이로부터의 몇 단어에만 기초한다. 그러나 각 단계에서 알고리즘은 배열에서 이동하여 내용을 한 번에 조금씩 스크램블합니다.


5
첫 단락을 제외하고는 대부분 동의합니다. 내장 임의 호출 (및 Unix 계열 시스템의 / dev / random)도 PRNG입니다. 시드가 예측하기 어려운 것이더라도 알고리즘 적으로 임의의 숫자를 생성하는 것은 PRNG입니다. 방사성 붕괴, 대기 잡음 등을 사용하는 몇 개의 "진정한"난수 발생기가 있지만, 이들은 종종 비교적 적은 비트 / 초를 생성합니다.
매트 크라우스

Linux 박스에서, /dev/randomPRNG가 아닌 장치 드라이버에서 얻은 실제 임의 소스입니다. 사용 가능한 비트가 충분하지 않으면 차단됩니다. 자매 장치 /dev/urandom도 차단하지 않지만 사용 가능한 경우 임의 비트로 업데이트되므로 여전히 정확하게 PRNG가 아닙니다.
Kaz

Math.Random () 함수가 운영 체제를 호출하여 시간을 얻는다면 이는 사실이 아닙니다. (내가 아는 자바 풍미 / 버전 중 하나)
bestsss

@bestsss 이것은 원래의 질문에서 온 것 입니다. Math.random이 System.nanoTime ()을 사용한 어딘가를 읽은 것을 기억 합니다. 귀하의 지식은 거기 또는 귀하의 답변에 추가 할 가치가 있습니다. 조건부로 if 와 함께 사용했습니다 . :)
Kaz

Kaz, nanoTime()+ counter / hash는 java.util.Randomoracle / OpenJDK 의 기본 시드에 사용됩니다 . 그것은 씨앗만을위한 것이고 표준 LCG입니다. 실제로 OP 제너레이터는 시드에 대해 2 개의 난수를 취하는데 이는 괜찮습니다 java.util.Random. System.currentTimeMillis()JDK1.4의 기본 시드였습니다
bestsss

7

많은 의사 난수 생성기가 있습니다. 예를 들어 Knuth의 ranarray , Mersenne twister 또는 LFSR 발전기를 찾으십시오. Knuth의 기념비적 인 "Seminumerical algorithms"는이 영역을 분석하고 일부 선형 합동 발생기 (구현하기 쉽고 빠름)를 제안합니다.

하지만 난 그냥 당신이에 충실 제안 java.util.Random 또는 Math.random, 그들은 빠르고 적어도 확인에서 가끔 사용 (즉, 게임 등). 배포판에 대한 편집증 (일부 Monte Carlo 프로그램 또는 유전자 알고리즘)에 대한 편집증이라면 구현을 확인하고 (어딘가에서 구할 수 있음) 운영 체제 또는 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의 원본보다 두 배 의 성능을 얻었습니다. 여전히.보다 느리지 QuickRandom만 훨씬 일관된 결과를 제공합니다. 정확히 말하면, 동일한 seed값과 단일 스레드 응용 프로그램의 경우 원래 클래스 와 동일한 의사 난수를 제공 합니다Random .


이 코드는 GNU GPL v2에 따라 라이센스가 부여 된 java.util.RandomOpenJDK 7u 의 최신 버전 기반으로합니다 .


10 개월 후 수정 :

방금 동기화되지 않은 Random인스턴스 를 얻기 위해 위의 코드를 사용할 필요가 없다는 것을 알게되었습니다 . JDK에도 하나 있습니다!

Java 7의 ThreadLocalRandom수업을보십시오. 그 안의 코드는 위의 코드와 거의 동일합니다. 이 클래스는 Random임의의 숫자를 빠르게 생성하는 데 적합한 로컬 스레드 분리 버전입니다. 내가 생각할 수있는 유일한 단점은 seed수동으로 설정할 수 없다는 것입니다 .

사용법 예 :

Random random = ThreadLocalRandom.current();

2
@Edit Hmm, 나는 너무 게으르지 않은 언젠가 QR, Math.random 및 ThreadLocalRandom을 비교할 수 있습니다. :)흥미 롭습니다. 감사합니다!
tckmn

1. 가장 높은 16 비트가 사용 된 비트에 영향을 미치지 않기 때문에 마스크를 삭제하여 더 빠른 속도를 얻을 수 있습니다. 2. 해당 비트를 사용하고 하나의 뺄셈을 저장하고 더 나은 생성기를 얻을 수 있습니다 (더 큰 상태; 제품의 가장 중요한 비트가 가장 잘 분산되어 있지만 일부 평가가 필요할 수 있습니다). 3. Sun 사람들은 Knuth의 고풍스러운 RNG를 구현하고 동기화를 추가했습니다. :(
maaartinus

3

'랜덤'은 숫자를 얻는 것 이상의 의미를 지닙니다. 당신이 가진 것은 의사 난수입니다

의사 랜덤이 목적에 충분하면 확실히 빠릅니다 (XOR + 비트 시프트는 기존보다 빠릅니다)

롤프

편집하다:

이 답변에 너무 성급한 후에 코드가 더 빠른 실제 이유에 대답하겠습니다.

Math.Random ()에 대한 JavaDoc에서

이 방법은 둘 이상의 스레드가 올바르게 사용할 수 있도록 올바르게 동기화됩니다. 그러나 많은 스레드가 의사 난수를 빠른 속도로 생성해야하는 경우 각 스레드가 자체 의사 난수 생성기를 갖도록 경합을 줄일 수 있습니다.

코드가 더 빠른 이유 일 수 있습니다.


3
하드웨어 노이즈 생성기 나 OS의 I / O 항목에 직접 연결되지 않은 것은 의사 랜덤입니다. 알고리즘만으로는 진정한 임의성을 생성 할 수 없습니다. 어딘가에서 소음이 필요합니다. (일부 OS의 RNG는 마우스를 어떻게 움직이는 지, 물건을 타이핑하는 등의 물건을 측정하여 입력을 얻습니다. 마이크로 초에서 나노초 단위로 측정하여 예측할 수없는 경우가
많습니다

@OliCharlesworth : 사실, 내가 아는 한 대기 노이즈를 사용하여 유일한 임의의 값을 찾을 수 있습니다.
Jeroen Vannevel

@me ... 성급하게 대답하는 바보. Math.random은 의사 난수이며 동기화 됩니다.
rolfl

@rolfl : 동기화가 왜 Math.random()느린 지 잘 설명 할 수 있습니다. Random매번 동기화하거나 새로운 것을 만들어야하며 , 성능 측면에서도 그리 매력적이지 않습니다. 내가 성능에 관심이 있다면, 나는 내 자신을 new Random만들고 그것을 사용합니다. : P
cHao

@JeroenVannevel 방사성 붕괴도 무작위입니다.
RxS

3

java.util.Random은 Knuth가 설명하는 기본 LCG와 크게 다르지 않습니다. 그러나 두 가지 주요 장점 / 차이점이 있습니다.

  • 스레드 안전-각 업데이트는 간단한 쓰기보다 비싸고 분기가 필요한 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) 이중 곱셈 / 모듈로보다 성능이 향상됩니다.

마지막 참고 사항 : Math.random간단한 테스트 이외의 용도로 사용해서는 안되며, 경합이 발생하기 쉬우 며 동시에 스레드를 호출하는 경우에도 성능이 저하됩니다. 알려진 역사적 특징 중 하나는 자바에서 CAS를 도입 한 것입니다. 악명 높은 벤치 마크를 능가하기 위해 (먼저 IBM에서 내장 함수를 통해 Sun에서 "CAS from Java"를 만들었습니다)


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
이것은 질문에 대한 답변을 제공하지 않습니다. 저자에게 비평을하거나 설명을 요청하려면 게시물 아래에 의견을 남기십시오.
John Willemse

원래 알고리즘이 충분하지 않다는 것이 이미 확립되었다고 생각합니까? 아마도 좋은 것의 예는 그것을 향상시키는 방법에 영감을 줄 수 있습니까?
Terje

예, 아마도 질문에 전혀 대답하지 못하며 알고리즘을 실제로 지원하는 데이터가 없습니다. 일반적으로 난수 알고리즘 및 밀접하게 관련된 암호화 알고리즘은 프로그래밍 언어로 구현 한 전문가의 알고리즘보다 결코 우수하지 않습니다. 따라서 귀하의 주장을 뒷받침하고 질문의 알고리즘보다 왜 그것이 더 나은지에 대해 자세히 설명 할 수 있다면 최소한 질문에 대답해야합니다.
John Willemse

글쎄 ... 프로그래밍 언어로 구현 한 전문가들은 "완벽한"배포를 목표로하지만 게임에서는 그럴 필요가 없습니다. 속도와 "충분히 좋은"배포를 원합니다. 이 코드는 이것을 제공합니다. 여기에 부적절하면 답변을 삭제하고 문제 없습니다.
Terje

멀티 스레딩과 관련하여 로컬 변수의 사용은 no-op입니다. 없이는 volatile컴파일러가 자유롭게 로컬 변수를 제거 (또는 도입) 할 수 있습니다.
maaartinus
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.