项目地址
https://git.oschina.net/haxzheng/repayment.git
1.等本等息
总收益=借款金额×借款利率×借款月数n/12
总本金=借款金额
总本息=总收益+总本金
2至n月收益=月还利息=总收益/借款月数,保留两位小数,第三位起舍去
2至n月收本金=月还本金=向下取整(借款金额/借款月数)
2至n月结算额=月还款额=月利息+月还本金,保留两位小数,不足补0,下同
1月收益=总收益-求和(2至n月收益)
1月本金=总本金-求和(2至n月本金)
1月本金及收益=1月收益+1月本金
package org.xzheng.repayment.service.impl;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
import org.xzheng.repayment.util.DateUtil;
/**
* 等本等息。 误差放在第一个月
*/
public class RePayAlgorithmAverageCapital implements IRepayPlanGenerator {
@Override
public Map<Integer, ReplayPlan> getRepayPlan(double principal, double yearRate, int totalmonth, Date beginDate) {
Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
DateTime before = null;
DateTime after = null;
DateTime beginBase = new DateTime(beginDate);
beginBase = beginBase.plusDays(-1);
// 总收益=借款金额×借款利率×借款月数n/12
BigDecimal totalInterest = new BigDecimal(principal);
try {
totalInterest = totalInterest.multiply(new BigDecimal(yearRate)).multiply(new BigDecimal(totalmonth))
.divide(new BigDecimal(12), 2, BigDecimal.ROUND_HALF_UP);
} catch (Exception e) {
e.printStackTrace();
}
totalInterest = totalInterest.setScale(2, BigDecimal.ROUND_HALF_UP);
// 2至n月收益
BigDecimal perMonthInterest = totalInterest.divide(new BigDecimal(totalmonth), 2, BigDecimal.ROUND_DOWN);
// 2至n月收本金
BigDecimal perMonthPrincipal = new BigDecimal(principal);
perMonthPrincipal = perMonthPrincipal.divide(new BigDecimal(totalmonth), 2, BigDecimal.ROUND_DOWN);
// 2至n月收益,总额
BigDecimal totaInterestWithOutFirst = perMonthInterest.multiply(new BigDecimal(totalmonth - 1));
// 2至n月收本金,总额
BigDecimal totaPrincipalWithOutFirst = perMonthPrincipal.multiply(new BigDecimal(totalmonth - 1));
for (int i = 0; i < totalmonth; i++) {
int period = i + 1;
if (after == null) {
before = new DateTime(beginDate);
} else {
before = after.plusDays(1);
}
after = new DateTime(DateUtil.add_month(beginBase.toDate(), period));
int days = Days.daysBetween(before, after).getDays() + 1;
ReplayPlan plan = new ReplayPlan(period, yearRate, perMonthInterest, perMonthPrincipal, before.toDate(),
after.toDate(), days);
map.put(period, plan);
}
ReplayPlan firstMonth = map.get(1);
// 1月收益=总收益-求和(2至n月收益)
BigDecimal firstInterest = new BigDecimal(totalInterest.doubleValue() - totaInterestWithOutFirst.doubleValue());
firstInterest = firstInterest.setScale(2, BigDecimal.ROUND_HALF_UP);
firstMonth.setInterest(firstInterest);
// 1月本金=总本金-求和(2至n月本金)
BigDecimal firstPrincipal = new BigDecimal(principal - totaPrincipalWithOutFirst.doubleValue());
firstPrincipal = firstPrincipal.setScale(2, BigDecimal.ROUND_HALF_UP);
firstMonth.setPrincipal(firstPrincipal);
// 1月本金及收益=1月收益+1月本金
firstMonth.setTotalAmount(firstInterest.add(firstPrincipal));
return map;
}
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date begin = sdf.parse("2017-08-30");
Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmAverageCapital().getRepayPlan(4000, 0.1238, 24, begin);
System.out.println("等本等息");
for (int i = 1; i <= detailPlan.size(); i++) {
ReplayPlan plan = detailPlan.get(i);
System.out.println(plan);
}
}
}
测试结果:
等本等息
期数:1,年化利率:0.1238,利息:41.42,本金:166.82,总额:208.24,计息天数:31,计息开始日期:2017-08-30,计息结束日:2017-09-29
期数:2,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2017-09-30,计息结束日:2017-10-29
期数:3,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2017-10-30,计息结束日:2017-11-29
期数:4,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2017-11-30,计息结束日:2017-12-29
期数:5,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2017-12-30,计息结束日:2018-01-29
期数:6,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:29,计息开始日期:2018-01-30,计息结束日:2018-02-27
期数:7,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-02-28,计息结束日:2018-03-29
期数:8,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-03-30,计息结束日:2018-04-29
期数:9,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-04-30,计息结束日:2018-05-29
期数:10,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-05-30,计息结束日:2018-06-29
期数:11,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-06-30,计息结束日:2018-07-29
期数:12,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-07-30,计息结束日:2018-08-29
期数:13,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-08-30,计息结束日:2018-09-29
期数:14,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-09-30,计息结束日:2018-10-29
期数:15,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-10-30,计息结束日:2018-11-29
期数:16,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-11-30,计息结束日:2018-12-29
期数:17,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-12-30,计息结束日:2019-01-29
期数:18,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:29,计息开始日期:2019-01-30,计息结束日:2019-02-27
期数:19,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2019-02-28,计息结束日:2019-03-29
期数:20,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2019-03-30,计息结束日:2019-04-29
期数:21,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2019-04-30,计息结束日:2019-05-29
期数:22,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2019-05-30,计息结束日:2019-06-29
期数:23,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2019-06-30,计息结束日:2019-07-29
期数:24,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2019-07-30,计息结束日:2019-08-29
2.按月付息到期还本
每月支付的利息=本金×年利率÷365×持有天数
package org.xzheng.repayment.service.impl;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
import org.xzheng.repayment.util.DateUtil;
/**
* 按月付息,到期还本.
*/
public class RePayAlgorithmMonth implements IRepayPlanGenerator {
@Override
public Map<Integer, ReplayPlan> getRepayPlan(double principal, double yearRate, int totalmonth, Date begin) {
Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
DateTime before = null;
DateTime after = null;
// 需求:当日作为计息开始日,计息结束日期为下个月当天的前一日。
// 算法:开始日期先减1天,以次为基线,循环在月份上加上期数。月份的加法使用数据库算法。
// 解释:
// 1.月份加法数据库算法和java算法差异:9-30加1月,数据库算法结果是10-31,java算法结果是10-30。
// 对java算法进行改造,加月份前先加1天再加月份最后再减1天
// 2。示例
// 计息开始日:8-30、8-31、9-1
// A.先加月份再减1天: 加1月9-30、9-30、10-1,减1天9-29、9-29、9-30。
// B.先减1天再加月份: 减1天8-29、8-30、8-31,加1月9-29、9-30、9-30。
// 计息开始日:9-29、9-30、10-1
// C.先加月份再减1天: 加1月10-29、10-31、11-1,减1天10-28、10-30、10-31。
// D.先减1天再加月份: 减1天9-28、9-29、9-30,加1月10-28、10-29、10-31。
// 其中:
// 示例A中计息开始日8-31的计息结束日为9-29,少了1天
// 示例C中计息开始日9-30计息结束日为10-30,多了1天
// 先减1天再加月份,算法比较合理
// 3.以开始日减一天作为基线,循环在月份上加上期数,如果有大小月情况出现,误差都是一致的。如果每期都是在上期的结果上减1天再加月份,每期都有可能出现误差。
DateTime beginBase = new DateTime(begin);
beginBase = beginBase.plusDays(-1);
for (int i = 0; i < totalmonth; i++) {
int period = i + 1;
if (after == null) {
before = new DateTime(begin);
} else {
before = after.plusDays(1);
}
after = new DateTime(DateUtil.add_month(beginBase.toDate(), period));
int days = Days.daysBetween(before, after).getDays() + 1;
BigDecimal interest = new BigDecimal(principal)
.multiply(new BigDecimal(yearRate).multiply(new BigDecimal(days)));
interest = interest.divide(new BigDecimal(365), 2, BigDecimal.ROUND_HALF_UP);
BigDecimal bprincipal = new BigDecimal(0);
if (period == totalmonth) {
bprincipal = new BigDecimal(principal);
}
ReplayPlan plan = new ReplayPlan(period, yearRate, interest, bprincipal, before.toDate(), after.toDate(),
days);
map.put(period, plan);
}
return map;
}
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date begin = sdf.parse("2017-07-21");
Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmMonth().getRepayPlan(3000, 0.1238, 3, begin);
System.out.println("按月付息到期还本");
for (int i = 1; i <= detailPlan.size(); i++) {
ReplayPlan plan = detailPlan.get(i);
System.out.println(plan);
}
}
}
测试结果
按月付息到期还本
期数:1,年化利率:0.1238,利息:31.54,本金:0.00,总额:31.54,计息天数:31,计息开始日期:2017-07-21,计息结束日:2017-08-20
期数:2,年化利率:0.1238,利息:31.54,本金:0.00,总额:31.54,计息天数:31,计息开始日期:2017-08-21,计息结束日:2017-09-20
期数:3,年化利率:0.1238,利息:30.53,本金:3000.00,总额:3030.53,计息天数:30,计息开始日期:2017-09-21,计息结束日:2017-10-20
3.一次性还本付息
到期一次性还本付息总额=投资本金×[1+年利率÷365×投资天数]
package org.xzheng.repayment.service.impl;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
/**
* 一次性还本付息算法.
*/
public class RePayAlgorithmOnce implements IRepayPlanGenerator {
@Override
public Map<Integer, ReplayPlan> getRepayPlan(double invest, double yearRate, int days, Date begin) {
Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
BigDecimal interest = new BigDecimal(invest).multiply(new BigDecimal(yearRate)).multiply(new BigDecimal(days))
.divide(new BigDecimal(365), 2, BigDecimal.ROUND_HALF_UP);
BigDecimal principal = new BigDecimal(invest);
DateTime dt = new DateTime(begin);
DateTime after = dt.plusDays(days - 1);
ReplayPlan plan = new ReplayPlan(1, yearRate, interest, principal, begin, after.toDate(), days);
map.put(1, plan);
return map;
}
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
DateTime start = new DateTime(sdf.parse("2017-07-02"));
Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmOnce().getRepayPlan(3000, 0.1, 10, start.toDate());
System.out.println("一次性还本付息");
for (int i = 1; i <= detailPlan.size(); i++) {
ReplayPlan plan = detailPlan.get(i);
System.out.println(plan);
}
}
}
测试结果
一次性还本付息
期数:1,年化利率:0.1,利息:8.22,本金:3000.00,总额:3008.22,计息天数:10,计息开始日期:2017-07-02,计息结束日:2017-07-11
4.等额本息
每月偿还本息=〔贷款本金×月利率×(1+月利率)^还款月数〕÷〔(1+月利率)^还款月数-1〕
每月偿还利息=贷款本金×月利率×〔(1+月利率)^还款月数-(1+月利率)^(还款月序号-1)〕÷〔(1+月利率)^还款月数-1〕
每月偿还本金 = 每月偿还本息 - 每月偿还利息
package org.xzheng.repayment.service.impl;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
import org.xzheng.repayment.util.DateUtil;
/**
* 等额本息还款,也称定期付息,即借款人每月按相等的金额偿还贷款本息,其中每月贷款利息按月初剩余贷款本金计算并逐月结清。把按揭贷款的本金总额与利息总额相加,
* 然后平均分摊到还款期限的每个月中。作为还款人,每个月还给银行固定金额,但每月还款额中的本金比重逐月递增、利息比重逐月递减。
*/
public class RePayAlgorithmPrincipal implements IRepayPlanGenerator {
@Override
public Map<Integer, ReplayPlan> getRepayPlan(double invest, double yearRate, int totalmonth, Date begin) {
Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
double monthRate = yearRate / 12;
DateTime before = null;
DateTime after = null;
DateTime beginBase = new DateTime(begin);
beginBase = beginBase.plusDays(-1);
// 每月偿还本息=〔贷款本金×月利率×(1+月利率)^还款月数〕÷〔(1+月利率)^还款月数-1〕
BigDecimal monthIncome = new BigDecimal(invest)
.multiply(new BigDecimal(monthRate * Math.pow(1 + monthRate, totalmonth)))
.divide(new BigDecimal(Math.pow(1 + monthRate, totalmonth) - 1), 2, BigDecimal.ROUND_DOWN);
for (int i = 0; i < totalmonth; i++) {
int period = i + 1;
if (after == null) {
before = new DateTime(begin);
} else {
before = after.plusDays(1);
}
after = new DateTime(DateUtil.add_month(beginBase.toDate(), period));
int days = Days.daysBetween(before, after).getDays() + 1;
BigDecimal multiply = new BigDecimal(invest).multiply(new BigDecimal(monthRate));
BigDecimal sub = new BigDecimal(Math.pow(1 + monthRate, totalmonth))
.subtract(new BigDecimal(Math.pow(1 + monthRate, i - 1)));
// 每月偿还利息=贷款本金×月利率×〔(1+月利率)^还款月数-(1+月利率)^(还款月序号-1)〕÷〔(1+月利率)^还款月数-1〕
BigDecimal monthInterest = multiply.multiply(sub)
.divide(new BigDecimal(Math.pow(1 + monthRate, totalmonth) - 1), 6, BigDecimal.ROUND_DOWN);
monthInterest = monthInterest.setScale(2, BigDecimal.ROUND_DOWN);
// 本金 = 每月偿还本息 - 每月偿还利息
BigDecimal monthPrincipal = monthIncome.subtract(monthInterest);
ReplayPlan plan = new ReplayPlan(period, yearRate, monthInterest, monthPrincipal, before.toDate(),
after.toDate(), days);
map.put(period, plan);
}
return map;
}
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date begin = sdf.parse("2017-07-23");
Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmPrincipal().getRepayPlan(3000, 0.1, 3, begin);
System.out.println("等额本息");
for (int i = 1; i <= detailPlan.size(); i++) {
ReplayPlan plan = detailPlan.get(i);
System.out.println(plan);
}
}
}
测试结果
等额本息
期数:1,年化利率:0.1,利息:33.19,本金:983.52,总额:1016.71,计息天数:31,计息开始日期:2017-07-23,计息结束日:2017-08-22
期数:2,年化利率:0.1,利息:24.99,本金:991.72,总额:1016.71,计息天数:31,计息开始日期:2017-08-23,计息结束日:2017-09-22
期数:3,年化利率:0.1,利息:16.73,本金:999.98,总额:1016.71,计息天数:30,计息开始日期:2017-09-23,计息结束日:2017-10-22
PS:日期月份加1算法
package org.xzheng.repayment.util;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.time.FastDateFormat;
public class DateUtil {
public static String dateToString(Date date, String format) {
FastDateFormat fdf = FastDateFormat.getInstance(format);
if (null == date)
return null;
return fdf.format(date);
}
/**
* 增加月份,解决大小月最后一天问题
*
* @param date
* @param month
* @return
*/
public static Date add_month(Date date, int month) {
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(Calendar.DAY_OF_MONTH, 1);
c.add(Calendar.MONTH, month);
c.add(Calendar.DAY_OF_MONTH, -1);
return c.getTime();
}
}