Completed
Push — develop ( 50ed76...6a48b5 )
by Adrien
63:50
created

Financial   F

Complexity

Total Complexity 367

Size/Duplication

Total Lines 2401
Duplicated Lines 0 %

Test Coverage

Coverage 65.61%

Importance

Changes 0
Metric Value
eloc 822
dl 0
loc 2401
ccs 561
cts 855
cp 0.6561
rs 1.778
c 0
b 0
f 0
wmc 367

52 Methods

Rating   Name   Duplication   Size   Complexity  
A isLastDayOfMonth() 0 3 1
A DOLLARDE() 0 19 4
B COUPDAYBS() 0 24 8
A ISPMT() 0 24 3
A FV() 0 19 5
A CUMIPMT() 0 24 6
B AMORLINC() 0 32 8
C DB() 0 44 16
B IRR() 0 49 11
B INTRATE() 0 26 7
C DDB() 0 36 14
A couponFirstPeriodDate() 0 19 4
B MIRR() 0 30 8
A NOMINAL() 0 12 3
B ACCRINTM() 0 25 8
B COUPPCD() 0 21 8
B isValidFrequency() 0 11 7
A CUMPRINC() 0 24 6
B COUPDAYSNC() 0 24 8
A DOLLARFR() 0 19 4
A IPMT() 0 21 5
A FVSCHEDULE() 0 10 2
B AMORDEGRC() 0 49 9
B ACCRINT() 0 27 9
A interestAndPrincipal() 0 11 4
B COUPNCD() 0 21 8
B NPER() 0 26 8
B DISC() 0 26 7
A NPV() 0 20 3
A EFFECT() 0 11 3
B PRICE() 0 39 9
B RRI() 0 14 7
A TBILLEQ() 0 24 4
B RATE() 0 42 9
A PMT() 0 19 5
B XNPV() 0 28 10
B PDURATION() 0 14 7
C XIRR() 0 52 13
A TBILLYIELD() 0 31 6
B YIELDMAT() 0 43 9
A PPMT() 0 21 5
B TBILLPRICE() 0 40 8
B YIELDDISC() 0 28 7
B PRICEMAT() 0 43 9
B RECEIVED() 0 23 7
B SYD() 0 17 7
A SLN() 0 16 5
A PV() 0 19 5
B PRICEDISC() 0 23 7
B COUPDAYS() 0 38 11
C COUPNUM() 0 37 13
B daysPerYear() 0 22 7

How to fix   Complexity   

Complex Class

Complex classes like Financial often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Financial, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use PhpOffice\PhpSpreadsheet\Shared\Date;
6
7
class Financial
8
{
9
    const FINANCIAL_MAX_ITERATIONS = 32;
10
11
    const FINANCIAL_PRECISION = 1.0e-08;
12
13
    /**
14
     * isLastDayOfMonth.
15
     *
16
     * Returns a boolean TRUE/FALSE indicating if this date is the last date of the month
17
     *
18
     * @param \DateTime $testDate The date for testing
19
     *
20
     * @return bool
21
     */
22 12
    private static function isLastDayOfMonth(\DateTime $testDate)
23
    {
24 12
        return $testDate->format('d') == $testDate->format('t');
25
    }
26
27 12
    private static function couponFirstPeriodDate($settlement, $maturity, $frequency, $next)
28
    {
29 12
        $months = 12 / $frequency;
30
31 12
        $result = Date::excelToDateTimeObject($maturity);
32 12
        $eom = self::isLastDayOfMonth($result);
33
34 12
        while ($settlement < Date::PHPToExcel($result)) {
35 12
            $result->modify('-' . $months . ' months');
36
        }
37 12
        if ($next) {
38 8
            $result->modify('+' . $months . ' months');
39
        }
40
41 12
        if ($eom) {
42 1
            $result->modify('-1 day');
43
        }
44
45 12
        return Date::PHPToExcel($result);
46
    }
47
48 22
    private static function isValidFrequency($frequency)
49
    {
50 22
        if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) {
51 16
            return true;
52
        }
53 6
        if ((Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) &&
54 6
            (($frequency == 6) || ($frequency == 12))) {
55
            return true;
56
        }
57
58 6
        return false;
59
    }
60
61
    /**
62
     * daysPerYear.
63
     *
64
     * Returns the number of days in a specified year, as defined by the "basis" value
65
     *
66
     * @param int|string $year The year against which we're testing
67
     * @param int|string $basis The type of day count:
68
     *                                    0 or omitted US (NASD)    360
69
     *                                    1                        Actual (365 or 366 in a leap year)
70
     *                                    2                        360
71
     *                                    3                        365
72
     *                                    4                        European 360
73
     *
74
     * @return int
75
     */
76 6
    private static function daysPerYear($year, $basis = 0)
77
    {
78 6
        switch ($basis) {
79 6
            case 0:
80 4
            case 2:
81 4
            case 4:
82 2
                $daysPerYear = 360;
83
84 2
                break;
85 4
            case 3:
86
                $daysPerYear = 365;
87
88
                break;
89 4
            case 1:
90 4
                $daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365;
91
92 4
                break;
93
            default:
94
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type integer.
Loading history...
95
        }
96
97 6
        return $daysPerYear;
98
    }
99
100 20
    private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
101
    {
102 20
        $pmt = self::PMT($rate, $nper, $pv, $fv, $type);
103 20
        $capital = $pv;
104 20
        for ($i = 1; $i <= $per; ++$i) {
105 20
            $interest = ($type && $i == 1) ? 0 : -$capital * $rate;
106 20
            $principal = $pmt - $interest;
107 20
            $capital += $principal;
108
        }
109
110 20
        return [$interest, $principal];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $interest does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $principal does not seem to be defined for all execution paths leading up to this point.
Loading history...
111
    }
112
113
    /**
114
     * ACCRINT.
115
     *
116
     * Returns the accrued interest for a security that pays periodic interest.
117
     *
118
     * Excel Function:
119
     *        ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis])
120
     *
121
     * @category Financial Functions
122
     *
123
     * @param mixed $issue the security's issue date
124
     * @param mixed $firstinterest the security's first interest date
125
     * @param mixed $settlement The security's settlement date.
126
     *                                    The security settlement date is the date after the issue date
127
     *                                    when the security is traded to the buyer.
128
     * @param float $rate the security's annual coupon rate
129
     * @param float $par The security's par value.
130
     *                                    If you omit par, ACCRINT uses $1,000.
131
     * @param int $frequency the number of coupon payments per year.
132
     *                                    Valid frequency values are:
133
     *                                        1    Annual
134
     *                                        2    Semi-Annual
135
     *                                        4    Quarterly
136
     *                                    If working in Gnumeric Mode, the following frequency options are
137
     *                                    also available
138
     *                                        6    Bimonthly
139
     *                                        12    Monthly
140
     * @param int $basis The type of day count to use.
141
     *                                        0 or omitted    US (NASD) 30/360
142
     *                                        1                Actual/actual
143
     *                                        2                Actual/360
144
     *                                        3                Actual/365
145
     *                                        4                European 30/360
146
     *
147
     * @return float|string
148
     */
149 7
    public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0)
150
    {
151 7
        $issue = Functions::flattenSingleValue($issue);
152 7
        $firstinterest = Functions::flattenSingleValue($firstinterest);
0 ignored issues
show
Unused Code introduced by
The assignment to $firstinterest is dead and can be removed.
Loading history...
153 7
        $settlement = Functions::flattenSingleValue($settlement);
154 7
        $rate = Functions::flattenSingleValue($rate);
155 7
        $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
0 ignored issues
show
introduced by
The condition $par === null is always false.
Loading history...
156 7
        $frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency);
0 ignored issues
show
Unused Code introduced by
The assignment to $frequency is dead and can be removed.
Loading history...
introduced by
The condition $frequency === null is always false.
Loading history...
157 7
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
158
159
        //    Validate
160 7
        if ((is_numeric($rate)) && (is_numeric($par))) {
161 6
            $rate = (float) $rate;
162 6
            $par = (float) $par;
163 6
            if (($rate <= 0) || ($par <= 0)) {
164 1
                return Functions::NAN();
165
            }
166 5
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
167 5
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenIssueAndSettlement) is always true.
Loading history...
168
                //    return date error
169 2
                return $daysBetweenIssueAndSettlement;
170
            }
171
172 3
            return $par * $rate * $daysBetweenIssueAndSettlement;
173
        }
174
175 1
        return Functions::VALUE();
176
    }
177
178
    /**
179
     * ACCRINTM.
180
     *
181
     * Returns the accrued interest for a security that pays interest at maturity.
182
     *
183
     * Excel Function:
184
     *        ACCRINTM(issue,settlement,rate[,par[,basis]])
185
     *
186
     * @category Financial Functions
187
     *
188
     * @param mixed $issue The security's issue date
189
     * @param mixed $settlement The security's settlement (or maturity) date
190
     * @param float $rate The security's annual coupon rate
191
     * @param float $par The security's par value.
192
     *                                    If you omit par, ACCRINT uses $1,000.
193
     * @param int $basis The type of day count to use.
194
     *                                        0 or omitted    US (NASD) 30/360
195
     *                                        1                Actual/actual
196
     *                                        2                Actual/360
197
     *                                        3                Actual/365
198
     *                                        4                European 30/360
199
     *
200
     * @return float|string
201
     */
202 5
    public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0)
203
    {
204 5
        $issue = Functions::flattenSingleValue($issue);
205 5
        $settlement = Functions::flattenSingleValue($settlement);
206 5
        $rate = Functions::flattenSingleValue($rate);
207 5
        $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
0 ignored issues
show
introduced by
The condition $par === null is always false.
Loading history...
208 5
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
209
210
        //    Validate
211 5
        if ((is_numeric($rate)) && (is_numeric($par))) {
212 4
            $rate = (float) $rate;
213 4
            $par = (float) $par;
214 4
            if (($rate <= 0) || ($par <= 0)) {
215 1
                return Functions::NAN();
216
            }
217 3
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
218 3
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenIssueAndSettlement) is always true.
Loading history...
219
                //    return date error
220 1
                return $daysBetweenIssueAndSettlement;
221
            }
222
223 2
            return $par * $rate * $daysBetweenIssueAndSettlement;
224
        }
225
226 1
        return Functions::VALUE();
227
    }
228
229
    /**
230
     * AMORDEGRC.
231
     *
232
     * Returns the depreciation for each accounting period.
233
     * This function is provided for the French accounting system. If an asset is purchased in
234
     * the middle of the accounting period, the prorated depreciation is taken into account.
235
     * The function is similar to AMORLINC, except that a depreciation coefficient is applied in
236
     * the calculation depending on the life of the assets.
237
     * This function will return the depreciation until the last period of the life of the assets
238
     * or until the cumulated value of depreciation is greater than the cost of the assets minus
239
     * the salvage value.
240
     *
241
     * Excel Function:
242
     *        AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
243
     *
244
     * @category Financial Functions
245
     *
246
     * @param float $cost The cost of the asset
247
     * @param mixed $purchased Date of the purchase of the asset
248
     * @param mixed $firstPeriod Date of the end of the first period
249
     * @param mixed $salvage The salvage value at the end of the life of the asset
250
     * @param float $period The period
251
     * @param float $rate Rate of depreciation
252
     * @param int $basis The type of day count to use.
253
     *                                        0 or omitted    US (NASD) 30/360
254
     *                                        1                Actual/actual
255
     *                                        2                Actual/360
256
     *                                        3                Actual/365
257
     *                                        4                European 30/360
258
     *
259
     * @return float
260
     */
