4

This maybe more mathematical question than programming. In JS I wanted to a function that returns a random integer number in an interval lets say 1-6 and this is what I found:

// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

I feel guilty if I copy and paste this in my code. I don't understand this : Why we subtract min from max, add 1, multiply the answer by Math.random() and then add the min. I tired with several numbers manually on paper and it work just fine ! But I don't understand why !

Adelin
  • 18,144
  • 26
  • 115
  • 175
  • The +1 is only there because `max` is inclusive, not exclusive. – Bergi Jul 07 '13 at 21:56
  • Check out [Generating random numbers in Javascript in a specific range?](http://stackoverflow.com/questions/1527803/generating-random-numbers-in-javascript-in-a-specific-range) for a good explanation – Bergi Jul 07 '13 at 21:58

4 Answers4

6

Assuming you already understand the behaviour of Math.floor and Math.random, here's the rest step by step:

  • Math.random() ↝ a random number between 0 (inclusive) and 1 (exclusive)
  • Math.random() * max ↝ a random number between 0 (inclusive) and max (exclusive)
  • Math.floor(Math.random() * max) ↝ a random integer between 0 (incl.) and max (excl.)
  • Math.floor(Math.random() * (max - min)) + min ↝ a random integer between min (incl.) and max (excl.)
  • Math.floor(Math.random() * ((max + 1) - min)) + min ↝ a random integer between min (incl.) and max+1 (excl.) (OR between min and max both inclusive)
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
coudy
  • 13,218
  • 5
  • 23
  • 26
3

Math.random() will give you a "real" number from 0 to 1 (not including 1.0).

That's cool and all, but what if I want a "real" number from 1 to 2?

The answer: "transform" your [0,1) into [1,2).

In practical terms, it means adding 1 to your result.

Try it out -- Math.random()+1 will give you a number from 1 to 2.

In mathematics this is known as a "mapping". That is -- for every possible real number in [0,1), find a way to "map" that real number to another real number in [1,2). That is, if I give you any real number between [0,1), you should be able to map that number -- apply that number to a function that will return a number between [1,2).

In our case, that function f(x) = x+1.

Do you see how this gives us random numbers between [1,2)? Visualize the two intervals next to each other and imagine a line going from every point in [0,1) to its corresponding map in [1,2). Now, pick a random point on [0,1) ... and follow the line. You'll follow the line to a random point in [1,2)!

Now, all complete one-to-one maps from [0,1) to [1,2) will turn a random number between [0,1) to a random number between [1,2)...but not all of them will give you an evenly distributed random number between [1,2). The mathematics behind what maps give you evenly distributed results is a bit complicated but in short, if your map only involves adding, subtracting, multiplying, and dividing by constants, it's "legal" in the sense that the results will also be evenly distributed.

So, now we know how to transform [0,1) into [1,2).

What if I want to map [0,1) onto [0,2)? I can't just add numbers anymore ...

How about I multiply everything by two?

This should work -- the function f(x) = x*2 does indeed map every point on [0,1) to a point on [0,2) --- and because it only involves multiplication by constants (2), it is a distribution-preserving map.

This works! Math.random()*2 will give you a random number between 0 and 2.

Okay, now something a bit more complicated ... transforming [0,1) into [1,3).

Multiplying by two doesn't work ... 0*2 = 0, and that's not in your target range. Adding one doesn't work... even though 0+1 is in your target range and 1+1 is, as well, there is no way you can ever reach 3.

If we can't transform [0,1) into [1,3), let's try and see if we can transform something else into [1,3).

How about [0,2)? Yes, we can do this ... the function f(x) = x+1 perfectly maps [0,2) to [1,3). You can think of + as "shifting" the range up.

And so the solution here is clear -- first, turn [0,1) into [0,2), then turn [0,2) into [1,3).

We already know the first (f(x) = x*2), and we figured out the second (f(x) = x+1). So the "combined" transformation/map is f(x) = (x*2)+1.

That is, Math.random()*2 + 1 will give you a number from 0 to 3.

Now for the final trick...mapping [0,1) to an arbitrary range [min,max).

The secret here is to re-write this as [min,min+range), where range = max-min.

Here you can see that it's simple to transform the range [0,range) to [min,min+range) -- you just add "min" to it. So if I had the range [0,range), and I wanted to get [min,min+range), i would use f(x) = x+min.

So how do we get from [0,1) to [0,range) ?

Multiply by range!

f(x) = (x*range) + min

Now writing things back to original terms, using range = max-min

f(x) = (x*(max-min)) + min

will transform a real number from [0,1) to a real number from [min,max)

I'll leave the rest (turning it into a useful integer) to you

Justin L.
  • 13,510
  • 5
  • 48
  • 83
  • Thank you very much for the nice answer I wish I was as good as you in math. If there where a way to give +100000000 votes I would ! – Adelin Jul 07 '13 at 22:28
1

Here's an explanation of your code:

  • Math.random() generates a random number between 0 and 1 (not including 1).
  • You need to scale that value based on the range of numbers you want. Your range is how far from your min desired number to your max desired number which is max - min.
  • If you want to include the max value in the range of numbers generated, then use max - min + 1
  • You then need to make sure the random number starts at the right base rather than 0 so you add min to it.
  • Then, if you want it to be an integer you call Math.floor() to truncate it to the next lowest integer.

So, if you just had this:

Math.floor(Math.random())

You would always get zero. Because Math.floor() of a float value between 0 and 1 (not including one) will always truncate down to 0.

Then, if you expand the range with:

Math.floor(Math.random() * (max - min + 1))

You would now get a random number between 0 and max - min including the larger value.

So, to then get it to start at the right base, you add in min like this:

Math.floor(Math.random() * (max - min + 1)) + min
jfriend00
  • 683,504
  • 96
  • 985
  • 979
1
0 <= Math.random() < 1  =>
0 <= Math.random() * 6 < 6 =>
0 <= Math.floor( Math.random() * 6 ) <= 5   

then you add 'min' so it would look like this:

min <= Math.floor( Math.random() * 6 ) <= 5 + min

in your exemple, for min = 1 you will have all the numbers in 1-6.

I hope now is much clear.

dashme
  • 73
  • 5