Math.random()返回大于一的值吗?

41

在JavaScript中随机数的探索中,我发现了一个令人惊讶的 bug,在Google Chrome的 V8 JavaScript 引擎中可能存在。例如:

// 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;
};

现在当我运行testRand5()时,我会得到以下结果(每次运行结果可能略有不同,您可能需要将"max"设置为更高的值来揭示错误):

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

有趣的是,我在node.js中看到了类似的结果,这让我相信它不是特定于Chrome的。有时会出现不同或多个神秘值(7、9等)。

有人能解释一下我为什么会得到我看到的结果吗?我猜想这可能与使用parseInt(而不是Math.floor())有关,但我仍然不确定为什么会发生这种情况。

3个回答

75

当你生成一个带有指数形式表示的非常小的数字时,例如9.546056389808655e-8,就会出现边界情况。

parseInt 结合使用时,它会将其参数解释为字符串,这个组合将会产生问题。如前面所建议的,可以使用 Math.floor 来解决这个问题。

尝试运行以下代码:

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

39
当然,这是一个 parseInt() 的陷阱。它首先将其参数转换为字符串,这可能会强制使用科学计数法,这将导致 parseInt 做出类似于以下内容的操作:
var x = 0.000000004;
(x).toString(); // => "4e-9"
parseInt(x); // => 4

我真傻...


2
哇,好棒的发现。我不知道parseInt甚至会先将数字转换为字符串。 - Matt
另一种选择是抛出 TypeError,这是 Java 的方式。JavaScript 中的大多数 API 只是尝试使用无效类型而不是抛出错误。 - Esailija

10

我建议将你的随机数函数改为这个:

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

这将可靠地生成一个介于1和5之间(包括1和5)的整数值。

您可以在此处查看测试函数的实际运行情况:http://jsfiddle.net/jfriend00/FCzjF/

在这种情况下,parseInt 不是最佳选择,因为它将把您的浮点数转换为一个字符串,该字符串可能有多种格式(包括科学计数法),然后尝试从中解析一个整数。更好的方法是直接使用 Math.floor()对浮点数进行操作。


他的函数是如何生成像7、9和10这样的数字的? - Matt
1
@Matt - 这可能与没有基数参数的parseInt操作有关,然后对转换为字符串的十进制数进行操作。所有这些都是不好的。 - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接