261 2
    public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
262
    {
263 2
        $cost = Functions::flattenSingleValue($cost);
264 2
        $purchased = Functions::flattenSingleValue($purchased);
265 2
        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
266 2
        $salvage = Functions::flattenSingleValue($salvage);
267 2
        $period = floor(Functions::flattenSingleValue($period));
268 2
        $rate = Functions::flattenSingleValue($rate);
269 2
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
270
271
        //    The depreciation coefficients are:
272
        //    Life of assets (1/rate)        Depreciation coefficient
273
        //    Less than 3 years            1
274
        //    Between 3 and 4 years        1.5
275
        //    Between 5 and 6 years        2
276
        //    More than 6 years            2.5
277 2
        $fUsePer = 1.0 / $rate;
278 2
        if ($fUsePer < 3.0) {
279
            $amortiseCoeff = 1.0;
280 2
        } elseif ($fUsePer < 5.0) {
281
            $amortiseCoeff = 1.5;
282 2
        } elseif ($fUsePer <= 6.0) {
283 1
            $amortiseCoeff = 2.0;
284
        } else {
285 1
            $amortiseCoeff = 2.5;
286
        }
287
288 2
        $rate *= $amortiseCoeff;
289 2
        $fNRate = round(DateTime::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost, 0);
290 2
        $cost -= $fNRate;
291 2
        $fRest = $cost - $salvage;
292
293 2
        for ($n = 0; $n < $period; ++$n) {
294 2
            $fNRate = round($rate * $cost, 0);
295 2
            $fRest -= $fNRate;
296
297 2
            if ($fRest < 0.0) {
298
                switch ($period - $n) {
299
                    case 0:
300
                    case 1:
301
                        return round($cost * 0.5, 0);
302
                    default:
303
                        return 0.0;
304
                }
305
            }
306 2
            $cost -= $fNRate;
307
        }
308
309 2
        return $fNRate;
310
    }
311
312
    /**
313
     * AMORLINC.
314
     *
315
     * Returns the depreciation for each accounting period.
316
     * This function is provided for the French accounting system. If an asset is purchased in
317
     * the middle of the accounting period, the prorated depreciation is taken into account.
318
     *
319
     * Excel Function:
320
     *        AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
321
     *
322
     * @category Financial Functions
323
     *
324
     * @param float $cost The cost of the asset
325
     * @param mixed $purchased Date of the purchase of the asset
326
     * @param mixed $firstPeriod Date of the end of the first period
327
     * @param mixed $salvage The salvage value at the end of the life of the asset
328
     * @param float $period The period
329
     * @param float $rate Rate of depreciation
330
     * @param int $basis The type of day count to use.
331
     *                                        0 or omitted    US (NASD) 30/360
332
     *                                        1                Actual/actual
333
     *                                        2                Actual/360
334
     *                                        3                Actual/365
335
     *                                        4                European 30/360
336
     *
337
     * @return float
338
     */
339 2
    public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
340
    {
341 2
        $cost = Functions::flattenSingleValue($cost);
342 2
        $purchased = Functions::flattenSingleValue($purchased);
343 2
        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
344 2
        $salvage = Functions::flattenSingleValue($salvage);
345 2
        $period = Functions::flattenSingleValue($period);
346 2
        $rate = Functions::flattenSingleValue($rate);
347 2
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
348
349 2
        $fOneRate = $cost * $rate;
350 2
        $fCostDelta = $cost - $salvage;
351
        //    Note, quirky variation for leap years on the YEARFRAC for this function
352 2
        $purchasedYear = DateTime::YEAR($purchased);
353 2
        $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
354
355 2
        if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($purchasedYear))) {
356 1
            $yearFrac *= 365 / 366;
357
        }
358
359 2
        $f0Rate = $yearFrac * $rate * $cost;
360 2
        $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
361
362 2
        if ($period == 0) {
363
            return $f0Rate;
364 2
        } elseif ($period <= $nNumOfFullPeriods) {
365 2
            return $fOneRate;
366
        } elseif ($period == ($nNumOfFullPeriods + 1)) {
367
            return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
368
        }
369
370
        return 0.0;
371
    }
372
373
    /**
374
     * COUPDAYBS.
375
     *
376
     * Returns the number of days from the beginning of the coupon period to the settlement date.
377
     *
378
     * Excel Function:
379
     *        COUPDAYBS(settlement,maturity,frequency[,basis])
380
     *
381
     * @category Financial Functions
382
     *
383
     * @param mixed $settlement The security's settlement date.
384
     *                                The security settlement date is the date after the issue
385
     *                                date when the security is traded to the buyer.
386
     * @param mixed $maturity The security's maturity date.
387
     *                                The maturity date is the date when the security expires.
388
     * @param int $frequency the number of coupon payments per year.
389
     *                                    Valid frequency values are:
390
     *                                        1    Annual
391
     *                                        2    Semi-Annual
392
     *                                        4    Quarterly
393
     *                                    If working in Gnumeric Mode, the following frequency options are
394
     *                                    also available
395
     *                                        6    Bimonthly
396
     *                                        12    Monthly
397
     * @param int $basis The type of day count to use.
398
     *                                        0 or omitted    US (NASD) 30/360
399
     *                                        1                Actual/actual
400
     *                                        2                Actual/360
401
     *                                        3                Actual/365
402
     *                                        4                European 30/360
403
     *
404
     * @return float|string
405
     */
406 5
    public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0)
407
    {
408 5
        $settlement = Functions::flattenSingleValue($settlement);
409 5
        $maturity = Functions::flattenSingleValue($maturity);
410 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
411 5
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
412
413 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
414 1
            return Functions::VALUE();
415
        }
416 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
417 1
            return Functions::VALUE();
418
        }
419
420 3
        if (($settlement > $maturity) ||
421 3
            (!self::isValidFrequency($frequency)) ||
422 3
            (($basis < 0) || ($basis > 4))) {
423 1
            return Functions::NAN();
424
        }
425
426 2
        $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
427 2
        $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
428
429 2
        return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear;
430
    }
431
432
    /**
433
     * COUPDAYS.
434
     *
435
     * Returns the number of days in the coupon period that contains the settlement date.
436
     *
437
     * Excel Function:
438
     *        COUPDAYS(settlement,maturity,frequency[,basis])
439
     *
440
     * @category Financial Functions
441
     *
442
     * @param mixed $settlement The security's settlement date.
443
     *                                The security settlement date is the date after the issue
444
     *                                date when the security is traded to the buyer.
445
     * @param mixed $maturity The security's maturity date.
446
     *                                The maturity date is the date when the security expires.
447
     * @param mixed $frequency the number of coupon payments per year.
448
     *                                    Valid frequency values are:
449
     *                                        1    Annual
450
     *                                        2    Semi-Annual
451
     *                                        4    Quarterly
452
     *                                    If working in Gnumeric Mode, the following frequency options are
453
     *                                    also available
454
     *                                        6    Bimonthly
455
     *                                        12    Monthly
456
     * @param int $basis The type of day count to use.
457
     *                                        0 or omitted    US (NASD) 30/360
458
     *                                        1                Actual/actual
459
     *                                        2                Actual/360
460
     *                                        3                Actual/365
461
     *                                        4                European 30/360
462
     *
463
     * @return float|string
464
     */
465 8
    public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0)
466
    {
467 8
        $settlement = Functions::flattenSingleValue($settlement);
468 8
        $maturity = Functions::flattenSingleValue($maturity);
469 8
        $frequency = (int) Functions::flattenSingleValue($frequency);
470 8
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
471
472 8
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
473 1
            return Functions::VALUE();
474
        }
475 7
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
476 1
            return Functions::VALUE();
477
        }
478
479 6
        if (($settlement > $maturity) ||
480 6
            (!self::isValidFrequency($frequency)) ||
481 6
            (($basis < 0) || ($basis > 4))) {
482 1
            return Functions::NAN();
483
        }
484
485
        switch ($basis) {
486 5
            case 3:
487
                // Actual/365
488 1
                return 365 / $frequency;
489 4
            case 1:
490
                // Actual/actual
491 3
                if ($frequency == 1) {
492 2
                    $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
493
494 2
                    return $daysPerYear / $frequency;
495
                }
496 1
                $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
497 1
                $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
498
499 1
                return $next - $prev;
500
            default:
501
                // US (NASD) 30/360, Actual/360 or European 30/360
502 1
                return 360 / $frequency;
503
        }
504
    }
505
506
    /**
507
     * COUPDAYSNC.
508
     *
509
     * Returns the number of days from the settlement date to the next coupon date.
510
     *
511
     * Excel Function:
512
     *        COUPDAYSNC(settlement,maturity,frequency[,basis])
513
     *
514
     * @category Financial Functions
515
     *
516
     * @param mixed $settlement The security's settlement date.
517
     *                                The security settlement date is the date after the issue
518
     *                                date when the security is traded to the buyer.
519
     * @param mixed $maturity The security's maturity date.
520
     *                                The maturity date is the date when the security expires.
521
     * @param mixed $frequency the number of coupon payments per year.
522
     *                                    Valid frequency values are:
523
     *                                        1    Annual
524
     *                                        2    Semi-Annual
525
     *                                        4    Quarterly
526
     *                                    If working in Gnumeric Mode, the following frequency options are
527
     *                                    also available
528
     *                                        6    Bimonthly
529
     *                                        12    Monthly
530
     * @param int $basis The type of day count to use.
531
     *                                        0 or omitted    US (NASD) 30/360
532
     *                                        1                Actual/actual
533
     *                                        2                Actual/360
534
     *                                        3                Actual/365
535
     *                                        4                European 30/360
536
     *
537
     * @return float|string
538
     */
539 5
    public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0)
540
    {
541 5
        $settlement = Functions::flattenSingleValue($settlement);
542 5
        $maturity = Functions::flattenSingleValue($maturity);
543 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
544 5
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
545
546 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
547 1
            return Functions::VALUE();
548
        }
549 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
550 1
            return Functions::VALUE();
551
        }
552
553 3
        if (($settlement > $maturity) ||
554 3
            (!self::isValidFrequency($frequency)) ||
555 3
            (($basis < 0) || ($basis > 4))) {
556 1
            return Functions::NAN();
557
        }
558
559 2
        $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
560 2
        $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
561
562 2
        return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear;
563
    }
