views:

210

answers:

6

I have a function to round a number given to the function to the nearest whole pence.

<script type='text/javascript'>
Math.roundNumber = function(a,b){
 if(!isNaN(parseInt(a))){
     c = Math.pow(10,b);
     return (Math.round(a*c)/c);
    }
    return false;
}
</script>

however it has come to my attention that the said number inputted into the function must only be rounded up to the nearest one pence however it must be rounded to two decimal places.

E.G.

15.236562213541684 would = 15.24  
9846.65456169846 would = 9846.66

I thought it would just be a case of changing return (Math.round(a*c)/c); To return (Math.ceil(a*c)/c);

Obviously I was very wrong.

Any help on this matter?

** EDIT **

Here is the formula that I'm trying to achieve maybe it'll help

a = intrest price
b = terms
c = a/b
d = c*(b-1)
e = a-d

so for example

a = 295.30
b = 156
c = 295.30/156 = 1.90 (rounded up to nearest decimal as above)
d = 1.90 * (b-1) = 294.50
e = 295.30 - 294.50 = 0.80

can anyone right a function to do the above?

Here is a link to the current code i have including the formula... its a very old formula that I made when I first started JavaScript (which is a while ago now) however I'm still no better now as I was back then.

Can anyone clean it up to see if they can see why its not working to match the function above?

+5  A: 

There's a built-in function for this already, Number.toFixed(numDigits), why not use this?

Parameter: digits
The number of digits to appear after the decimal point; this may be a value between 0 and 20, inclusive, and implementations may optionally support a larger range of values. If this argument is omitted, it is treated as 0.

Returns:
A string representation of number that does not use exponential notation and has exactly digits digits after the decimal place. The number is rounded if necessary, and the fractional part is padded with zeros if necessary so that it has the specified length. If number is greater than 1e+21, this method simply calls Number.toString() and returns a string in exponential notation.

Example:

>>> new Number(15.236562213541684).toFixed(2);
"15.24"
>>> new Number(9846.65456169846).toFixed(2);
"9846.65"

This last one doesn't match your example - 9846.65456169846 rounded to two decimal places should be 9846.65, not 9846.66.

matt b
toFixed rounds down as well. It's better than his Math.round solution, but it still wouldn't work as requested.
scragar
"This last one doesn't match your example - 9846.65456169846 rounded to two decimal places should be 9846.65, not 9846.66." - this is incorrect as I mentioned it needs to always be rounded UP to the nearest one pence.
Neil Hickman
Ah I didn't understand what he meant by that.
matt b
@scragar: toFixed() doesn't round down, but it uses the floating point representation and might therefore give unexpected results; also, there's an IE bug - see http://stackoverflow.com/questions/661562/how-to-format-a-float-in-javascript/661757#661757
Christoph
+1  A: 

Your code is more complex than what you need for this task. If you know the decimal point will always represent a stable value regardless of the length of digits on either side of that decimal point you are in luck.

1) Then you must merely multiple your original number by 100

2) if (Number(x.charAt(indexOF(".") + 1)) > 4) {x += 1} where x is your number

3) parseInt

4) divide by 100 and you are done.

"If you know the decimal point will always represent a stable value regardless of the length of digits on either side of that decimal point you are in luck." - care to elaborate on what you mean by this?
Neil Hickman
A: 

No, you were right.

I tested your code with the ceil modifictaion, and it works.

Guffa
ceil doesn't work... using ceil seems to make inaccuracies eg Interest Price: £295.30 155 payments of £1.90 followed by one payment of £0.81. that adds up to 295.31 where the one payment must be £0.80 ...
Neil Hickman
@Neil: As you are always rounding up you will of course often end up with a total that is larger than the total of the exact values. Even when rounding you get a difference sometimes. If you want the total to be the same you have to calculate the final payment as the difference between the total and the sum of the previous actual payments.
Guffa
@Guffa I understand this and if you check out what i've added in my edit you'll see the actual formula i'm trying to make... maybe you can see where numbers need to be applied for this formula to work.
Neil Hickman
+1  A: 

Replacing Math.round() with Math.ceil() should work: The bug must be somewhere else.

Also, I suggest dropping the isNaN(parseInt()) completely as the math functions will cast to number anyway, returning NaN if that's not possible. This will lead to the funtion returning NaN as well, which is a better fit than boolean false.

And btw: it's a bad idea to use floating point math with monetary values: use fixed point arithmetics instead!


Now that we know what you're trying to do, here's the code which computes the desired values for your example:

var value = 295.30;
var count = 156;
var payment = Math.ceil(value * 100 / count) / 100;
var last_payment = Math.round((value - (count - 1) * payment) * 100) / 100;

Changing round() to ceil() for all computations didn't work as the last payment has to be rounded normally to compensate the inaccuracies due to using floating point values.

It's generally better to only use integer math, so multiply all values with 100 and convert only when outputting the values:

var value = 29530;
var count = 156;
var payment = Math.ceil(value / count);
// rounding no longer necessary as integer math is precise:
var last_payment = value - (count - 1) * payment;
Christoph
i've added the formula that I'm trying to figure out... any ideas?
Neil Hickman
+1  A: 

Advice to use fixed-point arithmetic is good advice. Unfortunately, there is no fixed-point arithmetic in Javascript - all numbers are double-precision floating-point numbers. (Well, no fixed-point arithmetic without pulling in some explicit support library.)

For scrubbing input in text fields, I've started dealing with the numbers as strings (to whatever extent possible). Chop the input up into the parts to the left and right of the decimal point, then work in integer values (via Math.floor() when necessary). If you've got to deal with stray digits past the 2nd decimal place, either treat them as an error (what do they mean anyway?) or else, again, split them off and work with them as integers.

If you're not dealing with user-supplied values (i.e., stuff from text input fields), then you really shouldn't be doing this in Javascript, because, again, you don't have the numeric tools you really need.

Pointy
there may not be dedicated fixed-point arithmetics in JS, but you can always multiply all values by the apropriate factor to get integers; math will be accurate as long as you don't exhaust the 53 bit precision of doubles
Christoph
+1  A: 
function currency_fix(number)
{
 var unfixed_number = Math.ceil(number.toFixed(5) * 100) / 100;
 return unfixed_number.toFixed(2)
}

var a = currency_fix(295.30)
var b = currency_fix(156)
var c = currency_fix(a / b)
var d = currency_fix(c * (b - 1))
var e = currency_fix(a - d)

document.write(
 'a = ' + a + '<br />' +
 'b = ' + b + '<br />' +
 'c = ' + c + '<br />' +
 'd = ' + d + '<br />' +
 'e = ' + e + '<br />'
)

// OUTPUT
//a = 295.30
//b = 156.00
//c = 1.90
//d = 294.50
//e = 0.80

I got it to give the results you want in this case. Not sure if it's going to work every time but it seems to. I'd definitely check it works as you want it to first.

very well presented... this puts into concept of everything i've asked... you presented it simply and in a way that just makes sense. thanks for the time you took to write this.
Neil Hickman