Comment gérer la précision des nombres à virgule flottante en JavaScript?

620

J'ai le script de test factice suivant:

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

Cela imprimera le résultat 0.020000000000000004alors qu'il devrait simplement imprimer 0.02(si vous utilisez votre calculatrice). Pour autant que je sache, cela est dû à des erreurs dans la précision de multiplication en virgule flottante.

Quelqu'un a-t-il une bonne solution pour que dans ce cas j'obtienne le bon résultat 0.02? Je sais qu'il y a des fonctions comme toFixedou l'arrondi serait une autre possibilité, mais j'aimerais vraiment que le nombre entier soit imprimé sans couper ni arrondir. 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.

Juri
la source
118
En fait, l'erreur est due au fait qu'il n'existe aucun moyen de mapper 0.1à un nombre à virgule flottante binaire fini.
Aaron Digulla
10
La plupart des fractions ne peuvent pas être converties en décimales avec une précision exacte. Une bonne explication est ici: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg
7
doublon possible de Math's JavaScript est-il cassé?
epascarello
53
@SalmanA: Que votre runtime JavaScript vous cache ce problème ne signifie pas que je me trompe.
Aaron Digulla
5
En désaccord avec Aaron, il existe des moyens de coder 0.1 parfaitement et complètement en binaire. Mais IEEE 754 ne définit pas nécessairement cela. Imaginez une représentation où vous coderiez la partie entière en binaire d'une part, la partie décimale d'autre part, jusqu'à n décimales, en binaire aussi, comme un entier normal> 0, et enfin, la position du point décimal . Eh bien, vous représenteriez 0,1 parfaitement, sans erreur. D'ailleurs, puisque JS utilise un nombre fini de décimales en interne, ils pourraient aussi bien coder les tripes pour ne pas faire cette erreur sur les dernières décimales.
Fabien Haddadi

Réponses:

469

Du guide à virgule flottante :

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

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

  • Si vous avez vraiment besoin que vos résultats s'additionnent exactement, surtout lorsque vous travaillez avec de l'argent: utilisez un type de données décimal spécial.
  • Si vous ne voulez tout simplement pas voir toutes ces décimales supplémentaires: formatez simplement votre résultat arrondi à un nombre fixe de décimales lors de son affichage.
  • Si vous n'avez pas de type de données décimal disponible, une alternative est de travailler avec des entiers, par exemple faire des calculs d'argent entièrement en cents. Mais cela demande plus de travail et présente certains inconvénients.

Notez que le premier point ne s'applique que si vous avez vraiment besoin d'un comportement décimal précis et précis . 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 cligneront même pas à la même erreur si elle se produisait avec 1/3.

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

Michael Borgwardt
la source
11
J'ai remarqué votre lien mort pour BigDecimal et en cherchant un miroir, j'ai trouvé une alternative appelée BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr
4
@ bass-t: Oui, mais les flottants peuvent représenter exactement des entiers jusqu'à la longueur de la signification, et selon la norme ECMA, c'est un flottant de 64 bits. Il peut donc représenter exactement des nombres entiers jusqu'à 2 ^ 52
Michael Borgwardt
5
@Karl: La fraction décimale 1/10 ne peut pas être représentée comme une fraction binaire finie en base 2, et c'est ce que sont les nombres Javascript. Donc , il est en fait exactement le même problème.
Michael Borgwardt
12
J'ai appris aujourd'hui que même les entiers ont des problèmes de précision en javascript. Considérez que console.log(9332654729891549)réellement imprime 9332654729891548(c.-à-d. Par un!)
mlathe
12
@mlathe: Doh .. ;P... Entre 2⁵²= 4,503,599,627,370,496et 2⁵³= 9,007,199,254,740,992les nombres représentables sont exactement les entiers . Pour la plage suivante, de 2⁵³à 2⁵⁴, tout est multiplié par2 , donc les nombres représentables sont les nombres pairs , etc. Inversement, pour la plage précédente de 2⁵¹à 2⁵², l'espacement est 0.5, etc. Ceci est dû à une simple augmentation | diminution de la base | radix 2 | exposant binaire dans / de la valeur flottante 64 bits (ce qui explique à son tour le comportement "inattendu" rarement documenté des toPrecision()valeurs entre 0et 1).
GitaarLAB
126

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, cela arrondira à 0,999 ... répétition et est précis à plus / moins un sur le chiffre le moins significatif.

