Reputation: 139
I found in FreeCodeCamp that to solve the problem of getting a random whole number within a range is to use Math.floor
. It's inaccurate in rounding. It returns equal to or less than. It's not what I thought.
This is the given formula:
Math.floor(Math.random() * (max - min + 1)) + min
Does anyone know why it is more used for rounding to the nearest whole number?
Thanks in advance!
Upvotes: 10
Views: 15680
Reputation: 423
This is the real reason, at least the way I see it, Math.random returns a float number between 0 (Included) and 1 (Not included), something like 0 - 0.9999999999999999 (I assume). Looking at this information we can easily think, ok, this is a percentage, so all We have to do is something like:
let max = 10;
console.log(Math.round(Math.random() * max))
Right?, Well, let's take a look at what our Math.random number should really represent in our range.
Let's say our range is min=1 and max=5, that creates five different possibilities: 1,2,3,4,5. If 0.9999999999999999, or 1 for simplicity, is the maximum number you can get from Math.random, we can divide it into the number of possibilities our range has, something like 1 / max, getting roughly 0.2 as the result, this means each possibility will be at least equal to this number multiplied by it's value, that said Math.random values in the following ranges would satisfy each possibility:
From | To | Expected Result |
---|---|---|
0 | 0.2 | 1 |
0.21 | 0.4 | 2 |
0.41 | 0.6 | 3 |
0.61 | 0.8 | 4 |
0.81 | 0.9999999999999999 | 5 |
Now let's look at what Math.round does:
console.log(0.25 * 5, 'rounded to:', Math.round(0.25 * 5));
//Prints 1.25 'rounded to:' 1
console.log(0.31 * 5, 'rounded to:', Math.round(0.31 * 5));
//Prints 1.55 'rounded to:' 2
console.log(0.85 * 5, 'rounded to:', Math.round(0.85 * 5));
//Prints 4.25 'rounded to:' 4
As you can see, in some situations (The floats for your min and the floats for you max) Math.round won't return the result we might be expecting, like in the first example, we were expecting 2 because 0.25 is greater than 0.2 and in the last example we were expecting 5 because 0.85 is greater than 0.8. This means that every float from 0.21 to 0.299999999999999 and from 0.81 to 0.899999999999999 will result in unexpected results, and this is why a Math.floor solution is preferred.
How about we divide our randomly generated float by our fixed possibility float?
console.log(0.25 / (1 / 5));//Prints 1.25
console.log(0.31 / (1 / 5));//Prints 1.5499999999999998
console.log(0.85 / (1 / 5));//Prints 4.25
If you look at these results, they can be fixed by rounding them down and making sure we at least get the min value by adding the min value at the end:
let min = 1;
let max = 5;
let r = Math.random();
let f = 1 / max;
let x = Math.floor(r / f) + min;
console.log(r, x);
Now We have another problem, if We change our min value to any number other than 1, we need to adjust our formula to cover the offset created in the number of possibilities. So if our range changed to min=2 and max=5 We now have four possibilities: 2,3,4,5 and our formula now includes (max - min + 1) to calculate de number of possibilities; The + 1 makes the range inclusive creating the slot needed for the four possibilities:
let min = 2;
let max = 5;
let r = Math.random();
let f = 1 / (max - min + 1);
let x = Math.floor(r / f) + min;
console.log(r, x);
Our final function would look like this:
const randomNumber = (min, max) => {
return Math.floor(Math.random() / (1 / (max - min + 1))) + min;
}
console.log(randomNumber(2, 5));
//If Math.random ever returns 0.999999999999999
console.log(Math.floor(0.999999999999999 / (1 / (5 - 2 + 1))) + 2);//Prints 5
console.log(Math.floor(0.249999999999999 / (1 / (5 - 2 + 1))) + 2);//Print 2
//Hope this never happens
console.log(Math.floor(0.25 / (1 / (5 - 2 + 1))) + 2);//Print 3
Which is the same as:
const randomNumber = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomNumber(2, 5));
//If Math.random ever returns 0.999999999999999
console.log(Math.floor(0.999999999999999 * (5 - 2 + 1)) + 2);//Prints 5
console.log(Math.floor(0.249999999999999 * (5 - 2 + 1)) + 2);//Print 2
//Hope this never happens
console.log(Math.floor(0.25 * (5 - 2 + 1)) + 2);//Print 3
Upvotes: 2
Reputation: 12637
Summary: Because with Math.round()
the values in min
and max
are underrepresented.
let's have an example and compare the results when using Math.floor()
and Math.random()
respectively.
Just for clarity I've added the two formulas we're comparing:
min = 0;
max = 3;
result = Math.round(Math.random() * (max - min)) + min;
result = Math.floor(Math.random() * (max - min + 1)) + min;
| result | Math.round() | Math.floor() |
|:------:|:------------:|:------------:|
| 0 | 0.0 - 0.499 | 0 - 0.999 |
| 1 | 0.5 - 1.499 | 1 - 1.999 |
| 2 | 1.5 - 2.499 | 2 - 2.999 |
| 3 | 2.5 - 2.999 | 3 - 3.999 |
you see that 0
and 3
have only half as big of a range that produces them with Math.random()
than every other range in out example.
Added a snippet to show that effect:
// the same random numbers for all arrays, nonone should say these would have any influence.
const randomNumbers = Array(10000).fill().map(Math.random);
//min <= random <= max
const min = 4, max = 7;
// the +1 is to do `random <= max` otherwise it would produce `random < max`
const floored = randomNumbers.map(random => Math.floor(random * (max - min + 1) + min));
const rounded = randomNumbers.map(random => Math.round(random * (max - min) + min));
//utility to count the number of occurances per value
const count = (acc, nr) => (acc[nr] = (acc[nr] || 0) + 1, acc);
console.log({
"Math.floor()": floored.reduce(count, {}),
"Math.round()": rounded.reduce(count, {})
});
.as-console-wrapper{top:0;max-height:100%!important}
Upvotes: 9
Reputation: 1
Math.floor(Math.random())
will always return 0
while Math.round(Math.random())
will return 0 or 1
thus with the Math.round()
the random numbers will follow a non-uniform distribution. Which may not be acceptable for your needs.
Upvotes: 0
Reputation: 956
Math.floor(Math.random() * (max - min + 1)) + min
will give you a random number in the range of [min, max] because Math.random() gives you [0, 1). Let's use Math.round instead of Math.floor, Math.random() gives you [0, 1) and if you multiply it by 10, you will get [0, 10). This is a floating point and if you round it up, you will get [0, 10] as integer. However, if you round it down, you will get [0, 10) as integer.
In most random function, the norm is to return [min, max).
To answer your question, the author uses Math.floor so that the random number will be in the range of [min, max] instead of [min, max+1] if using Math.round.
FROM WIKIPEDIA
Intervals Main article: Interval (mathematics) Both parentheses, ( ), and square brackets, [ ], can also be used to denote an interval. The notation {\displaystyle [a,c)} [a, c) is used to indicate an interval from a to c that is inclusive of {\displaystyle a} a but exclusive of {\displaystyle c} c. That is, {\displaystyle [5,12)} [5, 12) would be the set of all real numbers between 5 and 12, including 5 but not 12. The numbers may come as close as they like to 12, including 11.999 and so forth (with any finite number of 9s), but 12.0 is not included. In some European countries, the notation {\displaystyle [5,12[} [5,12[ is also used for this.
Upvotes: 0