JavaScript找零计算函数Bug:便士找零始终少1分
找零计算代码的便士找零缺失问题修复方案
问题现象
当找零包含便士(PENNY)时总会少1分,例如支付100美元购买1.87美元的商品,应找零0.03美元便士,但实际输出仅为0.02美元。
问题根源
这是JavaScript浮点数精度缺陷导致的:十进制小数在二进制存储时存在精度损失,比如100 - 1.87的实际计算结果是98.12999999999999而非精确的98.13。循环减便士时,最后剩余的0.00999999999999会被判定为小于0.01,导致少统计一个便士。
修复方案
1. 核心修复思路
将所有金额转换为以分为单位的整数处理,彻底规避浮点数精度问题,最后再转换回美元格式展示。
2. 具体代码修改步骤
(1)调整基础金额变量和配置
- 把价格改为分单位:
let price = 187;(对应1.87美元) - 更新面额配置为分单位,并修正拼写错误:
const billValues = { "PENNY": 1, "NICKEL": 5, // 原代码拼写为NICKLE,已修正 "DIME": 10, "QUARTER": 25, "ONE": 100, "FIVE": 500, "TEN": 1000, "TWENTY": 2000, "ONE HUNDRED": 10000 }
(2)重写找零计算函数
修改calculateChange,用整数计算找零,避免精度损失:
const calculateChange = (amountTendered, price) => { // 将输入金额转换为分(处理用户输入的字符串) const tenderedCents = Math.round(parseFloat(amountTendered) * 100); const priceCents = price; let changeCents = tenderedCents - priceCents; let totalChange = []; // 复制cid数组再反转,避免修改原数组 const cidR = [...cid].reverse(); for (let i = 0; i < cidR.length; i++) { const [billName, drawerAmount] = cidR[i]; // 抽屉中的金额转为分 const drawerCents = Math.round(drawerAmount * 100); const billValueCents = billValues[billName]; // 计算该面额最多能取的数量:不超过找零需求,也不超过抽屉库存 const maxCount = Math.min( Math.floor(changeCents / billValueCents), Math.floor(drawerCents / billValueCents) ); if (maxCount > 0) { // 转回美元格式存入结果 const totalForBill = (maxCount * billValueCents) / 100; totalChange.push([billName, totalForBill]); // 扣除已分配的找零 changeCents -= maxCount * billValueCents; } } // 若仍有剩余找零,说明无法凑出 return changeCents === 0 ? totalChange : []; }
(3)修正展示和判断逻辑
- 修复
writeChangeDue函数的返回值问题,确保金额展示精确到分:const writeChangeDue = (arr) => { arr.forEach((item) => { changeDue.innerHTML += `<p class="change-due">${item[0]}: $${item[1].toFixed(2)}</p>`; }); } - 调整按钮点击事件中的金额判断逻辑:
purchaseBtn.addEventListener("click", () => { changeDue.innerHTML = ""; const tendered = parseFloat(cash.value); const actualPrice = price / 100; // 转回美元 if (isNaN(tendered) || tendered < actualPrice) { alert("Customer does not have enough money to purchase the item"); } else if (tendered === actualPrice) { changeDue.innerHTML += `<p>No change due - customer paid with exact cash</p>`; } else if (tendered > parseFloat(totalCashRoundedUp)) { changeDue.innerHTML += `<p>Status: INSUFFICIENT_FUNDS</p>`; } else { const changeResult = calculateChange(cash.value, price); if (changeResult.length === 0) { changeDue.innerHTML += `<p>Status: INSUFFICIENT_FUNDS</p>`; } else { changeDue.innerHTML += `<p class="change-due">Status: OPEN</p>`; writeChangeDue(changeResult); } } });
完整修复后代码
const cash = document.getElementById("cash"); const changeDue = document.getElementById("change-due"); const purchaseBtn = document.getElementById("purchase-btn"); const cashInDrawer = document.getElementById("cash-in-drawer"); const totalSpan = document.getElementById("total") // 价格以分为单位:187分 = 1.87美元 let price = 187; let cid = [ ["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100] ]; // 面额对应分 const billValues = { "PENNY": 1, "NICKEL": 5, "DIME": 10, "QUARTER": 25, "ONE": 100, "FIVE": 500, "TEN": 1000, "TWENTY": 2000, "ONE HUNDRED": 10000 } const totalCash = cid.reduce((acc, arr) => acc + arr[1], 0); const totalCashRoundedUp = totalCash.toFixed(2); // 渲染现金抽屉 cid.forEach((arr) => { cashInDrawer.innerHTML += `<li>${arr[0]}: $${arr[1].toFixed(2)}</li>`; }); totalSpan.textContent = `$${(price / 100).toFixed(2)}`; const calculateChange = (amountTendered, price) => { const tenderedCents = Math.round(parseFloat(amountTendered) * 100); const priceCents = price; let changeCents = tenderedCents - priceCents; let totalChange = []; const cidR = [...cid].reverse(); for (let i = 0; i < cidR.length; i++) { const [billName, drawerAmount] = cidR[i]; const drawerCents = Math.round(drawerAmount * 100); const billValueCents = billValues[billName]; const maxCount = Math.min( Math.floor(changeCents / billValueCents), Math.floor(drawerCents / billValueCents) ); if (maxCount > 0) { const totalForBill = (maxCount * billValueCents) / 100; totalChange.push([billName, totalForBill]); changeCents -= maxCount * billValueCents; } } return changeCents === 0 ? totalChange : []; } const writeChangeDue = (arr) => { arr.forEach((item) => { changeDue.innerHTML += `<p class="change-due">${item[0]}: $${item[1].toFixed(2)}</p>`; }); } purchaseBtn.addEventListener("click", () => { changeDue.innerHTML = ""; const tendered = parseFloat(cash.value); const actualPrice = price / 100; if (isNaN(tendered) || tendered < actualPrice) { alert("Customer does not have enough money to purchase the item"); } else if (tendered === actualPrice) { changeDue.innerHTML += `<p>No change due - customer paid with exact cash</p>`; } else if (tendered > parseFloat(totalCashRoundedUp)) { changeDue.innerHTML += `<p>Status: INSUFFICIENT_FUNDS</p>`; } else { const changeResult = calculateChange(cash.value, price); if (changeResult.length === 0) { changeDue.innerHTML += `<p>Status: INSUFFICIENT_FUNDS</p>`; } else { changeDue.innerHTML += `<p class="change-due">Status: OPEN</p>`; writeChangeDue(changeResult); } } });
内容的提问来源于stack exchange,提问作者Jibril Abshir
相关产品推荐
相关产品推荐