Remarque: Lorsque vous traitez avec des flottants 32 ou 64 bits, vous devez utiliser toPrecision (7) et toPrecision (15) pour de meilleurs résultats. Voir cette question pour savoir pourquoi.

linux_mike
la source
21
Une raison pour laquelle vous en avez choisi 12?
qwertymk
18
toPrecisionrenvoie une chaîne au lieu d'un nombre. Ce n'est pas toujours souhaitable.
SStanley
7
parseFloat (1.005) .toPrecision (3) => 1.00
Peter
5
@ user2428118, je sais, je voulais montrer l'erreur d'arrondi, le résultat est 1,00 au lieu de 1,01
Peter
9
Ce que @ user2428118 a dit n'est peut-être pas assez évident: (9.99*5).toPrecision(2)= 50 au lieu de 49,95 car toPrecision compte le nombre entier, pas seulement les décimales. Vous pouvez ensuite utiliser toPrecision(4), mais si votre résultat est> 100, alors vous n'avez plus de chance, car cela permettra aux trois premiers chiffres et à une décimale, de cette façon de déplacer le point et de le rendre plus ou moins inutilisable. J'ai fini par utiliser à la toFixed(2)place
aexl
79

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

L'approche recommandée est d'utiliser des facteurs de correction (multiplier par une puissance appropriée de 10 pour que l'arithmétique se fasse entre les 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 à:

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

SheetJS
la source
1
Il adore cette solution de contournement élégante, mais ne semble pas être parfaite: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) renvoie 115.10000000000002
nicolallias
3
Math.m (10,2332226616) me donne "-19627406800" qui est une valeur négative ... J'espère qu'il doit y avoir une limite supérieure - c'est peut-être la cause de ce problème. Veuillez suggérer
Shiva Komuravelly
1
Tout cela a l'air génial, mais semble avoir une ou deux erreurs quelque part.
MrYellow
5
Solution très rapide, a-t-il dit ... solution défectueuse que personne n'a jamais dite.
Cozzbie
2
N'utilisez pas le code ci-dessus. Ce n'est absolument pas une «solution rapide» si cela ne fonctionne pas. Il s'agit d'une question liée aux mathématiques, une précision est donc requise.
Drenai
49

Faites-vous uniquement de la multiplication? Si c'est le cas, vous pouvez utiliser à votre avantage un bon secret sur l'arithmétique décimale. Voilà NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. C'est-à-dire que si nous avons 0.123 * 0.12alors nous savons qu'il y aura 5 décimales car 0.123a 3 décimales et en 0.12a deux. Ainsi, si JavaScript nous a donné un nombre tel que 0.014760000002nous pouvons arrondir en toute sécurité à la 5ème décimale sans craindre de perdre en précision.

Nate Zaugg
la source
6
... et comment obtenir le nombre exact de décimales.
line-o
7
0,5 * 0,2 = 0,10; Vous pouvez toujours tronquer à 2 décimales (ou moins). Mais il n'y aura jamais de nombre ayant une signification mathématique au-delà de cette loi.
Nate Zaugg
3
Avez-vous une citation pour cela? Notez également qu'il n'en va pas de même pour la division.
Griffin
3
@NateZaugg vous ne pouvez pas tronquer les décimales débordantes, vous devez arrondir le montant, car 2090,5 * 8,61 est 17999,205 mais en flottant c'est 17999,204999999998
Lostfields
3
@Lostfields - Vous avez raison! J'ai mis à jour ma réponse.
Nate Zaugg
29

Vous recherchez une sprintfimplémentation pour JavaScript, afin que vous puissiez y écrire des flottants avec de petites erreurs (car ils sont stockés au format binaire) dans un format que vous attendez.

Essayez javascript-sprintf , vous l' appeleriez ainsi:

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

pour imprimer votre numéro sous forme de 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 avec une précision donnée.

