web-dev-qa-db-fra.com

Math.round (num) vs num.toFixed (0) et incohérences du navigateur

Considérons le code suivant:

for (var i=0;i<3;i++){
   var num = i + 0.50;
   var output = num + " " + Math.round(num) + " " + num.toFixed(0);
   alert(output);
}

Dans Opera 9.63, je reçois:

0,5 1 0

1,5 2 2

2,5 3 2

En FF 3.03 je reçois:

0,5 1 1

1,5 2 2

2,5 3 3

Dans IE 7, je reçois:

0,5 1 0

1,5 2 2

2,5 3 3

Notez les résultats en gras. Pourquoi ces incohérences sont-elles présentes? Est-ce que cela signifie que toFixed(0) devrait être évité? Quelle est la bonne façon d'arrondir un nombre au nombre entier le plus proche?

48
eft

Modifier: pour répondre à votre modification, utilisez Math.round. Vous pouvez également créer un prototype de l'objet Number pour qu'il le fasse si vous préférez cette syntaxe.

Number.prototype.round = function() {
  return Math.round(this);
}
var num = 3.5;
alert(num.round())

Je n'ai jamais utilisé Number.toFixed() auparavant (principalement parce que la plupart des bibliothèques JS fournissent une méthode toInt() ), mais à en juger par vos résultats, il serait plus cohérent d'utiliser les méthodes Math (round, floor, ceil) puis toFixed si vous recherchez la cohérence entre les navigateurs. 

30
tj111

Je pense que FF fait le bon choix avec toFixed, puisque l’étape 10 ci-dessous indique "S'il y en a deux, choisissez le plus grand n".

Et comme Grant Wagner a dit: Utilisez Math.ceil (x) ou Math.floor (x) au lieu de x.toFixed ().

Tout ce qui suit est extrait de la spécification du langage ECMAScript :

15.7.4.5 Number.prototype.toFixed (fractionDigits)

