PAYATHON 2026

如何将余额清零并计入最后一期付款

支付阿杰

结论

最后一期不能继续累加固定的 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。

备注:内容仅供参考。