Passed
Push — master ( 5dee5a...729c4d )
by
unknown
20:09 queued 09:33
created

Periodic::presentValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 18
ccs 9
cts 9
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
6
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
7
8
class Periodic
9
{
10
    const FINANCIAL_MAX_ITERATIONS = 128;
11
12
    const FINANCIAL_PRECISION = 1.0e-08;
13
14
    /**
15
     * IRR.
16
     *
17
     * Returns the internal rate of return for a series of cash flows represented by the numbers in values.
18
     * These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
19
     * at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
20
     * for an investment consisting of payments (negative values) and income (positive values) that occur at regular
21
     * periods.
22
     *
23
     * Excel Function:
24
     *        IRR(values[,guess])
25
     *
26
     * @param mixed $values An array or a reference to cells that contain numbers for which you want
27
     *                                    to calculate the internal rate of return.
28
     *                                Values must contain at least one positive value and one negative value to
29
     *                                    calculate the internal rate of return.
30
     * @param mixed $guess A number that you guess is close to the result of IRR
31
     */
32 10
    public static function rate(mixed $values, mixed $guess = 0.1): string|float
33
    {
34 10
        if (!is_array($values)) {
35
            return ExcelError::VALUE();
36
        }
37 10
        $values = Functions::flattenArray($values);
38 10
        $guess = Functions::flattenSingleValue($guess);
39 10
        if (!is_numeric($guess)) {
40
            return ExcelError::VALUE();
41
        }
42
43
        // create an initial range, with a root somewhere between 0 and guess
44 10
        $x1 = 0.0;
45 10
        $x2 = $guess;
46 10
        $f1 = self::presentValue($x1, $values);
47 10
        $f2 = self::presentValue($x2, $values);
48 10
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
49 10
            if (($f1 * $f2) < 0.0) {
50 7
                break;
51
            }
52 9
            if (abs($f1) < abs($f2)) {
53 4
                $f1 = self::presentValue($x1 += 1.6 * ($x1 - $x2), $values);
54
            } else {
55 6
                $f2 = self::presentValue($x2 += 1.6 * ($x2 - $x1), $values);
56
            }
57
        }
58 10
        if (($f1 * $f2) > 0.0) {
59 3
            return ExcelError::VALUE();
60
        }
61
62 7
        $f = self::presentValue($x1, $values);
63 7
        if ($f < 0.0) {
64 1
            $rtb = $x1;
65 1
            $dx = $x2 - $x1;
66
        } else {
67 6
            $rtb = $x2;
68 6
            $dx = $x1 - $x2;
69
        }
70
71 7
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
72 7
            $dx *= 0.5;
73 7
            $x_mid = $rtb + $dx;
74 7
            $f_mid = self::presentValue($x_mid, $values);
75 7
            if ($f_mid <= 0.0) {
76 7
                $rtb = $x_mid;
77
            }
78 7
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
79 7
                return $x_mid;
80
            }
81
        }
82
83
        return ExcelError::VALUE();
84
    }
85
86
    /**
87
     * MIRR.
88
     *
89
     * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
90
     *        the cost of the investment and the interest received on reinvestment of cash.
91
     *
92
     * Excel Function:
93
     *        MIRR(values,finance_rate, reinvestment_rate)
94
     *
95
     * @param mixed $values An array or a reference to cells that contain a series of payments and
96
     *                         income occurring at regular intervals.
97
     *                      Payments are negative value, income is positive values.
98
     * @param mixed $financeRate The interest rate you pay on the money used in the cash flows
99
     * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them
100
     *
101
     * @return float|string Result, or a string containing an error
102
     */
103 12
    public static function modifiedRate(mixed $values, mixed $financeRate, mixed $reinvestmentRate): string|float
104
    {
105 12
        if (!is_array($values)) {
106
            return ExcelError::DIV0();
107
        }
108 12
        $values = Functions::flattenArray($values);
109
        /** @var float */
110 12
        $financeRate = Functions::flattenSingleValue($financeRate);
111
        /** @var float */
112 12
        $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate);
113 12
        $n = count($values);
114
115 12
        $rr = 1.0 + $reinvestmentRate;
116 12
        $fr = 1.0 + $financeRate;
117
118 12
        $npvPos = $npvNeg = 0.0;
119 12
        foreach ($values as $i => $v) {
120 12
            if ($v >= 0) {
121 11
                $npvPos += $v / $rr ** $i;
122
            } else {
123 10
                $npvNeg += $v / $fr ** $i;
124
            }
125
        }
126
127 12
        if ($npvNeg === 0.0 || $npvPos === 0.0) {
128 3
            return ExcelError::DIV0();
129
        }
130
131 9
        $mirr = ((-$npvPos * $rr ** $n)
132 9
                / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
133
134 9
        return is_finite($mirr) ? $mirr : ExcelError::NAN();
135
    }
136
137
    /**
138
     * NPV.
139
     *
140
     * Returns the Net Present Value of a cash flow series given a discount rate.
141
     *
142
     * @param array $args
143
     */
144 15
    public static function presentValue(mixed $rate, ...$args): int|float
145
    {
146 15
        $returnValue = 0;
147
148
        /** @var float */
149 15
        $rate = Functions::flattenSingleValue($rate);
150 15
        $aArgs = Functions::flattenArray($args);
151
152
        // Calculate
153 15
        $countArgs = count($aArgs);
154 15
        for ($i = 1; $i <= $countArgs; ++$i) {
155
            // Is it a numeric value?
156 15
            if (is_numeric($aArgs[$i - 1])) {
157 15
                $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i;
158
            }
159
        }
160
161 15
        return $returnValue;
162
    }
163
}
164