564
565
    /**
566
     * COUPNCD.
567
     *
568
     * Returns the next coupon date after the settlement date.
569
     *
570
     * Excel Function:
571
     *        COUPNCD(settlement,maturity,frequency[,basis])
572
     *
573
     * @category Financial Functions
574
     *
575
     * @param mixed $settlement The security's settlement date.
576
     *                                The security settlement date is the date after the issue
577
     *                                date when the security is traded to the buyer.
578
     * @param mixed $maturity The security's maturity date.
579
     *                                The maturity date is the date when the security expires.
580
     * @param mixed $frequency the number of coupon payments per year.
581
     *                                    Valid frequency values are:
582
     *                                        1    Annual
583
     *                                        2    Semi-Annual
584
     *                                        4    Quarterly
585
     *                                    If working in Gnumeric Mode, the following frequency options are
586
     *                                    also available
587
     *                                        6    Bimonthly
588
     *                                        12    Monthly
589
     * @param int $basis The type of day count to use.
590
     *                                        0 or omitted    US (NASD) 30/360
591
     *                                        1                Actual/actual
592
     *                                        2                Actual/360
593
     *                                        3                Actual/365
594
     *                                        4                European 30/360
595
     *
596
     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
597
     *                        depending on the value of the ReturnDateType flag
598
     */
599 5
    public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0)
600
    {
601 5
        $settlement = Functions::flattenSingleValue($settlement);
602 5
        $maturity = Functions::flattenSingleValue($maturity);
603 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
604 5
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
605
606 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
607 1
            return Functions::VALUE();
608
        }
609 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
610 1
            return Functions::VALUE();
611
        }
612
613 3
        if (($settlement > $maturity) ||
614 3
            (!self::isValidFrequency($frequency)) ||
615 3
            (($basis < 0) || ($basis > 4))) {
616 1
            return Functions::NAN();
617
        }
618
619 2
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
620
    }
621
622
    /**
623
     * COUPNUM.
624
     *
625
     * Returns the number of coupons payable between the settlement date and maturity date,
626
     * rounded up to the nearest whole coupon.
627
     *
628
     * Excel Function:
629
     *        COUPNUM(settlement,maturity,frequency[,basis])
630
     *
631
     * @category Financial Functions
632
     *
633
     * @param mixed $settlement The security's settlement date.
634
     *                                The security settlement date is the date after the issue
635
     *                                date when the security is traded to the buyer.
636
     * @param mixed $maturity The security's maturity date.
637
     *                                The maturity date is the date when the security expires.
638
     * @param mixed $frequency the number of coupon payments per year.
639
     *                                    Valid frequency values are:
640
     *                                        1    Annual
641
     *                                        2    Semi-Annual
642
     *                                        4    Quarterly
643
     *                                    If working in Gnumeric Mode, the following frequency options are
644
     *                                    also available
645
     *                                        6    Bimonthly
646
     *                                        12    Monthly
647
     * @param int $basis The type of day count to use.
648
     *                                        0 or omitted    US (NASD) 30/360
649
     *                                        1                Actual/actual
650
     *                                        2                Actual/360
651
     *                                        3                Actual/365
652
     *                                        4                European 30/360
653
     *
654
     * @return int|string
655
     */
656 6
    public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0)
657
    {
658 6
        $settlement = Functions::flattenSingleValue($settlement);
659 6
        $maturity = Functions::flattenSingleValue($maturity);
660 6
        $frequency = (int) Functions::flattenSingleValue($frequency);
661 6
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
662
663 6
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
664 1
            return Functions::VALUE();
665
        }
666 5
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
667 1
            return Functions::VALUE();
668
        }
669
670 4
        if (($settlement > $maturity) ||
671 4
            (!self::isValidFrequency($frequency)) ||
672 4
            (($basis < 0) || ($basis > 4))) {
673 1
            return Functions::NAN();
674
        }
675
676 3
        $settlement = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
677 3
        $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * 365;
678
679 3
        switch ($frequency) {
680 3
            case 1: // annual payments
681 1
                return ceil($daysBetweenSettlementAndMaturity / 360);
682 2
            case 2: // half-yearly
683 1
                return ceil($daysBetweenSettlementAndMaturity / 180);
684 1
            case 4: // quarterly
685 1
                return ceil($daysBetweenSettlementAndMaturity / 90);
686
            case 6: // bimonthly
687
                return ceil($daysBetweenSettlementAndMaturity / 60);
688
            case 12: // monthly
689
                return ceil($daysBetweenSettlementAndMaturity / 30);
690
        }
691
692
        return Functions::VALUE();
693
    }
694
695
    /**
696
     * COUPPCD.
697
     *
698
     * Returns the previous coupon date before the settlement date.
699
     *
700
     * Excel Function:
701
     *        COUPPCD(settlement,maturity,frequency[,basis])
702
     *
703
     * @category Financial Functions
704
     *
705
     * @param mixed $settlement The security's settlement date.
706
     *                                The security settlement date is the date after the issue
707
     *                                date when the security is traded to the buyer.
708
     * @param mixed $maturity The security's maturity date.
709
     *                                The maturity date is the date when the security expires.
710
     * @param mixed $frequency the number of coupon payments per year.
711
     *                                    Valid frequency values are:
712
     *                                        1    Annual
713
     *                                        2    Semi-Annual
714
     *                                        4    Quarterly
715
     *                                    If working in Gnumeric Mode, the following frequency options are
716
     *                                    also available
717
     *                                        6    Bimonthly
718
     *                                        12    Monthly
719
     * @param int $basis The type of day count to use.
720
     *                                        0 or omitted    US (NASD) 30/360
721
     *                                        1                Actual/actual
722
     *                                        2                Actual/360
723
     *                                        3                Actual/365
724
     *                                        4                European 30/360
725
     *
726
     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
727
     *                        depending on the value of the ReturnDateType flag
728
     */
729 5
    public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0)
730
    {
731 5
        $settlement = Functions::flattenSingleValue($settlement);
732 5
        $maturity = Functions::flattenSingleValue($maturity);
733 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
734 5
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
735
736 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
737 1
            return Functions::VALUE();
738
        }
739 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
740 1
            return Functions::VALUE();
741
        }
742
743 3
        if (($settlement > $maturity) ||
744 3
            (!self::isValidFrequency($frequency)) ||
745 3
            (($basis < 0) || ($basis > 4))) {
746 1
            return Functions::NAN();
747
        }
748
749 2
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
750
    }
751
752
    /**
753
     * CUMIPMT.
754
     *
755
     * Returns the cumulative interest paid on a loan between the start and end periods.
756
     *
757
     * Excel Function:
758
     *        CUMIPMT(rate,nper,pv,start,end[,type])
759
     *
760
     * @category Financial Functions
761
     *
762
     * @param float $rate The Interest rate
763
     * @param int $nper The total number of payment periods
764
     * @param float $pv Present Value
765
     * @param int $start The first period in the calculation.
766
     *                            Payment periods are numbered beginning with 1.
767
     * @param int $end the last period in the calculation
768
     * @param int $type A number 0 or 1 and indicates when payments are due:
769
     *                                0 or omitted    At the end of the period.
770
     *                                1                At the beginning of the period.
771
     *
772
     * @return float|string
773
     */
774 9
    public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0)
775
    {
776 9
        $rate = Functions::flattenSingleValue($rate);
777 9
        $nper = (int) Functions::flattenSingleValue($nper);
778 9
        $pv = Functions::flattenSingleValue($pv);
779 9
        $start = (int) Functions::flattenSingleValue($start);
780 9
        $end = (int) Functions::flattenSingleValue($end);
781 9
        $type = (int) Functions::flattenSingleValue($type);
782
783
        // Validate parameters
784 9
        if ($type != 0 && $type != 1) {
785 1
            return Functions::NAN();
786
        }
787 8
        if ($start < 1 || $start > $end) {
788 1
            return Functions::VALUE();
789
        }
790
791
        // Calculate
792 7
        $interest = 0;
793 7
        for ($per = $start; $per <= $end; ++$per) {
794 7
            $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type);
795
        }
796
797 7
        return $interest;
798
    }
799
800
    /**
801
     * CUMPRINC.
802
     *
803
     * Returns the cumulative principal paid on a loan between the start and end periods.
804
     *
805
     * Excel Function:
806
     *        CUMPRINC(rate,nper,pv,start,end[,type])
807
     *
808
     * @category Financial Functions
809
     *
810
     * @param float $rate The Interest rate
811
     * @param int $nper The total number of payment periods
812
     * @param float $pv Present Value
813
     * @param int $start The first period in the calculation.
814
     *                            Payment periods are numbered beginning with 1.
815
     * @param int $end the last period in the calculation
816
     * @param int $type A number 0 or 1 and indicates when payments are due:
817
     *                                0 or omitted    At the end of the period.
818
     *                                1                At the beginning of the period.
819
     *
820
     * @return float|string
821
     */
822 9
    public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0)
823
    {
824 9
        $rate = Functions::flattenSingleValue($rate);
825 9
        $nper = (int) Functions::flattenSingleValue($nper);
826 9
        $pv = Functions::flattenSingleValue($pv);
827 9
        $start = (int) Functions::flattenSingleValue($start);
828 9
        $end = (int) Functions::flattenSingleValue($end);
829 9
        $type = (int) Functions::flattenSingleValue($type);
830
831
        // Validate parameters
832 9
        if ($type != 0 && $type != 1) {
833 1
            return Functions::NAN();
834
        }
835 8
        if ($start < 1 || $start > $end) {
836 1
            return Functions::VALUE();
837
        }
838
839
        // Calculate
840 7
        $principal = 0;
841 7
        for ($per = $start; $per <= $end; ++$per) {
842 7
            $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type);
843
        }
844
845 7
        return $principal;
846
    }
847
848
    /**
849
     * DB.
850
     *
851
     * Returns the depreciation of an asset for a specified period using the
852
     * fixed-declining balance method.
853
     * This form of depreciation is used if you want to get a higher depreciation value
854
     * at the beginning of the depreciation (as opposed to linear depreciation). The
855
     * depreciation value is reduced with every depreciation period by the depreciation
856
     * already deducted from the initial cost.
857
     *
858
     * Excel Function:
859
     *        DB(cost,salvage,life,period[,month])
860
     *
861
     * @category Financial Functions
862
     *
863
     * @param float $cost Initial cost of the asset
864
     * @param float $salvage Value at the end of the depreciation.
865
     *                                (Sometimes called the salvage value of the asset)
866
     * @param int $life Number of periods over which the asset is depreciated.
867
     *                                (Sometimes called the useful life of the asset)
868
     * @param int $period The period for which you want to calculate the
869
     *                                depreciation. Period must use the same units as life.
870
     * @param int $month Number of months in the first year. If month is omitted,
871
     *                                it defaults to 12.
872
     *
873
     * @return float|string
874
     */
875 16
    public static function DB($cost, $salvage, $life, $period, $month = 12)
