如何将余额清零并计入最后一期付款
支付阿杰
结论
最后一期不能继续累加固定的 paymentAmount。每期算出利息后,应先判断本期付款是否足以结清贷款。如果可以,就将“剩余本金 + 本期利息”作为实际付款额,同时把 theNewBalance 明确设为 0。
按照给定数据和现有计算方式,前 58 期每期支付 $199.19,第 59 期根据剩余应付金额支付 $199.26。总额为:
58 × 199.19 + 199.26 = 11752.28
修改方法
代码中需要区分两个金额:
paymentAmount:正常期数使用的固定付款额。actualPayment:当期实际付款额,最后一期可能与固定付款额不同。
判断当前是否为最后一期时,需要比较固定付款额与当期全部应付金额:
paymentAmount >= theNewBalance + paidInterest
如果条件成立,说明本期可以结清贷款。此时不再执行常规的本金扣减逻辑,而是一次偿还全部剩余本金。
完整代码
procedure TForm1.Button1Click(Sender: TObject);
var
paymentAmount, actualPayment: Extended;
principle, theSum, theNewBalance: Extended;
paidInterest, theRate: Extended;
numberOfPays, myCnt: Integer;
begin
myCnt := 0;
theNewBalance := 11100.00;
theRate := 0.192308;
paymentAmount := 199.19;
numberOfPays := 59;
theSum := 0.00;
ListBox1.Clear;
ListBox1.Items.Add(
'Starting balance : ' +
FloatToStr(SimpleRoundTo(theNewBalance, -2))
);
repeat
Inc(myCnt);
paidInterest := theRate / 100 * theNewBalance;
actualPayment := paymentAmount;
{ 判断本期是否能够清偿剩余本金及利息 }
if paymentAmount >= theNewBalance + paidInterest then
begin
actualPayment := SimpleRoundTo(
theNewBalance + paidInterest, -2
);
{ 最后一期偿还全部剩余本金 }
principle := theNewBalance;
{ 让付款额、本金和利息保持一致 }
paidInterest := actualPayment - principle;
{ 不依赖浮点数恰好计算为 0 }
theNewBalance := 0.00;
end
else
begin
principle := actualPayment - paidInterest;
theNewBalance := theNewBalance - principle;
end;
theSum := theSum + actualPayment;
ListBox1.Items.Add(
IntToStr(myCnt) + ' : ' +
FloatToStr(SimpleRoundTo(actualPayment, -2)) + ' : ' +
FloatToStr(SimpleRoundTo(paidInterest, -2)) + ' : ' +
FloatToStr(SimpleRoundTo(principle, -2)) + ' : ' +
FloatToStr(SimpleRoundTo(theNewBalance, -2))
);
until (myCnt = numberOfPays) or (theNewBalance = 0.00);
ListBox1.Items.Add(
'Total : ' + FloatToStr(SimpleRoundTo(theSum, -2))
);
end;
为什么原代码少了 $0.07
原代码在每一期都执行:
theSum := theSum + paymentAmount;
因此,59 期均按 $199.19 累加:
59 × 199.19 = 11752.21
但最后一期需要偿还的实际金额不是固定付款额,而是:
剩余本金 + 本期利息
这个金额舍入到分后是 $199.26。因此,最后一期应累加 actualPayment,不能始终累加 paymentAmount。
注意事项
不要依赖浮点数与零直接比较
对于一般的浮点计算结果,下面的判断并不可靠:
theNewBalance = 0.00
Extended 使用二进制浮点表示,计算结果可能非常接近零,却不等于零。本例可以使用这个终止条件,是因为最后一期分支明确执行了:
theNewBalance := 0.00;
如果代码没有显式清零,应使用带有误差范围的判断,例如:
Abs(theNewBalance) < 0.00001
明确统一舍入规则
贷款系统需要明确规定利息的舍入时机,例如:
- 每期利息都舍入到分;
- 只在显示时舍入,内部保留完整精度;
- 在最后一期统一调整累计误差。
采用不同规则,最终结果可能相差几分钱。上面的代码沿用了原程序的计算方式:中间计算保留完整精度,最后一期付款额通过 SimpleRoundTo(..., -2) 舍入到分。
金额类型优先考虑 Currency
Delphi 的 Currency 是定点类型,比 Extended 更适合存储货币金额。不过,Currency 内部保留四位小数。如果业务要求精确到分,仍需明确执行两位小数舍入。利率可以继续使用 Extended,金额则可以改用 Currency。
备注:内容仅供参考。
