views:

286

answers:

4

In php, we have number_format(). Passing it a value such as:

number_format(3.00 * 0.175, 2);

returns 0.53, which is what I would expect.

However, in JavaScript using toFixed()

var num = 3.00 * 0.175;
num.toFixed(2);

returns 0.52.

Ok, so perhaps toFixed is not what I want... Maybe something like this...

var num = 3.17 * 0.175;
var dec = 2;
Math.round( Math.round( num * Math.pow( 10, dec + 1 ) ) / Math.pow( 10, 1 ) ) / Math.pow(10,dec);

No, that doesn't work either. It will return 0.56.

How can I get a number_format function in JavaScript that doesn't give an incorrect answer?

Actually I did find an implementation of number_format for js, http://phpjs.org/functions/number_format, but it suffers from the same problem.

What is going on here with JavaScript rounding up? What am I missing?

A: 

Why didn't you just use Math.round( num * Math.pow( 10, dec ) ) / Math.pow( 10, dec) )?

EDIT: I see, the problem is that 3 * 0.175 gives you 0.52499999999999991, leading you to want an additional rounding step. Maybe just adding a small amount would work:

Math.round( num * Math.pow( 10, dec ) + 0.000000001 ) / Math.pow( 10, dec) )

Gabe
I actually did try that, but it also returned incorrect results. E.g 3.00 * 0.175 = 0.52. The expression I used was a wacky attempt at trying to force it to give the right answer, but it did not work :)
Bob
A: 

The toFixed function is working correctly. It truncates past the specified amount of fraction digits.

ChaosPandion
+1  A: 

I think the problem you are encountering is with floating point math as opposed to the rounding itself.

Using the firebug console for testing, logging the result of 3.00 * 0.175 given 0.524999.... So rounding this number down is actually correct.

I don't know if there is a good solution to your problem, but in my experience when working with currency: it is easier to work in the smallest unit (cents) and then convert for display.

Brenton Alker
+4  A: 

JavaScript does badly with floating point numbers (as do many other languages).

When I run

3.000 * 0.175

In my browser, I get

0.5249999999999999

Which will not round up to 0.525 with Math.round. To circumvent this, you kind of have to multiply both sides until you get them to be integers (relatively easy, knowing some tricks help though).

So do to this we can say something like this:

function money_multiply (a, b) {
    var log_10 = function (c) { return Math.log(c) / Math.log(10); },
        ten_e  = function (d) { return Math.pow(10, d); },
        pow_10 = -Math.floor(Math.min(log_10(a), log_10(b))) + 1;
    return ((a * ten_e(pow_10)) * (b * ten_e(pow_10))) / ten_e(pow_10 * 2);
}

This may look kind of funky, but here's some pseudo-code:

get the lowest power of 10 of the arguments (with log(base 10))
add 1 to make positive powers of ten (covert to integers)
multiply
divide by conversion factor (to get original quantities)

Hope this is what you are looking for. Here's a sample run:

3.000 * 0.175
0.5249999999999999

money_multiply(3.000, 0.175);
0.525
Dan Beam
Thanks, yes this is exactly what I was missing. The floating point math had me confused :)
Bob
Yeah, I had to whip out the high school / college knowledge for this one, hahaha.
Dan Beam
btw, figured out Math.log is base of e, not 10, adjusted for future reference
Dan Beam
+1 Nice math. You could improve this by making using of closures and moving `log_10` and `ten_e` outside of `money_multiply` and into an anonymous function to avoid having to redefine those functions repeatedly.
Justin Johnson