Maison >interface Web >js tutoriel >Number (). Tofixed () Erreurs d'arrondi: cassé mais réparable

Number (). Tofixed () Erreurs d'arrondi: cassé mais réparable

Jennifer Aniston
Jennifer Anistonoriginal
2025-02-15 10:02:12155parcourir

Number().toFixed() Rounding Errors: Broken But Fixable

Cet article a été initialement publié sur le blog de David Kaye

J'ai trouvé des erreurs d'arrondi dans le nombre (). Tofixed () dans tous les environnements JavaScript que j'ai essayés (Chrome, Firefox, Internet Explorer, Brave et Node.js). Étonnamment, le correctif est très simple. Continuez à lire ...

Points clés

  • NUBER () de JavaScript (). Tofixed () a des erreurs d'arrondi dans plusieurs environnements (Chrome, Firefox, Internet Explorer, Brave et Node.js). Cette erreur est incohérente et se produit lorsque le dernier nombre significatif de la partie décimale est de 5, et que la longueur de finition n'est réduite que d'une par une dans la partie décimale, et une valeur entière différente est appliquée.
  • Lorsque la valeur contient une décimale, cette erreur peut être corrigée en ajoutant "1" à la fin de la version de chaîne de la valeur à modifier. Cette solution ne nécessite pas de réexamen des opérations binaires ou à virgule flottante de niveau inférieur.
  • La fonction tofixed () peut être remplacée à l'aide de polyfill jusqu'à ce que toutes les implémentations soient modifiées. Cependant, tout le monde n'est pas satisfait de cette approche. Polyfill implique la fonction prototype.tofixée et vérifie si la longueur du résultat est nulle.

Réchauffement

Lors de la modification d'une fonction de formatage numérique qui remplit la même fonction que intl.numberformat # format (), j'ai trouvé une erreur d'arrondi dans cette version dans tofixed ().

<code class="language-javascript">(1.015).toFixed(2) // 返回 "1.01" 而不是 "1.02"</code>

Le test échoué est situé à la ligne 42 ici. Je ne l'ai pas trouvé avant décembre 2017, ce qui m'a incité à vérifier d'autres problèmes.

Voir mon tweet sur cette question:

  • Alerte d'erreur
  • Comparez avec intl.numberformat
  • Résumé
  • polyfill

Rapport d'erreur

Il y a un long historique de rapports d'erreurs lors de l'utilisation de TofixEd ().

  • chrome
  • Firefox
  • Firefox, voir aussi
  • Internet Explorer

Voici quelques petits exemples de problèmes de stackOverflow à propos de ce problème:

  • Exemple 1
  • Exemple 2

Habituellement, ceux-ci indiquent a a erreurs d'une valeur , mais aucune plage ou modèle de valeurs qui renvoie le résultat d'erreur (au moins je ne l'ai pas trouvé , J'ai peut-être manqué quelque chose). Cela permet aux programmeurs de se concentrer sur de petits problèmes et de ne pas voir de modèles plus grands. Je ne leur en blâme pas.

Trouver le motif

Les résultats inattendus basés sur l'entrée doivent provenir du mode partagé dans l'entrée. Ainsi, au lieu de revoir la spécification de Number (). Tofixed (), je me suis concentré sur le test avec une série de valeurs pour déterminer où les erreurs apparaissent dans chaque série.

Fonction de test

J'ai créé la fonction de test suivante pour effectuer une opération TofixEd () sur une série d'entiers de 1 à maxvalue, ajoutant une décimale de .005 à chaque entier. Le paramètre fixe (chiffre numérique) de tofixed () est calculé en fonction de la longueur de la valeur décimale.

<code class="language-javascript">(1.015).toFixed(2) // 返回 "1.01" 而不是 "1.02"</code>

Utilisation

L'exemple suivant effectue une opération sur les entiers 1 à 128, ajoute une décimale .015 pour chaque entier et renvoie un tableau de résultats "inattendu". Chaque résultat contient des champs donnés, attendus et réels. Ici, nous utilisons ce tableau et imprimons chaque élément.

