Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Saturday, May 17, 2008

Random Integers

If you have ever tried to get a random integer in JavaScript, you have probably been frustrated with converting the method ‘Math.random’ results (values between 0 and slightly less than 1) to integers. There are several gotchas when converting the floating points from ‘Math.random’, in JavaScript, to integers. Todays article will walk through how to build a proper random integer generator and each gotcha you might run into.

On a first pass, one would probably come up with a method like the following:

Example 1: Math.RadomInteger() Version 1

Math.RandomInteger = function(n) { var i = Math.random(); var num = Math.round(i * n); return num; };

For all RandomInteger methods that we discuss today, the parameter ‘n’ is the upper range of the random number, so if you want number 1 through 4, then ‘n’ would equal 4. There are two issues with this version of the method: 1) the ’round’ method will sometimes round down to 0 causing a distribution of 0 through 4, and 2) the numbers will distribute themselves more frequently to integers 1 through 9. Here is the distribution you can expect from this method (for 1000 tries and digits 1 through 10):

Example 2: Math.RadomInteger() Version 1 Distribution

0 = 36 1 = 118 2 = 93 3 = 100 4 = 108 5 = 99 6 = 99 7 = 109 8 = 98 9 = 96 10 = 44 11 = 0

A random integer generator should have an equal distribution between all desired integers (so values 1 through 10 should each have occurred about 100 times). As you can see, ZERO has been returned and ten has too low of a distribution. We fix that by adding 1 to the number returned by multiplication of the random value and ‘n’:

Example 3: Math.RadomInteger() Version 2

Math.RandomInteger = function(n) { var i = Math.random(); var num = Math.round(i * (n - 1) + 1); return num; };

Example 4: Math.RadomInteger() Version 2 Distribution

0 = 0 1 = 52 2 = 97 3 = 116 4 = 95 5 = 128 6 = 111 7 = 113 8 = 114 9 = 122 10 = 52 11 = 0

Now we are returning the correct integers (only 1 through 10), but both ten and one have the wrong distribution (about half of what they should). The problem is using ‘Math.round’ causes ten and one to loose about 50% of their values, because half rounds down for 9 < x < 9.5 and up for 1.5 <= x < 2. Instead we should use 'Math.floor' or 'Math.ceil', which will consider all values between two integers to round to only 1 integer, fixing the distribution:

Example 5: Math.RadomInteger() Version 3

Math.RandomInteger = function(n) { var i = Math.random(); var num = Math.ceil(i * n); return num; };

Example 6: Math.RadomInteger() Version 3 Distribution

0 = 0 1 = 91 2 = 112 3 = 103 4 = 82 5 = 105 6 = 99 7 = 112 8 = 110 9 = 99 10 = 87 11 = 0

This distribution looks great, so we are done, right? Well, no, because as I mentioned above, ‘Math.random’ returns ZERO through slightly less than 1. There is a very, very small chance that ZERO will be returned (although, out of several thousand tries, I was never able to get ZERO, you still want to prevent it). So, instead of using the ‘Math.ceil’, we should use ‘Math.floor’ on the value we rounded in Example 3. Here is the final method:

Example 7: Math.RadomInteger() Final Version

Math.RandomInteger = function(n) { var i = Math.random(); var num = Math.floor(i * n + 1); return num; };

Example 8: Math.RadomInteger() Final Version Distribution

0 = 0 1 = 96 2 = 114 3 = 83 4 = 108 5 = 105 6 = 98 7 = 97 8 = 106 9 = 93 10 = 100 11 = 0

This is a pretty good distribution, and there is no chance of getting either ZERO or 11. So, this is the correct function. However, lets make one final improvement, allowing you to pass both a maximum and minimum value, returning a random integer in that range, inclusive of the end points.

Example 9: Math.RadomInteger() Max & Min Version

Math.RandomInteger = function(n, m) { if (! m) {m = 1;} // default range starts at 1 var max = n > m ? n : m; // doesn’t matter which value is min or max var min = n === max ? m : n; // min is value that is not max var d = max - min + 1; // distribution range return Math.floor(Math.random() * d + min); };

Example 10: Math.RadomInteger() Max & Min Version (range 6-10)

0 = 0 1 = 0 2 = 0 3 = 0 4 = 0 5 = 0 6 = 197 7 = 203 8 = 204 9 = 215 10 = 181 11 = 0

Note that the 1000 values are nearly evenly distributed through six and ten. This method also supports passing just a single value (the maximum) and working as the Example 8, ranging between 1 and the maximum. I have also created a Math.RandomInteger test page, if you want to see the distributions yourself. Reload the page to have it compute the values again.

I have also included a new Math.js which I will use to augment the JavaScript native ‘Math’ Object in the future.

————–

A commenter pointed out that this method isn’t the most efficient/effective random number generator available. He is correct, this is just the simplest and most easy to understand version. If you want a better random number generator then check out Mersenne Twister (MT), which has already been converted to JavaScript.

posted by Matt Snider at 3:49 pm  

4 Comments »

  1. “The first problem is that random numbers on computers always have the inherit flaw of using the system clock to generate them. ”

    Um… no. There are plenty of sources of pseudo-randomness on a computer (any hardware interrupts, not just the clock), and of course computers that are online can obtain truly random seeds.

    “A random integer generator should have an equal distribution between all desired integers.”

    Also wrong. You’re effectively taking Math.random() mod 10 to get the last digit. But the range of Math.random() is not evenly divisible by 10, so it cannot possibly return every decimal digit with equal frequency.

    There are better algorithms available than Math.random() — look at the Mersenne Twister or GFSRs, both of which are faster and have better randomness than your Math.RandomInteger.

    Comment by Michael — May 19, 2008 @ 8:22 am

  2. To you first point, there was an issue I recall from my CSS schooling days, about the inherit flaw of all computer based randomizers. However, while there are many flaws (just do a google search), I didn’t find the answer I was looking for, so I removed the offending line.

    Also, ‘Math.random’ is seeded with the current time.

    Your second point is completely wrong, at least as far as the documentation for ‘Math.random’ is written. ‘Math.random’ chooses a random number between 0 (inclusive) and 1. Maybe you know something about the source-code for JavaScript ‘Math.random’ that I do not, but true randomness, will have a fairly even distribution of numbers between 0 and 1 when run a large number of times. Therefore, if you convert these to integers properly, you will have a fairly even distribution of integers between your min and max. Although, since it is truly random, it is possible in a sample of 100 tries to randomly get 1 each time, it is highly unlikely, and repeated testing (see test page) shows that the distribution is fairly even.

    The point of this article, was to show how to use ‘Math.random’ to generate random integers. I made no statements that this was the best random number generator available. I am glad to hear that there are more efficient and effective random number generators and have included a link to MT at the bottom of my post.

    Comment by Matt Snider — May 19, 2008 @ 9:11 am

  3. I didn’t even consider zero every being selected. That’s a nice piece information there.

    I like to attach a random function to my Number object, with the addition of a _ function, which takes a value and simply returns that value. So I can write

    _(3).random();

    and get a random number 1 through 3. Zero returns zero, negative numbers go from -N to -1. I also have an object called Range that would work something like this

    Range(”[4,7)”).random();

    In this case it would return 4, 5, or 6. It’s not really an improvement on your functionality. For some reason, I just don’t like using the Math object. Good post!

    Comment by MillsJROSS — May 20, 2008 @ 5:26 am

  4. Thanks for the comment MillsJROSS. I like the idea of attaching random to Number. Also, it’s good to note that the method discussed in this article, doesn’t support 0 or negative numbers well.

    Comment by Matt Snider — May 21, 2008 @ 8:54 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress