นี่เป็นอัลกอริทึมแบบสุ่ม "ดีพอ" หรือไม่ ทำไมจึงไม่ใช้ถ้ามันเร็วกว่า


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 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
"ดูเหมือนจะสุ่มสวย"; คุณควรสร้าง histogram และทำงานอัตบางอย่างเกี่ยวกับลำดับของคุณ ...
โอลิเวอร์ Charlesworth

63
เขาหมายถึง "ดูเหมือนสุ่มสวย ๆ " ไม่ใช่การวัดแบบสุ่มและคุณควรได้รับสถิติจริง
แมตต์เอช

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

68
@ BlueRaja-DannyPflughoeft PRNG ใด ๆ ที่คุณภาพของผลผลิตขึ้นอยู่กับมูลค่าเมล็ดเริ่มต้น (เมื่อเทียบกับค่าคงที่ภายใน) ที่ดูเหมือนว่าจะแตกสำหรับฉัน
CVn

22
กฎข้อแรกของสถิติ: พล็อตข้อมูล การวิเคราะห์ของคุณเป็นแบบเฉพาะจุด แต่การวางแผนฮิสโตแกรมแสดงให้เห็นว่าเร็วกว่านี้มาก ;-) (และมันเป็นสองบรรทัดใน R. )
Konrad Rudolph

37
คำพูดที่บังคับ:“ ทุกคนที่พิจารณาวิธีการทางเลขคณิตของการสร้างตัวเลขแบบสุ่มแน่นอนว่าอยู่ในสภาพของบาป” - John von Neumann (1951)“ ทุกคนที่ไม่ได้เห็นคำพูดข้างต้นในสถานที่อย่างน้อย 100 แห่งอาจไม่เก่ามาก” - DV Pryor (1993)“ ไม่ควรเลือกเครื่องกำเนิดเลขสุ่มโดยการสุ่ม” - Donald Knuth (1986)
Naps แฮปปี้กรี

133

สิ่งที่คุณจะอธิบายเป็นชนิดของเครื่องกำเนิดไฟฟ้าแบบสุ่มที่เรียกว่าตัวสร้างความสอดคล้องแบบเชิงเส้น เครื่องกำเนิดไฟฟ้าทำงานดังนี้:

  • เริ่มต้นด้วยค่าเมล็ดและทวีคูณ
  • วิธีสร้างหมายเลขสุ่ม:
    • ทวีคูณเมล็ดพืชด้วยตัวคูณ
    • ตั้งค่าเมล็ดเท่ากับค่านี้
    • ส่งคืนค่านี้

เครื่องกำเนิดไฟฟ้านี้มีคุณสมบัติที่ดีมากมาย แต่มีปัญหาสำคัญในฐานะแหล่งสุ่มที่ดี บทความ Wikipedia ที่ลิงก์ด้านบนอธิบายถึงจุดแข็งและจุดอ่อนบางอย่าง กล่าวโดยย่อหากคุณต้องการค่าสุ่มที่ดีนี่อาจไม่ใช่วิธีที่ดีมาก

หวังว่านี่จะช่วยได้!


@ louism- ไม่ใช่ "สุ่ม" จริงๆต่อ se ผลลัพธ์จะถูกกำหนดไว้ ที่กล่าวว่าฉันไม่ได้คิดว่าเมื่อเขียนคำตอบของฉัน; บางทีบางคนสามารถอธิบายรายละเอียดนั้นได้?
templatetypedef

2
ข้อผิดพลาดทางคณิตศาสตร์เกี่ยวกับเลขทศนิยมถูกออกแบบมาให้ใช้งาน เท่าที่ฉันรู้พวกเขามีความสอดคล้องสำหรับแพลตฟอร์มบางอย่าง แต่อาจแตกต่างกันเช่นระหว่างโทรศัพท์มือถือที่แตกต่างกันและระหว่างสถาปัตยกรรมพีซี แม้ว่าจะมี 'บิตยาม' พิเศษบางครั้งถูกเพิ่มเมื่อทำการคำนวณแบบจำนวนจุดลอยตัวในแถวและการมีหรือไม่มีบิตยามเหล่านี้สามารถทำให้การคำนวณแตกต่างกันอย่างละเอียดในผลลัพธ์ (บิตยามเป็นเช่นการขยายตัวของ 64 บิตคู่ถึง 80 บิต)
Patashu