Douglas
la source
4
Je pense que c'est la solution la plus propre. À moins que vous n'ayez vraiment besoin d'un résultat de 0,02, la petite erreur est négligeable. Il semble que ce qui est important, c'est que votre numéro s'affiche bien, pas que vous ayez une précision arbitraire.
Long Ouyang
2
Pour l'affichage, c'est en effet la meilleure option, pour les calculs compliqués, vérifiez la réponse de Borgwardt.
Non disponible du
4
Mais là encore, cela renverra exactement la même chaîne que yourNumber.toFixed (2).
Robert
27

Je trouve que BigNumber.js répond à mes besoins.

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

Il a une bonne documentation et l'auteur répond très diligemment aux commentaires.

Le même auteur possède 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 sœur 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>

Ronnie Overby
la source
3
À mon avis, utiliser une bibliothèque est certainement le meilleur choix.
Anthony
1
À partir de ce lien github.com/MikeMcl/big.js/issues/45 bignumber.js -> financial decimal.js -> scientific big.js -> ???
vee le
20
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
Shawndumas
la source
4
Je pense que cela poserait le même problème en conséquence. Vous renvoyez un point flottant, donc une grande chance que la valeur de retour soit également "incorrecte".
Gertjan
1
Très intelligent et utile, +1.
Jonatas Walker
18

Cette fonction déterminera la précision requise à partir de la multiplication de deux nombres à virgule flottante et retournera un résultat avec la précision appropriée. Élégant même s'il ne l'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); 
}
Gabriel
la source
Ew. Oui, convertissons les nombres en chaînes pour les mathématiques en virgule flottante et proposons également cela comme réponse.
Andrew
17

Étonnamment, cette fonction n'a pas encore été publiée bien que d'autres en aient des variantes similaires. Il provient des documents Web MDN pour Math.round (). Il est concis et permet une précision variable.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // sortie attendue: 1234,6

console.log (precisionRound (1234.5678, -1)); // sortie attendue: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

MISE À JOUR: 20 août 2019 Je viens de remarquer cette erreur. Je pense que c'est dû à une erreur de précision en virgule flottante avec Math.round ().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Ces conditions fonctionnent correctement:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Réparer:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

Cela ajoute juste un chiffre à droite lors de l'arrondi des décimales. MDN a mis à jour la page Math.round afin que quelqu'un puisse peut-être fournir une meilleure solution.

HelloWorldPeace
la source
mauvaise réponse. 10.2 retournera toujours 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
@ Žilvinas Le lien JSBin que vous avez publié n'utilise pas la fonction MDN répertoriée ci-dessus. Je pense que votre commentaire s'adresse à la mauvaise personne.
HelloWorldPeace
13

Vous n'avez qu'à vous décider sur le nombre de chiffres décimaux que vous voulez réellement - vous ne pouvez pas avoir le gâteau et le manger aussi :-)

Des erreurs numériques s'accumulent à chaque nouvelle opération et si vous ne les interrompez pas tôt, cela ne fera qu'augmenter. Les bibliothèques numériques qui présentent des résultats qui ont l'air propres coupent simplement les 2 derniers chiffres à chaque étape, les coprocesseurs 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 chers pour vous dans un script (multiplier et diviser et utiliser pov (...)). Une bonne bibliothèque de mathématiques fournirait un plancher (x, n) pour faire la coupure pour vous.

Donc, au minimum, vous devriez faire var / constant global avec pov (10, n) - ce qui signifie que vous avez décidé de la précision dont vous avez besoin :-) Ensuite, faites:

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

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

Si vous faites des comparaisons if-s / et que vous ne voulez pas couper, vous avez également besoin d'une petite constante, généralement appelée eps, qui est une décimale plus élevée que l'erreur maximale attendue. Supposons que votre seuil de coupure correspond aux deux dernières décimales - alors votre eps a 1 à la 3ème place de la dernière (3ème moins significatif) et vous pouvez l'utiliser pour comparer si le résultat est dans la plage eps attendue (0,02 -eps <0,1 * 0,2 <0,02 + eps).

ZXX
la source
Vous pouvez également ajouter 0,5 afin d'arrondir un pauvre: Math.floor (x * PREC_LIM + 0,5) / PREC_LIM
cmroanirgo
Notez cependant que , par exemple Math.floor(-2.1)est -3. Alors peut-être utiliser par exempleMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM
Pourquoi floorau lieu de round?
Quinn Comendant
12

