PHP Excel RATE年均递增付款(pmt)

PHP Excel RATE with anually increasing payment (pmt)

提问人:T.K 提问时间:7/21/2023 最后编辑:OlivierT.K 更新时间:8/5/2023 访问量:249

问:

我正在使用PHP版本的Excels RATE函数来计算年金的利率。

这符合预期。
我现在的问题是,我是否可以以某种方式使用一个变量,每年增加$pmt值。

示例:
第 1 年:付款($pmt):1,200 美元,每年增长 10%,剩余期限:20 年 第 2 年:付款($pmt):1,320 美元(1,200 美元 + 10%),剩余期限:19 年 第 3 年:付款($pmt):1,452 美元(1,320 美元 + 10%),剩余期限:18


等......

我不能使用付款总额,然后除以年数来获得平均$pmt值,因为这会扰乱 RATE() 函数的利息计算并产生不准确的结果

因此,理想情况下,我可以执行以下操作:RATE(60,10,-1200,0,80000),其中 10 是 1200 付款的年度增长。

function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1) {
    $financial_max_iterations = 20;
    $financial_precision = 0.00000008;

    $rate = $guess;
    if (abs($rate) < $financial_precision) {
        $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
    } else {
        $f = exp($nper * log(1 + $rate));
        $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
    }
    $y0 = $pv + $pmt * $nper + $fv;
    $y1 = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;

    // find root by secant method
    $i  = $x0 = 0.0;
    $x1 = $rate;
    while ((abs($y0 - $y1) > $financial_precision) && ($i < $financial_max_iterations)) {
        $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
        $x0 = $x1;
        $x1 = $rate;

        if (abs($rate) < $financial_precision) {
            $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
        } else {
            $f = exp($nper * log(1 + $rate));
            $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
        }

        $y0 = $y1;
        $y1 = $y;
        ++$i;
    }
    return $rate;
}  
php excel 算法 数学 金融

评论

1赞 Jack Fleeting 7/24/2023
也许我遗漏了一些东西,但使用 irr() 而不是 rate() 不是更好吗?
0赞 Olivier 7/27/2023
付款是按月还是按年支付?
0赞 A.L 7/29/2023
使用更具描述性的变量名称(如 代替 等)将更易于阅读此代码。它可以帮助其他用户回答这个问题。$payment$pmt

答:

4赞 kikon 7/27/2023 #1

碰巧的是,这种付款设置会带来一个很好的 原始公式的泛化。

用参数的标准含义,、、、、、 除了我们接受一个数字数组, 给出速率的等式是:$nper$pmt$pv$fv$typeguess$pmt$nper$rate

  • 如果$type == 0
$pv * (1 + $rate)**$nper + // present value after $nper
  $pmt[0] * (1 + $rate)**($nper-1) + // 1st payment, after $nper-1
  $pmt[1] * (1 + $rate)**($nper-2) + // 2nd payment, after $nper-2 
  // ................................\
  $pmt[n-2] * (1 + $rate)**1 + //  payment n-1, after 1
  $pmt[n-1] + // 2nd payment, after 0
     $fv // final value
     === 0
  • 如果 ,第一笔付款是立即支付的,因此 每次付款都会应用 +1 秒:$type == 1$rate
$pv * (1 + $rate)**$nper + // present value after $nper
  $pmt[0] * (1 + $rate)**$nper + // 1st payment, after $nper
  $pmt[1] * (1 + $rate)**($nper-2) + // 2nd payment, after $nper-1 
  // ................................\
  $pmt[n-2] * (1 + $rate)**2 + //  payment n-1, after 2
  $pmt[n-1] * (1 + $rate)**1 + // 2nd payment, after 1
     $fv // final value
     === 0

现在,正如问题所设定的那样,付款是 给出者 , 其中是第一笔付款,是付款率,两者都作为参数给出。$pmt[$i] = $pmt0 * (1 + $rate_pmt)**$i$pmt0$$rate_pmt

这样,公式简化为:

$pv * (1 + $rate)**$nper +
  (1 + $rate*$type)*((1+$rate)**$nper - (1+$rate_pmt)**$nper)/($rate-$rate_pmt)+
   $fv 
   === 0