2
นอกจากนี้โปรดจำไว้ว่าทฤษฎีที่อยู่เบื้องหลัง LCRNG ทุกคนคิดว่าคุณกำลังทำงานกับจำนวนเต็ม! การโยนตัวเลขทศนิยมที่จะไม่ให้ผลลัพธ์คุณภาพเดียวกัน
duskwuff -inactive-

1
@duskwuff คุณพูดถูก แต่ถ้าฮาร์ดแวร์จุดลอยตัวทำตามกฎที่มีสติการทำเช่นนี้เหมือนกับการทำแบบโมดูโลขนาดของมันติสซาและทฤษฎีใช้ เพียงแค่ต้องระมัดระวังเป็นพิเศษในสิ่งที่คุณกำลังทำ
vonbrand

113

ฟังก์ชั่นตัวเลขสุ่มของคุณไม่ดีเนื่องจากมีสถานะภายในน้อยเกินไป - เอาท์พุทตัวเลขโดยฟังก์ชั่นในขั้นตอนที่กำหนดจะขึ้นอยู่กับหมายเลขก่อนหน้าทั้งหมด ตัวอย่างเช่นถ้าเราสมมติว่าmagicNumberเป็น 2 (ตามตัวอย่าง) ดังนั้นลำดับ:

0.10 -> 0.20

สะท้อนอย่างมากจากลำดับที่คล้ายกัน:

0.09 -> 0.18
0.11 -> 0.22

ในหลายกรณีสิ่งนี้จะสร้างความสัมพันธ์ที่เห็นได้ชัดเจนในเกมของคุณ - ตัวอย่างเช่นหากคุณทำการเรียกฟังก์ชันของคุณอย่างต่อเนื่องเพื่อสร้างพิกัด X และ Y สำหรับวัตถุวัตถุจะมีรูปแบบเส้นทแยงมุมชัดเจน

หากคุณไม่มีเหตุผลที่ดีที่จะเชื่อว่าตัวสร้างตัวเลขสุ่มทำให้แอปพลิเคชันของคุณช้าลง (และนี่ก็ไม่น่าเป็นไปได้มาก) ไม่มีเหตุผลที่ดีที่จะลองเขียนเอง


36
+1 สำหรับคำตอบที่ใช้งานได้จริง ... ใช้สิ่งนี้ในการยิงปืนและวางไข่ศัตรูตามแนวทแยงมุมเพื่อให้ได้เฮดช็อตหลายอัน? : D
Wim

@wim: คุณไม่ต้องการ PRNG หากคุณต้องการรูปแบบดังกล่าว
โกหก,

109

ปัญหาที่แท้จริงของเรื่องนี้คือฮิสโตแกรมของเอาต์พุตขึ้นอยู่กับเมล็ดเริ่มต้นจนถึงมาก - มากเวลาที่มันจะจบลงด้วยเอาต์พุตใกล้เคียงกัน แต่เวลาส่วนใหญ่จะมีเอาต์พุตไม่สม่ำเสมอ

แรงบันดาลใจจากบทความนี้เกี่ยวกับวิธี PHP ที่ไม่ดีของrand()ฟังก์ชั่นคือผมทำบางภาพเมทริกซ์แบบสุ่มโดยใช้และQuickRandom System.Randomการวิ่งครั้งนี้แสดงให้เห็นว่าบางครั้งเมล็ดสามารถมีผลเสีย (ในกรณีนี้ชอบตัวเลขที่ต่ำกว่า) ซึ่งเป็น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
รหัสที่ดี ใช่แล้วมันเท่ห์ ฉันเคยทำเช่นนั้นบางครั้งมันก็ยากที่จะวัดปริมาณออกมาได้ แต่มันก็เป็นอีกวิธีที่ดีในการดูลำดับ และถ้าคุณต้องการดูลำดับที่ยาวกว่าความกว้าง * สูงคุณสามารถ xor รูปถัดไปด้วยพิกเซลต่อพิกเซลนี้ ฉันคิดว่ารูปภาพ QuickRandom เป็นที่ชื่นชอบทางสุนทรียะมากกว่านี้เพราะมันมีพื้นผิวเหมือนพรมสาหร่าย
Cris Stringfellow

ส่วนที่ชื่นชอบทางสุนทรียศาสตร์คือวิธีที่ลำดับเพิ่มขึ้นเมื่อคุณไปตามแต่ละแถว (จากนั้นกลับไปที่จุดเริ่มต้นอีกครั้ง) เมื่อการmagicNumberคูณสร้างหมายเลขที่คล้ายกับprevNumซึ่งแสดงการขาดแบบสุ่ม หากเราใช้เมล็ดพืชnew QuickRandom(0.01, 1.03)เราจะได้รับi.imgur.com/Q1Yunbe.png !
Callum Rogers

ใช่การวิเคราะห์ที่ดี เนื่องจากมันแค่คูณ mod 1 ด้วยค่าคงที่อย่างชัดเจนก่อนที่จะเกิดการพันจะมีการเพิ่มที่คุณอธิบาย ดูเหมือนว่าสิ่งนี้สามารถหลีกเลี่ยงได้ถ้าเราใช้ทศนิยมที่มีนัยสำคัญน้อยกว่าโดยพูดว่าคูณด้วย 1 พันล้านแล้วลด mod 256 จานสีลงไป
Cris Stringfellow

คุณสามารถบอกฉันว่าคุณใช้อะไรในการสร้างภาพที่ส่งออกเหล่านั้น? Matlab?
จันทร์ที่

@uDaY: ลองดูที่รหัส C # System.Drawing.Bitmapและ
Callum Rogers

37

ปัญหาหนึ่งของตัวสร้างหมายเลขสุ่มของคุณคือไม่มี 'สถานะที่ซ่อนอยู่' - ถ้าฉันรู้ว่าคุณสุ่มหมายเลขใดที่คุณส่งคืนเมื่อมีการโทรครั้งล่าสุดฉันรู้ว่าทุกหมายเลขสุ่มเดียวที่คุณจะส่งจนถึงสิ้นเวลาเนื่องจากมีเพียงหนึ่ง เป็นไปได้ผลต่อไปและอื่น ๆ และอื่น ๆ

สิ่งที่ต้องพิจารณาอีกอย่างคือ 'ระยะเวลา' ของตัวสร้างตัวเลขสุ่มของคุณ เห็นได้ชัดว่ามีขนาด จำกัด แน่นอนเท่ากับส่วนแมนทิสซาของสองเท่าก็จะสามารถกลับมาที่ค่ามากที่สุด 2 ^ 52 ก่อนที่จะวนลูป แต่นั่นเป็นกรณีที่ดีที่สุด - คุณสามารถพิสูจน์ได้ว่าไม่มีช่วงเวลา 1, 2, 3, 4 ... ? หากมี RNG ของคุณจะมีพฤติกรรมแย่มากในกรณีเหล่านั้น

นอกจากนี้การสร้างหมายเลขสุ่มของคุณจะมีการแจกแจงแบบสม่ำเสมอสำหรับจุดเริ่มต้นทั้งหมดหรือไม่ หากไม่เป็นเช่นนั้น RNG ของคุณจะลำเอียง - หรือแย่กว่านั้นจะลำเอียงในรูปแบบต่าง ๆ ขึ้นอยู่กับเมล็ดเริ่มต้น

หากคุณสามารถตอบคำถามเหล่านี้ได้ทั้งหมดน่ากลัว หากคุณทำไม่ได้คุณจะรู้ว่าทำไมคนส่วนใหญ่ไม่ประดิษฐ์ล้อเลื่อนอีกครั้งและใช้เครื่องสร้างตัวเลขสุ่มที่พิสูจน์แล้ว;)

(โดยวิธีการสุภาษิตที่ดีคือ: รหัสที่เร็วที่สุดคือรหัสที่ไม่ได้ทำงานคุณสามารถสร้างแบบสุ่มที่เร็วที่สุด () ในโลก แต่มันก็ไม่ดีถ้ามันไม่ได้สุ่มมาก)


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ฯลฯ )
duskwuff -inactive-

36

การทดสอบทั่วไปหนึ่งครั้งที่ฉันทำเมื่อพัฒนา PRNGs คือ:

  1. แปลงเอาต์พุตเป็นค่าถ่าน
  2. เขียนค่าตัวอักษรลงในไฟล์
  3. บีบอัดไฟล์

ให้ฉันทำซ้ำความคิดที่รวดเร็ว "PRNG" ที่ดีพอสำหรับลำดับประมาณ 1 ถึง 20 เมกะไบต์ นอกจากนี้ยังให้ภาพจากบนลงล่างที่ดีกว่าเพียงแค่ตรวจสอบด้วยตาเนื่องจาก PRNG ที่ "ดีพอ" ที่มีครึ่งคำพูดของรัฐอาจเกินความสามารถในการมองเห็นจุดรอบดวงตาของคุณอย่างรวดเร็ว

ถ้าฉันจู้จี้จุกจิกจริงๆฉันอาจใช้อัลกอริทึมที่ดีและเรียกใช้การทดสอบ DIEHARD / NIST กับพวกเขาเพื่อรับข้อมูลเชิงลึกมากขึ้นแล้วกลับไปปรับแต่งอีก

ข้อดีของการทดสอบการบีบอัดซึ่งตรงข้ามกับการวิเคราะห์ความถี่ก็คือมันง่ายที่จะสร้างการแจกแจงที่ดีเพียงส่งบล็อกยาว 256 ที่บรรจุค่าทั้งหมด 0 - 255 และทำ 100,000 ครั้ง แต่ลำดับนี้มีวงจรความยาว 256

ควรแจกอัลกอริธึมการบีบอัดโดยเฉพาะอย่างยิ่งถ้าคุณให้ลำดับ (1 เมกะไบต์) ที่เพียงพอสำหรับลำดับการทำงาน หากอักขระบางตัวหรือ bigrams หรือ n-grams เกิดขึ้นบ่อยครั้งอัลกอริธึมการบีบอัดสามารถเข้ารหัสการแจกแจงแบบนี้เอียงไปยังโค้ดที่สนับสนุนการเกิดขึ้นบ่อยครั้งด้วยคำที่สั้นกว่าและคุณจะได้รับการบีบอัดเดลต้า

เนื่องจากอัลกอริธึมการบีบอัดส่วนใหญ่นั้นเร็วและพวกมันไม่ต้องการการใช้งาน (เนื่องจากระบบปฏิบัติการมีเพียงแค่วางอยู่รอบ ๆ ) การทดสอบการบีบอัดจึงมีประโยชน์มากสำหรับการให้คะแนน Pass / Fail อย่างรวดเร็วสำหรับ PRNG ที่คุณอาจกำลังพัฒนา

ขอให้โชคดีกับการทดลองของคุณ!

โอ้ฉันได้ทำการทดสอบนี้ใน rng ที่คุณมีด้านบนโดยใช้ mod เล็ก ๆ ของรหัสของคุณ:

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 ของคุณจะทำได้ดีเพียง 16% จาก ~ 20 Megs นั้นค่อนข้างน่าประทับใจสำหรับการก่อสร้างที่เรียบง่าย แต่ฉันก็ยังถือว่ามันล้มเหลว


2
ฉันมีความคิดแบบเดียวกันกับปีที่แล้วเมื่อฉันทดสอบเครื่องกำเนิดไฟฟ้าแบบสุ่มของฉัน
Aristos

1
ขอบคุณ @Alexandre C. และ Aristos และ Aidan ฉันเชื่อคุณ.
Cris Stringfellow

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]

แนวคิดของการทดสอบχ²คือการตรวจสอบว่าหมายเลขที่สร้างขึ้นนั้นกระจายออกไปอย่างสมเหตุสมผลหรือไม่ หากเราสร้างตัวเลขบวกNน้อยกว่าrดังนั้นเราคาดหวังว่าจะได้ตัวเลขN / rของแต่ละค่า แต่ --- และนี่คือสาระสำคัญของเรื่อง --- ความถี่ของการกลับเป็นซ้ำของค่าทั้งหมดไม่ควรเหมือนกัน: นั่นจะไม่สุ่ม!

เราเพียงคำนวณหาผลรวมของกำลังสองของ frecuencies ที่เกิดขึ้นของแต่ละค่าโดยปรับตามความถี่ที่คาดหวังจากนั้นย่อขนาดของลำดับ หมายเลขนี้ "χ²สถิติ" อาจแสดงทางคณิตศาสตร์เป็น

สูตรไคสแควร์

หากสถิติχ²ใกล้เคียงกับ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, 1990, หน้า 516 ถึง 518


9
+1 สำหรับ xkcd ซึ่งเป็นที่น่าตื่นตาตื่นใจwobsite (โอ้และคำตอบที่ดี): P
tckmn

1
ขอบคุณและใช่ xkcd racks! XD
higuaro

ทฤษฎีดี แต่การดำเนินการไม่ดี: รหัสนั้นไวต่อการล้นจำนวนเต็ม ในจาวาทั้งหมดint[]จะเริ่มต้นเป็นศูนย์ดังนั้นไม่จำเป็นต้องมีส่วนนี้ การร่ายเพื่อลอยนั้นไม่มีจุดหมายเมื่อคุณทำงานด้วยการดับเบิล ครั้งสุดท้าย: การเรียกชื่อเมธอด random1 และ random2 นั้นค่อนข้างตลก
bestsss