Vous pouvez utiliser parseFloat()et toFixed()si vous souhaitez contourner ce problème pour une petite opération:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;
Softwareddy
la source
11

La fonction round () de phpjs.org fonctionne bien: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
À M
la source
2
@jrg Par convention, les nombres se terminant par un "5" sont arrondis au pair le plus proche (car toujours arrondir vers le haut ou vers le bas introduirait un biais dans vos résultats). Par conséquent, 4,725 arrondi à deux décimales devrait en effet être 4,72.
Mark A. Durham
9

0.6 * 3 c'est génial!)) Pour moi, cela fonctionne bien:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Très très simple))

Олег Всильдеревьев
la source
Est-ce que cela fonctionnerait avec quelque chose comme 8.22e-8 * 1.3?
Paul Carlton
0,6 x 3 = 1,8, le code vous donne des résultats à 2 ... donc pas bon.
Zyo
@Zyo Il renvoie 1,8 dans ce cas. Comment l'avez-vous géré?
Drenai
Intéressant. Vous pouvez échanger les opérateurs de multiplication et de division dans cela et cela fonctionne également.
Andrew
9

Notez que pour un usage général, ce comportement est susceptible d'être acceptable.
Le problème se pose lors de la comparaison de ces valeurs à virgule flottante pour déterminer une action appropriée.
Avec l'avènement d'ES6, une nouvelle constante Number.EPSILONest définie pour déterminer la marge d'erreur acceptable:
donc au lieu d'effectuer la comparaison comme celle-ci

0.1 + 0.2 === 0.3 // which returns false

vous pouvez définir une fonction de comparaison personnalisée, comme ceci:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Source: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon

user10089632
la source
Dans mon cas, Number.EPSILON était trop petit, ce qui a entraîné par exemple0.9 !== 0.8999999761581421
Tom
8

Le résultat que vous avez est correct et assez cohérent sur toutes les implémentations en virgule flottante dans différents langages, processeurs et systèmes d'exploitation - la seule chose qui change est le niveau de l'inexactitude lorsque le flottant est en fait un double (ou supérieur).

