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

j'ai le scénario de test du mannequin suivant:

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

cela affichera le résultat 0.020000000000000004 alors qu'il suffit d'imprimer 0.02 (si vous utilisez votre calculatrice). Que j'ai compris c'est en raison d'erreurs en virgule flottante, la multiplication de précision.

est-ce que quelqu'un a une bonne solution pour que dans un tel cas j'obtienne le résultat correct 0.02 ? Je sais qu'il y a des fonctions comme toFixed ou l'arrondissement serait un autre c'est possible, mais j'aimerais que le numéro entier soit imprimé sans être coupé ni arrondi. Je voulais juste savoir si l'un de vous avait une solution élégante.

bien sûr, sinon je vais arrondir à une dizaine de chiffres.

446
demandé sur Web_Designer 2009-09-22 11:34:42

30 réponses

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 de Pedros, ceci arrondira à 0,999...il se répète et est précis à plus / moins un sur le chiffre le moins significatif.

Note: pour les flotteurs 32 ou 64 bits, vous devez utiliser toPrecision(7) et toPrecision(15) pour obtenir les meilleurs résultats. Voir cette question pour savoir pourquoi.

96
répondu linux_mike 2017-05-23 12:18:17

pour les personnes à tendance mathématique: 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 de sorte que l'arithmétique se produit entre des entiers). Par exemple, dans le cas de 0.1 * 0.2 , le facteur de correction est 10 , et 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) semble quelque chose comme:

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 certainement l'utilisation d'une bibliothèque testée comme SinfulJS

56
répondu SheetJS 2013-09-20 02:55:52

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

35
répondu Nate Zaugg 2017-05-15 18:10:23

vous êtes à la recherche d'une implémentation sprintf pour JavaScript, de sorte que vous pouvez écrire des floats avec de petites erreurs en eux (puisqu'ils sont stockés dans un format binaire) dans un format que vous attendez.

Try javascript-sprintf , vous l'appelleriez comme ceci:

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

pour imprimer votre nombre comme un flotteur avec deux décimales.

vous pouvez également utiliser le numéro .toFixed() pour les besoins de l'affichage, si vous préférez ne pas inclure plus de fichiers simplement pour arrondir à une précision donnée.

24
répondu Douglas 2013-01-05 01:55:50
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

- - - également - - -

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

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

--- comme dans d' ---

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
14
répondu shawndumas 2010-08-09 12:14:25

cette fonction va déterminer la précision nécessaire à partir de la multiplication de deux nombres à virgule flottante et retourner un résultat avec la précision appropriée. Élégant bien qu'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); 
}
14
répondu Gabriel 2016-10-13 13:45:39

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

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

Il a une bonne documentation et l'auteur est très diligent de répondre à des commentaires.

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

grand.js

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

et décimal.js

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

voici quelques codes en 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>
13
répondu Ronnie Overby 2014-12-02 14:35:50

Vous n'avez qu'à faire votre esprit sur la façon dont beaucoup de chiffres après la virgule que vous voulez vraiment - ne peut pas avoir le beurre et l'argent du beurre :-)

les erreurs numériques s'accumulent avec chaque opération supplémentaire et si vous ne le coupez pas plus tôt, il va juste augmenter. Les bibliothèques numériques qui présentent des résultats qui semblent propres coupent simplement les deux derniers chiffres à chaque étape, les co-processeurs numériques ont également une longueur" normale "et" complète " pour la même raison. Cuf-offs sont bon marché pour une processeur mais très cher pour vous dans un script (multiplier et diviser et utiliser pov(...)). Une bonne literie mathématique fournirait le plancher (x, n) pour faire la coupure pour vous.

donc au moins vous devriez faire global var / constant avec pov (10, n) - ce qui signifie que vous avez décidé sur la précision dont vous avez besoin: -) puis faire:

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

vous pouvez aussi continuer à faire des maths et couper seulement à la fin-en supposant que vous n'affichez et ne faites pas si-s avec résultat. Si vous pouvez faire cela, alors .toFixed(...) pourrait être plus efficace.

si vous faites des comparaisons if-S/et que vous ne voulez pas les couper, alors vous avez aussi besoin d'une petite constante, généralement appelée eps, qui est une décimale plus élevée que l'erreur attendue max. Supposons que votre seuil soit deux dernières décimales - alors votre eps a 1 à la 3ème place de la dernière (3ème moins significative) et vous pouvez l'utiliser pour comparer si le résultat se situe dans la gamme EPS attendue (0.02-eps < 0.1*0.2 < 0.02 +eps).

10
répondu ZXX 2010-08-06 08:30:54

la fonction round () à phpjs.org fonctionne bien: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
8
répondu Tom 2012-03-01 19:50:30

le résultat obtenu est correct et assez cohérent à travers les implémentations en virgule flottante dans différents langages, processeurs et systèmes d'exploitation - la seule chose qui change est le niveau d'inexactitude lorsque le flotteur est en fait un double (ou plus élevé).

0.1 en points flottants binaires est comme 1/3 en décimales(i.e. 0.3333333333333... pour toujours), il n'y a aucun moyen précis pour le gérer.

si vous avez affaire à des flotteurs toujours attendez-vous à de petites erreurs d'arrondissement, vous aurez donc toujours à arrondir le résultat affiché à quelque chose de raisonnable. En retour, vous obtenez très arithmétique très rapide et puissant parce que tous les calculs sont dans le binaire natif du processeur.

la plupart du temps, la solution n'est pas de passer à l'arithmétique à point 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 font besoin de 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 avez envie d'appliquer les types de point fixe un langage statique est probablement mieux).

vous cherchez la solution élégante alors je crains que ce soit la bonne: les flotteurs sont rapides mais ont de petites erreurs d'arrondi - toujours autour de quelque chose de raisonnable lors de l'affichage de leurs résultats.

6
répondu Keith 2010-08-09 08:57:20

pour éviter cela, vous devriez travailler avec des valeurs entières au lieu de points flottants. Donc, quand vous voulez avoir 2 positions de travail de précision avec les valeurs * 100, pour 3 positions utiliser 1000. Lors de l'affichage vous utilisez un formateur à 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 (comme entier) au lieu de dollars/euros (comme point flottant).

6
répondu Gertjan 2010-08-09 12:12:52

Vous pouvez utiliser parseFloat() et toFixed() si vous voulez 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;
5
répondu Softwareddy 2015-04-10 11:38:13

étonnamment, cette fonction n'a pas encore été affiché bien que d'autres ont des variations similaires de lui. Ça vient du site Web de MDN docs for Math.rond.)( C'est concis et permet de varier la précision.

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

de la console.log(precisionRound(1234.5678, 1)); // résultats escomptés: 1234,6

de la console.log(precisionRound(1234.5678, -1)); // résultats escomptés: 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'>
5
répondu HelloWorldPeace 2018-04-09 09:30:11

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

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

très très simple))

4
répondu Олег Всильдеревьев 2015-02-12 09:14:30

regarder point Fixe de l'arithmétique . Il sera probablement résoudre votre problème, si la gamme de numéros que vous voulez opérer sur est petite (par exemple, la monnaie). Je voudrais arrondir à quelques valeurs décimales, ce qui est la solution la plus simple.

3
répondu Marius 2009-09-22 07:39:09

essayez Ma bibliothèque d'arithmétique chiliade, que vous pouvez voir ici . Si tu veux une version plus tard, je peux t'en trouver une.

3
répondu Robert L 2009-10-06 00:04:02

