JavaScript 浮点数陷阱及解法 https://github.com/camsong/blog/issues/9

//魔鬼的经历
(96.7 + 0.04) === 96.74 //false
96.7 + 0.04 // 96.74000000000001
(96.7 + 0.04).toFixed(2) //"96.74"
(96.7 + 0.04).toFixed(2)*1 //96.74
(96.7 + 0.04).toFixed(2)*1 === 96.74 //true

//原始版本留念 /(ㄒoㄒ)/~~
var currencyUnits = [100, 20, 10, 5, 1, 0.25, 0.1, 0.05, 0.01]

//抽屉钱数总额
function getDeposit(cid) {
  let sum = 0
  sum = cid.reduce((cash, obj) => {
    return cash + obj[1]
  }, 0)
  return sum
}
function checkCashRegister(price, cash, cid) {
// console.log(getDeposit(cid))
  //需要找的零钱额度
  let smallChange = cash - price
console.log("smallChange: " +smallChange)
  //抽屉存款够找零
  if(getDeposit(cid).toFixed(2)*1 > smallChange) {
console.log(`getDeposit(cid).toFixed(2)*1 > smallChange: ${getDeposit(cid).toFixed(2)*1 > smallChange}`)
console.log(`getDeposit(cid): ${getDeposit(cid)}`)
console.log(`getDeposit(cid).toFixed(2)*1: ${getDeposit(cid).toFixed(2)*1}`)
console.log(`smallChange: ${smallChange}`)
    let cidReverseCopy = [...cid].reverse()
    let getCashSum = 0
    let feedbackCid = {status: "OPEN", change:[]}
    //面额从大到小一个一个分析
    for(let i=0; i < currencyUnits.length; i++) {
// console.log("面额从大到小一个一个分析 - " + i)
      //当前面额 < 找零的钱,那么可以参与找零
      if(currencyUnits[i] < (smallChange - getCashSum).toFixed(2)*1) {
        //当前面额取几份
        for(let j = 1; true; j++) {
// console.log("当前面额取几份 - " + j)
          //(当前面额取的钱数 + 前面面额取得的钱数) < 找零的钱
          //currencyUnits[i] * j =  面额*张数
          //getCashSum = 找零计数
          //smallChange = 需要找多少钱
          //cidReverseCopy[i][1] = 这个面额有多少钱
          if((currencyUnits[i] * j + getCashSum).toFixed(2)*1 <= smallChange &&
             (currencyUnits[i] * j).toFixed(2)*1 <= cidReverseCopy[i][1]) {
// console.log("当前面额取几份 - 还可以取一份当前面额" + j)
if(cidReverseCopy[i][0] === "PENNY"){
// console.log(`--------------------------------------------`)
//   console.log(`currencyUnits[i] * j: ${currencyUnits[i] * j}`)
//   console.log(`getCashSum: ${getCashSum}`)
//   console.log(`cidReverseCopy[i][1]: ${cidReverseCopy[i][1]}`)
// console.log(`============================================`)
console.log(`--------------------------------------------`)
console.log(`                                                   j:  ${j}`)
console.log(`(currencyUnits[i] * j + getCashSum) <= smallChange  :  `+
            `${currencyUnits[i] * j} + ${getCashSum} <= ${smallChange}`)
console.log(`(currencyUnits[i] * j) <= cidReverseCopy[i][1])     :  `+
            `${currencyUnits[i] * j} <= ${cidReverseCopy[i][1]}`)
// console.log(`cidReverseCopy[i][1]: ${cidReverseCopy[i][1]}`)
console.log(`============================================`)
}
            //还可以取一份当前面额
          }else {
if(cidReverseCopy[i][0] === "PENNY"){
console.log(`--------------------------------------------`)
console.log(`                                                   j:  ${j}`)
console.log(`(currencyUnits[i] * j + getCashSum) <= smallChange  :  `+
            `${currencyUnits[i] * j} + ${getCashSum} <= ${smallChange}`)
console.log(`(currencyUnits[i] * j) <= cidReverseCopy[i][1])     :  `+
            `${currencyUnits[i] * j} <= ${cidReverseCopy[i][1]}`)
// console.log(`cidReverseCopy[i][1]: ${cidReverseCopy[i][1]}`)
console.log(`============================================`)
}
// console.log("当前面额取几份 - 当前面额不用再取了" + j)
            //当前面额不用再取了
            //先前取得找零的钱 + 当前面额总额 = 目前找零总额
            getCashSum += currencyUnits[i] * (j-1)
            getCashSum = getCashSum.toFixed(2)*1
            //取了几张,记录当前额度的总钱数,要反馈的
            if(currencyUnits[i]*(j-1) !== 0)
              feedbackCid.change.push([cidReverseCopy[i][0], currencyUnits[i]*(j-1)])
// console.log("当前面额取几份 - 当前面额不用再取了 - feedbackid: " + feedbackCid.change)
// console.log("当前面额取几份 - 额度:" + currencyUnits[i] + " * " + (j-1))
console.log(
  `面额额度: ${cidReverseCopy[i][0]}(${currencyUnits[i]}) * ${(j-1)}`+
  ` = ${getCashSum}`
)
// console.log("当前面额取几份 - 次数:" + (j-1))
            break//中止当前循环
          }
        }
// console.log("取完了一个面额的,够了吗? - getCashSum: " + getCashSum)
        //钱取够了,不用再继续分析下一个面额
        if(getCashSum === smallChange) {
console.log("抽屉存款够找零 - feedbackCid")
          return feedbackCid//进行反馈
        }
      }
    }
    if(getCashSum < smallChange) {
console.log("抽屉存款够找零 - feedbackCid")
      return {status: "INSUFFICIENT_FUNDS", change: []}
    }
  }
  //抽屉存款不够找零
  if(getDeposit(cid).toFixed(2)*1 < smallChange) {
    return {status: "INSUFFICIENT_FUNDS", change: []}
  }
  //抽屉存款 === 找零
  if(getDeposit(cid).toFixed(2)*1 === smallChange) {
    return {status: "CLOSED", change: cid}
  }

  // var change = 0;
  // return change;
}