0,1 en virgule flottante binaire équivaut à 1/3 en décimal (c'est-à-dire 0,3333333333333 ... pour toujours), il n'y a tout simplement aucun moyen précis de le gérer.

Si vous traitez toujours avec des flotteurs attendez-vous à de petites erreurs d'arrondi, vous devrez donc toujours arrondir le résultat affiché à quelque chose de sensible. En retour, vous obtenez une arithmétique très très rapide et puissante car tous les calculs sont dans le binaire natif du processeur.

La plupart du temps, la solution n'est pas de passer à l'arithmétique à virgule fixe, principalement parce que c'est beaucoup plus lent et 99% du temps, vous n'avez tout simplement pas besoin de la précision. Si vous avez affaire à des choses qui nécessitent ce niveau de précision (par exemple, les transactions financières), Javascript n'est probablement pas le meilleur outil à utiliser de toute façon (comme vous voulez appliquer les types à virgule fixe, un langage statique est probablement mieux ).

Vous cherchez la solution élégante, alors je crains que ce soit le cas: les flotteurs sont rapides mais ont de petites erreurs d'arrondi - toujours arrondis à quelque chose de sensé lors de l'affichage de leurs résultats.

Keith
la source
8

Pour éviter cela, vous devez travailler avec des valeurs entières au lieu de virgules flottantes. Donc, lorsque vous voulez avoir une précision de 2 positions, travaillez avec les valeurs * 100, pour 3 positions, utilisez 1000. Lors de l'affichage, vous utilisez un formateur pour mettre dans le séparateur.

De nombreux systèmes omettent de travailler avec des décimales de cette façon. C'est la raison pour laquelle de nombreux systèmes fonctionnent avec des cents (en entier) au lieu de dollars / euros (en virgule flottante).

Gertjan
la source
7

Problème

Le point flottant ne peut pas stocker toutes les valeurs décimales exactement. Ainsi, lors de l'utilisation de formats à virgule flottante, il y aura toujours des erreurs d'arrondi sur les valeurs d'entrée. Les erreurs sur les entrées résultent bien sûr d'erreurs sur la sortie. Dans le cas d'une fonction ou d'un opérateur discret, il peut y avoir de grandes différences sur la sortie autour du point où la fonction ou l'opérateur est discret.

Entrée et sortie pour les valeurs à virgule flottante

Ainsi, lorsque vous utilisez des variables à virgule flottante, vous devez toujours en être conscient. Et quelle que soit la sortie que vous souhaitez d'un calcul à virgule flottante, elle doit toujours être formatée / conditionnée avant d'être affichée dans cet esprit.
Lorsque seules des fonctions et des opérateurs continus sont utilisés, l'arrondi à la précision souhaitée fera souvent l'affaire (ne pas tronquer). Les fonctionnalités de formatage standard utilisées pour convertir les flottants en chaîne le feront généralement pour vous.
Étant donné que l'arrondi ajoute une erreur qui peut entraîner une erreur totale supérieure à la moitié de la précision souhaitée, la sortie doit être corrigée en fonction de la précision attendue des entrées et de la précision souhaitée de la sortie. Vous devriez

  • Arrondissez les entrées à la précision attendue ou assurez-vous qu'aucune valeur ne peut être entrée avec une précision supérieure.
  • Ajoutez une petite valeur aux sorties avant de les arrondir / les formater, qui est inférieure ou égale à 1/4 de la précision souhaitée et supérieure à l'erreur maximale attendue causée par les erreurs d'arrondi en entrée et pendant le calcul. Si cela n'est pas possible, la combinaison de la précision du type de données utilisé n'est pas suffisante pour fournir la précision de sortie souhaitée pour votre calcul.

Ces 2 choses ne sont généralement pas effectuées et dans la plupart des cas, les différences causées par le fait de ne pas les faire sont trop petites pour être importantes pour la plupart des utilisateurs, mais j'avais déjà un projet où la sortie n'était pas acceptée par les utilisateurs sans ces corrections.

Fonctions ou opérateurs discrets (comme le module)

Lorsque des opérateurs ou des fonctions discrets sont impliqués, des corrections supplémentaires peuvent être nécessaires pour s'assurer que la sortie est conforme aux attentes. L'arrondi et l'ajout de petites corrections avant l'arrondi ne peuvent pas résoudre le problème.
Une vérification / correction spéciale des résultats de calcul intermédiaires, immédiatement après l'application de la fonction discrète ou de l'opérateur, peut être requise. Pour un cas spécifique (opérateur de module), voir ma réponse à la question: Pourquoi l'opérateur de module renvoie-t-il un nombre fractionnaire en javascript?

Mieux vaut éviter d'avoir le problème

Il est souvent plus efficace d'éviter ces problèmes en utilisant des types de données (formats entiers ou à virgule fixe) pour des calculs comme celui-ci qui peuvent stocker l'entrée attendue sans erreurs d'arrondi. Un exemple de cela est que vous ne devez jamais utiliser de valeurs à virgule flottante pour les calculs financiers.

Stefan Mondelaers
la source
4

Jetez un oeil à l' arithmétique à virgule fixe . Cela résoudra probablement votre problème si la plage de nombres sur laquelle vous souhaitez opérer est petite (par exemple, la devise). Je voudrais l'arrondir à quelques valeurs décimales, ce qui est la solution la plus simple.

Marius
la source
5
Le problème n'est pas virgule flottante vs virgule fixe, le problème est binaire vs décimal.
Michael Borgwardt
4

Essayez ma bibliothèque d'arithmétique chiliadique, que vous pouvez voir ici . Si vous voulez une version ultérieure, je peux vous en procurer une.

Robert L
la source
4

Vous ne pouvez pas représenter exactement la plupart des fractions décimales avec des types binaires à virgule flottante (ce que ECMAScript utilise pour représenter les valeurs à virgule flottante). Il n'y a donc pas de solution élégante à moins que vous n'utilisiez des types arithmétiques à précision arbitraire ou un type à virgule flottante basé sur des décimales. Par exemple, l'application Calculatrice fournie avec Windows utilise désormais une arithmétique de précision arbitraire pour résoudre ce problème .

MSN
la source
4

entrez la description de l'image ici

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
Ashish Singhal
la source
3

Vous avez raison, la raison en est la précision limitée des nombres à virgule flottante. Stockez vos nombres rationnels comme une division de deux nombres entiers et dans la plupart des situations, vous pourrez stocker des nombres sans perte de précision. En ce qui concerne l'impression, vous souhaiterez peut-être afficher le résultat sous forme de fraction. Avec la représentation que j'ai proposée, cela devient trivial.

Bien sûr, cela n'aidera pas beaucoup avec des nombres irrationnels. Mais vous voudrez peut-être optimiser vos calculs de manière à ce qu'ils causent le moins de problèmes (par exemple, détecter des situations comme sqrt(3)^2).

skalee
la source
Vous avez raison, la raison en est la précision limitée des nombres à virgule flottante - en <pedant>fait, l'OP l'a mis à des opérations imprécises en virgule flottante, ce qui est faux</pedant>
détly
3

J'ai eu un problème d'erreur d'arrondi désagréable avec le mod 3. Parfois, quand je devrais obtenir 0, j'obtiens 0,000 ... 01. C'est assez facile à gérer, testez simplement <= .01. Mais parfois, j'obtenais 2,9999999999999998. AIE!

BigNumbers résolu le problème, mais a introduit un autre problème quelque peu ironique. En essayant de charger 8.5 dans BigNumbers, j'ai été informé que c'était vraiment 8.4999… et avait plus de 15 chiffres significatifs. Cela signifiait que BigNumbers ne pouvait pas l'accepter (je crois avoir mentionné que ce problème était quelque peu ironique).

Solution simple à un problème ironique:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);
mcgeo52
la source
2

