用JavaScript生成等额本息还款计划&尾差研究

timg.jpg

等额本息是指一种贷款的还款方式,指在还款期内,每月偿还同等数额的贷款(包括本金和利息)。

首先,需要确认的是,等额本息是一种非常优秀的还款方式。主要原因有两点,都写在名字里了:

一是“本息”,按占用本金的数量和时间来计算利息,从这个角度上来说,等额本息的利率一旦确定,借款周期无论怎么变,利率都是一样的,不用再额外去算什么综合利率;

二是“等额”,每期的还款金额一致,方便借款人记忆。

基于以上特性,银行等金融机构都爱使用等额本息,大多数时候,房贷也是按等额本息计算的,所以采用等额本息计息方式的房贷等有“月供”一说。

等额本息如何计算

等额本息每期还款额的计算公式是这样的:

cc11728b4710b912a1749141cefdfc03934522d3.jpg

其中,P是贷款本金,即如果借1万块钱,那这1万块就是贷款本金;R是月利率,即年利率除以12;N是还款期数,如果是按月还款,那就是月数。

以借10000元为例,年利率6%,12期还完,用以上公式计算,可得到每期还款的金额及包含的明细:

image.png

从表格中也可以看出,从第一期到最后一期,每期归还的本金越来越多,每期归还的利息越来越少,最后一期还完,剩余本金为0。

用HTML和JavaScript自动生成还款计划表

接下来,我们用HTML和JavaScript来生成还款计划表。大致的思路是这样:

  1. 写一个函数来计算等额本息算法下的每期还款额,让用户输入借款本金、借款利率、借款期数三个参数,然后系统获取到参数进行计算;
var principal = document.getElementById('money').value;    //定义初始本金
    var monthlyInterestRate = document.getElementById('interestRate').value/12;   //定义月利率
    var borrowingPeriod = document.getElementById('borrowingPeriod').value;    //定义期数
    var transition = Math.pow((1 + monthlyInterestRate),borrowingPeriod);  //用过渡变量表示(1+R)的N次方
    var monthlyRepayment = principal * monthlyInterestRate * transition / (transition - 1); //PMT公式
    monthlyRepayment = monthlyRepayment.toFixed(2);
    document.getElementById('monthlyRepayment').innerText = monthlyRepayment;  
  1. 根据初始的剩余本金(即借款金额)计算第一期所还的利息,用每期还款额-第1期应还利息=第1期应还本金,然后用初始剩余本金-第1期应还本金=第1期剩余本金。
IM截图20190812195827.jpg

之后的每期都遵循同样的逻辑,可以用for循环来解决。

//计算当期利息、当期本金、剩余本金
var returnInterest = new Array();    //定义一个数组用来存储每期的应还利息
var returnPrincipal = new Array();    //定义一个数组用来存储每期的应还本金
var residualPrincipal = new Array();    //定义一个数组来存储每期的剩余本金
var bridge = principal;               //用一个变量来存储当前剩余本金作为过渡值;
for(var i = 0;i < borrowingPeriod;i++)
    {
        returnInterest[i] = bridge * monthlyInterestRate;  //计算当期的应还利息
        returnPrincipal[i] = monthlyRepayment - returnInterest[i];  //计算当期的应还本金
        residualPrincipal[i] = bridge - returnPrincipal[i];     //计算当期的剩余本金
        bridge = residualPrincipal[i];
    }
  1. 把得到的每一期明细输出到HTML,生成表格。和计算每期明细类似,同样用for循环把每期计算的结果输出到表格中:
var row1 = document.getElementById('borrowingPeriod').value;       //根据期数定义行数
var col1 = 3;        //定义列数为3
var div1 = document.getElementById("div1");
var tab="<table border='1' width='500' height='10' font-size='12'><tr><td>期数</td><td>应还金额</td><td>应还本金</td><td>应还利息</td><td>剩余本金</td></tr><tr><td>0</td><td>0</td><td>0</td><td>0</td><td>" + principal + "</td></tr>";  //设置两行表头

//自动生成表格
for(var i = 1;i<=row1;i++)
    {
        tab += "<tr>";        //产生行
        tab += "<td>" + i + "</td>";
        tab += "<td>" + monthlyRepayment + "</td>";
        tab += "<td>" + returnPrincipal[i-1] + "</td>";
        tab += "<td>" + returnInterest[i-1] + "</td>";
        tab += "<td>" + residualPrincipal[i-1] + "</td>";
        }
        tab+="</table>"
        div1.innerHTML=tab;

等额本息的尾差

按照等额本息的算法,剩余本金到最后一期还完刚好是0元,且最后一期的月还款额等于上一期的剩余本金。

但实际上,用公式计算出来的月还款额是有很多位小数的。如果按四舍五入保留两位的,则会产生误差的。而其他几项明细都是基于此再进行测算得出的,到最后,月还款额的误差会传导给其他几项明细数据:

image.png

如果我们对每期还款额做四舍五入保留两位小数,对其他几项明细不做调整,则会得到这样的结果:

monthlyRepayment = monthlyRepayment.toFixed(2);
image.png

从金字塔型的小数部分可以清晰地看到误差的位数再一点点扩大,直到JavaScript保留的默认小数位数。

如何解决尾差

我们先来看看尾差的金额到底有多大,我们换一组参数,以房贷为例,借款200万人民币,30年还清,年利率5%。结果很长,我们看最后几期:

image.png

可以看出,最后一期的应还本金比上一期的剩余本金已经少了2元多,这两块多最后也被剩到末期的剩余本金里了,即没有还清。

当然,对于一笔30年200万的贷款,2元的尾差其实也不算多。

到这里我们已经发现,在每期还款额固定且四舍五入的情况下,一定会产生误差。我们可以这样处理,先在前端把结果调成看起来正常的样子:即最后一期应还本金等于上一期剩余本金,且最后一期剩余本金为0。

//手动算最后一期,抹除尾差
returnPrincipal[returnPrincipal.length-1] = residualPrincipal[residualPrincipal.length-2];
returnInterest[returnInterest.length-1] = (monthlyRepayment - returnPrincipal[returnPrincipal.length-1]).toFixed(2);
residualPrincipal[residualPrincipal.length-1] = 0;

然后把这部分看不见的尾差存到专门的尾差户,多收的话到期再返回给借款人,少收的话到期再补收,或者干脆每期都只“五入”不做“四舍”,最后再统一返还。

当然,如果可以接受最后一期还款额跟其他不一样的话,可以直接把这部分误差加到这期的还款额上。

推荐阅读更多精彩内容