876
    {
877 16
        $cost = Functions::flattenSingleValue($cost);
878 16
        $salvage = Functions::flattenSingleValue($salvage);
879 16
        $life = Functions::flattenSingleValue($life);
880 16
        $period = Functions::flattenSingleValue($period);
881 16
        $month = Functions::flattenSingleValue($month);
882
883
        //    Validate
884 16
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) {
885 15
            $cost = (float) $cost;
886 15
            $salvage = (float) $salvage;
887 15
            $life = (int) $life;
888 15
            $period = (int) $period;
889 15
            $month = (int) $month;
890 15
            if ($cost == 0) {
891 1
                return 0.0;
892 14
            } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) {
893 1
                return Functions::NAN();
894
            }
895
            //    Set Fixed Depreciation Rate
896 13
            $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
897 13
            $fixedDepreciationRate = round($fixedDepreciationRate, 3);
898
899
            //    Loop through each period calculating the depreciation
900 13
            $previousDepreciation = 0;
901 13
            for ($per = 1; $per <= $period; ++$per) {
902 13
                if ($per == 1) {
903 13
                    $depreciation = $cost * $fixedDepreciationRate * $month / 12;
904 11
                } elseif ($per == ($life + 1)) {
905 2
                    $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
906
                } else {
907 11
                    $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
908
                }
909 13
                $previousDepreciation += $depreciation;
910
            }
911 13
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
912
                $depreciation = round($depreciation, 2);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $depreciation does not seem to be defined for all execution paths leading up to this point.
Loading history...
913
            }
914
915 13
            return $depreciation;
916
        }
917
918 1
        return Functions::VALUE();
919
    }
920
921
    /**
922
     * DDB.
923
     *
924
     * Returns the depreciation of an asset for a specified period using the
925
     * double-declining balance method or some other method you specify.
926
     *
927
     * Excel Function:
928
     *        DDB(cost,salvage,life,period[,factor])
929
     *
930
     * @category Financial Functions
931
     *
932
     * @param float $cost Initial cost of the asset
933
     * @param float $salvage Value at the end of the depreciation.
934
     *                                (Sometimes called the salvage value of the asset)
935
     * @param int $life Number of periods over which the asset is depreciated.
936
     *                                (Sometimes called the useful life of the asset)
937
     * @param int $period The period for which you want to calculate the
938
     *                                depreciation. Period must use the same units as life.
939
     * @param float $factor The rate at which the balance declines.
940
     *                                If factor is omitted, it is assumed to be 2 (the
941
     *                                double-declining balance method).
942
     *
943
     * @return float|string
944
     */
945 15
    public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
946
    {
947 15
        $cost = Functions::flattenSingleValue($cost);
948 15
        $salvage = Functions::flattenSingleValue($salvage);
949 15
        $life = Functions::flattenSingleValue($life);
950 15
        $period = Functions::flattenSingleValue($period);
951 15
        $factor = Functions::flattenSingleValue($factor);
952
953
        //    Validate
954 15
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) {
955 14
            $cost = (float) $cost;
956 14
            $salvage = (float) $salvage;
957 14
            $life = (int) $life;
958 14
            $period = (int) $period;
959 14
            $factor = (float) $factor;
960 14
            if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) {
961 1
                return Functions::NAN();
962
            }
963
            //    Set Fixed Depreciation Rate
964 13
            $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
965 13
            $fixedDepreciationRate = round($fixedDepreciationRate, 3);
0 ignored issues
show
Unused Code introduced by
The assignment to $fixedDepreciationRate is dead and can be removed.
Loading history...
966
967
            //    Loop through each period calculating the depreciation
968 13
            $previousDepreciation = 0;
969 13
            for ($per = 1; $per <= $period; ++$per) {
970 13
                $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation));
971 13
                $previousDepreciation += $depreciation;
972
            }
973 13
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
974
                $depreciation = round($depreciation, 2);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $depreciation does not seem to be defined for all execution paths leading up to this point.
Loading history...
975
            }
976
977 13
            return $depreciation;
978
        }
979
980 1
        return Functions::VALUE();
981
    }
982
983
    /**
984
     * DISC.
985
     *
986
     * Returns the discount rate for a security.
987
     *
988
     * Excel Function:
989
     *        DISC(settlement,maturity,price,redemption[,basis])
990
     *
991
     * @category Financial Functions
992
     *
993
     * @param mixed $settlement The security's settlement date.
994
     *                                The security settlement date is the date after the issue
995
     *                                date when the security is traded to the buyer.
996
     * @param mixed $maturity The security's maturity date.
997
     *                                The maturity date is the date when the security expires.
998
     * @param int $price The security's price per $100 face value
999
     * @param int $redemption The security's redemption value per $100 face value
1000
     * @param int $basis The type of day count to use.
1001
     *                                        0 or omitted    US (NASD) 30/360
1002
     *                                        1                Actual/actual
1003
     *                                        2                Actual/360
1004
     *                                        3                Actual/365
1005
     *                                        4                European 30/360
1006
     *
1007
     * @return float|string
1008
     */
1009 5
    public static function DISC($settlement, $maturity, $price, $redemption, $basis = 0)
1010
    {
1011 5
        $settlement = Functions::flattenSingleValue($settlement);
1012 5
        $maturity = Functions::flattenSingleValue($maturity);
1013 5
        $price = Functions::flattenSingleValue($price);
1014 5
        $redemption = Functions::flattenSingleValue($redemption);
1015 5
        $basis = Functions::flattenSingleValue($basis);
1016
1017
        //    Validate
1018 5
        if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) {
1019 4
            $price = (float) $price;
1020 4
            $redemption = (float) $redemption;
1021 4
            $basis = (int) $basis;
1022 4
            if (($price <= 0) || ($redemption <= 0)) {
1023 1
                return Functions::NAN();
1024
            }
1025 3
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1026 3
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
1027
                //    return date error
1028 1
                return $daysBetweenSettlementAndMaturity;
1029
            }
1030
1031 2
            return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity;
1032
        }
1033
1034 1
        return Functions::VALUE();
1035
    }
1036
1037
    /**
1038
     * DOLLARDE.
1039
     *
1040
     * Converts a dollar price expressed as an integer part and a fraction
1041
     *        part into a dollar price expressed as a decimal number.
1042
     * Fractional dollar numbers are sometimes used for security prices.
1043
     *
1044
     * Excel Function:
1045
     *        DOLLARDE(fractional_dollar,fraction)
1046
     *
1047
     * @category Financial Functions
1048
     *
1049
     * @param float $fractional_dollar Fractional Dollar
1050
     * @param int $fraction Fraction
1051
     *
1052
     * @return float|string
1053
     */
1054 9
    public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
1055
    {
1056 9
        $fractional_dollar = Functions::flattenSingleValue($fractional_dollar);
1057 9
        $fraction = (int) Functions::flattenSingleValue($fraction);
1058
1059
        // Validate parameters
1060 9
        if ($fractional_dollar === null || $fraction < 0) {
1061 1
            return Functions::NAN();
1062
        }
1063 8
        if ($fraction == 0) {
1064 1
            return Functions::DIV0();
1065
        }
1066
1067 7
        $dollars = floor($fractional_dollar);
1068 7
        $cents = fmod($fractional_dollar, 1);
1069 7
        $cents /= $fraction;
1070 7
        $cents *= pow(10, ceil(log10($fraction)));
1071
1072 7
        return $dollars + $cents;
1073
    }
1074
1075
    /**
1076
     * DOLLARFR.
1077
     *
1078
     * Converts a dollar price expressed as a decimal number into a dollar price
1079
     *        expressed as a fraction.
1080
     * Fractional dollar numbers are sometimes used for security prices.
1081
     *
1082
     * Excel Function:
1083
     *        DOLLARFR(decimal_dollar,fraction)
1084
     *
1085
     * @category Financial Functions
1086
     *
1087
     * @param float $decimal_dollar Decimal Dollar
1088
     * @param int $fraction Fraction
1089
     *
1090
     * @return float|string
1091
     */
1092 9
    public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
1093
    {
1094 9
        $decimal_dollar = Functions::flattenSingleValue($decimal_dollar);
1095 9
        $fraction = (int) Functions::flattenSingleValue($fraction);
1096
1097
        // Validate parameters
1098 9
        if ($decimal_dollar === null || $fraction < 0) {
1099 1
            return Functions::NAN();
1100
        }
1101 8
        if ($fraction == 0) {
1102 1
            return Functions::DIV0();
1103
        }
1104
1105 7
        $dollars = floor($decimal_dollar);
1106 7
        $cents = fmod($decimal_dollar, 1);
1107 7
        $cents *= $fraction;
1108 7
        $cents *= pow(10, -ceil(log10($fraction)));
1109
1110 7
        return $dollars + $cents;
1111
    }
1112
1113
    /**
1114
     * EFFECT.
1115
     *
1116
     * Returns the effective interest rate given the nominal rate and the number of
1117
     *        compounding payments per year.
1118
     *
1119
     * Excel Function:
1120
     *        EFFECT(nominal_rate,npery)
1121
     *
1122
     * @category Financial Functions
1123
     *
1124
     * @param float $nominal_rate Nominal interest rate
1125
     * @param int $npery Number of compounding payments per year
1126
     *
1127
     * @return float|string
1128
     */
1129 5
    public static function EFFECT($nominal_rate = 0, $npery = 0)
1130
    {
1131 5
        $nominal_rate = Functions::flattenSingleValue($nominal_rate);
1132 5
        $npery = (int) Functions::flattenSingleValue($npery);
1133
1134
        // Validate parameters
1135 5
        if ($nominal_rate <= 0 || $npery < 1) {
1136 1
            return Functions::NAN();
1137
        }
1138
1139 4
        return pow((1 + $nominal_rate / $npery), $npery) - 1;
1140
    }
1141
1142
    /**
1143
     * FV.
1144
     *
1145
     * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
1146
     *
1147
     * Excel Function:
1148
     *        FV(rate,nper,pmt[,pv[,type]])
1149
     *
1150
     * @category Financial Functions
1151
     *
1152
     * @param float $rate The interest rate per period
1153
     * @param int $nper Total number of payment periods in an annuity
1154
     * @param float $pmt The payment made each period: it cannot change over the
1155
     *                            life of the annuity. Typically, pmt contains principal
1156
     *                            and interest but no other fees or taxes.
1157
     * @param float $pv present Value, or the lump-sum amount that a series of
1158
     *                            future payments is worth right now
1159
     * @param int $type A number 0 or 1 and indicates when payments are due:
1160
     *                                0 or omitted    At the end of the period.
1161
     *                                1                At the beginning of the period.
1162
     *
1163
     * @return float|string
1164
     */
1165 8
    public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0)
1166
    {
1167 8
        $rate = Functions::flattenSingleValue($rate);
1168 8
        $nper = Functions::flattenSingleValue($nper);
1169 8
        $pmt = Functions::flattenSingleValue($pmt);
1170 8
        $pv = Functions::flattenSingleValue($pv);
1171 8
        $type = Functions::flattenSingleValue($type);
1172
1173
        // Validate parameters
1174 8
        if ($type != 0 && $type != 1) {
1175 1
            return Functions::NAN();
1176
        }
1177
1178
        // Calculate
1179 7
        if ($rate !== null && $rate != 0) {
1180 6
            return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
1181
        }
1182
1183 1
        return -$pv - $pmt * $nper;
1184
    }
