Math.random() returns value greater than one?
Asked Answered
S

3

41

While playing around with random numbers in JavaScript I discovered a surprising bug, presumably in the V8 JavaScript engine in Google Chrome. Consider:

// Generate a random number [1,5].
var rand5 = function() {
  return parseInt(Math.random() * 5) + 1;
};

// Return a sample distribution over MAX times.
var testRand5 = function(dist, max) {
  if (!dist) { dist = {}; }
  if (!max) { max = 5000000; }
  for (var i=0; i<max; i++) {
    var r = rand5();
    dist[r] = (dist[r] || 0) + 1;
  }
  return dist;
};

Now when I run testRand5() I get the following results (of course, differing slightly with each run, you might need to set "max" to a higher value to reveal the bug):

var d = testRand5();
d = {
  1: 1002797,
  2: 998803,
  3: 999541,
  4: 1000851,
  5: 998007,
  10: 1 // XXX: Math.random() returned 4.5?!
}

Interestingly, I see comparable results in node.js, leading me to believe it's not specific to Chrome. Sometimes there are different or multiple mystery values (7, 9, etc).

Can anyone explain why I might be getting the results I see? I'm guessing it has something to do with using parseInt (instead of Math.floor()) but I'm still not sure why it could happen.

Skirting answered 8/9, 2011 at 19:46 Comment(0)
S
75

The edge case occurs when you happen to generate a very small number, expressed with an exponent, like this for example 9.546056389808655e-8.

Combined with parseInt, which interprets the argument as a string, hell breaks loose. And as suggested before me, it can be solved using Math.floor.

Try it yourself with this piece of code:

var test = 9.546056389808655e-8;

console.log(test); // prints 9.546056389808655e-8
console.log(parseInt(test)); // prints 9 - oh noes!
console.log(Math.floor(test)) // prints 0 - this is better
Sibeal answered 8/9, 2011 at 19:56 Comment(0)
S
39

Of course, it's a parseInt() gotcha. It converts its argument to a string first, and that can force scientific notation which will cause parseInt to do something like this:

var x = 0.000000004;
(x).toString(); // => "4e-9"
parseInt(x); // => 4

Silly me...

Skirting answered 8/9, 2011 at 19:54 Comment(2)
wow, nice catch. I didn't know parseInt converted even a number to a string first.Lima
Well the other alternative is to throw a TypeError, which is the Java way. Most APIs in javascript just try to work with invalid types rather than throw the error.Embody
V
10

I would suggest changing your random number function to this:

var rand5 = function() {
  return(Math.floor(Math.random() * 5) + 1);
};

This will reliably generate an integer value between 1 and 5 inclusive.

You can see your test function in action here: http://jsfiddle.net/jfriend00/FCzjF/.

In this case, parseInt isn't the best choice because it's going to convert your float to a string which can be a number of different formats (including scientific notation) and then try to parse an integer out of it. Much better to just operate on the float directly with Math.floor().

Viceregal answered 8/9, 2011 at 19:52 Comment(2)
How does his function end up generating numbers like 7,9, and 10?Lima
@Lima - it likely has to do with parseInt without a radix parameter then operating on a decimal converted to a string. All of those are bad.Viceregal

© 2022 - 2024 — McMap. All rights reserved.