2010-05-20 9 views
96

Dans l'intérêt de créer du code multiplateforme, j'aimerais développer une application financière simple en JavaScript. Les calculs requis impliquent un intérêt composé et des nombres décimaux relativement longs. Je voudrais savoir quelles erreurs éviter en utilisant JavaScript pour faire ce type de maths - si c'est possible du tout!Calcul financier précis en JavaScript. Quels sont les Gotchas?

Répondre

82

Vous devriez probablement mettre à l'échelle vos valeurs décimales par 100 et représenter toutes les valeurs monétaires en cents entiers. C'est pour éviter problems with floating-point logic and arithmetic. Il n'y a pas de type de données décimal en JavaScript - le seul type de données numérique est en virgule flottante. Par conséquent, il est généralement recommandé de gérer l'argent comme 2550 cents au lieu de 25.50 dollars.

Tenir compte que JavaScript:

var result = 1.0 + 2.0;  // (result === 3.0) returns true 

Mais:

var result = 0.1 + 0.2;  // (result === 0.3) returns false 

L'expression 0.1 + 0.2 === 0.3 rendements false, mais heureusement l'arithmétique entière en virgule flottante est exacte, alors les erreurs de représentation décimale peut être évitée par mise à l'échelle . Notez que si l'ensemble des nombres réels est infini, seul un nombre fini d'entre eux (18,437,736,874,454,810,627 pour être exact) peut être représenté exactement par le format à virgule flottante JavaScript. Par conséquent, la représentation des autres nombres sera une approximation du nombre réel .


Douglas Crockford: JavaScript: The Good Parts: Appendix A - Awful Parts (page 105).
David Flanagan: JavaScript: The Definitive Guide, Fourth Edition: 3.1.3 Floating-Point Literals (page 31).

+2

Et pour rappel, toujours arrondir les calculs au cent, et le faire de la manière la moins bénéfique pour le consommateur, I.E. Si vous calculez la taxe, arrondissez à la hausse. Si vous calculez l'intérêt gagné, tronquez-le. – Josh

+0

Est-ce que cela s'applique à tous les cas? Si vous voulez obtenir 5,999999999% ou 3,000.57, il est suggéré que je transforme le premier à 5 999 999 999 et le dernier à 300 057 et garder la trace de la valeur d'origine de chacun afin d'effectuer le calcul correct? –

+5

@Cirrostratus: Vous pouvez vérifier http://stackoverflow.com/questions/744099/. Si vous continuez avec la méthode de mise à l'échelle, en général, vous voudrez mettre à l'échelle votre valeur par le nombre de chiffres décimaux que vous souhaitez conserver la précision. Si vous avez besoin de deux décimales, d'une échelle de 100, si vous avez besoin de 4, d'une échelle de 10000. –

5

Il n'y a pas de calcul financier "précis" en raison de seulement deux chiffres décimaux, mais c'est un problème plus général.

En JavaScript, vous pouvez mettre à l'échelle chaque valeur de 100 et utiliser Math.round() chaque fois qu'une fraction peut se produire.

Vous pouvez utiliser un objet pour stocker les nombres et inclure l'arrondi dans sa méthode de prototypes valueOf(). Comme ceci:

sys = require('sys'); 

var Money = function(amount) { 
     this.amount = amount; 
    } 
Money.prototype.valueOf = function() { 
    return Math.round(this.amount*100)/100; 
} 

var m = new Money(50.42355446); 
var n = new Money(30.342141); 

sys.puts(m.amount + n.amount); //80.76569546 
sys.puts(m+n); //80.76 

De cette façon, chaque fois que vous utilisez un objet de l'argent, il sera représenté sous la forme arrondie à deux décimales. La valeur non arrondie est toujours accessible via m.amount.

Vous pouvez intégrer votre propre algorithme d'arrondi dans Money.prototype.valueOf(), si vous le souhaitez.

+0

J'aime cette approche orientée objet, le fait que l'objet Money possède les deux valeurs est très utile. C'est le type exact de fonctionnalité que j'aime créer dans mes classes Objective-C personnalisées. –

+2

Ce n'est pas assez précis pour arrondir. –

+3

Ne devrait pas sys.puts (m + n); //80.76 lit réellement sys.puts (m + n); //80.77? Je crois que vous avez oublié d'arrondir le .5. –

1

Votre problème provient de l'inexactitude dans les calculs à virgule flottante. Si vous utilisez simplement l'arrondi pour résoudre cela, vous aurez une plus grande erreur lorsque vous multipliez et divisez.

La solution est ci-dessous, une explication suivante:

Vous aurez besoin de penser les mathématiques derrière cela pour le comprendre. Les nombres réels comme 1/3 ne peuvent pas être représentés en math avec des valeurs décimales puisqu'ils sont infinis (par exemple - .333333333333333 ...). Certains nombres en décimal ne peuvent pas être représentés correctement en binaire. Par exemple, 0.1 ne peut pas être représenté correctement en binaire avec un nombre limité de chiffres.

Pour une description plus détaillée regarder ici: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Jetez un oeil à la mise en œuvre de la solution:http://floating-point-gui.de/languages/javascript/

6

Mise à l'échelle chaque valeur de 100 est la solution. Le faire à la main est probablement inutile, puisque vous pouvez trouver des bibliothèques qui le font pour vous. Je recommande moneysafe, qui offre une API fonctionnelle bien adaptée pour les applications ES6:

const { in$, $ } = require('moneysafe'); 
console.log(in$($(10.5) + $(.3)); // 10.8 

https://github.com/ericelliott/moneysafe

travaille à la fois dans Node.js et le navigateur.

+1

Refusée. Le point "scale by 100" est déjà couvert dans la réponse acceptée, mais il est bon que vous ayez ajouté une option de progiciel avec une syntaxe JavaScript moderne. FWIW Les noms de valeur 'in $, $' sont ambigus pour quelqu'un qui n'a pas déjà utilisé le paquet. Je sais que c'était le choix d'Eric de nommer les choses de cette façon, mais je pense toujours que c'est assez d'une erreur que je les renomme probablement dans l'instruction require import/destructured. –