Anthony Correia
Anthony Correia

Reputation: 149

Rounding Issue with Javascript using Math

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

Answers (4)

Wiimm
Wiimm

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

mti2935
mti2935

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

M3rt
M3rt

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

see sharper
see sharper

Reputation: 12055

Change the ordering as follows:

finalAmount = ((Math.round(selectedAmount / num * 100) / 100)).toFixed(2);

Dividing later messes things up.

Upvotes: 2

Related Questions