Math.random () renvoie une valeur supérieure à un?

En jouant avec des nombres aléatoires en JavaScript, j’ai découvert un bogue surprenant, probablement dans le moteur JavaScript V8 de Google Chrome. Considérer:

// Generate a random number [1,5]. var rand5 = function() { return parseInt(Math.random() * 5) + 1; }; // Return a sample dissortingbution 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; }; 

Maintenant, quand je lance testRand5() j’obtiens les résultats suivants (bien sûr, légèrement différents à chaque exécution, vous devrez peut-être définir “max” sur une valeur plus élevée pour révéler le bogue):

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

Fait intéressant, je vois des résultats comparables dans node.js, ce qui me porte à croire qu’il n’est pas spécifique à Chrome. Parfois, il existe des valeurs de mystère différentes ou multiples (7, 9, etc.).

Quelqu’un peut-il expliquer pourquoi je pourrais obtenir les résultats que je vois? Je suppose que cela a quelque chose à voir avec l’utilisation de parseInt (au lieu de Math.floor() ) mais je ne suis toujours pas sûr de savoir pourquoi cela pourrait arriver.

Le cas d’arête se produit lorsque vous générez un très petit nombre, exprimé avec un exposant, comme par exemple 9.546056389808655e-8 .

Combiné avec parseInt , qui interprète l’argument comme une chaîne , l’enfer se déchaîne. Et comme suggéré avant moi, il peut être résolu en utilisant Math.floor .

Essayez vous-même avec ce morceau de 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 

Bien sûr, c’est une parseInt() gotcha. Il convertit d’abord son argument en une chaîne , ce qui peut forcer la notation scientifique, ce qui entraînera une parsing similaire à celle-ci:

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

Que je suis bête…

Je suggère de changer votre fonction de nombre aléatoire à ceci:

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

Cela générera de manière fiable un nombre entier compris entre 1 et 5 inclus.

Vous pouvez voir votre fonction de test en action ici: http://jsfiddle.net/jfriend00/FCzjF/ .

Dans ce cas, parseInt n’est pas le meilleur choix car il va convertir votre float en une chaîne qui peut être un certain nombre de formats différents (y compris la notation scientifique), puis essayer d’parsingr un entier en dehors. Il vaut beaucoup mieux opérer sur le float directement avec Math.floor() .