这个不错的结果用于下面的函数中。然而 可以看出,这些款项相当脆弱;一个可以设置 例如,通过四舍五入付款来消除它们。因此,我也 选择了一种更务实的解决方案,尽管效率较低, 只需在代码中计算总和,而不是使用数学 结果。这是在函数中实现的。二 函数具有相同的签名,并且应该(并且确实)给出 相同的结果。RATE_VP1RATE_VP

/**
 * RATE_VP
 *
 * The variable payment version of excel's RATE
 *
 * @param    float    $nper       The total number of payment periods
 * @param    float    $rate_pmt   The rate by which each payment increases
 *                                wrt the previous one (percent)
 * @param    float    $pmt0       The value of the first payment
 * @param    float    $pv         The present value (see RATE)  
 * @param    float    $fv         The future value (see RATE)
 * @param    integer  $type       The number 0 or 1 and indicates 
 *                                when payments are due.
 * @param    float    $guess      Initial guess of the result
 *                                
 * @return    float
 */
function RATE_VP($nper, $rate_pmt, $pmt0, $pv, $fv = 0.0, $type = 0, $guess = 0.1) {
    // computing the sums in code
    $financial_max_iterations = 20;
    $financial_precision = 0.00000008;
    
    $pmts = array_fill(0, $nper, $pmt0);
    for($i = 1; $i < $nper; $i++){
        $pmts[$i] = $pmts[$i-1] * (1+$rate_pmt);
    }
    $y0 = $pv + array_sum($pmts) + $fv;
    
    $guess_strategy = [$guess, 2*$guess, $guess/2, 5*$guess, $guess/5, $guess];
    foreach($guess_strategy as $rate){
      $f = (abs($rate) < $financial_precision) ? 1 + $rate*$nper : exp($nper * log(1 + $rate));
      $y = $f * $pv;
      $fact = $type == 0 ? 1 : 1 + $rate;
      for($j = $nper - 1; $j >= 0; $j--){
          $y += $pmts[$j] * $fact;
          $fact *= 1 + $rate;
      }
      $y += $fv;
      $rate1 = - $y0 * $rate / ($y - $y0);
      if($rate1 > -1){
        break;
      }
    }
    
    $y1 = $y;
    
    // find root by secant method
    $i = $x0 = 0.0;
    $x1 = $rate;
    while ((abs($y0 - $y1) > $financial_precision) and ($i < $financial_max_iterations)) {
        $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
        $x0 = $x1;
        $x1 = $rate;

        $f = abs($rate) < $financial_precision ? 1 + $rate*$nper : exp($nper * log(1 + $rate));
        $y = $f * $pv;
        $fact = $type == 0 ? 1 : 1 + $rate;
        for($j = $nper - 1; $j >= 0; $j--){
            $y += $pmts[$j] * $fact;
            $fact *= 1 + $rate;
        }
        $y += $fv;
        
        $y0 = $y1;
        $y1 = $y;
        ++$i;
    }
    if(abs($y) > $financial_precision){
      throw new ErrorException("RATE_VP diverges", 0);
    }
    return $rate;
} 

