Cash Register failing tests 2 and 3

Tell us what’s happening:

I wrote this code for the Cash Register project. All of my results appear to be correct, but I keep failing the second and third tests. This is baffling me because I compared my results with the expected result like so…

let myResult = checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]])

console.log(myResult)

let fccResult = {status: "OPEN", change: [["QUARTER", 0.5]]};

console.log(fccResult)

if( JSON.stringify(myResult) === JSON.stringify(fccResult) ){console.log("they are the same")}

and it is still saying our results are different. I am stumped at this point. Any advice? Sorry for my code being such a mess.

Your code so far


//** CREATE A CURRENCY OBJECT */
function getCurrency(change, cid){
    let num;
    let str;
    let index;

    if(change >= 100 && cid[8][1] >= 100){
      num = 100;
      str = "ONE HUNDRED";
      index = 8;
    }
    else if(change >= 20 && cid[7][1] >= 20){
      num = 20;
      str = "TWENTY";
      index = 7;
    }
    else if (change >= 10 && cid[6][1] >= 10){
      num = 10;
      str = "TEN";
      index = 6;
    }
    else if (change >= 5 && cid[5][1] >= 5){
      num = 5;
      str = "FIVE";
      index = 5;
    }
    else if (change >= 1 && cid[4][1] >= 1){
      num = 1;
      str = "ONE";
      index = 4;
    }
    else if (change >= 0.25 && cid[3][1] >= .25){
      num = 0.25;
      str = "QUARTER";
      index = 3;
    }
    else if (change >= .1 && cid[2][1] >= .1){
      num = 0.1;
      str = "DIME";
      index = 2;
    }
    else if (change >= .05 && cid[1][1] >= .05){
      num = 0.05;
      str = "NICKEL";
      index = 1;
    }
    else if (change >= .01 && cid[0][1] >= .01){
      num = 0.01;
      str = "PENNY";
      index = 0;
    }

    return {
      num:num,
      str:str,
      index:index,
      change:change
    }

}

//** COUNT ALL FUNDS IN REGISTER */
function sumRegister(change, cid){
  let sum = 0;
  cid.forEach(function(element){
    sum+=element[1];
  });
  return sum;
}
//** COUNT ALL FUNDS IN REGISTER THAT CAN BE USED FOR THE GIVEN CHANGE */
function sumUsableRegister(change, cid){
  let sumOfUsableCurrency = 0;
  let highestUsableCurrency = getCurrency(change,cid);
  for(let i = highestUsableCurrency.index; i >= 0; i--){
    sumOfUsableCurrency += cid[i][1];
  }
  return sumOfUsableCurrency;
}

let changeArr = []

//** GET CHANGE */
function generateChange(change, cid){

    let currency = getCurrency(change,cid);

    let remainder = 0;
    let availableCash = cid[currency.index][1];
    let toAdd;

    if(Math.floor((currency.change / currency.num)) * currency.num > availableCash){ 
      toAdd = (availableCash / currency.num) * currency.num;
    }
    else{
      toAdd = Math.floor(change/currency.num) * currency.num;
    }

    remainder = currency.change - toAdd;
    remainder = remainder.toFixed(2); //fix round issue\

    console.log(`Add ${toAdd} in ${currency.str}s`);
    console.log(`Remainder of ${remainder}`);
    

    let subArr = [currency.str, toAdd];
    changeArr.push(subArr);
  
    if(remainder > 0){
      generateChange(remainder,cid);
    }
    
    return;

}

//** MAIN */
function checkCashRegister(price, cash, cid) {
  var change  = cash - price;
  let status;
  let result;

  //STATUS OF THE REGISTER
  if(sumUsableRegister(change, cid) < change){
    status = "INSUFFICIENT_FUNDS";
    return{status:status, change:[]}
  }
  else if (sumRegister(change, cid) == change){
    status = "CLOSED";
    return{status:status, change:cid}
  }
  else{ 
    status = "OPEN";
    console.log("open status")
    generateChange(change,cid);
    let result = {status: status, change: changeArr};
    console.log(result)
    return result;
  }

}

