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

反復計算の数を変更しても、すべての場合に問題が解決するわけではありません

投稿したサンプル計算の場合、レートアルゴリズムは、小数点以下7桁まで(小数点以下7桁まで正確)である40回の反復内のレートを検出しませんでした。

これは、逆コンパイルしたVb.net財務関数のプログラミングコードですか?

もしそうなら、プログラマーは割線法でRATE関数をコーディングするという貧弱な仕事をしました

ひどい割線法よりも金利を見つけるために使用するはるかに優れたアルゴリズムがあります

反復を100に変更して問題を解決したと思われる場合は、さまざまなデータを使用してコードをテストし、すべてのインスタンスでRATEを取得できるかどうかを確認してください。

于 2013-02-02T04:30:11.837 に答える
0

レート関数を使用しても同じ問題が発生しました。ドキュメントの備考に概説されているように、収束を確実にするために「推測」が非常に重要であることがわかりました。

備考

推測とnperを指定するために使用する単位について一貫していることを確認してください。4年間のローンで年利12%で毎月の支払いを行う場合は、推測に12%/ 12を使用し、nperに4*12を使用します。同じローンで年払いを行う場合は、推測に12%、nperに4%を使用します。

于 2018-08-14T08:51:25.743 に答える