一文让你搞懂贷款计算方式,怎么提前还款划算,js实现房贷计算器。

723 阅读5分钟

贷款怎么还最划算,先说答案:越早越划算

很多人没有研究过房贷怎么算的,就觉得刚开始还的都是利息,到后期利息少了,就没必要提前还了。这是一种错觉,后期还的少的原因是因为你的贷款本金少了,当月的利息多少之和当月的持有本金相关。

等额本息的还款方式是利用的一个数学公式让每个月的还款相同,两者相差不多,本篇以等额本金为例。

等额本金

等额本金从表面来看就是每个月还的本金固定为贷款金额/(贷款年限*12),利率逐月减少,可以说每个月少还的几块钱都是利息的减少。

等额本金的计算公式:月利息 = 贷款利率*当月贷款持有本金 / 12

代码实现

function getMonthlyPayment(moneyRate, totalPrincipal, year) {
  // 结果列表
  const resultList = [];

  // 总月份
  const totalMonth = year * 12;

  // 剩余持有本金
  let tp = totalPrincipal;

  // 总利息
  let interest = 0;

  // 每个月的还款本金
  let oneMonthPrincipal = tp / totalMonth;
  for (let i = 1; i <= totalMonth; i++) {
    // 本月的利息
    const curInterest = (tp * moneyRate) / 12;
    interest += curInterest;

    let curPrincipal = oneMonthPrincipal;
    tp -= oneMonthPrincipal;

    resultList.push({
      期数: i,
      贷款剩余本金: Math.round(tp),
      已还总利息: Math.round(interest),
      本期归还本金: Math.round(curPrincipal),
      本期利息: Math.round(curInterest),
    });
  }

  return resultList;
}

提前还款

等额本金的计算逻辑很简单,接下来我们看看提前还款的逻辑,

提前还款会有两种方式:月供不变年限减少年限不变月供减少,看起来月供不变年限减少这种方式更省利息,其实在提前还款没有违约金的情况下,这两种方式区别不大,对于月供压力大的人最好选择年限不变月供减少降低压力。

也可以用这种方式,提前还剩下几百,每个月就剩几毛钱的贷款 用来抵税。

月供不变年限减少相当于减少的是贷款年限(贷款的时候少贷几年)

年限不变月供减少相当于减少的是贷款金额(贷款的时候少贷几万)

年限不变月供减少 提前还款计算代码实现

  • 写一个计算月差的函数,d1和d2都是月份,比如(2024-5)
function subMonth(startDateStr, endDateStr) {
  // 将日期字符串解析为日期对象
  // 注意:这里我们假设日期的日部分是1,因为我们只关心年和月
  const startDate = new Date(
    startDateStr.split("-")[0],
    startDateStr.split("-")[1] - 1,
    1
  );
  const endDate = new Date(
    endDateStr.split("-")[0],
    endDateStr.split("-")[1] - 1,
    1
  );

  const yearsDiff = endDate.getFullYear() - startDate.getFullYear();

  const monthsDiff =
    yearsDiff * 12 + (endDate.getMonth() - startDate.getMonth()) + 1;

  return monthsDiff - 1;
}
  • 设定提前还款的列表是下面这样的,date是提前还款时间,money是提前还款金额
const prePayList = [
  { date: "2024-9", money: 10000 },
  // { date: "2024-10", money: 20000 },
];
  • 写一个函数给这个列表添加是第几期,比如如果第一次还款是2024-5,那么2024-9就是第5期,则添加一个num:5
function addNum(prePayList, firstPayment) {
  const list = [...prePayList];
  list.forEach((item) => {
    const num = subMonth(item.date, firstPayment);
    item.num = num + 1;
  });
  return list;
}
  • 核心逻辑
function getMonthlyPayment(
  moneyRate,
  totalPrincipal,
  year,
  firstRepayment,
  repaymentList
) {
  const totalMonth = year * 12;
  let tp = totalPrincipal;
  const prePayListWithNum = addNum(repaymentList, firstRepayment);
  let interest = 0;
  const resultList = [];
  let oneMonthPrincipal;
  oneMonthPrincipal = tp / totalMonth;
  for (let i = 1; i <= totalMonth; i++) {
    const prePayItem = prePayListWithNum.find((item) => {
      return item.num === i;
    });
    const curInterest = (tp * moneyRate) / 12;
    interest += curInterest;
    let curPrincipal = oneMonthPrincipal;
    tp -= oneMonthPrincipal;
    if (prePayItem) {
      tp -= prePayItem.money;
      oneMonthPrincipal = tp / (totalMonth - i);
    }
    resultList.push({
      期数: i,
      贷款剩余本金: Math.round(tp),
      已还总利息: Math.round(interest),
      本期归还本金: Math.round(curPrincipal),
      本期利息: Math.round(curInterest),
      提前还款: prePayItem?.money || 0,
      本期还款总额: curPrincipal + (prePayItem?.money || 0),
    });
  }

  return resultList;
}
  • 测试