<code class="language-javascript">function test({fraction, maxValue}) {
  fraction = fraction.toString()
  var fixLength = fraction.split('.')[1].length - 1

  var last = Number(fraction.charAt(fraction.length - 1))
  var fixDigit = Number(fraction.charAt(fraction.length - 2))

  last >= 5 && (fixDigit = fixDigit + 1)

  var expectedFraction = fraction.replace(/[\d]{2,2}$/, fixDigit)

  return Array(maxValue).fill(0)
    .map(function(ignoreValue, index) {
      return index + 1
    })
    .filter(function(integer) {
      var number = integer + Number(fraction)
      var actual = number.toFixed(fixLength)
      var expected = Number(number + '1').toFixed(fixLength)

      return expected != actual
    })
    .map(function(integer) {
      var number = Number(integer) + Number(fraction)
      return {
        given: number.toString(),
        expected: (Number(integer.toFixed(0)) + Number(expectedFraction)).toString(),
        actual: number.toFixed(fixLength)
      }
    })
}</code>

sortie

Pour cette situation, il y a 6 résultats inattendus.

<code class="language-javascript">test({ fraction: .015, maxValue: 128 })
  .forEach(function(item) {
    console.log(item)
  })</code>

Découvrez

J'ai trouvé que cette erreur contient trois parties:

  1. Le dernier chiffre significatif de la partie décimale doit être de 5 (.015 et .01500 produire le même résultat).
  2. La longueur de finition doit être réduite d'une seule partie décimale.
  3. Avec différentes valeurs entières appliquées, des erreurs se produisent de manière incohérente.

incohérent?

Par exemple, pour les entiers 1 à 128, (valeur) .tofixée (2) utilise des 3 décimales différentes se terminant par 5, ce qui entraîne les résultats suivants:

  • Les nombres tordus se terminant par .005 échouent toujours (!!)
  • Les nombres tordus se terminant par 0,015 échouent pour 1, puis 4 à 7, puis 128
  • Les nombres tordus se terminant par 0,025 échouent pour 1, 2, 3, puis 16 à 63
  • Les nombres tordus se terminant par 0,035 échouent pour 1, puis 32 à 128
  • Les nombres tordus se terminant par 0,045 échouent pour 1 à 15, puis 128
  • Les nombres tordus se terminant par 0,055 échouent pour 1, puis 4 à 63
  • Les nombres tordus se terminant par 0,065 échouer pour 1, 2, 3, puis 8 à 15, puis 32 à 128
  • Les nombres tordus se terminant par 0,075 échouent pour 1, puis 8 à 31, puis 128
  • Les nombres tordus se terminant par 0,085 échouer pour 1 à 7, puis 64 à 127 (!!)
  • Les nombres tordus se terminant par 0,095 échouent pour 1, puis 4 à 7, puis 16 à 128

Ceux qui en savent plus sur les mathématiques binaires et à points flottants que moi pourra peut déduire la cause profonde. Je laisse ceci au lecteur comme un exercice.

Correction de tofixed ()

Fixé par Plus d'une décimale pour corriger la valeur est toujours arrondie correctement; par exemple, (1.0151). Le test et le polyfill utilisent ces connaissances pour les contrôles d'exactitude.

Cela signifie que toutes les implémentations Tofixed () ont une solution simple: si la valeur contient une décimale, ajoutez "1" à la fin de la version de chaîne de la valeur à modifier. Cela peut ne pas être "conforme aux spécifications", mais cela signifie que nous obtiendrons les résultats attendus sans avoir à revisiter le niveau inférieur des opérations binaires ou de points flottants.

polyfill

Avant que toutes les implémentations ne soient modifiées, si vous êtes prêt à le faire (tout le monde ne le veut pas), vous pouvez remplacer Tofixed () avec le polyfill suivant.

<code>Object { given: "1.015", expected: "1.02", actual: "1.01" }
Object { given: "4.015", expected: "4.02", actual: "4.01" }
Object { given: "5.015", expected: "5.02", actual: "5.01" }
Object { given: "6.015", expected: "6.02", actual: "6.01" }
Object { given: "7.015", expected: "7.02", actual: "7.01" }
Object { given: "128.015", expected: "128.02", actual: "128.01" }</code>

Ensuite, exécutez à nouveau le test et vérifiez si la longueur du résultat est nulle.

<code class="language-javascript">(1.005).toFixed(2) == "1.01" || (function(prototype) {
  var toFixed = prototype.toFixed

  prototype.toFixed = function(fractionDigits) {
    var split = this.toString().split('.')
    var number = +(!split[1] ? split[0] : split.join('.') + '1')

    return toFixed.call(number, fractionDigits)
  }
}(Number.prototype));</code>

