Matematyka.random() Zwraca wartość większą niż 1?

Bawiąc się losowymi liczbami w JavaScript odkryłem zaskakujący błąd, prawdopodobnie w silniku JavaScript V8 w 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;
};

Teraz, gdy uruchamiam testRand5() otrzymuję następujące wyniki (oczywiście, różniące się nieco przy każdym uruchomieniu, może być konieczne ustawienie " max " na wyższą wartość, aby ujawnić błąd):

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

Co ciekawe, widzę porównywalne wyniki w node.js, skłaniając mnie do przekonania, że to nie jest specyficzne dla Chrome. Czasami są różne lub wiele wartości tajemniczych (7, 9, itd.).

Czy ktoś może wyjaśnić, dlaczego mogę uzyskać wyniki, które widzę? Domyślam się, że ma to coś wspólnego z używaniem parseInt (zamiast Math.floor()), ale nadal nie jestem pewien, dlaczego tak się stało.

Author: Josh Lee, 2011-09-08

3 answers

Wielkość liter występuje, gdy zdarza się wygenerować bardzo małą liczbę, wyrażoną wykładnikiem, jak na przykład 9.546056389808655e-8.

W połączeniu z parseInt, Któreinterpretuje argument jako ciąg znaków , hell breaks loose. I tak jak sugerowałem, można to rozwiązać za pomocą Math.floor.

Spróbuj sam z tym kawałkiem kodu:

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
 75
Author: Jakob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-14 17:52:20

Oczywiście, to parseInt() mam cię. Konwertuje swój argument na string pierwszy, a to może wymusić notację naukową, która spowoduje, że parseInt zrobi coś takiego:

var x = 0.000000004;
(x).toString(); // => "4e-9"
parseInt(x); // => 4
Głuptas ze mnie...
 37
Author: maerics,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-08 19:54:40

Sugerowałbym zmianę funkcji liczby losowej na taką:

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

To niezawodnie wygeneruje wartość całkowitą z zakresu od 1 do 5 włącznie.

Możesz zobaczyć swoją funkcję testową w akcji tutaj: http://jsfiddle.net/jfriend00/FCzjF/.

W tym przypadku, parseInt nie jest najlepszym wyborem, ponieważ konwertuje Twój float do ciągu znaków, który może być w wielu różnych formatach (w tym notacji naukowej), a następnie spróbować parsować liczbę całkowitą z niego. Much lepiej po prostu operować na pływaku bezpośrednio z Math.floor().

 10
Author: jfriend00,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-08 20:16:39