0

I'm writing a change-making script and JavaScript is processing 76.74 - 20 as 56.739999999999995. The line in question is marked clearly in a comment below.

function checkCashRegister(price, cash, cid) {
  let drawer = cid; // Changeable copy of cash in drawer
  let change = [];
  let changeLeft = cash - price;
  console.log(`initial changeLeft = ${changeLeft}`);
  let registerTemplate = [
    ["PENNY", 0.01], // 0
    ["NICKEL", 0.05], // 1
    ["DIME", 0.10], // 2
    ["QUARTER", 0.25], // 3
    ["ONE", 1.00], // 4
    ["FIVE", 5.00], // 5
    ["TEN", 10.00], // 6
    ["TWENTY", 20.00], // 7
    ["ONE HUNDRED", 100.00] // 8
  ];
  for (let i = 8; i >= 0; i--) {
    if (changeLeft >= registerTemplate[i][1]) {
      let counter = 0;
      while (drawer[i][1] >= registerTemplate[i][1] && changeLeft >= registerTemplate[i][1]) {
        drawer[i][1] -= registerTemplate[i][1];
        changeLeft -= registerTemplate[i][1]; // THIS IS THE LINE IN QUESTION
        console.log(`just removed ${registerTemplate[i][1]} and changeLeft = ${changeLeft}`);
        counter++;
      }
      if (counter > 0) {
        
        change.push([registerTemplate[i][0], counter * registerTemplate[i][1]]);
      }
    }
  }

  console.log(`changeLeft is ${changeLeft}`);

  if (changeLeft > 0) {
    return {status: "INSUFFICIENT_FUNDS", change: []};
  } else if (changeLeft == 0) {
    return {status: "OPEN", change: change};
  } else {
    return {status: "OPEN", change: [...change]};
  }
}

console.log(checkCashRegister(3.26, 100, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]));

This is the console readout after running the script (I'm in VS Code if that's relevant):

initial changeLeft = 96.74
just removed 20 and changeLeft = 76.74
just removed 20 and changeLeft = 56.739999999999995
just removed 20 and changeLeft = 36.739999999999995
just removed 10 and changeLeft = 26.739999999999995
just removed 10 and changeLeft = 16.739999999999995
just removed 5 and changeLeft = 11.739999999999995
just removed 5 and changeLeft = 6.739999999999995
just removed 5 and changeLeft = 1.7399999999999949
just removed 1 and changeLeft = 0.7399999999999949
just removed 0.25 and changeLeft = 0.4899999999999949
just removed 0.25 and changeLeft = 0.23999999999999488
just removed 0.1 and changeLeft = 0.13999999999999488
just removed 0.1 and changeLeft = 0.03999999999999487
just removed 0.01 and changeLeft = 0.02999999999999487
just removed 0.01 and changeLeft = 0.01999999999999487
just removed 0.01 and changeLeft = 0.009999999999994869
changeLeft is 0.009999999999994869

As you can see, the second time the value 20 is subtracted is when the calculation starts to be off, and then there are a few more times when the values after the decimal point change slightly again in unexpected ways. The only solution I can think of is to force it to round to two decimal places, which would work as a band-aid fix but not address whatever the underlying problem is here. I found another question that had a similar problem when dealing with high-precision floating points, but that doesn't seem to be the case here since the highest precision is two decimal places. Another possible band-aid fix could be to do all calculations on integers by multiplying everything by 100, then dividing by 100 only at the end, but this again doesn't address the issue.

Thank you in advance.

Connor Mooneyhan
  • 547
  • 6
  • 17
  • 1
    Seems like a floating point precision error, try this: https://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript – randomcoder Jun 10 '21 at 00:10
  • You know floating point can only represent a small subset of possible numbers exactly, right? Instead of using dollars and fractions of them, what if you used number of cents? They'd always be whole numbers.. ;) – enhzflep Jun 10 '21 at 00:15
  • @Astro It is not an error. It is by design (the old "it's a feature, not a bug"). It is however a misunderstanding on the part of the programmer – slebetman Jun 10 '21 at 01:13
  • 1
    @enhzflep That's why cryptocurrencies don't use decimal points. Instead they implement their main currency as thousands or millions of the base unit eg. one Bitcoin is actually 100 million Satoshis - they only calculate in integers (of course, you still need to round your numbers when multiplying by interest rates or discounts etc. but rounding is easier to explain to people than floating point math) – slebetman Jun 10 '21 at 01:16

0 Answers0