ou exécutez simplement la conversion initiale qui démarre cet article.

<code class="language-javascript">test({ fraction: .0015, maxValue: 516 }) // Array []
test({ fraction: .0015, maxValue: 516 }).length // 0</code>

Merci d'avoir lu!

FAQS (FAQ) sur le numéro de JavaScript.tofixed () Méthode

Quel est le but de la méthode Number.tofixed () en javascript?

La méthode Number.tofixed () dans JavaScript est utilisée pour formater les nombres en utilisant la notation à point fixe. Il renvoie une représentation de chaîne d'un nombre qui n'utilise pas de représentation exponentielle et a exactement le nombre spécifié de chiffres après le point décimal. Si nécessaire, le nombre est arrondi et la chaîne de résultat a un point décimal après la longueur.

Pourquoi la méthode Number.tofixed () donne-t-elle parfois des résultats inexacts?

En raison de la façon dont JavaScript gère les nombres de points flottants binaires, la méthode Number.tofixed () donne parfois des résultats inexacts. JavaScript utilise des nombres de points flottants binaires, qui ne peuvent pas représenter avec précision toutes les décimales. Cette inexactitude peut conduire à des résultats inattendus lorsque le nombre est arrondi à un nombre décimal spécifique en utilisant la méthode Number.tofixed ().

Comment corriger l'erreur d'arrondi dans Number.tofixed () Méthode?

Une façon de corriger les erreurs d'arrondi dans Number.tofixed () est d'utiliser une fonction d'arrondi personnalisée. Cette fonction peut prendre en compte les caractéristiques du traitement numérique de JavaScript et fournir des résultats plus précis. Par exemple, vous pouvez utiliser une fonction qui multiplie le nombre par une puissance de 10, le tourne vers l'entier le plus proche et le divise par la même puissance de 10.

Puis-je utiliser la méthode Number.tofixed () avec des valeurs non nucères?

Non, la méthode Number.tofixed () ne peut être utilisée qu'avec des valeurs numériques. Si vous essayez de l'utiliser avec une valeur non numérique, JavaScript lancera un type EERROR. Si vous devez utiliser cette méthode avec une valeur qui peut ne pas être un nombre, vous devez d'abord vérifier si la valeur est un nombre.

Existe-t-il une différence de performance entre la méthode Number.tofixed () et d'autres méthodes d'arrondi?

La différence de performance entre la méthode

nombre.tofixed () et d'autres méthodes d'arrondi est généralement négligeable. Cependant, si vous effectuez beaucoup d'opérations, l'utilisation d'une fonction d'arrondi personnalisée peut être légèrement plus rapide que d'utiliser la méthode numéro.tofixed ().

Puis-je utiliser la méthode numéro.tofixed () pour arrondir à plus de 20 décimales?

Non, la méthode Number.tofixed () ne peut qu'arrondir à un maximum de 20 décimales. Si vous essayez de contourner à plus de 20 décimales, JavaScript lancera un RangeError.

Comment gérer les nombres négatifs?

Number.tofixed () La méthode gère les nombres négatifs de la même manière que les nombres positifs. Il arrond la valeur absolue du nombre au nombre spécifié de décimales et ajoute un signe négatif.

Puis-je utiliser la méthode numéro.tofixed () dans tous les environnements JavaScript?

Oui, la méthode numéro.tofixed () est une partie standard de JavaScript et doit être disponible dans tous les environnements JavaScript. Cependant, comme différents moteurs JavaScript gèrent les nombres différemment, les résultats peuvent ne pas être exactement les mêmes dans tous les environnements.

Que se passe-t-il si je ne transmets aucun argument à la méthode numéro.tofixed ()?

Si vous ne transmettez aucun argument à la méthode Number.tofixed (), il arrivera à 0 décimal par défaut. Cela signifie qu'il complètera le numéro à l'entier le plus proche.

Puis-je utiliser la méthode numéro.tofixed () avec de grands nombres?

Oui, vous pouvez utiliser la méthode numéro.tofixed () avec de grands nombres. Cependant, n'oubliez pas que JavaScript ne peut représenter avec précision que les nombres jusqu'à 2 ^ 53 - 1. Si vous essayez d'utiliser la méthode Number.tofixed () avec des nombres supérieurs à ce nombre, il peut ne pas donner de résultats précis.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn