J'ai le script de test factice suivant:
function test(){
var x = 0.1 * 0.2;
document.write(x);
}
test();
Ceci imprimera le résultat 0.020000000000000004
alors qu'il devrait imprimer 0.02
(si vous utilisez votre calculatrice). D'après ce que j'ai compris, 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 résultat correct 0.02
? Je sais qu'il existe des fonctions telles que toFixed
ou l'arrondi serait une autre possibilité, mais j'aimerais vraiment que le nombre entier soit imprimé sans découpage ni arrondi. Je voulais juste savoir si l'un d'entre vous a une solution élégante et agréable.
Bien sûr, sinon j'arrondirai à une dizaine de chiffres environ.
Du Guide des virgules flottantes :
Que puis-je faire pour éviter ce problème?
Cela dépend de quel genre de calculs que vous faites.
- Si vous avez vraiment besoin que vos résultats s’ajoutent exactement, surtout quand vous travailler avec de l'argent: utilisez une décimale spéciale Type de données.
- Si vous ne voulez simplement pas voir toutes ces décimales supplémentaires: simplement formatez votre résultat arrondi à une valeur fixe nombre de décimales lorsque l'afficher.
- Si vous ne disposez d'aucun type de données décimal, une autre solution consiste à travailler avec des nombres entiers, par exemple 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'un comportement précis précis décimal. La plupart des gens n'en ont pas besoin, ils sont simplement irrités par le fait que leurs programmes ne fonctionnent pas correctement avec des nombres tels que 1/10 sans se rendre compte qu'ils ne cligneraient même pas à la même erreur si elle se produisait avec 1/3.
Si le premier point vous concerne vraiment, utilisez BigDecimal pour JavaScript , qui n’est pas élégant du tout, mais résout le problème plutôt que de fournir une solution de contournement imparfaite.
J'aime la solution de Pedro Ladaria et utilise quelque chose de similaire.
function strip(number) {
return (parseFloat(number).toPrecision(12));
}
Contrairement à la solution Pedros, ceci arrondit 0,999 ... répétition et est précis à plus/moins un sur le chiffre le moins significatif.
Remarque: pour les flottants 32 ou 64 bits, vous devez utiliser toPrecision (7) et toPrecision (15) pour obtenir les meilleurs résultats. Voir cette question pour savoir pourquoi.
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 (multipliez par une puissance appropriée de 10 afin que l'arithmétique se produise entre 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) ressemble à 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 vivement d'utiliser une bibliothèque testée comme SinfulJS
Est-ce que vous ne faites que la multiplication? Si c'est le cas, vous pouvez utiliser à votre avantage un secret bien gardé sur l'arithmétique décimale. C'est cela NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. Autrement dit, si nous avons 0.123 * 0.12
, nous savons qu'il y aura 5 décimales, car 0.123
aura 3 décimales et 0.12
en aura deux. Ainsi, si JavaScript nous a attribué un nombre tel que 0.014760000002
, nous pouvons arrondir à la 5ème décimale sans craindre de perdre en précision.
Vous recherchez une implémentation sprintf
pour JavaScript, afin de pouvoir écrire des flottants contenant de petites erreurs (car ils sont stockés au format binaire) dans un format que vous attendez.
Essayez javascript-sprintf , vous l'appelleriez ainsi:
var yourString = sprintf("%.2f", yourNumber);
pour imprimer votre nombre sous forme de float 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 en virgule flottante avec une précision donnée.
Je trouve BigNumber.js répond à mes besoins.
Bibliothèque JavaScript pour l'arithmétique décimale et non décimale à précision arbitraire.
Il a une bonne documentation et l'auteur est très diligent et répond aux commentaires.
Le même auteur a 2 autres bibliothèques similaires:
Une petite bibliothèque JavaScript rapide pour l'arithmétique décimale à précision arbitraire. La petite soeur à bignumber.js.
et Decimal.js
Un type décimal à 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 × .2 = <span id="product"></span><br>
.1 + .2 = <span id="sum"></span><br>
Cette fonction déterminera la précision requise à partir de la multiplication de deux nombres à virgule flottante et renverra 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);
}
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;
};
--- 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
Vous devez juste décider du nombre de chiffres décimaux que vous voulez réellement - vous ne pouvez pas avoir le gâteau et le manger aussi :-)
Les erreurs numériques s'accumulent à chaque nouvelle opération et si vous ne les interrompez pas plus tôt, cela ne fera que croître. Les bibliothèques numériques qui présentent des résultats qui semblent clairs 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-off sont bon marché pour un processeur mais très chers pour un script (multiplier, diviser et utiliser pov (...)). Une bonne bibliothèque de mathématiques fournirait un plancher (x, n) pour effectuer la coupure pour vous.
Donc, au minimum, vous devriez faire un 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 que les résultats à la fin - en supposant que vous n’affichez que des résultats et que vous ne les faites pas. Si vous pouvez le faire, alors .toFixed (...) pourrait être plus efficace.
Si vous faites des comparaisons si-s/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 correspond aux deux dernières décimales - alors votre eps en a 1 à la 3ème place de la dernière (3ème la moins significative) et vous pouvez l'utiliser pour comparer si le résultat se situe dans la plage des eps attendue (0,02 -eps <0,1 * 0,2 <0,02 + eps).
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
Étonnamment, cette fonction n’a pas encore été publiée, bien que d’autres 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)); // résultat attendu: 1234.6
console.log (precisionRound (1234.5678, -1)); // résultat attendu: 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'>
Le résultat obtenu est correct et relativement cohérent pour 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 flottant est double (ou supérieur).
0,1 en virgule flottante binaire correspond à 1/3 en décimal (0,3333333333333 ... pour toujours), il n’existe aucun moyen précis de le gérer.
Si vous traitez avec floats always , attendez-vous à de petites erreurs d'arrondi. Vous devrez donc toujours arrondir le résultat affiché à une valeur raisonnable. 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 en virgule fixe, principalement parce que c'est beaucoup plus lent et que 99% du temps, vous n'avez simplement pas besoin de précision. Si vous avez affaire à des choses qui nécessitent ce niveau de précision (par exemple, des transactions financières), le Javascript n’est probablement pas le meilleur outil à utiliser (le langage statique est probablement préférable, car vous voulez imposer les types à virgule fixe). ).
Vous recherchez la solution élégante, alors je crains que ce ne soit le cas: les flotteurs sont rapides mais comportent de petites erreurs d’arrondi - toujours arrondis à quelque chose de sensé lors de l’affichage de leurs résultats.
Pour éviter cela, utilisez des valeurs entières plutôt que des points flottants. Donc, lorsque vous voulez avoir un travail de précision à 2 positions avec les valeurs * 100, utilisez 1 pour 1000. Lorsque vous affichez, utilisez un formateur pour le séparer.
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).
Vous pouvez utiliser parseFloat()
et toFixed()
si vous souhaitez éviter 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;
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 entraînent bien sûr des 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 voulez d'un calcul avec des points flottants, vous devez toujours la formater/la conditionner avant de l'afficher dans cet esprit.
Lorsque seules des fonctions et des opérateurs continus sont utilisés, l'arrondir à la précision souhaitée suffit souvent (ne pas tronquer). Les fonctionnalités de formatage standard utilisées pour convertir les éléments flottants en chaîne le font généralement pour vous.
Comme l’arrondi ajoute une erreur qui peut avoir pour conséquence que l’erreur totale soit 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
Ces deux choses ne sont généralement pas terminées et dans la plupart des cas, les différences causées par leur non-réalisation sont trop minimes pour être importantes pour la plupart des utilisateurs, mais j'avais déjà un projet pour lequel la sortie n'était pas acceptée par les utilisateurs sans ces corrections.
Fonctions ou opérateurs discrets (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 conforme aux attentes. Arrondir et ajouter de petites corrections avant d'arrondir ne peuvent pas résoudre le problème.
Une vérification/correction spéciale sur les 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 modula), voir ma réponse à la question: Pourquoi l'opérateur modulus renvoie-t-il 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 de nombre entier ou à virgule fixe) pour des calculs comme celui-ci, qui permettent de stocker l'entrée attendue sans erreurs d'arrondi .Un exemple est que vous ne devez jamais utiliser de valeurs à virgule flottante. pour les calculs financiers.
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))
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.
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
Vous ne pouvez pas représenter exactement la plupart des fractions décimales avec des types à virgule flottante binaire (ce que ECMAScript utilise pour représenter les valeurs à virgule flottante). Il n’ya donc pas de solution élégante à moins d’utiliser des types arithmétiques à précision arbitraire ou un type à virgule flottante à base décimale. Par exemple, l'application Calculatrice livrée avec Windows utilise désormais une arithmétique de précision arbitraire pour résoudre ce problème .
Regardez Arithmétique en virgule fixe . Cela résoudra probablement votre problème si la plage de numéros que vous souhaitez utiliser est petite (par exemple, une devise). Je terminerais par quelques décimales, ce qui est la solution la plus simple.
Notez que pour l'utilisation générale, ce comportement est susceptible d'être acceptable.
Le problème se pose lors de la comparaison de ces valeurs en points flottants pour déterminer une action appropriée.
Avec l'avènement de l'ES6, une nouvelle constante Number.EPSILON
est définie pour déterminer la marge d'erreur acceptable:
Donc, au lieu de faire la comparaison comme ceci
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
Utilisation
var x = 0.1*0.2;
x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
J'ai eu un vilain problème d'arrondi avec le mod 3. Parfois, quand je devais avoir 0, j'obtiendrais .000 ... 01. C'est assez facile à gérer, il suffit de tester <= .01. Mais parfois, je recevrais 2.99999999999998. AIE!
BigNumbers résolu le problème, mais introduit un autre problème, quelque peu ironique. En essayant de charger 8.5 dans BigNumbers, on m'a dit que c'était vraiment 8,4999… et que plus de 15 chiffres étaient significatifs. Cela signifiait que BigNumbers ne pouvait pas l'accepter (je pense avoir mentionné que ce problème était quelque peu ironique).
Solution simple au 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);
decimal.js , big.js ou bignumber.js peut être utilisé pour éviter les problèmes de manipulation de 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 seulement.
bignumber.js: bases 2-64; options de configuration; NaN; Infini; précision spécifiée en décimales; précision appliquée à la division seulement; préfixes de base.
decimal.js: bases 2-64; options de configuration; NaN; Infini; pouvoirs non entiers, exp, ln, log; précision spécifiée en chiffres significatifs; précision toujours appliquée; nombres aléatoires.
Vous avez raison, la raison en est une précision limitée des nombres en virgule flottante. Stockez vos nombres rationnels sous forme de division de deux nombres entiers et, dans la plupart des cas, vous pourrez stocker des nombres sans perte de précision. En matière d’impression, vous voudrez 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 les nombres irrationnels. Mais vous voudrez peut-être optimiser vos calculs de la manière dont ils causeront le moins de problèmes (par exemple, en détectant des situations telles que sqrt(3)^2)
.
pas élégant mais fait le travail (supprime les zéros à la fin)
var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
Utilisez le numéro (1.234443) .toFixed (2); il imprimera 1.23
function test(){
var x = 0.1 * 0.2;
document.write(Number(x).toFixed(2));
}
test();
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})$/, '.$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)
.
Je ne suis pas très doué en programmation, mais ce sujet m'intéressait beaucoup. J'ai donc essayé de comprendre comment résoudre ce problème sans utiliser aucune bibliothèque ou script
J'ai écrit ça sur le bloc-notes
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 aurez peut-être besoin de refactoriser ce code, car je ne suis pas doué en programmation :)
tout en ajoutant deux valeurs flottantes, il ne donne jamais les valeurs précises.
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
Si vous ne voulez pas avoir à appeler des fonctions à chaque fois, vous pouvez créer une classe qui gère la conversion pour vous.
class Decimal {
constructor(value = 0, scale = 4) {
this.intervalValue = value;
this.scale = scale;
}
get value() {
return this.intervalValue;
}
set value(value) {
this.intervalValue = Decimal.toDecimal(value, this.scale);
}
static toDecimal(val, scale) {
const factor = 10 ** scale;
return Math.round(val * factor) / factor;
}
}
Usage:
const d = new Decimal(0, 4);
d.value = 0.1 + 0.2; // 0.3
d.value = 0.3 - 0.2; // 0.1
d.value = 0.1 + 0.2 - 0.3; // 0
d.value = 5.551115123125783e-17; // 0
d.value = 1 / 9; // 0.1111
Bien sûr, quand on traite avec Decimal, il y a des mises en garde:
d.value = 1/3 + 1/3 + 1/3; // 1
d.value -= 1/3; // 0.6667
d.value -= 1/3; // 0.3334
d.value -= 1/3; // 0.0001
Vous voudriez idéalement utiliser une échelle élevée (telle que 12), puis la convertir lorsque vous avez besoin de la présenter ou de la stocker quelque part. Personnellement, j'ai essayé de créer un UInt8Array et d'essayer de créer une valeur de précision (un peu comme le type SQL Decimal), mais comme Javascript ne permet pas de surcharger les opérateurs, il devient un peu fastidieux de ne pas pouvoir utiliser d'opérateurs mathématiques de base. (+
, -
, /
, *
) et utiliser des fonctions telles que add()
, substract()
, mult()
. Pour mes besoins, ça ne vaut pas la peine.
Mais si vous avez besoin de ce niveau de précision et que vous êtes prêt à supporter l'utilisation de fonctions pour les mathématiques, je vous recommande alors la bibliothèque decimal.js .