我正在使用 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;
}
碰巧的是,这种付款方式带来了不错的结果 原始公式的推广。
具有标准含义 参数,
$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
。