1185
1186
    /**
1187
     * FVSCHEDULE.
1188
     *
1189
     * Returns the future value of an initial principal after applying a series of compound interest rates.
1190
     * Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
1191
     *
1192
     * Excel Function:
1193
     *        FVSCHEDULE(principal,schedule)
1194
     *
1195
     * @param float $principal the present value
1196
     * @param float[] $schedule an array of interest rates to apply
1197
     *
1198
     * @return float
1199
     */
1200 3
    public static function FVSCHEDULE($principal, $schedule)
1201
    {
1202 3
        $principal = Functions::flattenSingleValue($principal);
1203 3
        $schedule = Functions::flattenArray($schedule);
1204
1205 3
        foreach ($schedule as $rate) {
1206 3
            $principal *= 1 + $rate;
1207
        }
1208
1209 3
        return $principal;
1210
    }
1211
1212
    /**
1213
     * INTRATE.
1214
     *
1215
     * Returns the interest rate for a fully invested security.
1216
     *
1217
     * Excel Function:
1218
     *        INTRATE(settlement,maturity,investment,redemption[,basis])
1219
     *
1220
     * @param mixed $settlement The security's settlement date.
1221
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1222
     * @param mixed $maturity The security's maturity date.
1223
     *                                The maturity date is the date when the security expires.
1224
     * @param int $investment the amount invested in the security
1225
     * @param int $redemption the amount to be received at maturity
1226
     * @param int $basis The type of day count to use.
1227
     *                                        0 or omitted    US (NASD) 30/360
1228
     *                                        1                Actual/actual
1229
     *                                        2                Actual/360
1230
     *                                        3                Actual/365
1231
     *                                        4                European 30/360
1232
     *
1233
     * @return float|string
1234
     */
1235 5
    public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis = 0)
1236
    {
1237 5
        $settlement = Functions::flattenSingleValue($settlement);
1238 5
        $maturity = Functions::flattenSingleValue($maturity);
1239 5
        $investment = Functions::flattenSingleValue($investment);
1240 5
        $redemption = Functions::flattenSingleValue($redemption);
1241 5
        $basis = Functions::flattenSingleValue($basis);
1242
1243
        //    Validate
1244 5
        if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) {
1245 4
            $investment = (float) $investment;
1246 4
            $redemption = (float) $redemption;
1247 4
            $basis = (int) $basis;
1248 4
            if (($investment <= 0) || ($redemption <= 0)) {
1249 1
                return Functions::NAN();
1250
            }
1251 3
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1252 3
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
1253
                //    return date error
1254 1
                return $daysBetweenSettlementAndMaturity;
1255
            }
1256
1257 2
            return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);
1258
        }
1259
1260 1
        return Functions::VALUE();
1261
    }
1262
1263
    /**
1264
     * IPMT.
1265
     *
1266
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1267
     *
1268
     * Excel Function:
1269
     *        IPMT(rate,per,nper,pv[,fv][,type])
1270
     *
1271
     * @param float $rate Interest rate per period
1272
     * @param int $per Period for which we want to find the interest
1273
     * @param int $nper Number of periods
1274
     * @param float $pv Present Value
1275
     * @param float $fv Future Value
1276
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1277
     *
1278
     * @return float|string
1279
     */
1280 15
    public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
1281
    {
1282 15
        $rate = Functions::flattenSingleValue($rate);
1283 15
        $per = (int) Functions::flattenSingleValue($per);
1284 15
        $nper = (int) Functions::flattenSingleValue($nper);
1285 15
        $pv = Functions::flattenSingleValue($pv);
1286 15
        $fv = Functions::flattenSingleValue($fv);
1287 15
        $type = (int) Functions::flattenSingleValue($type);
1288
1289
        // Validate parameters
1290 15
        if ($type != 0 && $type != 1) {
1291 1
            return Functions::NAN();
1292
        }
1293 14
        if ($per <= 0 || $per > $nper) {
1294 1
            return Functions::VALUE();
1295
        }
1296
1297
        // Calculate
1298 13
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1299
1300 13
        return $interestAndPrincipal[0];
1301
    }
1302
1303
    /**
1304
     * IRR.
1305
     *
1306
     * Returns the internal rate of return for a series of cash flows represented by the numbers in values.
1307
     * These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
1308
     * at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
1309
     * for an investment consisting of payments (negative values) and income (positive values) that occur at regular
1310
     * periods.
1311
     *
1312
     * Excel Function:
1313
     *        IRR(values[,guess])
1314
     *
1315
     * @param float[] $values An array or a reference to cells that contain numbers for which you want
1316
     *                                    to calculate the internal rate of return.
1317
     *                                Values must contain at least one positive value and one negative value to
1318
     *                                    calculate the internal rate of return.
1319
     * @param float $guess A number that you guess is close to the result of IRR
1320
     *
1321
     * @return float|string
1322
     */
1323 5
    public static function IRR($values, $guess = 0.1)
1324
    {
1325 5
        if (!is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
1326
            return Functions::VALUE();
1327
        }
1328 5
        $values = Functions::flattenArray($values);
1329 5
        $guess = Functions::flattenSingleValue($guess);
1330
1331
        // create an initial range, with a root somewhere between 0 and guess
1332 5
        $x1 = 0.0;
1333 5
        $x2 = $guess;
1334 5
        $f1 = self::NPV($x1, $values);
1335 5
        $f2 = self::NPV($x2, $values);
1336 5
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
1337 5
            if (($f1 * $f2) < 0.0) {
1338 5
                break;
1339
            }
1340 4
            if (abs($f1) < abs($f2)) {
1341 3
                $f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values);
1342
            } else {
1343 1
                $f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values);
1344
            }
1345
        }
1346 5
        if (($f1 * $f2) > 0.0) {
1347
            return Functions::VALUE();
1348
        }
1349
1350 5
        $f = self::NPV($x1, $values);
1351 5
        if ($f < 0.0) {
1352
            $rtb = $x1;
1353
            $dx = $x2 - $x1;
1354
        } else {
1355 5
            $rtb = $x2;
1356 5
            $dx = $x1 - $x2;
1357
        }
1358
1359 5
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
1360 5
            $dx *= 0.5;
1361 5
            $x_mid = $rtb + $dx;
1362 5
            $f_mid = self::NPV($x_mid, $values);
1363 5
            if ($f_mid <= 0.0) {
1364 5
                $rtb = $x_mid;
1365
            }
1366 5
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
1367 5
                return $x_mid;
1368
            }
1369
        }
1370
1371
        return Functions::VALUE();
1372
    }
1373
1374
    /**
1375
     * ISPMT.
1376
     *
1377
     * Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
1378
     *
1379
     * Excel Function:
1380
     *     =ISPMT(interest_rate, period, number_payments, PV)
1381
     *
1382
     * interest_rate is the interest rate for the investment
1383
     *
1384
     * period is the period to calculate the interest rate.  It must be betweeen 1 and number_payments.
1385
     *
1386
     * number_payments is the number of payments for the annuity
1387
     *
1388
     * PV is the loan amount or present value of the payments
1389
     */
1390 4
    public static function ISPMT(...$args)
1391
    {
1392
        // Return value
1393 4
        $returnValue = 0;
1394
1395
        // Get the parameters
1396 4
        $aArgs = Functions::flattenArray($args);
1397 4
        $interestRate = array_shift($aArgs);
1398 4
        $period = array_shift($aArgs);
1399 4
        $numberPeriods = array_shift($aArgs);
1400 4
        $principleRemaining = array_shift($aArgs);
1401
1402
        // Calculate
1403 4
        $principlePayment = ($principleRemaining * 1.0) / ($numberPeriods * 1.0);
1404 4
        for ($i = 0; $i <= $period; ++$i) {
1405 4
            $returnValue = $interestRate * $principleRemaining * -1;
1406 4
            $principleRemaining -= $principlePayment;
1407
            // principle needs to be 0 after the last payment, don't let floating point screw it up
1408 4
            if ($i == $numberPeriods) {
1409
                $returnValue = 0;
1410
            }
1411
        }
1412
1413 4
        return $returnValue;
1414
    }
1415
1416
    /**
1417
     * MIRR.
1418
     *
1419
     * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
1420
     *        the cost of the investment and the interest received on reinvestment of cash.
1421
     *
1422
     * Excel Function:
1423
     *        MIRR(values,finance_rate, reinvestment_rate)
1424
     *
1425
     * @param float[] $values An array or a reference to cells that contain a series of payments and
1426
     *                                            income occurring at regular intervals.
1427
     *                                        Payments are negative value, income is positive values.
1428
     * @param float $finance_rate The interest rate you pay on the money used in the cash flows
1429
     * @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them
1430
     *
1431
     * @return float|string
1432
     */
1433 5
    public static function MIRR($values, $finance_rate, $reinvestment_rate)
1434
    {
1435 5
        if (!is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
1436
            return Functions::VALUE();
1437
        }
1438 5
        $values = Functions::flattenArray($values);
1439 5
        $finance_rate = Functions::flattenSingleValue($finance_rate);
1440 5
        $reinvestment_rate = Functions::flattenSingleValue($reinvestment_rate);
1441 5
        $n = count($values);
1442
1443 5
        $rr = 1.0 + $reinvestment_rate;
1444 5
        $fr = 1.0 + $finance_rate;
1445
1446 5
        $npv_pos = $npv_neg = 0.0;
1447 5
        foreach ($values as $i => $v) {
1448 5
            if ($v >= 0) {
1449 5
                $npv_pos += $v / pow($rr, $i);
1450
            } else {
1451 5
                $npv_neg += $v / pow($fr, $i);
1452
            }
1453
        }
1454
1455 5
        if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) {
0 ignored issues
show
introduced by
The condition $npv_neg == 0 is always true.
Loading history...
1456
            return Functions::VALUE();
1457
        }
1458
1459 5
        $mirr = pow((-$npv_pos * pow($rr, $n))
1460 5
                / ($npv_neg * ($rr)), (1.0 / ($n - 1))) - 1.0;
1461
1462 5
        return is_finite($mirr) ? $mirr : Functions::VALUE();
1463
    }
1464
1465
    /**
1466
     * NOMINAL.
1467
     *
1468
     * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
1469
     *
1470
     * @param float $effect_rate Effective interest rate
1471
     * @param int $npery Number of compounding payments per year
1472
     *
1473
     * @return float|string
1474
     */
1475 5
    public static function NOMINAL($effect_rate = 0, $npery = 0)
1476
    {
1477 5
        $effect_rate = Functions::flattenSingleValue($effect_rate);
1478 5
        $npery = (int) Functions::flattenSingleValue($npery);
1479
1480
        // Validate parameters
1481 5
        if ($effect_rate <= 0 || $npery < 1) {
1482 1
            return Functions::NAN();
1483
        }
1484
1485
        // Calculate
1486 4
        return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
1487
    }
