Microsoft.VisualBasic.Financial.Rate错误“无法使用提供的参数计算费率”

时间:2013-01-29 05:18:43

标签: asp.net .net vb.net exception-handling finance

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

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

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

3 个答案:

答案 0 :(得分:3)

回答我自己的问题,对于遇到此问题的任何未来编码人员 - 我们使用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;
    }

答案 1 :(得分:1)

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

对于您公布的样本计算,费率算法未在40次迭代中找到正确的7位小数(精确到7位小数)

这是您已反编译的Vb.net财务功能的编程代码

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

与糟糕的正割方法相比,有更好的算法可用于寻找利率

如果您认为通过将迭代更改为100来解决问题,那么尝试使用各种数据测试代码,看看是否可以在所有实例中获得RATE

答案 2 :(得分:0)

使用Rate函数时,我遇到了同样的问题。 发现{猜测}对于确保收敛非常重要-如Remarks in the documentation所述:

  

备注

     

请确保您使用的单位是一致的   指定猜测和nper。如果您在四年内每月付款   年利率为12%的贷款,则使用12%/ 12进行猜测,并使用4 * 12作为   nper。如果您以同一笔贷款每年还款,请使用12%作为猜测   而nper则为4。