Is this a "good enough" random algorithm; why isn't it used if it's faster?

14,064

Solution 1

Your QuickRandom implementation hasn't really an uniform distribution. The frequencies are generally higher at the lower values while Math.random() has a more uniform distribution. Here's a SSCCE which shows that:

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

}

The average result looks like this:

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

If you repeat the test, you'll see that the QR distribution varies heavily, depending on the initial seeds, while the MR distribution is stable. Sometimes it reaches the desired uniform distribution, but more than often it doesn't. Here's one of the more extreme examples, it's even beyond the borders of the graph:

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  :                                                  

Solution 2

What you are describing is a type of random generator called a linear congruential generator. The generator works as follows:

  • Start with a seed value and multiplier.
  • To generate a random number:
    • Multiply the seed by the multiplier.
    • Set the seed equal to this value.
    • Return this value.

This generator has many nice properties, but has significant problems as a good random source. The Wikipedia article linked above describes some of the strengths and weaknesses. In short, if you need good random values, this is probably not a very good approach.

Hope this helps!

Solution 3

Your random number function is poor, as it has too little internal state -- the number output by the function at any given step is entirely dependent on the previous number. For instance, if we assume that magicNumber is 2 (by way of example), then the sequence:

0.10 -> 0.20

is strongly mirrored by similar sequences:

0.09 -> 0.18
0.11 -> 0.22

In many cases, this will generate noticeable correlations in your game -- for instance, if you make successive calls to your function to generate X and Y coordinates for objects, the objects will form clear diagonal patterns.

Unless you have good reason to believe that the random number generator is slowing your application down (and this is VERY unlikely), there is no good reason to try and write your own.

Solution 4

The real problem with this is that it's output histogram is dependent on the initial seed far to much - much of the time it will end up with a near uniform output but a lot of the time will have distinctly un-uniform output.

Inspired by this article about how bad php's rand() function is, I made some random matrix images using QuickRandom and System.Random. This run shows how sometimes the seed can have a bad effect (in this case favouring lower numbers) where as System.Random is pretty uniform.

QuickRandom

System.Random

Even Worse

If we initialise QuickRandom as new QuickRandom(0.01, 1.03) we get this image:

The Code

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

Solution 5

One problem with your random number generator is that there is no 'hidden state' - if I know what random number you returned on the last call, I know every single random number you will send until the end of time, since there is only one possible next result, and so on and so on.

Another thing to consider is the 'period' of your random number generator. Obviously with a finite state size, equal to the mantissa portion of a double, it will only be able to return at most 2^52 values before looping. But that's in the best case - can you prove that there are no loops of period 1, 2, 3, 4...? If there are, your RNG will have awful, degenerate behavior in those cases.

In addition, will your random number generation have a uniform distribution for all starting points? If it does not, then your RNG will be biased - or worse, biased in different ways depending on the starting seed.

If you can answer all of these questions, awesome. If you can't, then you know why most people do not re-invent the wheel and use a proven random number generator ;)