1488
1489
    /**
1490
     * NPER.
1491
     *
1492
     * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
1493
     *
1494
     * @param float $rate Interest rate per period
1495
     * @param int $pmt Periodic payment (annuity)
1496
     * @param float $pv Present Value
1497
     * @param float $fv Future Value
1498
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1499
     *
1500
     * @return float|string
1501
     */
1502 9
    public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0)
1503
    {
1504 9
        $rate = Functions::flattenSingleValue($rate);
1505 9
        $pmt = Functions::flattenSingleValue($pmt);
1506 9
        $pv = Functions::flattenSingleValue($pv);
1507 9
        $fv = Functions::flattenSingleValue($fv);
1508 9
        $type = Functions::flattenSingleValue($type);
1509
1510
        // Validate parameters
1511 9
        if ($type != 0 && $type != 1) {
1512 1
            return Functions::NAN();
1513
        }
1514
1515
        // Calculate
1516 8
        if ($rate !== null && $rate != 0) {
1517 6
            if ($pmt == 0 && $pv == 0) {
1518 1
                return Functions::NAN();
1519
            }
1520
1521 5
            return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
1522
        }
1523 2
        if ($pmt == 0) {
1524 1
            return Functions::NAN();
1525
        }
1526
1527 1
        return (-$pv - $fv) / $pmt;
1528
    }
1529
1530
    /**
1531
     * NPV.
1532
     *
1533
     * Returns the Net Present Value of a cash flow series given a discount rate.
1534
     *
1535
     * @return float
1536
     */
1537 9
    public static function NPV(...$args)
1538
    {
1539
        // Return value
1540 9
        $returnValue = 0;
1541
1542
        // Loop through arguments
1543 9
        $aArgs = Functions::flattenArray($args);
1544
1545
        // Calculate
1546 9
        $rate = array_shift($aArgs);
1547 9
        $countArgs = count($aArgs);
1548 9
        for ($i = 1; $i <= $countArgs; ++$i) {
1549
            // Is it a numeric value?
1550 9
            if (is_numeric($aArgs[$i - 1])) {
1551 9
                $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
1552
            }
1553
        }
1554
1555
        // Return
1556 9
        return $returnValue;
1557
    }
1558
1559
    /**
1560
     * PDURATION.
1561
     *
1562
     * Calculates the number of periods required for an investment to reach a specified value.
1563
     *
1564
     * @param float $rate Interest rate per period
1565
     * @param float $pv Present Value
1566
     * @param float $fv Future Value
1567
     *
1568
     * @return float|string
1569
     */
1570 8
    public static function PDURATION($rate = 0, $pv = 0, $fv = 0)
1571
    {
1572 8
        $rate = Functions::flattenSingleValue($rate);
1573 8
        $pv = Functions::flattenSingleValue($pv);
1574 8
        $fv = Functions::flattenSingleValue($fv);
1575
1576
        // Validate parameters
1577 8
        if (!is_numeric($rate) || !is_numeric($pv) || !is_numeric($fv)) {
1578 1
            return Functions::VALUE();
1579 7
        } elseif ($rate <= 0.0 || $pv <= 0.0 || $fv <= 0.0) {
1580 2
            return Functions::NAN();
1581
        }
1582
1583 5
        return (log($fv) - log($pv)) / log(1 + $rate);
1584
    }
1585
1586
    /**
1587
     * PMT.
1588
     *
1589
     * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
1590
     *
1591
     * @param float $rate Interest rate per period
1592
     * @param int $nper Number of periods
1593
     * @param float $pv Present Value
1594
     * @param float $fv Future Value
1595
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1596
     *
1597
     * @return float
1598
     */
1599 20
    public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
1600
    {
1601 20
        $rate = Functions::flattenSingleValue($rate);
1602 20
        $nper = Functions::flattenSingleValue($nper);
1603 20
        $pv = Functions::flattenSingleValue($pv);
1604 20
        $fv = Functions::flattenSingleValue($fv);
1605 20
        $type = Functions::flattenSingleValue($type);
1606
1607
        // Validate parameters
1608 20
        if ($type != 0 && $type != 1) {
1609
            return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
1610
        }
1611
1612
        // Calculate
1613 20
        if ($rate !== null && $rate != 0) {
1614 20
            return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
1615
        }
1616
1617
        return (-$pv - $fv) / $nper;
1618
    }
1619
1620
    /**
1621
     * PPMT.
1622
     *
1623
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1624
     *
1625
     * @param float $rate Interest rate per period
1626
     * @param int $per Period for which we want to find the interest
1627
     * @param int $nper Number of periods
1628
     * @param float $pv Present Value
1629
     * @param float $fv Future Value
1630
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1631
     *
1632
     * @return float
1633
     */
1634 7
    public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
1635
    {
1636 7
        $rate = Functions::flattenSingleValue($rate);
1637 7
        $per = (int) Functions::flattenSingleValue($per);
1638 7
        $nper = (int) Functions::flattenSingleValue($nper);
1639 7
        $pv = Functions::flattenSingleValue($pv);
1640 7
        $fv = Functions::flattenSingleValue($fv);
1641 7
        $type = (int) Functions::flattenSingleValue($type);
1642
1643
        // Validate parameters
1644 7
        if ($type != 0 && $type != 1) {
1645
            return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
1646
        }
1647 7
        if ($per <= 0 || $per > $nper) {
1648
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
1649
        }
1650
1651
        // Calculate
1652 7
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1653
1654 7
        return $interestAndPrincipal[1];
1655
    }
1656
1657
    public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0)
1658
    {
1659
        $settlement = Functions::flattenSingleValue($settlement);
1660
        $maturity = Functions::flattenSingleValue($maturity);
1661
        $rate = (float) Functions::flattenSingleValue($rate);
1662
        $yield = (float) Functions::flattenSingleValue($yield);
1663
        $redemption = (float) Functions::flattenSingleValue($redemption);
1664
        $frequency = (int) Functions::flattenSingleValue($frequency);
1665
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
1666
1667
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
1668
            return Functions::VALUE();
1669
        }
1670
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
1671
            return Functions::VALUE();
1672
        }
1673
1674
        if (($settlement > $maturity) ||
1675
            (!self::isValidFrequency($frequency)) ||
1676
            (($basis < 0) || ($basis > 4))) {
1677
            return Functions::NAN();
1678
        }
1679
1680
        $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
1681
        $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis);
1682
        $n = self::COUPNUM($settlement, $maturity, $frequency, $basis);
1683
        $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis);
1684
1685
        $baseYF = 1.0 + ($yield / $frequency);
1686
        $rfp = 100 * ($rate / $frequency);
1687
        $de = $dsc / $e;
1688
1689
        $result = $redemption / pow($baseYF, (--$n + $de));
1690
        for ($k = 0; $k <= $n; ++$k) {
1691
            $result += $rfp / (pow($baseYF, ($k + $de)));
1692
        }
1693
        $result -= $rfp * ($a / $e);
1694
1695
        return $result;
1696
    }
1697
1698
    /**
1699
     * PRICEDISC.
1700
     *
1701
     * Returns the price per $100 face value of a discounted security.
1702
     *
1703
     * @param mixed $settlement The security's settlement date.
1704
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1705
     * @param mixed $maturity The security's maturity date.
1706
     *                                The maturity date is the date when the security expires.
1707
     * @param int $discount The security's discount rate
1708
     * @param int $redemption The security's redemption value per $100 face value
1709
     * @param int $basis The type of day count to use.
1710
     *                                        0 or omitted    US (NASD) 30/360
1711
     *                                        1                Actual/actual
1712
     *                                        2                Actual/360
1713
     *                                        3                Actual/365
1714
     *                                        4                European 30/360
1715
     *
1716
     * @return float
1717
     */
1718 2
    public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0)
1719
    {
1720 2
        $settlement = Functions::flattenSingleValue($settlement);
1721 2
        $maturity = Functions::flattenSingleValue($maturity);
1722 2
        $discount = (float) Functions::flattenSingleValue($discount);
1723 2
        $redemption = (float) Functions::flattenSingleValue($redemption);
1724 2
        $basis = (int) Functions::flattenSingleValue($basis);
1725
1726
        //    Validate
1727 2
        if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) {
0 ignored issues
show
introduced by
The condition is_numeric($redemption) is always true.
Loading history...
introduced by
The condition is_numeric($basis) is always true.
Loading history...
1728 2
            if (($discount <= 0) || ($redemption <= 0)) {
1729
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
1730
            }
1731 2
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1732 2
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
1733
                //    return date error
1734
                return $daysBetweenSettlementAndMaturity;
1735
            }
1736
1737 2
            return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
1738
        }
1739
1740
        return Functions::VALUE();
1741
    }
1742
1743
    /**
1744
     * PRICEMAT.
1745
     *
1746
     * Returns the price per $100 face value of a security that pays interest at maturity.
1747
     *
1748
     * @param mixed $settlement The security's settlement date.
1749
     *                                The security's settlement date is the date after the issue date when the security is traded to the buyer.
1750
     * @param mixed $maturity The security's maturity date.
1751
     *                                The maturity date is the date when the security expires.
1752
     * @param mixed $issue The security's issue date
1753
     * @param int $rate The security's interest rate at date of issue
1754
     * @param int $yield The security's annual yield
1755
     * @param int $basis The type of day count to use.
1756
     *                                        0 or omitted    US (NASD) 30/360
1757
     *                                        1                Actual/actual
1758
     *                                        2                Actual/360
1759
     *                                        3                Actual/365
1760
     *                                        4                European 30/360
1761
     *
1762
     * @return float
1763
     */
1764
    public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
1765
    {
1766
        $settlement = Functions::flattenSingleValue($settlement);
1767
        $maturity = Functions::flattenSingleValue($maturity);
1768
        $issue = Functions::flattenSingleValue($issue);
1769
        $rate = Functions::flattenSingleValue($rate);
1770
        $yield = Functions::flattenSingleValue($yield);
1771
        $basis = (int) Functions::flattenSingleValue($basis);
1772
1773
        //    Validate
1774
        if (is_numeric($rate) && is_numeric($yield)) {
1775
            if (($rate <= 0) || ($yield <= 0)) {
1776
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
1777
            }
1778
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
1779
            if (!is_numeric($daysPerYear)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysPerYear) is always true.
Loading history...
1780
                return $daysPerYear;
1781
            }
1782
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
1783
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenIssueAndSettlement) is always true.
Loading history...
1784
                //    return date error
1785
                return $daysBetweenIssueAndSettlement;
1786
            }
1787
            $daysBetweenIssueAndSettlement *= $daysPerYear;
1788
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
1789
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenIssueAndMaturity) is always true.
Loading history...
1790
                //    return date error
1791
                return $daysBetweenIssueAndMaturity;
1792
            }
1793
            $daysBetweenIssueAndMaturity *= $daysPerYear;
1794
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1795
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
1796
                //    return date error
1797
                return $daysBetweenSettlementAndMaturity;