vous ne pouvez pas représenter la plupart des fractions décimales exactement avec les types binaires à virgule flottante (qui est 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 de précision arbitraires ou un type de virgule flottante basé sur une décimale. Par exemple, L'application de Calculatrice que les navires avec Windows utilise maintenant l'arithmétique de précision arbitraire pour résoudre ce problème .

3
répondu MSN 2010-08-05 17:44:35

problème

Floating point ne peut pas stocker toutes les valeurs décimales exactement. Ainsi, lors de l'utilisation des formats à virgule flottante, il y aura toujours des erreurs d'arrondissement sur les valeurs d'entrée. Les erreurs sur les entrées résultent bien sûr sur les 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ées et sorties pour les valeurs à virgule flottante

ainsi, lorsque vous utilisez des variables à virgule flottante, vous devriez toujours être conscient de cela. Et quelle que soit la sortie que vous voulez d'un calcul avec des points flottants devrait toujours être formaté/conditionné avant l'affichage avec ceci à l'esprit.

Lorsque seulement des fonctions et des opérateurs continus sont utilisés, l'arrondi à la précision désirée fera souvent l'affaire (ne pas tronquer). Les fonctionnalités de formatage Standard utilisées pour convertir les flotteurs en string le fera d'habitude pour toi.

Comme l'arrondissement ajoute une erreur qui peut faire en sorte que l'erreur totale représente plus de la moitié de la précision désirée, la sortie doit être corrigée en fonction de la précision attendue des entrées et de la précision désirée de la sortie. Vous devez

  • des entrées rondes à la précision attendue ou s'assurer qu'aucune valeur ne peut être entrée avec une plus grande précision.
  • ajoute une petite valeur aux sorties avant de les arrondir ou de les formater, qui est inférieur ou égal au quart de la précision désirée et supérieur à l'erreur maximale prévue causée par les erreurs d'arrondissement à l'entrée et pendant le calcul. Si cela n'est pas possible, la combinaison de la précision du type de données n'est pas suffisant pour fournir le résultat souhaité de précision pour votre calcul.

ces deux choses ne sont généralement pas faites et dans la plupart des cas, les différences causées par ne pas les faire sont trop petit pour être important pour la plupart des utilisateurs, mais j'ai déjà eu un projet où la sortie n'a pas été acceptée par les utilisateurs sans ces corrections.

Discrète des fonctions ou opérateurs (comme modula)

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 comme prévu. Arrondir et ajouter de petites corrections avant d'arrondir ne peut pas résoudre le problème.

Un un contrôle/correction spécial sur les résultats du calcul intermédiaire, immédiatement après l'application de la fonction discrète ou de l'opérateur peut être nécessaire. 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 fractionnel en javascript?

mieux éviter d'avoir le problème

Il est souvent plus efficace d'éviter ces problèmes en utilisant des types de données (entier ou formats de points fixes) pour des calculs comme celui-ci qui peut stocker l'entrée attendue sans erreurs d'arrondi. Par exemple, vous ne devriez jamais utiliser les valeurs à virgule flottante pour les calculs financiers.

3
répondu Stefan Mondelaers 2017-07-27 14:04:12

Utiliser

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
2
répondu Himadri 2009-09-22 08:15:26

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

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
1
répondu Peter 2009-09-22 12:48:50

vous avez raison, la raison pour cela 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 serez en mesure de stocker des nombres sans aucune perte de précision. Quand il s'agit de l'impression, vous pouvez afficher le résultat sous forme de fraction. Avec la représentation que j'ai proposée, ça devient insignifiant.

bien sûr que ça n'aidera pas beaucoup avec les nombres irrationnels. Mais vous pouvez optimiser votre les calculs de la façon dont ils causeront le moins de problèmes (par exemple, la détection de situations comme sqrt(3)^2) .

1
répondu skalee 2010-08-07 11:21:26

Numéro D'Utilisation (1.234443).toFixed(2); il permet d'imprimer 1.23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
1
répondu Harish.bazee 2014-10-08 10:46:17

j'ai eu un méchant problème d'erreur d'arrondi avec mod 3. Parfois, quand je devais avoir 0, Je l'avais .000...01. C'est assez facile à manipuler, juste tester pour <= .01. Mais parfois, j'obtenais 2.999999999998. OUCH!

BigNumbers a résolu le problème, mais a introduit un autre, quelque peu ironique, problème. En essayant de charger 8.5 dans les BigNumbers, on m'a dit qu'il était en fait 8.4999... et qu'il avait plus de 15 chiffres significatifs. Cela signifiait BigNumbers ne pouvait pas l'accepter (je crois que j'ai mentionné ce problème était quelque peu ironique).

solution Simple ironique problème:

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);
1
répondu mcgeo52 2014-12-14 17:34:22

enter image description here

    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
1
répondu Ashish Singhal 2015-05-28 11:45:11

Notez que pour l'usage général, ce comportement est susceptible d'être acceptable.

Le problème se pose lorsque l'on compare ces valeurs de points flottants pour déterminer une action appropriée.

Avec L'avènement de ES6, une nouvelle constante Number.EPSILON est définie pour déterminer la marge d'erreur acceptable:

Donc, au lieu d'effectuer la comparaison comme ceci

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

, vous pouvez définir un 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

1
répondu user10089632 2017-09-15 23:44:42

ça marche 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
0
répondu Antonio Max 2013-10-03 19:29:26

Sortie 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})$/, '.');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.');
    } 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) .

0
répondu Júlio Paulillo 2014-06-19 23:51:05

tout en ajoutant deux valeurs de flotteur son jamais donner les valeurs précises, nous avons donc besoin de fixer ce à un certain nombre qui nous aidera à comparer.

console.log [(parseFloat(0.1) + parseFloat(0.2)].toFixed(1) = = parseFloat (0.3).toFixed(1));
0
répondu aditya 2016-07-14 09:09:19

Je ne suis pas très bon en programmation, mais j'étais vraiment intéressé par ce sujet, donc j'ai essayé de comprendre comment résoudre cela sans utiliser de bibliothèques ou de scripts

j'ai écrit ceci sur scratchpad

var toAlgebraic = function(f1, f2) {
    let f1_base = Math.pow(10, f1.split('.')[1].length);
    let f2_base = Math.pow(10, f2.split('.')[1].length);
    f1 = parseInt(f1.replace('.', ''));
    f2 = parseInt(f2.replace('.', ''));

    let dif, base;
    if (f1_base > f2_base) {
        dif = f1_base / f2_base;
        base = f1_base;
        f2 = f2 * dif;
    } else {
        dif = f2_base / f1_base;
        base = f2_base;
        f1 = f1 * dif;
    }

    return (f1 * f2) / base;
};

console.log(0.1 * 0.2);
console.log(toAlgebraic("0.1", "0.2"));

vous pourriez avoir besoin de refactor de ce code, parce que je ne suis pas bon à la programmation :)

0
répondu Jeeva 2017-07-10 11:13:22