En JavaScript, les nombres sont représentés à l'aide du format à virgule flottante à double précision de 64 bits selon la norme IEEE 754. Parmi eux, le bit de signe S, les bits d'exposant E et les bits de mantisse M occupent respectivement 1, 11 et 52 bits, et dans laLa spécification ES5il est indiqué que la plage de valeurs des bits d'exposant E est[-1074, 971]。
Résumé des problèmes de précision
De toute évidence, il est impossible de représenter des nombres infinis avec un nombre fini de bits, ce qui donne lieu à une série de problèmes de précision :
- Problèmes de précision des nombres à virgule flottante, tels que
0.1 + 0.2 !== 0.3 - Problèmes de précision des grands nombres, tels que
9999 9999 9999 9999 == 1000 0000 0000 0000 1 - Résultats d'arrondi toFixed inexacts, tels que
1.335.toFixed(2) == 1.33
Les problèmes de précision des nombres à virgule flottante et toFixed appartiennent en fait à la même catégorie de problèmes, tous deux causés par l'incapacité à représenter précisément les nombres à virgule flottante, comme suit :
(1.335).toPrecision(20); // "1.3349999999999999645"
En ce qui concerne le problème de précision des grands nombres, nous pouvons d'abord regarder l'extrait de code suivant :
// 能精确表示的整数范围上限,S为1个0,E为11个0,S为52个1 Math.pow(2, 53) - 1 === Number.MAX_SAFE_INTEGER // true // 能精确表示的整数范围下限,S为1个1,E为11个0,S为52个1 -(Math.pow(2, 53) - 1) === Number.MIN_SAFE_INTEGER // true // 能表示的最大数字,S为1个0,E为971,S为52个1 (Math.pow(2, 53) - 1) * Math.pow(2, 971) === Number.MAX_VALUE // true // 能表示的最接近于0的正数,S为1个0,E为-1074,S为0 Math.pow(2, -1074) === Number.MIN_VALUE // true
D'après ce qui précède, nous pouvons comprendre que[MIN_SAFE_INTEGER, MAX_SAFE_INTEGER]les entiers dans cette plage peuvent être représentés précisément, mais les entiers au-delà de cette plage ne peuvent pas nécessairement être représentés précisément. Cela entraîne ce que l'on appelle le problème de perte de précision des grands nombres.
Approches de résolution
Tout d'abord, examinons comment résoudre le problème de précision de l'arithmétique à virgule flottante. Il existe 3 approches :
- En considérant que l'écart de chaque opération à virgule flottante est très faible (en réalité, ce n'est pas le cas), vous pouvez arrondir le résultat à une précision spécifiée, par exemple, vous pouvez
parseFloat(result.toFixed(12)); - Convertir les nombres à virgule flottante en entiers pour le calcul, puis diviser le résultat. Par exemple, 0.1 + 0.2 peut être converti en
(1*2)/3。 - Convertir les nombres à virgule flottante en chaînes de caractères et simuler le processus de calcul réel.
Examinons d'abord la première approche. Dans la plupart des cas, elle peut produire des résultats corrects, mais pour certains cas extrêmes, toFixed à 12 chiffres ne suffit pas, par exemple :
210000 * 10000 * 1000 * 8.2 // 17219999999999.998 parseFloat(17219999999999.998.toFixed(12)); // 17219999999999.998,而正确结果为 17220000000000
Pour les situations ci-dessus, si vous voulez que le résultat soit correct, vous deveztoFixed(2), ce qui est manifestement inacceptable.
Regardons maintenant la deuxième approche, par exemplenumber-precisionCette bibliothèque utilise cette approche, mais elle a aussi des problèmes, par exemple :
// 这两个浮点数,转化为整数之后,相乘的结果已经超过了 MAX_SAFE_INTEGER 123456.789 * 123456.789 // 转化为 (123456789 * 123456789)/1000000,结果是 15241578750.19052
Par conséquent, finalement, nous envisageons d'utiliser la troisième approche. Actuellement, il existe déjà de nombreuses bibliothèques matures, telles quebignumber.js,decimal.js, etbig.jsetc. Nous pouvons choisir les outils correspondants en fonction de nos besoins. De plus, ces bibliothèques ne se contentent pas de résoudre les problèmes de précision de l'arithmétique à virgule flottante, mais elles prennent également en charge les opérations sur grands nombres et corrigent les résultats inexacts de la méthode native toFixed.
Remarque
Il y a un autre problème lié au calcul en JavaScript, à savoirMath.round(x), bien qu'il ne cause pas de problèmes de précision, il présente un petit piège facile à négliger. Voici sa stratégie d'arrondi :
- Si la partie fractionnaire est supérieure à 0.5, arrondir à l'entier suivant ayant une valeur absolue plus grande.
- Si la partie fractionnaire est inférieure à 0.5, arrondir à l'entier suivant ayant une valeur absolue plus petite.
- Si la partie fractionnaire est égale à 0.5, alorsarrondir à l'entier suivant en direction de l'infini positif。
Par conséquent, pour Math.round(-1.5), le résultat est -1, ce qui peut ne pas être le résultat que nous voulons.
Bien sûr, big.js et les autres bibliothèques mentionnées ci-dessus fournissent toutes leurs propres fonctions d'arrondi, et permettent de spécifier des règles d'arrondi pour éviter ce problème.
Adresse d'origine : https://xwjgo.github.io/2018/03/17/js%E4%B8%AD%E7%B2%BE%E5%BA%A6%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/