@ bests ขอขอบคุณสำหรับข้อสังเกต! ฉันทำการแปลโดยตรงจากรหัส C และไม่ได้สนใจมันมาก = (. ฉันทำการแก้ไขและปรับปรุงคำตอบฉันขอขอบคุณข้อเสนอแนะเพิ่มเติมใด ๆ
higuaro

14

ฉันรวบรวมขั้นตอนวิธีแบบย่อของคุณใน JavaScript เพื่อประเมินผลลัพธ์ มันสร้าง 100,000 จำนวนเต็มสุ่มจาก 0 - 99 และติดตามตัวอย่างของแต่ละจำนวนเต็ม

สิ่งแรกที่ฉันสังเกตเห็นคือคุณมีแนวโน้มที่จะได้รับจำนวนต่ำกว่าจำนวนสูง คุณเห็นสิ่งนี้มากที่สุดเมื่อseed1สูงและseed2ต่ำ ในสองสามกรณีฉันได้รับเพียง 3 หมายเลข

ที่ดีที่สุดอัลกอริทึมของคุณต้องมีการปรับแต่ง


8

หากMath.Random()ฟังก์ชันนั้นเรียกใช้ระบบปฏิบัติการเพื่อให้ได้เวลาในแต่ละวันคุณจะไม่สามารถเปรียบเทียบกับฟังก์ชันของคุณได้ ฟังก์ชั่นของคุณเป็น PRNG ในขณะที่ฟังก์ชั่นนั้นกำลังพยายามหาตัวเลขสุ่มจริง แอปเปิ้ลและส้ม

PRNG ของคุณอาจเร็ว แต่ไม่มีข้อมูลสถานะเพียงพอที่จะได้รับเป็นเวลานานก่อนที่จะทำซ้ำ (และตรรกะของมันไม่ซับซ้อนพอที่จะบรรลุช่วงเวลาที่เป็นไปได้ด้วยข้อมูลสถานะมากนั้น)

Period คือความยาวของลำดับก่อนที่ PRNG ของคุณจะเริ่มทำซ้ำ สิ่งนี้จะเกิดขึ้นทันทีที่เครื่อง PRNG เปลี่ยนสถานะเป็นรัฐซึ่งเหมือนกับบางรัฐในอดีต จากตรงนั้นมันจะทำซ้ำการเปลี่ยนซึ่งเริ่มขึ้นในสถานะนั้น ปัญหาอีกประการหนึ่งของ PRNG อาจเป็นลำดับที่ไม่ซ้ำกันในจำนวนที่น้อย นอกจากนี้ยังอาจมีรูปแบบที่ไม่พึงประสงค์ ตัวอย่างเช่นสมมติว่า PRNG ดูค่อนข้างสุ่มเมื่อตัวเลขถูกพิมพ์เป็นทศนิยม แต่การตรวจสอบค่าในไบนารีแสดงว่าบิต 4 นั้นสลับระหว่าง 0 และ 1 ในการโทรแต่ละครั้ง อ๊ะ!

ลองดูที่ Mersenne Twister และอัลกอริธึมอื่น ๆ มีวิธีการสมดุลระหว่างระยะเวลาและรอบ CPU วิธีการพื้นฐานหนึ่ง (ใช้ใน Mersenne Twister) คือการวนรอบในเวกเตอร์สถานะ กล่าวคือเมื่อมีการสร้างตัวเลขมันไม่ได้ขึ้นอยู่กับสถานะทั้งหมดเพียงแค่ไม่กี่คำจากอาร์เรย์สถานะที่ต้องดำเนินการเล็กน้อย แต่ในแต่ละขั้นตอนอัลกอริธึมก็เคลื่อนที่ไปรอบ ๆ อาเรย์เพื่อแย่งเนื้อหาครั้งละเล็กน้อย


5
ฉันเห็นด้วยส่วนใหญ่ยกเว้นวรรคแรกของคุณ การโทรแบบสุ่มในตัว (และ / dev / random บนระบบที่เหมือน Unix) ก็เป็น PRNG เช่นกัน ฉันจะเรียกสิ่งที่สร้างตัวเลขสุ่มโดยอัลกอริทึม PRNG แม้ว่าเมล็ดนั้นเป็นสิ่งที่ยากที่จะคาดเดา มีตัวสร้างตัวเลขสุ่ม "จริง" อยู่สองสามตัวที่ใช้การสลายตัวของกัมมันตภาพรังสีเสียงบรรยากาศ ฯลฯ แต่สิ่งเหล่านี้มักจะสร้างบิตที่ค่อนข้างน้อย / วินาที
Matt Krause

บนกล่อง Linux /dev/randomเป็นแหล่งที่มาของการสุ่มของจริงที่ได้รับจากไดรเวอร์อุปกรณ์ไม่ใช่ PRNG มันบล็อกเมื่อมีบิตไม่เพียงพอ อุปกรณ์น้องสาว/dev/urandomยังไม่ได้ปิดกั้น แต่ก็ยังไม่ได้เป็น PRNG อย่างแน่นอนเนื่องจากมีการอัปเดตด้วยบิตสุ่มเมื่อพร้อมใช้งาน
Kaz

หากฟังก์ชัน Math.Random () เรียกใช้ระบบปฏิบัติการเพื่อให้ได้เวลาของวัน - สิ่งนี้ไม่จริง (ในรสชาติ / เวอร์ชันของ Java ที่ฉันรู้จัก)
bestsss

@bestsss นี้เป็นจากคำถามเดิม: ผมจำได้ว่าอ่านบางที่ Math.random ใช้ System.nanoTime () ความรู้ของคุณอาจมีมูลค่าเพิ่มหรือมีคำตอบของคุณ ฉันใช้มันตามเงื่อนไขที่มีถ้า :)
Kaz

