众所周知,如今银行的分期贷款利率是颇有诱惑性人。表面看利率是很低的,例如招行的闪电贷有时给个人利率是4.3%web
可是,因为贷款是分期还本的,我手上的本金每个月都在减小,到最后一个月时手上只有少许本金,可是还的利息却仍是跟第一个月同样。算法
excel提供了一个公式叫irr,专门用来计算这种分期贷款实际利率的。函数
irr函数有两个参数,第一个是现金流,第二个是预估值。只要咱们根据贷款状况填好总贷款金额和每个月还款金额就能够算出每个月的内部收益率。测试
月内部收益率*12就是咱们的实际贷款利率。预估值通常不用填,只有irr计算失败返回#NUM!才要考虑填,具体可见office官方说明。ui
https://support.office.com/zh-cn/article/IRR-%E5%87%BD%E6%95%B0-64925eaa-9988-495b-b290-3ad0c163c1bcspa
为方便计算,我作了一个excel表格,有兴趣你们能够去下载。只要输入下面图片黄底黑体列,便可自动得出折算年利率。招行的闪电贷利率表面看是4.3%,实际年化利率是7.84%。.net
若是你把钱投理财产品,没有7.84%以上你实际是亏本的。excel
固然,本文重点不是介绍irr函数,而是我写(抄)的一个计算irr的程序(函数)。使用的是二分迭代法(网上看还有牛顿迭代法和加速迭代法,这两种须要用到数学知识)code
之因此用c语言写一个,缘由是咱们最近项目组有一个c程序须要计算内部收益率。我从网上找了一个程序改了一下,变成一个函数,并加上注释,方便理解和调用。orm
程序和代码还有前面提到的表格我都已经上传,有须要能够去下载。实现代码以下:
// testirr.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <math.h> //const double zero = 1e-5; //int n; /* //代码是在csdn上找到的 我拿下来改了一下 //https://blog.csdn.net/dinghaoseu/article/details/50322117 //这是原来的代码 double quickpow(double a, int b) { double ans = 1; while(b) { if(b & 1) ans = ans * a; a = a * a; b >>= 1; } return ans; } double makeans(double irr) { double ans = 0; for(int i = 1; i <= n; i++) ans += ( a[i] / (quickpow(1 + irr, i)) ); return ans; } */ //计算流出Npv double getNpvOut(double irr, const double arrOutMoney[], int n) { double npv = 0; double denominator = 1 + irr; //分母 double multiplier = denominator; //乘数 for (int i = 1; i <= n; ++i) { npv += arrOutMoney[i] / denominator; //即:arrOutMoney[i] / (quickpow(1 + irr, i) denominator *= multiplier; } return npv; } //最小最大IRR(内部收益率) 用于折算年化率计算 //最小值必须大于-1 最大值1其实就足够了,折算成年化收益率是120%(国家规定利率不能超过30%) //数字越小计算速度越快,为保险计这里填10 #define IRR_MIN -1.0f #define IRR_MAX 10.0f //二分迭代寻找合适的irr值(若是存在多个irr,取第一次找到的值,不保证大小顺序) //arrInOutMoney是分期现金流 nArrLen是arrInOutMoney元素个数 //arrInOutMoney[0]必须是负数,表明总分期金额(负数),后面是每期还款金额(正数) //返回[IRR_MIN, IRR_MAX]之间的数字表明符合要求的IRR值,<IRR_MIN表明找不到合适的IRR double binarySearchGetIrr(const double arrInOutMoney[], int nArrLen) { double l = IRR_MIN, r = IRR_MAX; //irr取值-1~10之间 int n = nArrLen - 1; //int nCnt = 0; while (l < r) { //每次从最大和最小指望IRR中间取一个数值进行npv测算 double mid = (l + r) / 2; //如今是用除法求Npv,其实能够改为用乘法求,效率会高一点 //由于计算机处理乘法速度比较快 double npvOut = getNpvOut(mid, arrInOutMoney, n); //++nCnt; //若是结果等于0说明找到符合要求的IRR if (fabs(npvOut + arrInOutMoney[0]) <= 1e-5) //double类型不能直接与0比较判断是否相等 { //printf("nCnt = %d\n", nCnt); //经测试,通常分12期迭代次数在30~60之间 return mid; } //irr越大,npvOut越小,故npvOut太大时irr就应该落在mid和r之间,反之则反之 else if (npvOut > -arrInOutMoney[0]) { l = mid; } else { r = mid; } } //printf("nCnt = %d\n", nCnt); //找不到返回比IRR_MIN还小的值 return IRR_MIN - 1; } //输入按月分期现金流,输出对应irr和折算年化收益率 //arrInOutMoney是分期现金流 nArrLen是arrInOutMoney元素个数(通常是7或者13) //arrInOutMoney[0]必须是负数,表明总分期金额,后面是每期还款金额(正数) //nCheckFlag = 0,表明直接计算irr,不然会先对数据合法性作检查 //返回0表明成功 其它表明失败 失败缘由存放在errBuf(调用者须要保证至少有256个字节空间) int GetIrrAndAnnualizedRate(const double arrInOutMoney[], int nArrLen, OUT double *pIrr, OUT double *pAnnualizedRate, int nCheckFlag, OUT char *errBuf) { double irr = 0; if (nCheckFlag != 0) { double inMoney, outMoney; int i; if (arrInOutMoney == NULL || nArrLen < 2) { strcpy(errBuf, "arrInOutMoney须要非空而且元素个数大于2个"); return -10; } inMoney = arrInOutMoney[0]; outMoney = 0; for (i = 1; i < nArrLen; ++i) { if (arrInOutMoney[i] < 0) { //不支持屡次现金流入(由于没这个需求,不要浪费计算力) outMoney = -1; break; } outMoney += arrInOutMoney[i]; } if (inMoney >= 0 || (-inMoney > outMoney)) { strcpy(errBuf, "第一个元素必须是负现金流,以后每一个元素均是正现金流," "而且正现金流之和要大于负现金流"); return -20; } } irr = binarySearchGetIrr(arrInOutMoney, nArrLen); if (irr < IRR_MIN) { sprintf(errBuf, "%.5f(%.2f%%)~%.5f(%.2f%%)之间没法找到合适的irr,请检查现金流是否输入异常", IRR_MIN, IRR_MIN * 12 * 100, IRR_MAX, IRR_MAX * 12 * 100); return -30; } *pIrr = irr; *pAnnualizedRate = irr * 12 * 100; return 0; } int getIrrDemo() { double irr, annualizedRate; double a[100 * 12 + 1]; int n, nRet; char errBuf[256]; printf("**************若是要退出,请在还款期数填0**************\n"); while ((printf("input 还款期数 n(0表明退出):")) && ~scanf("%d", &n) && n) { printf("n = %d\n", n); if (n >= sizeof(a) / sizeof(a[0])) { printf("n值太大,不支持\n"); continue; } printf("输入分期金额(负数):"); if (scanf("%lf", &a[0]) != 1) { printf("输入的金额不能包含非数字和小数点\n"); getchar(); continue; } printf("输入%d期还款金额(正数),每输入一期按一次回车:", n); for (int i = 1; i <= n; i++) { scanf("%lf", &a[i]); } nRet = GetIrrAndAnnualizedRate(a, n + 1, &irr, &annualizedRate, 1, errBuf); if (nRet != 0) { printf("error:[%s]\n", errBuf); continue; } //计算irr经常使用的方法是迭代计算,即不断尝试可能值,根据尝试结果缩小范围,直到找到符合要求的值 //网上能找到的迭代算法有二分迭代,牛顿迭代,加速迭代,其中二分迭代最好理解,最容易开发 irr = binarySearchGetIrr(a, n + 1); if (irr < IRR_MIN) { printf("找不到合适的irr\n"); } else { printf("irr = %.6f 年化收益率(12 * irr) = %.4f%%\n", irr, annualizedRate); } } return 0; } int main(int argc, char *argv[]) { getIrrDemo(); //system("pause"); return 0; }