<?php namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; class Periodic { const FINANCIAL_MAX_ITERATIONS = 128; const FINANCIAL_PRECISION = 1.0e-08; /** * IRR. * * Returns the internal rate of return for a series of cash flows represented by the numbers in values. * These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur * at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received * for an investment consisting of payments (negative values) and income (positive values) that occur at regular * periods. * * Excel Function: * IRR(values[,guess]) * * @param mixed $values An array or a reference to cells that contain numbers for which you want * to calculate the internal rate of return. * Values must contain at least one positive value and one negative value to * calculate the internal rate of return. * @param mixed $guess A number that you guess is close to the result of IRR * * @return float|string */ public static function rate($values, $guess = 0.1) { if (!is_array($values)) { return ExcelError::VALUE(); } $values = Functions::flattenArray($values); $guess = Functions::flattenSingleValue($guess); // create an initial range, with a root somewhere between 0 and guess $x1 = 0.0; $x2 = $guess; $f1 = self::presentValue($x1, $values); $f2 = self::presentValue($x2, $values); for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { if (($f1 * $f2) < 0.0) { break; } if (abs($f1) < abs($f2)) { $f1 = self::presentValue($x1 += 1.6 * ($x1 - $x2), $values); } else { $f2 = self::presentValue($x2 += 1.6 * ($x2 - $x1), $values); } } if (($f1 * $f2) > 0.0) { return ExcelError::VALUE(); } $f = self::presentValue($x1, $values); if ($f < 0.0) { $rtb = $x1; $dx = $x2 - $x1; } else { $rtb = $x2; $dx = $x1 - $x2; } for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { $dx *= 0.5; $x_mid = $rtb + $dx; $f_mid = self::presentValue($x_mid, $values); if ($f_mid <= 0.0) { $rtb = $x_mid; } if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { return $x_mid; } } return ExcelError::VALUE(); } /** * MIRR. * * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both * the cost of the investment and the interest received on reinvestment of cash. * * Excel Function: * MIRR(values,finance_rate, reinvestment_rate) * * @param mixed $values An array or a reference to cells that contain a series of payments and * income occurring at regular intervals. * Payments are negative value, income is positive values. * @param mixed $financeRate The interest rate you pay on the money used in the cash flows * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them * * @return float|string Result, or a string containing an error */ public static function modifiedRate($values, $financeRate, $reinvestmentRate) { if (!is_array($values)) { return ExcelError::DIV0(); } $values = Functions::flattenArray($values); $financeRate = Functions::flattenSingleValue($financeRate); $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate); $n = count($values); $rr = 1.0 + $reinvestmentRate; $fr = 1.0 + $financeRate; $npvPos = $npvNeg = self::$zeroPointZero; foreach ($values as $i => $v) { if ($v >= 0) { $npvPos += $v / $rr ** $i; } else { $npvNeg += $v / $fr ** $i; } } if ($npvNeg === self::$zeroPointZero || $npvPos === self::$zeroPointZero) { return ExcelError::DIV0(); } $mirr = ((-$npvPos * $rr ** $n) / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; return is_finite($mirr) ? $mirr : ExcelError::NAN(); } /** * Sop to Scrutinizer. * * @var float */ private static $zeroPointZero = 0.0; /** * NPV. * * Returns the Net Present Value of a cash flow series given a discount rate. * * @param mixed $rate * @param array $args * * @return float */ public static function presentValue($rate, ...$args) { $returnValue = 0; $rate = Functions::flattenSingleValue($rate); $aArgs = Functions::flattenArray($args); // Calculate $countArgs = count($aArgs); for ($i = 1; $i <= $countArgs; ++$i) { // Is it a numeric value? if (is_numeric($aArgs[$i - 1])) { $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i; } } return $returnValue; } }