如何计算 Reprofiled Amount 的最后一笔付款
支付阿杰
结论
金额计算时,统一换算成“分”,再用整数运算。前三笔付款均为 106017.96,研究成本为 424071.85,最后一笔付款应为:
424071.85 - 106017.96 × 3 = 106017.97
toLocaleString() 只能格式化显示结果,无法修正 JavaScript 浮点数的精度误差,因此不能依赖它处理金额计算。
原因
JavaScript 的 Number 采用 IEEE 754 二进制浮点数。很多十进制小数无法用二进制精确表示,所以计算:
424071.85 - (106017.96 * 3)
可能得到:
106017.96999999997
业务金额并没有减少,这是浮点数内部表示产生的误差。
formatAsCurrency() 返回的是格式化字符串。应先完成所有金额计算,再进行格式化,不能把用于显示的结果当作精确金额继续计算。
推荐方案:以“分”为单位计算
function toCents(amount) {
return Math.round(Number(amount) * 100);
}
function formatCents(cents) {
return (cents / 100).toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
const RC = 424071.85;
const numAmounts = [106017.96, 106017.96, 106017.96];
const rcCents = toCents(RC);
const paidCents = numAmounts.reduce(
(total, amount) => total + toCents(amount),
0
);
const remainingCents = rcCents - paidCents;
const remainingAmount = remainingCents / 100;
console.log(remainingAmount); // 106017.97
console.log(formatCents(remainingCents)); // "106,017.97"
验证总额时,同样比较整数形式的“分”:
const allPaymentCents =
paidCents + remainingCents;
console.log(allPaymentCents === rcCents); // true
如果最后一笔已存在,需要把差额补到最后一笔
如果数组已经包含全部季度付款,先把每笔金额换算成分,再将总额差额加到最后一笔:
const RC = 424071.85;
const numAmounts = [
106017.96,
106017.96,
106017.96,
106017.96
];
const rcCents = Math.round(RC * 100);
const paymentCents = numAmounts.map(amount => Math.round(amount * 100));
const currentTotalCents = paymentCents.reduce(
(total, amount) => total + amount,
0
);
const differenceCents = rcCents - currentTotalCents;
paymentCents[paymentCents.length - 1] += differenceCents;
const adjustedAmounts = paymentCents.map(cents => cents / 100);
console.log(adjustedAmounts);
// [106017.96, 106017.96, 106017.96, 106017.97]
最后再格式化显示:
const formattedAmounts = paymentCents.map(cents =>
(cents / 100).toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
);
console.log(formattedAmounts);
// ["106,017.96", "106,017.96", "106,017.96", "106,017.97"]
注意事项
- 计算金额时使用整数“分”,只有在展示时才除以
100。 toFixed()和toLocaleString()用于格式化,不应承担账务精度控制。- 不要直接用
===比较浮点金额的计算结果,应比较换算后的整数分值。 - 如果金额可能超过 JavaScript 的安全整数范围,或涉及多币种、税率和更高精度,应使用
BigInt或专用的十进制定点数库。 - 这种方案适用于金额最多保留两位小数的情况。
备注:内容仅供参考。
