众所周知, 现在银行的分期贷款利率是很有诱惑性人. 表面看利率是很低的, 例如招行的闪电贷有时给我的利率是 4.3%
但是, 由于贷款是分期还本的, 我手上的本金每月都在减少, 到最后一个月时手上只有少量本金, 但是还的利息却还是跟第一个月一样.
Excel 提供了一个公式叫 irr, 专门用来计算这种分期贷款实际利率的.
irr 函数有两个参数, 第一个是现金流, 第二个是预估值. 只要我们根据贷款情况填好总贷款金额和每月还款金额就可以算出每月的内部收益率.
月内部收益率 * 12 就是我们的实际贷款利率. 预估值一般不用填, 只有 irr 计算失败返回 #NUM! 才要考虑填, 具体可见 Office 官方说明.
https://support.office.com/zh-cn/article/IRR-函数-64925eaa-9988-495b-b290-3ad0c163c1bc
为方便计算, 我做了一个 Excel 表格, 有兴趣大家可以去下载. 只要输入下面图片黄底黑体列, 即可自动得出折算年利率. 招行的闪电贷利率表面看是 4.3%, 实际年化利率是 7.84%.
如果你把钱投理财产品, 没有 7.84% 以上你实际是亏本的.
当然, 本文重点不是介绍 irr 函数, 而是我写 (抄) 的一个计算 irr 的程序(函数). 使用的是二分迭代法(网上看还有牛顿迭代法和加速迭代法, 这两种需要用到数学知识)
之所以用 c 语言写一个, 原因是我们最近项目组有一个 c 程序需要计算内部收益率. 我从网上找了一个程序改了一下, 变成一个函数, 并加上注释, 方便理解和调用.
程序和代码还有前面提到的表格我都已经上传, 有需要可以去下载. 实现代码如下:
- // 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;
- }
来源: https://www.cnblogs.com/kingstarer/p/10322343.html