1798
            }
1799
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
1800
1801
            return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
1802
                   (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
1803
                   (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
1804
        }
1805
1806
        return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
1807
    }
1808
1809
    /**
1810
     * PV.
1811
     *
1812
     * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
1813
     *
1814
     * @param float $rate Interest rate per period
1815
     * @param int $nper Number of periods
1816
     * @param float $pmt Periodic payment (annuity)
1817
     * @param float $fv Future Value
1818
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1819
     *
1820
     * @return float
1821
     */
1822 5
    public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0)
1823
    {
1824 5
        $rate = Functions::flattenSingleValue($rate);
1825 5
        $nper = Functions::flattenSingleValue($nper);
1826 5
        $pmt = Functions::flattenSingleValue($pmt);
1827 5
        $fv = Functions::flattenSingleValue($fv);
1828 5
        $type = Functions::flattenSingleValue($type);
1829
1830
        // Validate parameters
1831 5
        if ($type != 0 && $type != 1) {
1832
            return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
1833
        }
1834
1835
        // Calculate
1836 5
        if ($rate !== null && $rate != 0) {
1837 5
            return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
1838
        }
1839
1840
        return -$fv - $pmt * $nper;
1841
    }
1842
1843
    /**
1844
     * RATE.
1845
     *
1846
     * Returns the interest rate per period of an annuity.
1847
     * RATE is calculated by iteration and can have zero or more solutions.
1848
     * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
1849
     * RATE returns the #NUM! error value.
1850
     *
1851
     * Excel Function:
1852
     *        RATE(nper,pmt,pv[,fv[,type[,guess]]])
1853
     *
1854
     * @category Financial Functions
1855
     *
1856
     * @param float $nper The total number of payment periods in an annuity
1857
     * @param float $pmt The payment made each period and cannot change over the life
1858
     *                                    of the annuity.
1859
     *                                Typically, pmt includes principal and interest but no other
1860
     *                                    fees or taxes.
1861
     * @param float $pv The present value - the total amount that a series of future
1862
     *                                    payments is worth now
1863
     * @param float $fv The future value, or a cash balance you want to attain after
1864
     *                                    the last payment is made. If fv is omitted, it is assumed
1865
     *                                    to be 0 (the future value of a loan, for example, is 0).
1866
     * @param int $type A number 0 or 1 and indicates when payments are due:
1867
     *                                        0 or omitted    At the end of the period.
1868
     *                                        1                At the beginning of the period.
1869
     * @param float $guess Your guess for what the rate will be.
1870
     *                                    If you omit guess, it is assumed to be 10 percent.
1871
     *
1872
     * @return float
1873
     */
1874
    public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1)
1875
    {
1876
        $nper = (int) Functions::flattenSingleValue($nper);
1877
        $pmt = Functions::flattenSingleValue($pmt);
1878
        $pv = Functions::flattenSingleValue($pv);
1879
        $fv = ($fv === null) ? 0.0 : Functions::flattenSingleValue($fv);
0 ignored issues
show
introduced by
The condition $fv === null is always false.
Loading history...
1880
        $type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type);
0 ignored issues
show
introduced by
The condition $type === null is always false.
Loading history...
1881
        $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
0 ignored issues
show
introduced by
The condition $guess === null is always false.
Loading history...
1882
1883
        $rate = $guess;
1884
        if (abs($rate) < self::FINANCIAL_PRECISION) {
1885
            $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
0 ignored issues
show
Unused Code introduced by
The assignment to $y is dead and can be removed.
Loading history...
1886
        } else {
1887
            $f = exp($nper * log(1 + $rate));
1888
            $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
1889
        }
1890
        $y0 = $pv + $pmt * $nper + $fv;
1891
        $y1 = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $f does not seem to be defined for all execution paths leading up to this point.
Loading history...
1892
1893
        // find root by secant method
1894
        $i = $x0 = 0.0;
1895
        $x1 = $rate;
1896
        while ((abs($y0 - $y1) > self::FINANCIAL_PRECISION) && ($i < self::FINANCIAL_MAX_ITERATIONS)) {
1897
            $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
1898
            $x0 = $x1;
1899
            $x1 = $rate;
1900
            if (($nper * abs($pmt)) > ($pv - $fv)) {
1901
                $x1 = abs($x1);
1902
            }
1903
            if (abs($rate) < self::FINANCIAL_PRECISION) {
1904
                $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
1905
            } else {
1906
                $f = exp($nper * log(1 + $rate));
1907
                $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
1908
            }
1909
1910
            $y0 = $y1;
1911
            $y1 = $y;
1912
            ++$i;
1913
        }
1914
1915
        return $rate;
1916
    }
1917
1918
    /**
1919
     * RECEIVED.
1920
     *
1921
     * Returns the price per $100 face value of a discounted security.
1922
     *
1923
     * @param mixed $settlement The security's settlement date.
1924
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1925
     * @param mixed $maturity The security's maturity date.
1926
     *                                The maturity date is the date when the security expires.
1927
     * @param int $investment The amount invested in the security
1928
     * @param int $discount The security's discount rate
1929
     * @param int $basis The type of day count to use.
1930
     *                                        0 or omitted    US (NASD) 30/360
1931
     *                                        1                Actual/actual
1932
     *                                        2                Actual/360
1933
     *                                        3                Actual/365
1934
     *                                        4                European 30/360
1935
     *
1936
     * @return float
1937
     */
1938
    public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0)
1939
    {
1940
        $settlement = Functions::flattenSingleValue($settlement);
1941
        $maturity = Functions::flattenSingleValue($maturity);
1942
        $investment = (float) Functions::flattenSingleValue($investment);
1943
        $discount = (float) Functions::flattenSingleValue($discount);
1944
        $basis = (int) Functions::flattenSingleValue($basis);
1945
1946
        //    Validate
1947
        if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) {
0 ignored issues
show
introduced by
The condition is_numeric($discount) is always true.
Loading history...
introduced by
The condition is_numeric($basis) is always true.
Loading history...
1948
            if (($investment <= 0) || ($discount <= 0)) {
1949
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
1950
            }
1951
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1952
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
1953
                //    return date error
1954
                return $daysBetweenSettlementAndMaturity;
1955
            }
1956
1957
            return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));
1958
        }
1959
1960
        return Functions::VALUE();
1961
    }
1962
1963
    /**
1964
     * RRI.
1965
     *
1966
     * Calculates the interest rate required for an investment to grow to a specified future value .
1967
     *
1968
     * @param float $nper The number of periods over which the investment is made
1969
     * @param float $pv Present Value
1970
     * @param float $fv Future Value
1971
     *
1972
     * @return float|string
1973
     */
1974 6
    public static function RRI($nper = 0, $pv = 0, $fv = 0)
1975
    {
1976 6
        $nper = Functions::flattenSingleValue($nper);
1977 6
        $pv = Functions::flattenSingleValue($pv);
1978 6
        $fv = Functions::flattenSingleValue($fv);
1979
1980
        // Validate parameters
1981 6
        if (!is_numeric($nper) || !is_numeric($pv) || !is_numeric($fv)) {
1982 1
            return Functions::VALUE();
1983 5
        } elseif ($nper <= 0.0 || $pv <= 0.0 || $fv < 0.0) {
1984 2
            return Functions::NAN();
1985
        }
1986
1987 3
        return pow($fv / $pv, 1 / $nper) - 1;
1988
    }
1989
1990
    /**
1991
     * SLN.
1992
     *
1993
     * Returns the straight-line depreciation of an asset for one period
1994
     *
1995
     * @param mixed $cost Initial cost of the asset
1996
     * @param mixed $salvage Value at the end of the depreciation
1997
     * @param mixed $life Number of periods over which the asset is depreciated
1998
     *
1999
     * @return float|string
2000
     */
2001 9
    public static function SLN($cost, $salvage, $life)
2002
    {
2003 9
        $cost = Functions::flattenSingleValue($cost);
2004 9
        $salvage = Functions::flattenSingleValue($salvage);
2005 9
        $life = Functions::flattenSingleValue($life);
2006
2007
        // Calculate
2008 9
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
2009 8
            if ($life < 0) {
2010 1
                return Functions::NAN();
2011
            }
2012
2013 7
            return ($cost - $salvage) / $life;
2014
        }
2015
2016 1
        return Functions::VALUE();
2017
    }
2018
2019
    /**
2020
     * SYD.
2021
     *
2022
     * Returns the sum-of-years' digits depreciation of an asset for a specified period.
2023
     *
2024
     * @param mixed $cost Initial cost of the asset
2025
     * @param mixed $salvage Value at the end of the depreciation
2026
     * @param mixed $life Number of periods over which the asset is depreciated
2027
     * @param mixed $period Period
2028
     *
2029
     * @return float|string
2030
     */
2031 10
    public static function SYD($cost, $salvage, $life, $period)
2032
    {
2033 10
        $cost = Functions::flattenSingleValue($cost);
2034 10
        $salvage = Functions::flattenSingleValue($salvage);
2035 10
        $life = Functions::flattenSingleValue($life);
2036 10
        $period = Functions::flattenSingleValue($period);
2037
2038
        // Calculate
2039 10
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
2040 9
            if (($life < 1) || ($period > $life)) {
2041 1
                return Functions::NAN();
2042
            }
2043
2044 8
            return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
2045
        }
2046
2047 1
        return Functions::VALUE();
2048
    }
2049
2050
    /**
2051
     * TBILLEQ.
2052
     *
2053
     * Returns the bond-equivalent yield for a Treasury bill.
2054
     *
2055
     * @param mixed $settlement The Treasury bill's settlement date.
2056
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2057
     * @param mixed $maturity The Treasury bill's maturity date.
2058
     *                                The maturity date is the date when the Treasury bill expires.
2059
     * @param int $discount The Treasury bill's discount rate
2060
     *
2061
     * @return float
2062
     */
2063
    public static function TBILLEQ($settlement, $maturity, $discount)
2064
    {
2065
        $settlement = Functions::flattenSingleValue($settlement);
2066
        $maturity = Functions::flattenSingleValue($maturity);
2067
        $discount = Functions::flattenSingleValue($discount);
2068
2069
        //    Use TBILLPRICE for validation
2070
        $testValue = self::TBILLPRICE($settlement, $maturity, $discount);
2071
        if (is_string($testValue)) {
0 ignored issues
show
introduced by
The condition is_string($testValue) is always false.
Loading history...
2072
            return $testValue;
2073
        }
2074
2075
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2076
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2077
        }
2078
2079
        if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2080
            ++$maturity;
2081
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2082
        } else {
2083
            $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2084
        }
2085
2086
        return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
2087
    }
2088
2089
    /**
2090
     * TBILLPRICE.
2091
     *
2092
     * Returns the yield for a Treasury bill.
2093
     *
2094
     * @param mixed $settlement The Treasury bill's settlement date.
2095
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2096
     * @param mixed $maturity The Treasury bill's maturity date.
2097
     *                                The maturity date is the date when the Treasury bill expires.
2098
     * @param int $discount The Treasury bill's discount rate
2099
     *
2100
     * @return float
2101
     */
