PHP Excel RATE 随着付款逐年增加 (pmt)

问题描述 投票:0回答:1

我正在使用 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 algorithm math finance
1个回答
0
投票

碰巧的是,这种付款方式带来了不错的结果 原始公式的推广。

具有标准含义 参数,

$nper
$pmt
$pv
$fv
$type
guess
、 除了我们将
$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
  • 如果
    $type == 1
    ,第一笔付款是立即的,所以 每笔付款都会获得+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_VP1
中。然而, 可以看出,这些数额相当脆弱;一个可以设置 例如,通过四舍五入付款的方式来解决这些问题。于是,我也 选择了更务实的解决方案,尽管效率较低 简单地计算代码中的总和,而不是使用数学 结果。这是在函数
RATE_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);
    }
    
    $rate = $guess;
    $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 = $pv + array_sum($pmts) + $fv;
    $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;
    }
    
    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));
    $rate = $guess;
    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 * $nper + $fv; 
        }
        else{
            $y = $pv * $f + $pmt0 * (1 + $rate * $type) * ($f - $f_pmt)/($rate - $rate_pmt) + $fv;
        }
    }
    if(abs($rate_pmt) < $financial_precision){
        $y0 = $pv + $pmt0 *  $nper + $fv;
    }
    else{
        $y0 = $pv + $pmt0 * ($f_pmt-1)/$rate_pmt * $nper + $fv;
    }
    $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;
    }
    
    return $rate;
} 

OP中的例子:

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

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

我使用了与原始

RATE
函数中使用的完全相同的模式, 尽管进行了一些改进(例如,避免代码重复) 可以想象。

Excel的

IRR
功能可以用来检查结果, 这是 google 表格版本, 除了
IRR
的模型不包含
fv
-未来值, 也不是
type=1
- 在期间开始时付款,因此这些应该有 默认零值。

同样为了验证目的,我介绍了详细的打印内容 PHP 沙箱中的计算结果 通过函数

rate_detailed

© www.soinside.com 2019 - 2024. All rights reserved.