(By the way, a good adage is: The fastest code is code that does not run. You could make the fastest random() in the world, but it's no good if it is not very random)

Share:
14,064

Related videos on Youtube

tckmn
Author by

tckmn

https://tck.mn Elected moderator ♦ on Code Golf. Email [email protected] or find me in chat: The Nineteenth Byte (for Code Golf), The Sphinx's Lair (for Puzzling), :chat! (for Vi and Vim), and SE Chess (informal group in which I lose a lot).

Updated on August 23, 2022

Comments

  • tckmn
    tckmn almost 2 years

    I made a class called QuickRandom, and its job is to produce random numbers quickly. It's really simple: just take the old value, multiply by a double, and take the decimal part.

    Here is my QuickRandom class in its entirety:

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

    And here is the code I wrote to test it:

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

    It is a very simple algorithm that simply multiplies the previous double by a "magic number" double. I threw it together pretty quickly, so I could probably make it better, but strangely, it seems to be working fine.

    This is sample output of the commented-out lines in the main method:

    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. Pretty random. In fact, that would work for a random number generator in a game.

    Here is sample output of the non-commented out part:

    5456313909
    1427223941
    

    Wow! It performs almost 4 times faster than Math.random.

    I remember reading somewhere that Math.random used System.nanoTime() and tons of crazy modulus and division stuff. Is that really necessary? My algorithm performs a lot faster and it seems pretty random.

    I have two questions:

    • Is my algorithm "good enough" (for, say, a game, where really random numbers aren't too important)?
    • Why does Math.random do so much when it seems just simple multiplication and cutting out the decimal will suffice?
    • Oliver Charlesworth
      Oliver Charlesworth over 11 years
      "seems pretty random"; you should generate a histogram and run some autocorrelation on your sequence...
    • tckmn
      tckmn over 11 years
      @OliCharlesworth I... err... what? I have no idea what you just said :P
    • Matt H
      Matt H over 11 years
      He means "seems pretty random" isn't really an objective measure of randomness and you should get some actual statistics.
    • tckmn
      tckmn over 11 years
      @MattH The problem is I have no idea how to do that. Which is why I am asking here.
    • Oliver Charlesworth
      Oliver Charlesworth over 11 years
      @Doorknob: In layman's terms, you should investigate whether your numbers have a "flat" distribution between 0 and 1, and see whether there are any periodic/repetitive patterns over time.
    • tckmn
      tckmn over 11 years
      @OliCharlesworth See previous comment :P Also they don't need to be perfectly random, just "good enough."
    • Jeroen Vannevel
      Jeroen Vannevel over 11 years
      What does random mean to you? If every value in a range occurs an equal amount of times after a big enough amount of tests has been done, you can conclude it's random.
    • Matt H
      Matt H over 11 years
    • tckmn
      tckmn over 11 years
      @JeroenVannevel I don't need it to be really, really random. Just good enough to have a human player think it's random enough.
    • Oliver Charlesworth
      Oliver Charlesworth over 11 years
      Also, Math.random uses Random.nextDouble, which really isn't as complex as you make out. Take a look at its source code!
    • tckmn
      tckmn over 11 years
      @OliCharlesworth Hrm. I've been lied to my whole life :P
    • Oliver Charlesworth
      Oliver Charlesworth over 11 years
      @Doorknob: Ok, but that's not really a question we can answer here (how do you define "random enough"?). But you should just use Random (or Math.random).
    • tckmn
      tckmn over 11 years
      @OliCharlesworth 1. It's faster, that's why I think I should use it 2. I just want to know if there's any problems or anything. 3. Hey, this isn't off topic :P
    • Jeroen Vannevel
      Jeroen Vannevel over 11 years
      @Doorknob: Then approximate it to an equal amount. Execute your result a few million times, hold an array which represents each value you could have and increment its value in the array. Afterwards you can output some statistics (max occurrence, min occurence, range, etc) to determine whether or not it's random enough for you.
    • user2398029
      user2398029 over 11 years
      I'm currently trying to look for periodicity. Still running, but from what I have now its > 10^9. By comparison, the Mersenne Twister has a periodicity of 10^6001.
    • Oliver Charlesworth
      Oliver Charlesworth over 11 years
      @louism: You need to do a proper autocorrelation, at a minimum. And you probably also need to investigate the properties of individual bit positions as well (but this is getting beyond my area of expertise ;) )
    • FrankieTheKneeMan
      FrankieTheKneeMan over 11 years
      Try new QuickRandom(0,5) or new QuickRandom(.5, 2). Those will both repeatedly output 0 for your number.
    • steveha
      steveha over 11 years
      Writing your own random number generation algorithm is like writing your own encryption algorithm. There is so much prior art, by people who are hyper-qualified, that it's senseless to spend your time trying to get it right. There is no reason not to use the Java library functions, and if you really want to write your own for some reason, visit Wikipedia and look up algorithms there like Mersenne Twister.
    • steveha
      steveha over 11 years
      Also, an RNG that has unexpected patterns can make a game less fun. For example, if you shuffle a card deck and the distribution isn't really random, the game will suffer. And on the Urban Dead online web-based game, the RNG is actually something the players complain about in the discussion forums. (In Urban Dead, the game does things in response to clicks, and the expected percentages are known, so the players can actually get a sense of whether the RNG is random or not!)
    • zxcdw
      zxcdw over 11 years
      @steveha I wouldn't go as far as claiming that you absolutely should not write your own RNG if the intended use is does not bear much responsibility. Of course for things like crypto you definitely should not do your own implementations, as lots of crypto relies on the fact that the values from the function are indisinguishable from a truly random generator. The whole cryptosystem goes down if your RNG fails at this. There are valid reasons for requiring high-bandwidth random sequences for which the Java library implementation might be inadequate, that's when you need to hack a bit. :)
    • steveha
      steveha over 11 years
      @zxcdw, the point I meant to make is that other people have worked really hard to figure out really good algorithms, and those algorithms are public. Given that, the sensible thing to do is to just use the public algorithms with their known good qualities. If someone just wants to play around with RNG algorithms, I have no objection. But I think the Java library RNG is unlikely to be a real bottleneck in a game, and if Doorknob really needs something faster, I would recommend using something from: en.wikipedia.org/wiki/List_of_random_number_generators
    • Thomas
      Thomas over 11 years
      @steveha I agree with you, with the sole exception of curiosity. If one is genuinely interested in pseudorandom number generation, it is commendable to try and create one's own algorithm (provided it never leaves the workbench, of course - it's just for fun). But if your goal is to just get a PRNG up and running, yes, do not reinvent the wheel (obviously).
    • Jannis Froese
      Jannis Froese over 11 years
      Also, a comparison against Math.random is not fair. Math.random has to be thread save, which is using up a lot of runtime, while your class isn't thread save. ThreadLocalRandom.getCurrent().nextFloat() is a version which avoids thread issues, and is therefore much more comparable to your class. As ThreadLocalRandom is about two to three times faster than Random it also achieves somewhat similar runtime (while giving better randomness)
    • Dave Newton
      Dave Newton over 11 years
      I'll add that even if a user can't precisely identify what the non-randomness is, it can be detected. I have a hand-held Yahtzee game that rolls random-ishly, but there are detectable sequences where once I've gotten one of (four of a kind, full house, large straight) I'm much more likely to get the other two within 2-3 more rolls. It's predictable enough that it messes with me when I'm playing a more-random version (like with dice).
    • Kip
      Kip over 11 years
      You have a bug in your default constructor. Math.random() * 10 produces a value between 0 and 10, not 1 and 10. You want Math.random() * 9 + 1. As written, I think you'll get an exception approximately 10% of the time...
    • Cris Stringfellow
      Cris Stringfellow over 11 years
      @steveha - Someone needs to speak up for those who find writing compression, PRNGs and encryption fun. Like myself. And actually, probably like the researchers who do it for a living, I'm sure they find it fun, too. Don't ever let anyone tell you to not do something because 1) You are not good enough; or 2) it has all been done. If you like what you are doing, both 1) and 2) are total bullshit. And actually as far as algorithms go, these are pretty basic and accessible and v cool. More people should play with them not less. A few folk once told Heath Ledger he would never make it as an actor.
    • Zano
      Zano over 11 years
      @CrisStringfellow: I agree. But keep it away from production code.
    • Cris Stringfellow
      Cris Stringfellow over 11 years
      @Zano : Sure, unless it happens to win an AES, the Wikipedia compression challenge, or actually just kicks arse as a PRNG. PRNGs are the easiest to hack together, you just have to test them properly : compression, and DIEHARD/NIST. Maker-revolution!
    • Cris Stringfellow
      Cris Stringfellow over 11 years
      Actually people writing their own algorithms is much needed -- don't advise them not to, the very fact that people don't know about it is the very reason more people should play and learn about it. There is a big lack of awareness about crypto and randomness. If more people can write and play with their own stuff, more people are going to learn. That can only be good. An argument could be made that "programming" is very hard, there is so much prior art, and so many experts, no novices should even think about coming to the table. Clearly, this argument is false. Same for new langs/crypto
    • steveha
      steveha over 11 years
      Let me just repeat one part of my comment: "If someone just wants to play around with RNG algorithms, I have no objection." You can't become a master without becoming a journeyman first, and everyone has to start somewhere, and I have no objection to additional people working on the problem of RNG. But in this case, Doorknob wants to write a game, and clearly doesn't have a strong background in statistics or math as they relate to RNGs. I think Doorknob would be better served by using the Java RNG or a known RNG algorithm, and focusing on the actual game. Or forget the game and study RNG.
    • Admin
      Admin over 11 years
      I very much recommend finding a copy of Numerical Recipes by Press, Teukolksy, Vetterling, and Flannery (make sure it's the third edition, no other edition will suffice), and read the section on random number generators. In addition to the theory, they give a good history lesson about how many people once thought the way you did.
    • Cris Stringfellow
      Cris Stringfellow over 11 years
      @steveha I think we are all agreed then. He could also do both game and RNG. However, sometimes mistaken glitches lead to interesting stuff, a unique signature of the games weakly random generator.
    • Scratz
      Scratz over 11 years
      "Just good enough to have a human player think it's random enough.", may be more difficult than you think... Quoth the creator: "I have used Math.random, Random.org and other sources, but have always received numerous complaints that the dice are not random enough." - Dice-O-Matic
    • Andrew Lazarus
      Andrew Lazarus over 11 years
      Knuth also has good stuff on Random in The Art of Computer Programming. I'll add my vote that RNGs are fun to play with, but (a) you need to know how to test them and (b) you should know something about prior art.
    • Larry Gritz
      Larry Gritz over 11 years
      "Looks pretty random" is the devil talking to you. Humans are notoriously bad at judging what is random. There is extensive literature about what tests are good to evaluate a RNG. Knuth isn't a bad place to start!
    • eversor
      eversor over 11 years
  • cHao
    cHao over 11 years
    Pretty much anything that doesn't involve a hardware noise generator or a direct line into the OS's I/O stuff, is going to be pseudo-random. Genuine randomness can't be generated by an algorithm alone; you need noise from somewhere. (Some OSes' RNGs get their input by measuring stuff like how/when you move the mouse, type stuff, etc. Measured on a scale of microseconds to nanoseconds, that can be highly unpredictable.)
  • Jeroen Vannevel
    Jeroen Vannevel over 11 years
    @OliCharlesworth: indeed, as far as I know the only true random values are found using atmospheric noise.
  • rolfl
    rolfl over 11 years
    @me ... stupid to answer hastily. The Math.random is pseudorandom, and also, it is synchronized.
  • cHao
    cHao over 11 years
    @rolfl: Synchronization could very well explain why Math.random() is slower. It'd either have to synchronize or create a new Random every time, and neither one's very attractive performancewise. If i cared about performance, i'd create my own new Random and just use that. :P
  • templatetypedef
    templatetypedef over 11 years
    @louism- It's not really "random," per se. The results will be deterministic. That said, I didn't think about that when writing my answer; perhaps someone can clarify that detail?
  • Patashu
    Patashu over 11 years
    Floating point arithmetic errors are implementation designed. As far as I know, they are consistent for a certain platform but can differ e.g. between different mobile phones and between PC architectures. Although there are extra 'guard bits' sometimes added when doing a series of floating point calculations in a row, and the presence or absence of these guard bits can make a calculation subtly differ in the result. (guard bits being, e.g., the expansion of a 64 bit double to 80 bits)
  • Admin
    Admin over 11 years
    There is at least one trivial loop on this generator for all seeds: 0 -> 0. Depending on the seed, there may be many others. (For instance, with a seed of 3.0, 0.5 -> 0.5, 0.25 -> 0.75 -> 0.25, 0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2, etc.)
  • Maciej Piechotka
    Maciej Piechotka over 11 years
    +1 for numerical data - although looking on raw numbers can be misleading as it does not mean that they have statistically significant difference.
  • wim
    wim over 11 years
    +1 for a practical answer ... use this in a shoot em up and spawn enemies along diagonals for epic multiple headshots? :D
  • Admin
    Admin over 11 years
    Also, keep in mind that the theory behind LCRNGs all assumes that you're working with integers! Throwing floating-point numbers at it will not yield the same quality of results.
  • Petr Janeček
    Petr Janeček over 11 years
    These results vary heavily with the initial seeds passed to QuickRandom. Sometimes, it's close to uniform, sometimes it's much worse than this.
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 11 years
    This is enough to eyeball the non-randomness. 10,000 per bin implies an expected sqrt{variance} of 100, and these bins are repeatedly many times that.
  • user
    user over 11 years
    @BlueRaja-DannyPflughoeft Any PRNG where the quality of the output depends highly on the initial seed value(s) (as opposed to internal constants) seems broken to me.
  • Konrad Rudolph
    Konrad Rudolph over 11 years
    First rule of statistics: plot the data. Your analysis is spot-on, but plotting a histogram shows this much quicker. ;-) (And it’s two lines in R.)
  • vonbrand
    vonbrand over 11 years
    @duskwuff, you are right. But if the floating point hardware does follow sane rules, doing this is the same as doing it modulo the mantissa size, and the theory applies. Just need extra care in what you are doing.
  • Matt Krause
    Matt Krause over 11 years
    I mostly agree, except with your first paragraph. The built-in random calls (and /dev/random on Unix-like systems) are also PRNGs. I'd call anything that produces random numbers algorithmically a PRNG, even if the seed is something that is hard to predict. There are a few "true" random number generators that use radioactive decay, atmospheric noise, etc but these often generate relatively few bits/second.
  • BlueRaja - Danny Pflughoeft
    BlueRaja - Danny Pflughoeft over 11 years
    @MichaelKjörling: If we treat this as an LCG (which is it), magicNumber is not actually considered part of the seed...
  • Kaz
    Kaz over 11 years
    On Linux boxes, /dev/random is a source of real randomness obtained from device drivers, and not a PRNG. It blocks when not enough bits are available. The sister device /dev/urandom also does not block, but it is still not exactly a PRNG since it is updated with random bits when they are available.
  • Aristos
    Aristos over 11 years
    Imaging it or not, I have the same idea with the zip years ago when I test my random generators.
  • bestsss
    bestsss over 11 years
    If the Math.Random() function calls the operating system to get the time of day -- this is absolutely untrue. (in any of the java flavors/versions I know of)
  • Kaz
    Kaz over 11 years
    @bestsss This is from the original question: I remember reading somewhere that Math.random used System.nanoTime(). Your knowledge may be worth adding there or in your answer. I used it conditionally with an if. :)
  • bestsss
    bestsss over 11 years
    Kaz, both nanoTime()+counter/hash is used for the default seed of java.util.Random of oracle/OpenJDK. That's for the seed only then it's a standard LCG. In effect the OP generator takes 2 random numbers for seed, which is ok - so no difference than java.util.Random. System.currentTimeMillis() was the default seed in JDK1.4-
  • Cris Stringfellow
    Cris Stringfellow over 11 years
    Thanks @Alexandre C. and Aristos and aidan. I believe you.
  • Lie Ryan
    Lie Ryan over 11 years
    @wim: you don't need a PRNG if you want such patterns.
  • user
    user over 11 years
    @BlueRaja-DannyPflughoeft The simple fact is that the initial value for magicNumber is set when the generator is constructed (either by passing it as a parameter, or generated randomly). If as you say the quality of the output depends highly on that value, it should be a carefully selected constant (or possibly selected from a set of such constants), not a random value. So whether or not we call the initial value for magicNumber a "seed" is really splitting hairs; the important part is that it offloads a huge burden on the user of the PRNG in the form of good constants selection.
  • BlueRaja - Danny Pflughoeft
    BlueRaja - Danny Pflughoeft over 11 years
    @MichaelKjörling: Yes, it should be a carefully constructed constant, not a random value or chosen by the user.. which is why this "test" is meaningless! That's my point!
  • Callum Rogers
    Callum Rogers over 11 years
  • Happy Green Kid Naps
    Happy Green Kid Naps over 11 years
    Mandatory quotes: “Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin.” - John von Neumann (1951) “Anyone who has not seen the above quotation in at least 100 places is probably not very old.” - D. V. Pryor (1993) “Random number generators should not be chosen at random.” - Donald Knuth (1986)
  • Cris Stringfellow
    Cris Stringfellow over 11 years
    Nice code. Yes, that is cool. I used to do that too sometimes, it's hard to get a quantifiable measure out of it, but it's another good way to look at the sequence. And if you wanted get a look at sequences of longer than width*height you could xor the next image with this one pixel-per-pixel. I think the QuickRandom picture is much more aesthetically pleasing though, on account of it being textured like a seaweed carpet.
  • Callum Rogers
    Callum Rogers over 11 years
    The aesthetically pleasing part is how the sequence tends to increase as you go along each row (and then back to the start again) as the magicNumber multiplication produces a number similar to prevNum, which shows the lack of randomness. If we use the seeds new QuickRandom(0.01, 1.03) then we get this i.imgur.com/Q1Yunbe.png!
  • Cris Stringfellow
    Cris Stringfellow over 11 years
    Yes, great analysis. Since it just multiplies mod 1 by a constant clearly before wrapping occurs there will be the increase you describe. Seems like this could be avoided if we took the less significant decimal placings by say multiplying by 1 billion then reducing mod a 256 color palette.
  • tckmn
    tckmn over 11 years
    +1 for xkcd which is an amazing wobsite (oh, and the great answer) :P
  • higuaro
    higuaro over 11 years
    Thanks, and yes xkcd racks! XD
  • bestsss
    bestsss over 11 years
    The theory is fine but the execution is poor: the code is susceptible to integer overflow. In java all int[] are initialized to zero, so no need for this part. Casting to float is pointless when you work w/ doubles. Last: calling the methods names random1 and random2 is quite funny.
  • higuaro
    higuaro over 11 years
    @bestsss Thanks for the observations! I made a direct translation from the C code and didn't pay much attention to it =(. I made some modifications and updated the answer. I'd appreciate any additional suggestion
  • uday
    uday over 11 years
    Can you tell me what did you use to generate those output images? Matlab?
  • Callum Rogers
    Callum Rogers over 11 years
    @uDaY: Take a look at the code, C# and System.Drawing.Bitmap.
  • Cassandra S.
    Cassandra S. over 11 years
    @JeroenVannevel radioactive decay is random too.
  • Diego Bascans
    Diego Bascans over 11 years
    I don't care about random number generators, but I just learnt about sscce.org which will help me next time I want to post a problem on here. Useful post.
  • tckmn
    tckmn over 10 years
    @Edit Hmm, I may compare QR, Math.random, and ThreadLocalRandom sometime when I'm not too lazy :) That's interesting, thanks!
  • John Willemse
    John Willemse about 10 years
    This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post.
  • Terje
    Terje about 10 years
    I think it was already established that the original algorithm is not good enough? Perhaps an example of what is good enough can lead to inspiration on how to improve it?
  • John Willemse
    John Willemse about 10 years
    Yes, maybe, but it doesn't answer the question at all and there's no data supporting your algorithm is actually "good enough". Generally, random number algoritms and closely related encryption algorithms are never as good as the ones by the experts that implemented them in a programming language. So, if you could support your claim and elaborate on why it is better than the algorithm in the Question you would at least answer a question asked.
  • Terje
    Terje about 10 years
    Well... Experts that implemented them in a programming language aim for "perfect" distribution, whereas in a game, you never need that. You want speed, and "good enough" distribution. This code offers this. If it's inappropriate here, I'll delete the answer, no problem.
  • LegendLength
    LegendLength over 8 years
    It's a nice way to create 3d spikes though!
  • maaartinus
    maaartinus about 7 years
    1. You can gain some more speed by dropping the mask as the highest 16 bits don't influence the used bits. 2. You can use those bits, save one subtraction and get a better generator (bigger state; the most significant bits of a product are the most nicely distributed, but some evaluation would be needed). 3. The Sun guys simply implemented an archaic RNG by Knuth and added synchronization. :(
  • maaartinus
    maaartinus about 7 years
    Concerning multithreading, your usage of the local variable is a no-op, as without volatile, the compiler is free to eliminate (or introduce) local variables at will.