Kaz ทั้งnanoTime()+ เคาน์เตอร์ / แฮชใช้สำหรับเมล็ดเริ่มต้นjava.util.Randomของ oracle / OpenJDK สำหรับเมล็ดเท่านั้นแล้วมันเป็น LCG มาตรฐาน ในผลกำเนิด OP จะใช้เวลา 2 หมายเลขสุ่มสำหรับเมล็ดพันธุ์ซึ่งเป็น ok - java.util.Randomดังนั้นความแตกต่างกว่าไม่มี System.currentTimeMillis()เป็นเมล็ดเริ่มต้นใน JDK1.4-
bestsss

7

มีหลายตัวสร้างตัวเลขสุ่มหลอกหลายออกมี ตัวอย่างเช่นการวิ่งของ Knuth , Twers Mersenneหรือมองหาเครื่องกำเนิด LFSR อนุสาวรีย์ "อัลกอริธึมเชิงตัวเลข" ของ Knuth วิเคราะห์พื้นที่และเสนอเครื่องกำเนิดไฟฟ้าเชิงเส้นเชิงเส้นบางส่วน (ง่ายต่อการใช้งานรวดเร็ว)

แต่ฉันขอแนะนำให้คุณใช้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ชั้นจะ


รหัสนี้จะขึ้นอยู่กับปัจจุบันjava.util.Randomใน OpenJDK 7Uซึ่งได้รับใบอนุญาตภายใต้GNU GPL v2


แก้ไข 10 เดือนต่อมา:

ฉันเพิ่งค้นพบว่าคุณไม่จำเป็นต้องใช้รหัสของฉันด้านบนเพื่อรับRandomอินสแตนซ์ที่ไม่ซิงโครไนซ์ มีอยู่ใน JDK ด้วย!

ดูThreadLocalRandomคลาสของ Java 7 รหัสที่อยู่ภายในนั้นเกือบจะเหมือนกับรหัสของฉันด้านบน คลาสเป็นเพียงRandomเวอร์ชันโลคัลเธรดที่แยกได้ซึ่งเหมาะสำหรับการสร้างตัวเลขสุ่มอย่างรวดเร็ว ข้อเสียเดียวที่ฉันนึกได้ก็คือคุณไม่สามารถตั้งค่าseedด้วยตนเองได้

ตัวอย่างการใช้งาน:

Random random = ThreadLocalRandom.current();

2
@ แก้ไขอืมฉันอาจเปรียบเทียบ QR, Math.random และ ThreadLocalRandom บางครั้งเมื่อฉันไม่ขี้เกียจเกินไป:)น่าสนใจขอบคุณ!
tckmn

