40

JavaScript で乱数をいじっていたときに、おそらく 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()なぜそれが起こるのかまだわかりません。

4

3 に答える 3

76

エッジ ケースは、たとえば次のように、指数で表される非常に小さな数を生成した場合に発生します9.546056389808655e-8

引数を string として解釈すると組み合わせると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
于 2011-09-08T19:56:12.433 に答える
38

もちろん、それはparseInt()落とし穴です。最初にその引数を文字列に変換します。これにより、parseInt に次のようなことをさせる科学表記法を強制することができます。

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

愚かな私...

于 2011-09-08T19:54:40.407 に答える
10

乱数関数を次のように変更することをお勧めします。

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

これにより、1 から 5 までの整数値が確実に生成されます。

テスト関数の動作はhttp://jsfiddle.net/jfriend00/FCzjF/で確認できます。

この場合、parseIntfloat をさまざまな形式 (科学表記法を含む) の文字列に変換し、そこから整数を解析しようとするため、 は最適な選択ではありません。でフロートを直接操作する方がはるかに優れていMath.floor()ます。

于 2011-09-08T19:52:15.563 に答える