web-dev-qa-db-ja.com

Math.random()は1より大きい値を返しますか?

JavaScriptで乱数をいじっているときに、おそらくGoogleChromeのV8JavaScriptエンジンに驚くべきバグを発見しました。考えてみましょう:

_// 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など)。

私が見た結果が得られる理由を誰かが説明できますか? (Math.floor()の代わりに)parseIntを使用することと関係があると思いますが、なぜそれが発生するのかはまだわかりません。

40
maerics

エッジケースは、たとえば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
76
Jakob

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

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

愚かな私...

38
maerics

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

_var Rand5 = function() {
  return(Math.floor(Math.random() * 5) + 1);
};
_

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

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

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

10
jfriend00