views:

209

answers:

2

In the interest of creating cross-platform code, I'd like to develop a simple financial application in JavaScript. The calculations required involve compound interest and relatively long decimal numbers. I'd like to know what mistakes to avoid when using JavaScript to do this type of math—if it is possible at all!

+4  A: 

You should probably scale your decimal values by 100, and represent all the monetary values in whole cents. This is to avoid problems with floating-point logic and arithmetic. There is no decimal data type in JavaScript - the only numeric data type is floating-point. Therefore it is generally recommended to handle money as 2550 cents instead of 25.50 dollars.

Consider that in JavaScript:

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

But:

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

The expression 0.1 + 0.2 === 0.3 returns false, but fortunately integer arithmetic in floating-point is exact, so decimal representation errors can be avoided by scaling1.

Note that while the set of real numbers is infinite, only a finite number of them (18,437,736,874,454,810,627 to be exact) can be represented exactly by the JavaScript floating-point format. Therefore the representation of the other numbers will be an approximation of the actual number2.


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

Daniel Vassallo
And as a reminder, always round calculations to the cent, and do so in the least beneficial way to the consumer, I.E. If you're calculating tax, round up. If you're calculating interest earned, truncate.
Josh
Does this extend to all cases? If want to get 5.999999999% or 3,000.57, it's suggested I turn the former to 5,999,999,999 and the latter to 300,057 and keep track of the original value of each in order to perform the correct calculation?
Cirrostratus
@Cirrostratus: You may want to check http://stackoverflow.com/questions/744099/. If you go ahead with the scaling method, in general you would want to scale your value by the number of decimal digits you wish to retain precision. If you need 2 decimal places, scale by 100, if you need 4, scale by 10000.
Daniel Vassallo
... Regarding the 3000.57 value, yes, if you store that value in JavaScript variables, and you intend to do arithmetic on it, you might want to store it scaled to 300057 (number of cents). Because `3000.57 + 0.11 === 3000.68` returns `false`.
Daniel Vassallo
+1  A: 

There's no such thing as "precise" financial calculation because of just two decimal fraction digits but that's a more general problem.

In JavaScript, you can scale every value by 100 and use Math.round() everytime a fraction can occur.

You could use an object to store the numbers and include the rounding in its prototypes valueOf() method. Like this:

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

That way, everytime you use a Money-object, it will be represented as rounded to two decimals. The unrounded value is still accessible via m.amount.

You can build in your own rounding algorithm into Money.prototype.valueOf(), if you like.

Techpriester
I like this object-oriented approach, the fact that the Money object holds both values is very useful. It's the exact type of functionality I like to create in my custom Objective-C classes.
Cirrostratus