Renvoie une chaîne contenant le nombre représenté par un point fixe notation avec fractionDigits chiffres après le point décimal. Si fractionDigits n'est pas défini, 0 est supposé. Plus précisément, effectuez le Etapes suivantes:

  1. Soit f être ToInteger(fractionDigits). (Si fractionDigits n'est pas défini, Cette étape produit la valeur 0).
  2. Si f < 0 ou f > 20, lève une exception RangeError.
  3. Soit x cette valeur numérique.
  4. Si x est NaN, retournez la chaîne "NaN".
  5. Soit s la chaîne vide.
  6. Si x ≥ 0, passez à l'étape 9.
  7. Soit s "-".
  8. Laissez x = –x.
  9. Si x ≥ 10^21, laissez m = ToString(x) et passez à l’étape 20.
  10. Soit n un entier pour lequel la valeur mathématique exacte de n ÷ 10^f – x est aussi proche de zéro que possible. S'il y en a deux tel n, choisissez le plus grand n.
  11. Si n = 0, insérons m la chaîne "0". Sinon, laissez m être le chaîne constituée des chiffres de la représentation décimale de n (dans l’ordre, sans zéros au début).
  12. Si f = 0, passez à l'étape 20.
  13. Soit k le nombre de caractères de m.
  14. Si k > f, passez à l'étape 18.
  15. Soit z la chaîne constituée par les occurrences f+1–k du caractère '0'.
  16. Soit m la concaténation de chaînes z et m.
  17. Laissez k = f + 1.
  18. Soit a les premiers k–f caractères de m, et b, le f caractères restants de m.
  19. Soit m la concaténation des trois chaînes a, "." et b.
  20. Renvoie la concaténation des chaînes s et m.

La propriété length de la méthode toFixed est 1.

Si la méthode toFixed est appelée avec plusieurs arguments, alors le le comportement n'est pas défini (voir section 15).

Une implémentation est autorisée à étendre le comportement de toFixed pour les valeurs de fractionDigits inférieures à 0 ou supérieures à 20. Dans ce cas toFixed ne jetterait pas nécessairement RangeError pour de telles valeurs.

NOTE La sortie de toFixed peut être plus précise que toString pour certaines valeurs parce que toString n’imprime que suffisamment de chiffres significatifs pour distinguer le nombre des valeurs numériques adjacentes. Par exemple, (1000000000000000128).toString() renvoie "1000000000000000100", alors que (1000000000000000128).toFixed(0) renvoie "1000000000000000128".

11
some

Pour répondre à vos deux d'origine questions/questions:

Math.round (num) vs num.toFixed (0)

Le problème ici réside dans l'idée fausse que ceux-ci devraient toujours donner le même résultat. En fait, ils sont régis par des règles différentes. Regardez les nombres négatifs, par exemple. Parce que Math.round utilise "arrondir la moitié" comme règle, vous verrez que Math.round(-1.5) est évalué à -1 même si Math.round(1.5) est évalué à 2.

Number.prototype.toFixed, d'autre part, utilise ce qui est fondamentalement équivalent à "arrondir la moitié de zéro" comme règle, selon l'étape 6 de la spécification , qui dit essentiellement de traiter les négatifs comme des nombres positifs, puis rajoutez le signe négatif à la fin. Ainsi, (-1.5).toFixed(0) === "-2" et (1.5).toFixed(0) === "2" sont des instructions vraies dans tous les navigateurs conformes aux spécifications. Notez que ces valeurs sont des chaînes, pas des nombres. Notez en outre que -1.5.toFixed(0) et -(1.5).toFixed(0) sont tous deux === -2 (le nombre) en raison de la priorité de l'opérateur.

Incohérences du navigateur

La plupart des navigateurs modernesou du moins ceux que vous pourriez être amené à supporter au moment d'écrire ces lignes à l'exception de IE - doit implémenter correctement les spécifications. (Selon le commentaire de Renee , le problème toFixed que vous avez signalé dans Opera a été corrigé, vraisemblablement depuis qu’ils ont commencé à utiliser le même moteur JS que Chrome.) Il convient de rappeler que même si les spécifications ont été mises en œuvre de manière cohérente dans tous les cas. navigateurs, le comportement défini dans la spécification, en particulier pour les arrondis toFixed, peut encore sembler peu intuitif pour les développeurs JS "simples mortels" qui attendent une vraie précision mathématique - voir Javascript toFixed Not Rounding et cela "fonctionne comme prévu "bug qui a été archivé sur le moteur V8 JS pour des exemples.

Conclusion

En bref, il s’agit de deux fonctions différentes avec deux types de retour différents et deux ensembles de règles différents pour l’arrondi.

Comme d'autres l'ont suggéré, je voudrais également dire "utilisez la fonction qui convient à votre cas d'utilisation particulier" (en prenant un soin particulier à noter les particularités de toFixed, en particulier la mise en œuvre errante d'IE). Personnellement, je serais plutôt enclin à recommander une combinaison explicite de Math.round/ceil/floor, encore une fois, comme d'autres l'ont mentionné. Edit: ... cependant, après avoir lu votre clarification, votre cas d'utilisation (arrondi à un nombre entier) appelle définitivement la fonction bien nommée Math.round.

9
Noyo

toFixed () renvoie une valeur de chaîne. De Javascript: Le guide définitif 

Convertit un nombre en chaîne contenant un nombre spécifié de chiffres après la décimale.

Math.round () retourne un entier.

De toute évidence, toFixed () semble être plus utile pour l'argent, par exemple, 

'$' + 12.34253.toFixed (2) = '12,34 $'

Il semble vraiment dommage que toFixed () ne semble pas arrondir correctement!

6
Matt

Au lieu de toFixed(0), utilisez Math.ceil() ou Math.floor(), selon les besoins.

2
Grant Wagner

Cela semble définitivement le cas, si vous obtenez des réponses incohérentes.

Je peux seulement deviner que votre intention avec toinfixed (0) est de transformer un nombre décimal en entier, auquel cas je recommande Math.floor (). Il y a un peu plus de discussion sur la meilleure façon de le faire dans cette question .

0
Daniel Lew