const prePayList = [
  { date: "2024-9", money: 10000 },
  { date: "2024-10", money: 20000 },
];
const payment = getMonthlyPayment(0.0345, 600000, 20, "2024-5", prePayList);

// 这个结果应该等于或者趋近于贷款金额
console.log(
  payment.reduce((pre, cur) => {
    return pre + cur["本期还款总额"];
  }, 0)
);
  • 如果用的node环境,控制台不方便展示太多,可以写入文件中
require("fs").writeFileSync("payment.json", JSON.stringify(payment, null, 2));

月供不变年限减少 提前还款计算代码实现

  • 只需要修改核心代码的这里的逻辑
   if (prePayItem) {
      tp -= prePayItem.money;
      totalMonth = prePayItem.num + Math.ceil(tp / oneMonthPrincipal);
    }
  • 核心逻辑代码
function getMonthlyPayment(
  moneyRate,
  totalPrincipal,
  year,
  firstRepayment,
  repaymentList
) {
  let totalMonth = year * 12;
  let tp = totalPrincipal;
  const prePayListWithNum = addNum(repaymentList, firstRepayment);
  let interest = 0;
  const resultList = [];
  let oneMonthPrincipal;
  oneMonthPrincipal = tp / totalMonth;
  for (let i = 1; i <= totalMonth; i++) {
    const prePayItem = prePayListWithNum.find((item) => {
      return item.num === i;
    });
    const curInterest = (tp * moneyRate) / 12;
    interest += curInterest;
    let curPrincipal = oneMonthPrincipal;
    tp -= oneMonthPrincipal;
    if (prePayItem) {
      tp -= prePayItem.money;
      totalMonth = prePayItem.num + Math.ceil(tp / oneMonthPrincipal);
      console.log(totalMonth);
    }
    resultList.push({
      期数: i,
      贷款剩余本金: Math.round(tp),
      已还总利息: Math.round(interest),
      本期归还本金: Math.round(curPrincipal),
      本期利息: Math.round(curInterest),
      提前还款: prePayItem?.money || 0,
      本期还款总额: curInterest + curPrincipal + (prePayItem?.money || 0),
      提前还款日期: prePayItem?.date || "",
    });
  }

  return resultList;
}

完整代码

function subMonth(startDateStr, endDateStr) {
  // 将日期字符串解析为日期对象
  // 注意:这里我们假设日期的日部分是1,因为我们只关心年和月
  const startDate = new Date(
    startDateStr.split("-")[0],
    startDateStr.split("-")[1] - 1,
    1
  );
  const endDate = new Date(
    endDateStr.split("-")[0],
    endDateStr.split("-")[1] - 1,
    1
  );

  const yearsDiff = endDate.getFullYear() - startDate.getFullYear();

  const monthsDiff =
    yearsDiff * 12 + (endDate.getMonth() - startDate.getMonth()) + 1;

  return monthsDiff - 1;
}

function addNum(prePayList, firstPayment) {
  const list = [...prePayList];
  list.forEach((item) => {
    const num = subMonth(item.date, firstPayment);
    item.num = num + 1;
  });
  return list;
}
function getMonthlyPayment(
  moneyRate,
  totalPrincipal,
  year,
  firstRepayment,
  repaymentList
) {
  const totalMonth = year * 12;
  let tp = totalPrincipal;
  const prePayListWithNum = addNum(repaymentList, firstRepayment);
  let interest = 0;
  const resultList = [];
  let oneMonthPrincipal;
  oneMonthPrincipal = tp / totalMonth;
  for (let i = 1; i <= totalMonth; i++) {
    const prePayItem = prePayListWithNum.find((item) => {
      return item.num === i;
    });
    const curInterest = (tp * moneyRate) / 12;
    interest += curInterest;
    let curPrincipal = oneMonthPrincipal;
    tp -= oneMonthPrincipal;
    if (prePayItem) {
      tp -= prePayItem.money;
      oneMonthPrincipal = tp / (totalMonth - i);
    }
    resultList.push({
      期数: i,
      贷款剩余本金: Math.round(tp),
      已还总利息: Math.round(interest),
      本期归还本金: Math.round(curPrincipal),
      本期利息: Math.round(curInterest),
      提前还款: prePayItem?.money || 0,
      本期还款总额: curPrincipal + (prePayItem?.money || 0),
    });
  }

  return resultList;
}

const prePayList = [
  { date: "2024-9", money: 10000 },
  // { date: "2024-10", money: 20000 },
];
const payment = getMonthlyPayment(0.0345, 600000, 20, "2024-5", prePayList);

console.log(
  payment.reduce((pre, cur) => {
    return pre + cur["本期还款总额"];
  }, 0)
);

require("fs").writeFileSync("payment.json", JSON.stringify(payment, null, 2));