Question Comment gérer la précision du nombre à virgule flottante en JavaScript?


J'ai le script de test factice suivant:

function test(){
    var x = 0.1 * 0.2;
    document.write(x);
}
test();

Cela va imprimer le résultat 0.020000000000000004 alors qu'il devrait juste imprimer 0.02 (si vous utilisez votre calculatrice). Pour autant que j'ai compris cela est dû à des erreurs dans la précision de multiplication à virgule flottante.

Est-ce que quelqu'un a une bonne solution pour que, dans ce cas, j'obtienne le résultat correct 0.02? Je sais qu'il y a des fonctions comme toFixed ou l'arrondi serait une autre possibilité, mais je voudrais vraiment que le nombre entier soit imprimé sans aucun découpage ni arrondi. Je voulais juste savoir si l'un de vous a une solution élégante et agréable.

Bien sûr, sinon je vais arrondir à environ 10 chiffres ou plus.


435
2017-09-22 07:34


origine


Réponses:


Du Guide de virgule flottante:

Que puis-je faire pour éviter ce problème?

Cela dépend de quel type de   calculs que vous faites.

  • Si vous avez vraiment besoin de vos résultats pour additionner exactement, surtout quand vous   travailler avec de l'argent: utiliser une décimale spéciale   Type de données.
  • Si vous ne voulez pas voir toutes ces décimales supplémentaires: simplement   formater votre résultat arrondi à un fixe   nombre de décimales lorsque   l'afficher
  • Si vous n'avez pas de type de données décimal disponible, une alternative est de travailler   avec des entiers, par ex. faire de l'argent   calculs entièrement en cents. Mais   c'est plus de travail et a quelques   désavantages.

Notez que le premier point ne s'applique que si vous avez vraiment besoin d'une précision précise décimal comportement. La plupart des gens n'en ont pas besoin, ils sont simplement irrités que leurs programmes ne fonctionnent pas correctement avec des nombres comme 1/10 sans se rendre compte qu'ils ne cligneraient même pas la même erreur si cela se produisait avec 1/3.

Si le premier point s'applique vraiment à vous, utilisez BigDecimal pour JavaScript, qui n'est pas du tout élégant, mais résout le problème plutôt que de fournir une solution imparfaite.


380
2017-08-09 12:30



J'aime la solution de Pedro Ladaria et j'utilise quelque chose de similaire.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

Contrairement à la solution Pedros, le résultat sera arrondi à 0,999 ... répétitif et sera précis à plus / moins un sur le chiffre le moins significatif.

Remarque: Lorsque vous utilisez des flottants de 32 ou 64 bits, vous devez utiliser toPrecision (7) et toPrecision (15) pour obtenir les meilleurs résultats. Voir cette question pour info sur pourquoi.


96
2017-09-04 23:00



Pour les mathématiquement inclinés: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

L'approche recommandée consiste à utiliser des facteurs de correction (multiplier par une puissance appropriée de 10 pour que l'arithmétique se produise entre entiers). Par exemple, dans le cas de 0.1 * 0.2, le facteur de correction est 10et vous effectuez le calcul:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

Une solution (très rapide) ressemble à ceci:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

Dans ce cas:

> Math.m(0.1, 0.2)
0.02

Je recommande vraiment d'utiliser une bibliothèque testée comme SinfulJS


55
2017-09-20 02:55



Êtes-vous seulement en train de multiplier? Si c'est le cas, vous pouvez utiliser à votre avantage un secret sur l'arithmétique décimale. C'est ça NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. C'est-à-dire que si nous avons 0.123 * 0.12alors on sait qu'il y aura 5 décimales car 0.123 a 3 décimales et 0.12 a deux. Donc, si JavaScript nous a donné un numéro comme 0.014760000002 nous pouvons arrondir à la 5e décimale sans crainte de perdre en précision.


34
2017-08-11 14:33



Vous cherchez un sprintf implémentation pour JavaScript, de sorte que vous pouvez écrire des flottants avec de petites erreurs (car ils sont stockés au format binaire) dans un format que vous attendez.

Essayer javascript-sprintf, tu appellerais ça comme ça:

var yourString = sprintf("%.2f", yourNumber);

pour imprimer votre numéro comme un flottant avec deux décimales.

Vous pouvez également utiliser Number.toFixed ()  à des fins d'affichage, si vous préférez ne pas inclure plus de fichiers simplement pour l'arrondi à virgule flottante à une précision donnée.


24
2017-08-06 08:38



Cette fonction détermine la précision nécessaire à partir de la multiplication de deux nombres à virgule flottante et renvoie un résultat avec la précision appropriée. Élégant si ce n'est pas.

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

14
2017-08-08 03:35



var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---ou---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---aussi---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- un péché ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

13
2017-08-08 03:16



je trouve BigNumber.js répond à mes besoins.

Une bibliothèque JavaScript pour l'arithmétique décimale et non décimale de précision arbitraire.

Il a bien Documentation et l'auteur est très diligent en répondant aux commentaires.

Le même auteur a 2 autres bibliothèques similaires:

Big.js

Une petite bibliothèque JavaScript rapide pour l'arithmétique décimale de précision arbitraire. La petite soeur de bignumber.js.

et Decimal.js

Un type décimal de précision arbitraire pour JavaScript.

Voici du code utilisant BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>


12
2017-12-02 14:35



Vous devez juste décider combien de chiffres décimaux vous voulez réellement - ne peut pas avoir le gâteau et le manger aussi :-)

Les erreurs numériques s'accumulent à chaque nouvelle opération et si vous ne la coupez pas tôt, elle ne fera que croître. Les bibliothèques numériques qui présentent des résultats qui semblent propres coupent simplement les 2 derniers chiffres à chaque étape, les co-processeurs numériques ont également une longueur "normale" et "complète" pour la même raison. Les cuf-offs sont bon marché pour un processeur mais très coûteux pour vous dans un script (multiplier et diviser et utiliser pov (...)). Une bonne lib maths fournirait le plancher (x, n) pour faire la coupure pour vous.

Donc, à tout le moins, vous devriez faire un var / constant global avec pov (10, n) - ce qui signifie que vous avez choisi la précision dont vous avez besoin :-) Alors faites:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

Vous pouvez également continuer à faire des calculs et seulement à la fin - en supposant que vous affichez et ne faites pas si-si avec les résultats. Si vous pouvez le faire, alors .toFixed (...) pourrait être plus efficace.

Si vous faites if-s / comparisons et que vous ne voulez pas couper, vous avez également besoin d'une petite constante, généralement appelée eps, qui correspond à une décimale supérieure à l'erreur maximale attendue. Supposons que votre limite est la dernière des deux dernières décimales - alors votre eps a 1 à la 3ème place de la dernière (3ème moins importante) et vous pouvez l'utiliser pour comparer si le résultat se situe dans les limites de l'ESP attendues (0.02 -eps <0.1 * 0,2 <0,02 + eps).


9
2017-08-06 08:30