// Example cash-in-drawer array:
// [["PENNY", 1.01],
// ["NICKEL", 2.05],
// ["DIME", 3.1],
// ["QUARTER", 4.25],
// ["ONE", 90],
// ["FIVE", 55],
// ["TEN", 20],
// ["TWENTY", 60],
// ["ONE HUNDRED", 100]]

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

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36.

Link to the challenge:
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/cash-register

I see at least one test for equality, as here.

else if (sumRegister(change, cid) == change){

Remember - floating point representations are always approximate; must always be rounded appropriately (two or zero decimals in this project, for money and quantity respectively); and do not adhere to the associative property of the real numbers that they approximate. (That latter means that performing two operations on floating point numbers are not guaranteed to give the same result when performed in the opposite order.)

Therefore equality comparisons are always a floating point bug - regardless of how many times they have tested successful in the past.

In this particular challenge for example, the successive subtraction of 0.01 from 0.50 a total of 49 times can result in a value less than 0.01 - preventing a naive implementation from successfully generating the 50th penny of change.

Hopefully that helps.

So would using the toFixed() function and set it to two decimal places resolve the floating point bug? Like so:

function sumRegister(change, cid){
  let sum = 0;
  cid.forEach(function(element){
    sum += element[1];
  });
  sum = sum.toFixed(2);
  return sum;
}

Thank you for pointing that out. However, I still have the same issue with the objects I am returning seeming to be identical to the expected result but still not passing the second and third tests.

What I did when I solved the challenge way back was to to multiply everything by 100 to start, do all my calculations in integers, then divide everything by 100 before returning the final results.

Personally, I think the cash register challenge should be rewritten with all denominations specified in pennies; that is, omitting the floating point entirely.

Your third test case fails explicitly - by returning an extra instance of QUARTER at the front of the Change array. Likely you are not initializing the Change array properly on each iteration.

Yep - there it is. changeArr is a global variable initialized only once, globally. That’s an error; it should be initialized within the function that sets its value.

Global variables are only not bugs-in-waiting.when they are already bugs.

That is the whole point. Perhaps it should be made explicitly but it cannot be repeated enough:

The Floating Point data type, used by default for number in Java script, is wholly inadequate for any and all accounting applications.

Proper use of Math.round, on every operational result before assignment to a variable, (or possibly toFixed() - I haven’t looked at that in JS yet) can make applications functionally correct - but at tremendous risk and imposition on the programmer.

Back in the day CS Majors spent a whole term on how to properly perform calculations robustly in Floating Point for various application needs: Accounting, Engineering, Mathematics, etc.

Hazaaaah! I got it working. You’re a hero pgeerkens!

I resolved the third case error shortly after posting.

In my hubris I dared to add a global thinking I could get away with it. Once I put it into the function scope it worked like a charm. That is something I am still not clear on though. Why do globals cause such errors? The change object I was returning when I added the global still had all the properties and values the test was looking for.

Though I found this challenge frustrating at times, I learned a lot. If the purpose is to familiarize students with how bad javaScript is at accounting or any serious math, then it succeeded with me. It seems like importing special libraries or using another language all together is necessary for projects heavy in non integer math.

There is nothing about floating point errors anywhere in the project specifications or even hints though. It may be worth adding something in the specifications or hints that reminds us of the dangers inherent of javaScript’s default number type.

Re:

Why do globals cause such errors?

Because the test script runs all tests in succession (into separate results containers), then verifies them in succession, precisely to catch errors such as this.

Terminology correction:

Accounting requires integer, as in fixed-point, mathematics. The number data type (a floating-point type) is fine for many scientific and engineering applications, where absolute precision is unnecessary. However even there one must be careful. The mathematically elegant formulas one sees in Math texts are often very ill-behaved in floating-point mathematics. For more detail lookup something like Numerical Methods in Programming.

The decimal, money, and currency data-types in languages such as SQL, Java, C#, C++, etc. are all fixed-point data types, and perform all calculations to absolute precision - until they over-flow.

There is nothing about floating point errors anywhere in the project specifications or even hints though.

Likely because the thought is that this is far too complex a topic to open at this point in the curriculum. I tend to agree, though a heads-up might be of help to students.