function RATE_VP1($nper, $rate_pmt, $pmt0, $pv, $fv = 0.0, $type = 0, $guess = 0.1) {
    // using mathematical summation
    $financial_max_iterations = 20;
    $financial_precision = 0.00000008;
    
    $f_pmt = (abs($rate_pmt) < $financial_precision) ? 1 + $rate_pmt*$nper : exp($nper * log(1 + $rate_pmt));
    if(abs($rate_pmt) < $financial_precision){
        $y0 = $pv + $pmt0 *  $nper + $fv;
    }
    else{
        $y0 = $pv + $pmt0 * ($f_pmt-1)/$rate_pmt  + $fv;
    }

    $rate = $guess;    
    $guess_strategy = [$guess, 2*$guess, $guess/2, 5*$guess, $guess/5, $guess];
    foreach($guess_strategy as $rate){
      if (abs($rate) < $financial_precision && abs($rate_pmt) < $financial_precision){
          $y = $pv * (1 + $rate*$nper) + (1 + $rate*$type)*($rate-$rate_pmt)*($nper-1) + $fv; 
      }
      else{
          $f = (abs($rate) < $financial_precision) ? 1 + $rate*$nper : exp($nper * log(1 + $rate));
          if (abs($rate - $rate_pmt) < $financial_precision){
              $y = $pv * $f + $pmt0 * (1 + $rate * $type) * $f/(1+$rate) *$nper + $fv; 
          }
          else{
              $y = $pv * $f + $pmt0 * (1 + $rate * $type) * ($f - $f_pmt)/($rate - $rate_pmt) + $fv;
          }
      }
      $rate1 = - $y0 * $rate / ($y - $y0);
      if($rate1 > -1){
        break;
      }
    }
    
    $y1 = $y;
    
    // find root by secant method
    $i  = $x0 = 0.0;
    $x1 = $rate;
    while ((abs($y0 - $y1) > $financial_precision) and ($i < $financial_max_iterations)) {
        $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
        $x0 = $x1;
        $x1 = $rate;

        if (abs($rate) < $financial_precision && abs($rate_pmt) < $financial_precision){
            $y = $pv * (1 + $rate*$nper) + (1 + $rate*$type)*($rate-$rate_pmt)*($nper-1) + $fv; 
        }
        else{
            $f = (abs($rate) < $financial_precision) ? 1 + $rate*$nper : exp($nper * log(1 + $rate));
            $y = $pv * $f + $pmt0 * (1 + $rate * $type) * ($f - $f_pmt)/($rate - $rate_pmt) + $fv;
        }

        $y0 = $y1;
        $y1 = $y;
        ++$i;
    }
    if(abs($y) > $financial_precision){
      throw new ErrorException("RATE_VP1 diverges", 0);
    }
    return $rate;
} 

OP 中的示例:

RATE_VP(20, 0.1, -1200, 80000)*100

RATE_VP1(20, 0.1, -1200, 80000)*100

我使用了与原始函数相同的完全相同的模式, 尽管有一些改进(例如,避免代码重复)是 想得到的。RATE

Excel的功能可用于检查结果, 这是 Google 表格版本, 除了 的模型不包括 -future 值, 也不是 - 在期初付款,所以那些应该有 默认值为零。IRRIRRfvtype=1

此外,出于验证目的,我介绍了详细的打印 通过函数在此PHP沙箱中计算的结果。rate_detailed

在更新中:我将代码和phpsandbox链接更改为

  • 添加对找到解决方案的失败检测,方法是抛出ErrorException
  • 添加“猜测策略”,即参数的一组变体,以避免在迭代的第一步导致失败的棘手或边缘情况。$guess
  • 更正了 中初始猜测的一些公式。RATE_VP1

评论

1赞 T.K 8/4/2023
直到今天我才能够测试它,但这个解决方案、解释和参考都很棒!非常感谢!
0赞 T.K 8/4/2023
我这里有一个问题:RATE_VP(18, 0.1, -1800, 20000)*100 我的返回值是 -14.879626806473114,这是正确的,如果运行 RATE_VP(18, 0.1, -1800, 17913)*100,我的返回值是 141747.2808113353 - 浮点数上是否发生溢出?如果是这样,有什么办法可以解决吗?
1赞 kikon 8/5/2023
发生的事情是,该特定情况下的代码存在分歧,一个重要因素是该比率等于付款率(但也等于其余数据)。在函数之后,我的代码没有检测到背离。我现在添加了这样的检测,如果解决方案不正常,就会抛出一个。在这种情况下,可以使用另一个值重试 。基于此,我添加了我所谓的“猜测策略”——尝试一些值的变化,以避免第一次迭代被抛出负值的“边缘情况”。$guessRATEErrorException$guess$guess
0赞 T.K 8/7/2023
欣赏异常处理,不知何故,即使使用$guess策略,这个异常在我的计算中也经常被相对抛出,这是有问题的,我会尝试看看我是否可以以某种方式避免这种情况的发生
0赞 kikon 8/7/2023
请向我发送一些抛出异常的情况,以便我检查会发生什么 - 这可能是一个简单的问题,但如果有更深层次的问题,我们可以尝试使用比割线方法更强的数值过程。