Search code examples
javascriptgoogle-chromerandomv8parseint

Math.random() returns value greater than one?


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.


Solution

  • 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