// 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]]);
// 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]])
// checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 1], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]])
checkCashRegister(19.5, 20, [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]])

//整理版1

var currencyUnits = [100, 20, 10, 5, 1, 0.25, 0.1, 0.05, 0.01]

//抽屉钱数总额
function getDeposit(cid) {
  let sum = 0
  sum = cid.reduce((cash, obj) => {
    return cash + obj[1]
  }, 0)
  return sum
}
function checkCashRegister(price, cash, cid) {
  //需要找的零钱额度
  let smallChange = cash - price
  //抽屉存款够找零
  if(getDeposit(cid).toFixed(2)*1 > smallChange) {
    let cidReverseCopy = [...cid].reverse()
    let getCashSum = 0
    let feedbackCid = {status: "OPEN", change:[]}
    //面额从大到小一个一个分析
    for(let i=0; i < currencyUnits.length; i++) {
      //当前面额 < 找零的钱,那么可以参与找零
      if(currencyUnits[i] < (smallChange - getCashSum).toFixed(2)*1) {
        //当前面额取几份
        for(let j = 1; true; j++) {
          //(当前面额取的钱数 + 前面面额取得的钱数) < 找零的钱
          //currencyUnits[i] * j =  面额*张数
          //getCashSum = 找零计数
          //smallChange = 需要找多少钱
          //cidReverseCopy[i][1] = 这个面额有多少钱
          if((currencyUnits[i] * j + getCashSum).toFixed(2)*1 <= smallChange &&
             (currencyUnits[i] * j).toFixed(2)*1 <= cidReverseCopy[i][1]) {
            //还可以取一份当前面额
          }else {
            //当前面额不用再取了
            //先前取得找零的钱 + 当前面额总额 = 目前找零总额
            getCashSum += currencyUnits[i] * (j-1)
            getCashSum = getCashSum.toFixed(2)*1
            //取了几张,记录当前额度的总钱数,要反馈的
            if(currencyUnits[i]*(j-1) !== 0)
              feedbackCid.change.push([cidReverseCopy[i][0], currencyUnits[i]*(j-1)])
            break//中止当前循环
          }
        }
        if(getCashSum === smallChange) {
          return feedbackCid//进行反馈
        }
      }
    }
    if(getCashSum < smallChange) {
      return {status: "INSUFFICIENT_FUNDS", change: []}
    }
  }
  //抽屉存款不够找零
  if(getDeposit(cid).toFixed(2)*1 < smallChange) {
    return {status: "INSUFFICIENT_FUNDS", change: []}
  }
  //抽屉存款 === 找零
  if(getDeposit(cid).toFixed(2)*1 === smallChange) {
    return {status: "CLOSED", change: cid}
  }
}
checkCashRegister(19.5, 20, 
  [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], 
   ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]])

测试数据:

···

//别人家的孩子
//TL;NR
//2019-11-03, 02:51
//(●'◡'●)

// Create an array of objects which hold the denominations and their values
var denom = [
  { name: "ONE HUNDRED", val: 100.0 },
  { name: "TWENTY", val: 20.0 },
  { name: "TEN", val: 10.0 },
  { name: "FIVE", val: 5.0 },
  { name: "ONE", val: 1.0 },
  { name: "QUARTER", val: 0.25 },
  { name: "DIME", val: 0.1 },
  { name: "NICKEL", val: 0.05 },
  { name: "PENNY", val: 0.01 }
];

function checkCashRegister(price, cash, cid) {
  var output = { status: null, change: [] };
  var change = cash - price;

  // Transform CID array into drawer object
  var register = cid.reduce(
    function(acc, curr) {
      acc.total += curr[1];
      acc[curr[0]] = curr[1];
      return acc;
    },
    { total: 0 }
  );

  // Handle exact change
  if (register.total === change) {
    output.status = "CLOSED";
    output.change = cid;
    return output;
  }

  // Handle obvious insufficient funds
  if (register.total < change) {
    output.status = "INSUFFICIENT_FUNDS";
    return output;
  }

  // Loop through the denomination array
  var change_arr = denom.reduce(function(acc, curr) {
    var value = 0;
    // While there is still money of this type in the drawer
    // And while the denomination is larger than the change remaining
    while (register[curr.name] > 0 && change >= curr.val) {
      change -= curr.val;
      register[curr.name] -= curr.val;
      value += curr.val;

      // Round change to the nearest hundreth deals with precision errors
      change = Math.round(change * 100) / 100;
    }
    // Add this denomination to the output only if any was used.
    if (value > 0) {
      acc.push([curr.name, value]);
    }
    return acc; // Return the current change_arr
  }, []); // Initial value of empty array for reduce

  // If there are no elements in change_arr or we have leftover change, return
  // the string "Insufficient Funds"
  if (change_arr.length < 1 || change > 0) {
    output.status = "INSUFFICIENT_FUNDS";
    return output;
  }

  // Here is your change, ma'am.
  output.status = "OPEN";
  output.change = change_arr;
  return output;
}

// test here
checkCashRegister(19.5, 20.0, [
  ["PENNY", 1.01],
  ["NICKEL", 2.05],
  ["DIME", 3.1],
  ["QUARTER", 4.25],
  ["ONE", 90.0],
  ["FIVE", 55.0],
  ["TEN", 20.0],
  ["TWENTY", 60.0],
  ["ONE HUNDRED", 100.0]
]);

让人头秃的草纸