欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用c语言计算分期贷款折算年化收益率(内部收益率IRR*12)

程序员文章站 2022-03-10 09:44:48
众所周知,现在银行的分期贷款利率是很有诱惑性人。表面看利率是很低的,例如招行的闪电贷有时给我的利率是4.3% 但是,由于贷款是分期还本的,我手上的本金每月都在减少,到最后一个月时手上只有少量本金,但是还的利息却还是跟第一个月一样。 excel提供了一个公式叫irr,专门用来计算这种分期贷款实际利率的 ......

  众所周知,现在银行的分期贷款利率是很有诱惑性人。表面看利率是很低的,例如招行的闪电贷有时给我的利率是4.3%

但是,由于贷款是分期还本的,我手上的本金每月都在减少,到最后一个月时手上只有少量本金,但是还的利息却还是跟第一个月一样。

  excel提供了一个公式叫irr,专门用来计算这种分期贷款实际利率的。

irr函数有两个参数,第一个是现金流,第二个是预估值。只要我们根据贷款情况填好总贷款金额和每月还款金额就可以算出每月的内部收益率。

月内部收益率*12就是我们的实际贷款利率。预估值一般不用填,只有irr计算失败返回#num!才要考虑填,具体可见office官方说明。

  https://support.office.com/zh-cn/article/irr-%e5%87%bd%e6%95%b0-64925eaa-9988-495b-b290-3ad0c163c1bc

  为方便计算,我做了一个excel表格,有兴趣大家可以去下载。只要输入下面图片黄底黑体列,即可自动得出折算年利率。招行的闪电贷利率表面看是4.3%,实际年化利率是7.84%。

  如果你把钱投理财产品,没有7.84%以上你实际是亏本的。

使用c语言计算分期贷款折算年化收益率(内部收益率IRR*12)

  当然,本文重点不是介绍irr函数,而是我写(抄)的一个计算irr的程序(函数)。使用的是二分迭代法(网上看还有牛顿迭代法和加速迭代法,这两种需要用到数学知识)

之所以用c语言写一个,原因是我们最近项目组有一个c程序需要计算内部收益率。我从网上找了一个程序改了一下,变成一个函数,并加上注释,方便理解和调用。

使用c语言计算分期贷款折算年化收益率(内部收益率IRR*12)

  程序和代码还有前面提到的表格我都已经上传,有需要可以去下载。实现代码如下:

// 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;
}