Utilisez le numéro (1.234443) .àFixed (2); il imprimera 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
Harish.bazee
la source
2

decimal.js , big.js ou bignumber.js peuvent être utilisés pour éviter les problèmes de manipulation à virgule flottante en Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: minimaliste; facile à utiliser; précision spécifiée en décimales; précision appliquée à la division uniquement.

bignumber.js: bases 2-64; options de configuration; NaN; Infini; précision spécifiée en décimales; précision appliquée uniquement à la division; préfixes de base.

decimal.js: bases 2-64; options de configuration; NaN; Infini; puissances non entières, exp, ln, log; précision spécifiée en chiffres significatifs; précision toujours appliquée; nombres aléatoires.

lien vers des comparaisons détaillées

Erikas Pliauksta
la source
2

Élégant, prévisible et réutilisable

Traitons le problème de manière élégante et réutilisable. Les sept lignes suivantes vous permettront d'accéder à la précision en virgule flottante que vous désirez sur n'importe quel nombre simplement en ajoutant .decimalà la fin du nombre, de la formule ou de la Mathfonction intégrée.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

À votre santé!

Bernesto
la source
2
Si vous choisissez de voter contre, fournissez au moins une raison.
Bernesto
1

Utilisation

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
Himadri
la source
4
Hmm ... mais notez que cela arrondit toujours à 2 décimales. Ce serait bien sûr une option, mais qu'en est-il du calcul 0,55 * 0,55 (puisque je ne connais pas les nombres exacts à l'avance. Cela donnerait 0,3 au lieu de 0,3025. Bien sûr, je pourrais alors utiliser Math.round(x*Math.pow(10,4))/Math.pow(10,4);. L'arrondi est toujours une option, mais je voulais juste savoir s'il y avait une meilleure solution
Juri
10.2 renvoie toujours 10.19 jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas
1

pas élégant mais fait le travail (supprime les zéros de fin)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Peter
la source
6
toFixed ne fonctionne pas toujours: stackoverflow.com/questions/661562/…
Sam Hasler
1

Cela fonctionne pour moi:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54
Antonio Max
la source
1
malheureusement, round_up (4.15,2) => 4.16.
2014 à 8h14
1

Sortie en utilisant la fonction suivante:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

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

test();

Faites attention à la sortie toFixedCurrency(x).

Júlio Paulillo
la source