2102
    public static function TBILLPRICE($settlement, $maturity, $discount)
2103
    {
2104
        $settlement = Functions::flattenSingleValue($settlement);
2105
        $maturity = Functions::flattenSingleValue($maturity);
2106
        $discount = Functions::flattenSingleValue($discount);
2107
2108
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2109
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2110
        }
2111
2112
        //    Validate
2113
        if (is_numeric($discount)) {
2114
            if ($discount <= 0) {
2115
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2116
            }
2117
2118
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2119
                ++$maturity;
2120
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2121
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
2122
                    //    return date error
2123
                    return $daysBetweenSettlementAndMaturity;
2124
                }
2125
            } else {
2126
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2127
            }
2128
2129
            if ($daysBetweenSettlementAndMaturity > 360) {
2130
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2131
            }
2132
2133
            $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
2134
            if ($price <= 0) {
2135
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2136
            }
2137
2138
            return $price;
2139
        }
2140
2141
        return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2142
    }
2143
2144
    /**
2145
     * TBILLYIELD.
2146
     *
2147
     * Returns the yield for a Treasury bill.
2148
     *
2149
     * @param mixed $settlement The Treasury bill's settlement date.
2150
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2151
     * @param mixed $maturity The Treasury bill's maturity date.
2152
     *                                The maturity date is the date when the Treasury bill expires.
2153
     * @param int $price The Treasury bill's price per $100 face value
2154
     *
2155
     * @return float
2156
     */
2157
    public static function TBILLYIELD($settlement, $maturity, $price)
2158
    {
2159
        $settlement = Functions::flattenSingleValue($settlement);
2160
        $maturity = Functions::flattenSingleValue($maturity);
2161
        $price = Functions::flattenSingleValue($price);
2162
2163
        //    Validate
2164
        if (is_numeric($price)) {
2165
            if ($price <= 0) {
2166
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2167
            }
2168
2169
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2170
                ++$maturity;
2171
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2172
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
2173
                    //    return date error
2174
                    return $daysBetweenSettlementAndMaturity;
2175
                }
2176
            } else {
2177
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2178
            }
2179
2180
            if ($daysBetweenSettlementAndMaturity > 360) {
2181
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2182
            }
2183
2184
            return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
2185
        }
2186
2187
        return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2188
    }
2189
2190
    public static function XIRR($values, $dates, $guess = 0.1)
2191
    {
2192
        if ((!is_array($values)) && (!is_array($dates))) {
2193
            return Functions::VALUE();
2194
        }
2195
        $values = Functions::flattenArray($values);
2196
        $dates = Functions::flattenArray($dates);
2197
        $guess = Functions::flattenSingleValue($guess);
2198
        if (count($values) != count($dates)) {
2199
            return Functions::NAN();
2200
        }
2201
2202
        // create an initial range, with a root somewhere between 0 and guess
2203
        $x1 = 0.0;
2204
        $x2 = $guess;
2205
        $f1 = self::XNPV($x1, $values, $dates);
2206
        $f2 = self::XNPV($x2, $values, $dates);
2207
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
2208
            if (($f1 * $f2) < 0.0) {
2209
                break;
2210
            } elseif (abs($f1) < abs($f2)) {
2211
                $f1 = self::XNPV($x1 += 1.6 * ($x1 - $x2), $values, $dates);
2212
            } else {
2213
                $f2 = self::XNPV($x2 += 1.6 * ($x2 - $x1), $values, $dates);
2214
            }
2215
        }
2216
        if (($f1 * $f2) > 0.0) {
2217
            return Functions::VALUE();
2218
        }
2219
2220
        $f = self::XNPV($x1, $values, $dates);
2221
        if ($f < 0.0) {
2222
            $rtb = $x1;
2223
            $dx = $x2 - $x1;
2224
        } else {
2225
            $rtb = $x2;
2226
            $dx = $x1 - $x2;
2227
        }
2228
2229
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
2230
            $dx *= 0.5;
2231
            $x_mid = $rtb + $dx;
2232
            $f_mid = self::XNPV($x_mid, $values, $dates);
2233
            if ($f_mid <= 0.0) {
2234
                $rtb = $x_mid;
2235
            }
2236
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
2237
                return $x_mid;
2238
            }
2239
        }
2240
2241
        return Functions::VALUE();
2242
    }
2243
2244
    /**
2245
     * XNPV.
2246
     *
2247
     * Returns the net present value for a schedule of cash flows that is not necessarily periodic.
2248
     * To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
2249
     *
2250
     * Excel Function:
2251
     *        =XNPV(rate,values,dates)
2252
     *
2253
     * @param float $rate the discount rate to apply to the cash flows
2254
     * @param array of float    $values     A series of cash flows that corresponds to a schedule of payments in dates.
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\Calculation\of was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
2255
     *                                         The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
2256
     *                                         If the first value is a cost or payment, it must be a negative value. All succeeding payments are discounted based on a 365-day year.
2257
     *                                         The series of values must contain at least one positive value and one negative value.
2258
     * @param array of mixed    $dates      A schedule of payment dates that corresponds to the cash flow payments.
2259
     *                                         The first payment date indicates the beginning of the schedule of payments.
2260
     *                                         All other dates must be later than this date, but they may occur in any order.
2261
     *
2262
     * @return float
2263
     */
2264
    public static function XNPV($rate, $values, $dates)
2265
    {
2266
        $rate = Functions::flattenSingleValue($rate);
2267
        if (!is_numeric($rate)) {
2268
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2269
        }
2270
        if ((!is_array($values)) || (!is_array($dates))) {
2271
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2272
        }
2273
        $values = Functions::flattenArray($values);
2274
        $dates = Functions::flattenArray($dates);
2275
        $valCount = count($values);
2276
        if ($valCount != count($dates)) {
2277
            return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2278
        }
2279
        if ((min($values) > 0) || (max($values) < 0)) {
2280
            return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2281
        }
2282
2283
        $xnpv = 0.0;
2284
        for ($i = 0; $i < $valCount; ++$i) {
2285
            if (!is_numeric($values[$i])) {
2286
                return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2287
            }
2288
            $xnpv += $values[$i] / pow(1 + $rate, DateTime::DATEDIF($dates[0], $dates[$i], 'd') / 365);
2289
        }
2290
2291
        return (is_finite($xnpv)) ? $xnpv : Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return is_finite($xnpv) ...tion\Functions::VALUE() also could return the type string which is incompatible with the documented return type double.
Loading history...
2292
    }
2293
2294
    /**
2295
     * YIELDDISC.
2296
     *
2297
     * Returns the annual yield of a security that pays interest at maturity.
2298
     *
2299
     * @param mixed $settlement The security's settlement date.
2300
     *                                    The security's settlement date is the date after the issue date when the security is traded to the buyer.
2301
     * @param mixed $maturity The security's maturity date.
2302
     *                                    The maturity date is the date when the security expires.
2303
     * @param int $price The security's price per $100 face value
2304
     * @param int $redemption The security's redemption value per $100 face value
2305
     * @param int $basis The type of day count to use.
2306
     *                                        0 or omitted    US (NASD) 30/360
2307
     *                                        1                Actual/actual
2308
     *                                        2                Actual/360
2309
     *                                        3                Actual/365
2310
     *                                        4                European 30/360
2311
     *
2312
     * @return float
2313
     */
2314
    public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0)
2315
    {
2316
        $settlement = Functions::flattenSingleValue($settlement);
2317
        $maturity = Functions::flattenSingleValue($maturity);
2318
        $price = Functions::flattenSingleValue($price);
2319
        $redemption = Functions::flattenSingleValue($redemption);
2320
        $basis = (int) Functions::flattenSingleValue($basis);
2321
2322
        //    Validate
2323
        if (is_numeric($price) && is_numeric($redemption)) {
2324
            if (($price <= 0) || ($redemption <= 0)) {
2325
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2326
            }
2327
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2328
            if (!is_numeric($daysPerYear)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysPerYear) is always true.
Loading history...
2329
                return $daysPerYear;
2330
            }
2331
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2332
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
2333
                //    return date error
2334
                return $daysBetweenSettlementAndMaturity;
2335
            }
2336
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2337
2338
            return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
2339
        }
2340
2341
        return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2342
    }
2343
2344
    /**
2345
     * YIELDMAT.
2346
     *
2347
     * Returns the annual yield of a security that pays interest at maturity.
2348
     *
2349
     * @param mixed $settlement The security's settlement date.
2350
     *                                   The security's settlement date is the date after the issue date when the security is traded to the buyer.
2351
     * @param mixed $maturity The security's maturity date.
2352
     *                                   The maturity date is the date when the security expires.
2353
     * @param mixed $issue The security's issue date
2354
     * @param int $rate The security's interest rate at date of issue
2355
     * @param int $price The security's price per $100 face value
2356
     * @param int $basis The type of day count to use.
2357
     *                                        0 or omitted    US (NASD) 30/360
2358
     *                                        1                Actual/actual
2359
     *                                        2                Actual/360
2360
     *                                        3                Actual/365
2361
     *                                        4                European 30/360
2362
     *
2363
     * @return float
2364
     */
2365
    public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0)
2366
    {
2367
        $settlement = Functions::flattenSingleValue($settlement);
2368
        $maturity = Functions::flattenSingleValue($maturity);
2369
        $issue = Functions::flattenSingleValue($issue);
2370
        $rate = Functions::flattenSingleValue($rate);
2371
        $price = Functions::flattenSingleValue($price);
2372
        $basis = (int) Functions::flattenSingleValue($basis);
2373
2374
        //    Validate
2375
        if (is_numeric($rate) && is_numeric($price)) {
2376
            if (($rate <= 0) || ($price <= 0)) {
2377
                return Functions::NAN();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...lation\Functions::NAN() returns the type string which is incompatible with the documented return type double.
Loading history...
2378
            }
2379
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2380
            if (!is_numeric($daysPerYear)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysPerYear) is always true.
Loading history...
2381
                return $daysPerYear;
2382
            }
2383
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
2384
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenIssueAndSettlement) is always true.
Loading history...
2385
                //    return date error
2386
                return $daysBetweenIssueAndSettlement;
2387
            }
2388
            $daysBetweenIssueAndSettlement *= $daysPerYear;
2389
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
2390
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenIssueAndMaturity) is always true.
Loading history...
2391
                //    return date error
2392
                return $daysBetweenIssueAndMaturity;
2393
            }
2394
            $daysBetweenIssueAndMaturity *= $daysPerYear;
2395
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2396
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
2397
                //    return date error
2398
                return $daysBetweenSettlementAndMaturity;
2399
            }
2400
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2401
2402
            return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
2403
                   (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
2404
                   ($daysPerYear / $daysBetweenSettlementAndMaturity);
2405
        }
2406
2407
        return Functions::VALUE();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...tion\Functions::VALUE() returns the type string which is incompatible with the documented return type double.
Loading history...
2408
    }
2409
}
2410