1. คุณสามารถเพิ่มความเร็วได้โดยการวางมาสก์เนื่องจากบิตสูงสุด 16 บิตไม่มีผลต่อบิตที่ใช้ 2. คุณสามารถใช้บิตเหล่านั้นบันทึกการลบหนึ่งครั้งและรับตัวสร้างที่ดีขึ้น (สถานะใหญ่กว่าบิตที่สำคัญที่สุดของผลิตภัณฑ์นั้นมีการกระจายอย่างดีที่สุด แต่จะต้องมีการประเมินผลบางอย่าง) 3. The Sun guys ใช้งาน RNG โบราณโดย Knuth และเพิ่มการซิงโครไนซ์ :(
maaartinus

3

'สุ่ม' เป็นมากกว่าเพียงแค่การรับตัวเลข .... สิ่งที่คุณมีคือการสุ่มหลอก

หากหลอกแบบสุ่มดีพอสำหรับวัตถุประสงค์ของคุณแน่นอนว่ามันเร็วกว่านี้ (และ XOR + Bitshift จะเร็วกว่าสิ่งที่คุณมี)

Rolf

แก้ไข:

ตกลงหลังจากคำตอบนี้รีบร้อนเกินไปให้ฉันตอบเหตุผลที่แท้จริงว่าทำไมโค้ดของคุณถึงเร็วกว่า:

จาก JavaDoc สำหรับ Math.Random ()

วิธีนี้มีการซิงโครไนซ์อย่างถูกต้องเพื่ออนุญาตการใช้งานที่ถูกต้องโดยเธรดมากกว่าหนึ่งเธรด อย่างไรก็ตามหากเธรดจำนวนมากจำเป็นต้องสร้างหมายเลขเทียมเทียมในอัตราที่ดีมันอาจลดความขัดแย้งสำหรับแต่ละเธรดเพื่อให้มีตัวสร้างหมายเลขเทียมเทียมของตัวเอง

นี่เป็นสาเหตุที่รหัสของคุณเร็วขึ้น


3
อะไรก็ตามที่ไม่เกี่ยวข้องกับตัวกำเนิดสัญญาณรบกวนฮาร์ดแวร์หรือสายตรงเข้าไปในสิ่ง I / O ของระบบปฏิบัติการจะสุ่มหลอก อัลกอริธึมของแท้ไม่สามารถสร้างขึ้นได้โดยลำพัง คุณต้องการเสียงรบกวนจากที่ไหนสักแห่ง (RNGs บางระบบปฏิบัติการได้รับข้อมูลของพวกเขาโดยการวัดสิ่งที่ชอบวิธีการ / เมื่อคุณเลื่อนเมาส์ประเภทสิ่งอื่น ๆ ที่วัดโย microseconds เพื่อนาโนวินาทีที่สามารถคาดเดาไม่ได้สูง.)
เจ้าพระยา

@OliCharlesworth: แน่นอนเท่าที่ฉันรู้ค่าสุ่มจริงเท่านั้นที่พบโดยใช้เสียงบรรยากาศ
Jeroen Vannevel

@ ฉัน ... งี่เง่าที่จะตอบอย่างเร่งด่วน Math.random เป็น pseudorandom และก็จะทำข้อมูลให้ตรงกัน
rolfl

@rolfl: การซิงโครไนซ์เป็นอย่างดีสามารถอธิบายได้ว่าทำไมMath.random()ช้าลง มันอาจจะต้องทำการซิงโครไนซ์หรือสร้างใหม่Randomทุกครั้ง ถ้าฉันใส่ใจเกี่ยวกับประสิทธิภาพฉันจะสร้างของตัวเองnew Randomและใช้มัน : P
cHao

@JeroenVannevel การสลายตัวของสารกัมมันตรังสีก็สุ่มเช่นกัน
RxS

3

java.util.Random ไม่แตกต่างกันมาก LCG พื้นฐานที่อธิบายโดย Knuth อย่างไรก็ตามมันมีข้อดี / ข้อแตกต่างที่สำคัญ 2 ประการ:

  • เธรดที่ปลอดภัย - การอัปเดตแต่ละครั้งเป็น CAS ซึ่งมีราคาแพงกว่าการเขียนอย่างง่ายและต้องการสาขา มันอาจมีความแตกต่างอย่างมีนัยสำคัญ
  • สถานะภายในที่ไม่เปิดเผย - นี่เป็นสิ่งสำคัญมากสำหรับสิ่งที่ไม่สำคัญ คุณต้องการให้ตัวเลขสุ่มไม่สามารถคาดเดาได้

ด้านล่างนี้เป็นชุดคำสั่งหลักที่สร้างจำนวนเต็มแบบสุ่มใน 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 ผ่านทางอินทรินและจากนั้นซันทำ "CAS จาก 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

เกี่ยวกับมัลติเธรดการใช้งานตัวแปรโลคอลของคุณนั้นเป็นแบบไม่มีตัวเลือกหากไม่มีvolatileคอมไพเลอร์มีอิสระที่จะกำจัด (หรือแนะนำ) ตัวแปรท้องถิ่นที่ต้องการ
maaartinus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.