3

因此,我们的 ASP.NET4 Web 应用程序中有一个费率计算器类,它使用 Microsoft.VisualBasic.Financial.Rate 来计算名义费率(基于输入参数)。

我们注意到,对于较高的 NPer 值(付款期总数,例如 50 年 x 每月付款 = 600),该函数会抛出异常:无法使用提供的参数计算利率。

四处搜索我们没有找到任何解决方案,所以我在这里发布解决方案。我们的一个要求是维护一个尽可能接近实现与上述相同算法的函数,因为我们需要产生完全相同的输出。

4

3 回答 3

4

回答我自己的问题,对于遇到此问题的任何未来编码人员 - 我们使用 dotPeek 反编译模块,生成以下内容:

public static double Rate(double NPer, double Pmt, double PV, double FV = 0.0, DueDate Due = DueDate.EndOfPeriod, double Guess = 0.1)
{
  if (NPer <= 0.0)
    throw new ArgumentException(Utils.GetResourceString("Rate_NPerMustBeGTZero"));
  double Rate1 = Guess;
  double num1 = Financial.LEvalRate(Rate1, NPer, Pmt, PV, FV, Due);
  double Rate2 = num1 <= 0.0 ? Rate1 * 2.0 : Rate1 / 2.0;
  double num2 = Financial.LEvalRate(Rate2, NPer, Pmt, PV, FV, Due);
  int num3 = 0;
  do
  {
    if (num2 == num1)
    {
      if (Rate2 > Rate1)
        Rate1 -= 1E-05;
      else
        Rate1 -= -1E-05;
      num1 = Financial.LEvalRate(Rate1, NPer, Pmt, PV, FV, Due);
      if (num2 == num1)
        throw new ArgumentException(Utils.GetResourceString("Financial_CalcDivByZero"));
    }
    double Rate3 = Rate2 - (Rate2 - Rate1) * num2 / (num2 - num1);
    double num4 = Financial.LEvalRate(Rate3, NPer, Pmt, PV, FV, Due);
    if (Math.Abs(num4) < 1E-07)
      return Rate3;
    double num5 = num4;
    num1 = num2;
    num2 = num5;
    double num6 = Rate3;
    Rate1 = Rate2;
    Rate2 = num6;
    checked { ++num3; }
  }
  while (num3 <= 39);
  throw new ArgumentException(Utils.GetResourceString("Financial_CannotCalculateRate"));
}

private static double LEvalRate(double Rate, double NPer, double Pmt, double PV, double dFv, DueDate Due)
{
  if (Rate == 0.0)
    return PV + Pmt * NPer + dFv;
  double num1 = Math.Pow(Rate + 1.0, NPer);
  double num2 = Due == DueDate.EndOfPeriod ? 1.0 : 1.0 + Rate;
  return PV * num1 + Pmt * num2 * (num1 - 1.0) / Rate + dFv;
}

我们可以看到如果超过 num3 就会抛出错误,因为它的硬限制是 39。我们稍微整理了代码,并将限制增加到 100:

private static double CalculateUpfrontNominalRate(double numberOfPeriods, double payment, double presentValue, double futureValue = 0.0, DueDate Due = DueDate.EndOfPeriod, double Guess = 0.1)
    {
        if (numberOfPeriods <= 0.0)
        {
            throw new ArgumentException("CalculateUpfrontNominalRate: Number of periods must be greater than zero");
        }

        var rateUpperBoundary = Guess;
        var lEvalRate1 = LEvalRate(rateUpperBoundary, numberOfPeriods, payment, presentValue, futureValue, Due);
        var rateLowerBoundary = lEvalRate1 <= 0.0 ? rateUpperBoundary * 2.0 : rateUpperBoundary / 2.0;
        var lEvalRate2 = LEvalRate(rateLowerBoundary, numberOfPeriods, payment, presentValue, futureValue, Due);

        for (var i = 0; i < 100; i++)
        {
            if (lEvalRate2 == lEvalRate1)
            {
                if (rateLowerBoundary > rateUpperBoundary)
                    rateUpperBoundary -= 1E-05;
                else
                    rateUpperBoundary -= -1E-05;

                lEvalRate1 = LEvalRate(rateUpperBoundary, numberOfPeriods, payment, presentValue, futureValue, Due);
                if (lEvalRate2 == lEvalRate1)
                {
                    throw new ArgumentException("CalculateUpfrontNominalRate: Inputs will cause a divsion by zero");
                }
            }

            double temporaryRate = rateLowerBoundary - (rateLowerBoundary - rateUpperBoundary) * lEvalRate2 / (lEvalRate2 - lEvalRate1);
            double lEvalRate3 = LEvalRate(temporaryRate, numberOfPeriods, payment, presentValue, futureValue, Due);

            if (Math.Abs(lEvalRate3) < 1E-07)
            {
                return temporaryRate;
            }

            lEvalRate1 = lEvalRate2;
            lEvalRate2 = lEvalRate3;
            rateUpperBoundary = rateLowerBoundary;
            rateLowerBoundary = temporaryRate;
        }

        throw new ArgumentException("CalculateUpfrontNominalRate: The maximum number of iterations has been exceeded, unable to calculate rate");
    }

    private static double LEvalRate(double Rate, double NPer, double Pmt, double PV, double dFv, DueDate Due)
    {
        if (Rate == 0.0)
            return PV + Pmt * NPer + dFv;
        double num1 = Math.Pow(Rate + 1.0, NPer);
        double num2 = Due == DueDate.EndOfPeriod ? 1.0 : 1.0 + Rate;
        return PV * num1 + Pmt * num2 * (num1 - 1.0) / Rate + dFv;
    }
于 2013-01-29T05:18:43.350 回答
1

更改迭代计算的计数并不能解决所有情况的问题

对于您发布的示例计算,速率算法在 40 次迭代中找不到正确到小数点后 7 位的速率(精确到小数点后 7 位)

这是你反编译的vb.net金融函数的编程代码吗

如果是这样,程序员在使用 Secant 方法对 RATE 函数进行编码方面做得很差

有比糟糕的正割法更好的算法来计算利率

如果您认为通过将迭代次数更改为 100 已经解决了问题,请尝试使用各种数据测试代码,看看是否可以在所有情况下获得 RATE

于 2013-02-02T04:30:11.837 回答
0

我在使用 Rate 函数时遇到了同样的问题。发现您的“猜测”对于确保收敛非常重要 - 如文档中的备注中所述

备注

确保您对用于指定guess 和nper 的单位保持一致。如果您以 12% 的年利率每月支付四年期贷款,请使用 12%/12 进行猜测,使用 4*12 进行 nper。如果您对同一笔贷款进行年度还款,请使用 12% 的猜测值和 4 的 nper。

于 2018-08-14T08:51:25.743 回答