Reputation: 149
I'm working on a project where I have an input field where a user puts in a dollar amount and depending on the frequency they would like to pay it divides that amount by said value (2, 4, or 12). So for instance if I put in an amount of $5 and had the frequency set at 2, I would have it display $2.50 for the charge amount. The issue I am having is if a user inputs $20.27 with frequency set at 2, I would expect to see $10.14 as the display but for some reason am getting $10.13 even after using Math.round
and a toFixed(2)
. If I input $2.27 or $0.27 I do get the expected $1.14 and $.14 respectively but as soon as I get to more than $10 it no longer rounds correctly. I cannot figure out what I am doing wrong. Here is my code:
const setAmount = (num) => {
scope.selectAmount(selectedAmount / num);
finalAmount = ((Math.round(selectedAmount * 100) / 100) / num).toFixed(2);
selectedAmountText = '$' + finalAmount;
$('#pay-schedule span').text(selectedAmountText);
}
Any help would be tremendously appreciated. Thanks!
Upvotes: 1
Views: 501
Reputation: 3570
The issue is, that the number 20.27 does not exists. On input it is rounded to the nearest possible value:
var a = 20.27;
a.toFixed(18) // output: "20.269999999999999574"
var b = a/2;
b.toFixed(18); // output: "10.134999999999999787"
I use toFixed(18)
because it shows all relevant digits. From this point of view, 10.13
is the correct and expected value.
Rounding with Math.round(b*100)/100
may solve this example, but can produce other errors. Here b*100 is rounded to the next whole integer. And in this case, dividing is by 100 returns a value >10.14
(not =10.14
).
c = Math.round(b*100);
c.toFixed(18); // output: "1014.000000000000000000"
(c/100).toFixed(18); // output: "10.140000000000000568"
This numerical problem is common with all languages, that support IEEE floating point numbers. That the reason, that financial software uses cents or e.g. BCD (binary coded decimals) numbers. And that is the reason, that the old programming language COBOL is used for many financial programs.
Upvotes: 0
Reputation: 12037
I'm not sure why you need toFixed()
.
var amount=20.27;
var frequency=2;
var finalamount=amount/frequency;
finalamountrounded=Math.round(finalamount * 100) / 100
console.log(finalamountrounded)
produces
10.14
Upvotes: 0
Reputation: 388
I don't have an exact explanation for this but dividing by 2 at the end makes it somewhat inconsistent. So diving the number by 2 in the first place solves the issue for the specific values that you've provided.
function round(num) {
let val = num / 2
return (Math.round(val * 100) / 100).toFixed(2);
}
Upvotes: 1
Reputation: 12055
Change the ordering as follows:
finalAmount = ((Math.round(selectedAmount / num * 100) / 100)).toFixed(2);
Dividing later messes things up.
Upvotes: 2