Completed
Push — develop ( c96e2d...d2f55f )
by Adrien
48:43 queued 44:11
created

Financial::PPMT()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0909

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 3
nop 6
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
ccs 11
cts 13
cp 0.8462
crap 5.0909
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 = 128;
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 19
    private static function isValidFrequency($frequency)
49
    {
50 19
        if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) {
51 13
            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 $year The year against which we're testing
67
     * @param int $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 4
    private static function daysPerYear($year, $basis = 0)
77
    {
78
        switch ($basis) {
79 4
            case 0:
80 2
            case 2:
81 2
            case 4:
82 2
                $daysPerYear = 360;
83
84 2
                break;
85 2
            case 3:
86
                $daysPerYear = 365;
87
88
                break;
89 2
            case 1:
90 2
                $daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365;
91
92 2
                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 4
        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
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 can never be true.
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 can never be true.
Loading history...
157 7
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null can never be true.
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();
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...
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) can never be 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();
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...
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
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 can never be true.
Loading history...
208 5
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null can never be true.
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();
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...
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) can never be 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();
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...
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 can never be true.
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 can never be true.
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
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 can never be true.
Loading history...
412
413 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
414 1
            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...
415
        }
416 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
417 1
            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...
418
        }
419
420 3
        if (($settlement > $maturity) ||
421 3
            (!self::isValidFrequency($frequency)) ||
422 3
            (($basis < 0) || ($basis > 4))) {
423 1
            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...
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
464
     */
465 5
    public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0)
466
    {
467 5
        $settlement = Functions::flattenSingleValue($settlement);
468 5
        $maturity = Functions::flattenSingleValue($maturity);
469 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
470 5
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null can never be true.
Loading history...
471
472 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
473 1
            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...
474
        }
475 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
476 1
            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...
477
        }
478
479 3
        if (($settlement > $maturity) ||
480 3
            (!self::isValidFrequency($frequency)) ||
481 3
            (($basis < 0) || ($basis > 4))) {
482 1
            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...
483
        }
484
485
        switch ($basis) {
486 2
            case 3:
487
                // Actual/365
488
                return 365 / $frequency;
489 2
            case 1:
490
                // Actual/actual
491 1
                if ($frequency == 1) {
492
                    $daysPerYear = self::daysPerYear(DateTime::YEAR($maturity), $basis);
493
494
                    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
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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
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 can never be true.
Loading history...
545
546 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
547 1
            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...
548
        }
549 4
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
550 1
            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...
551
        }
552
553 3
        if (($settlement > $maturity) ||
554 3
            (!self::isValidFrequency($frequency)) ||
555 3
            (($basis < 0) || ($basis > 4))) {
556 1
            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...
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 can never be true.
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
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 can never be true.
Loading history...
662
663 6
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
664 1
            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 integer.
Loading history...
665
        }
666 5
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
667 1
            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 integer.
Loading history...
668
        }
669
670 4
        if (($settlement > $maturity) ||
671 4
            (!self::isValidFrequency($frequency)) ||
672 4
            (($basis < 0) || ($basis > 4))) {
673 1
            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...
674
        }
675
676 3
        $settlement = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
677 3
        $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * 365;
678
679
        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();
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 integer.
Loading history...
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 can never be true.
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
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();
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...
786
        }
787 8
        if ($start < 1 || $start > $end) {
788 1
            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...
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
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();
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...
834
        }
835 8
        if ($start < 1 || $start > $end) {
836 1
            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...
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
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();
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...
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();
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...
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
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();
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...
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();
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...
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
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();
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...
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) can never be 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();
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...
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
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();
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...
1062
        }
1063 8
        if ($fraction == 0) {
1064 1
            return Functions::DIV0();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...ation\Functions::DIV0() returns the type string which is incompatible with the documented return type double.
Loading history...
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
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();
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...
1100
        }
1101 8
        if ($fraction == 0) {
1102 1
            return Functions::DIV0();
0 ignored issues
show
Bug Best Practice introduced by
The expression return PhpOffice\PhpSpre...ation\Functions::DIV0() returns the type string which is incompatible with the documented return type double.
Loading history...
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
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();
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...
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
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();
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...
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
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();
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...
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) can never be 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();
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...
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
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();
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...
1292
        }
1293 14
        if ($per <= 0 || $per > $nper) {
1294 1
            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...
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
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) can never be 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();
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...
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();
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...
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
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) can never be 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 || $npv_po...reinvestment_rate <= -1 can never be false.
Loading history...
1456
            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...
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
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();
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...
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
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();
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...
1513
        }
1514
1515
        // Calculate
1516 8
        if ($rate !== null && $rate != 0) {
1517 6
            if ($pmt == 0 && $pv == 0) {
1518 1
                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...
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();
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...
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
     * PMT.
1561
     *
1562
     * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
1563
     *
1564
     * @param float $rate Interest rate per period
1565
     * @param int $nper Number of periods
1566
     * @param float $pv Present Value
1567
     * @param float $fv Future Value
1568
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1569
     *
1570
     * @return float
1571
     */
1572 20
    public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
1573
    {
1574 20
        $rate = Functions::flattenSingleValue($rate);
1575 20
        $nper = Functions::flattenSingleValue($nper);
1576 20
        $pv = Functions::flattenSingleValue($pv);
1577 20
        $fv = Functions::flattenSingleValue($fv);
1578 20
        $type = Functions::flattenSingleValue($type);
1579
1580
        // Validate parameters
1581 20
        if ($type != 0 && $type != 1) {
1582
            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...
1583
        }
1584
1585
        // Calculate
1586 20
        if ($rate !== null && $rate != 0) {
1587 20
            return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
1588
        }
1589
1590
        return (-$pv - $fv) / $nper;
1591
    }
1592
1593
    /**
1594
     * PPMT.
1595
     *
1596
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1597
     *
1598
     * @param float $rate Interest rate per period
1599
     * @param int $per Period for which we want to find the interest
1600
     * @param int $nper Number of periods
1601
     * @param float $pv Present Value
1602
     * @param float $fv Future Value
1603
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1604
     *
1605
     * @return float
1606
     */
1607 7
    public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
1608
    {
1609 7
        $rate = Functions::flattenSingleValue($rate);
1610 7
        $per = (int) Functions::flattenSingleValue($per);
1611 7
        $nper = (int) Functions::flattenSingleValue($nper);
1612 7
        $pv = Functions::flattenSingleValue($pv);
1613 7
        $fv = Functions::flattenSingleValue($fv);
1614 7
        $type = (int) Functions::flattenSingleValue($type);
1615
1616
        // Validate parameters
1617 7
        if ($type != 0 && $type != 1) {
1618
            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...
1619
        }
1620 7
        if ($per <= 0 || $per > $nper) {
1621
            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...
1622
        }
1623
1624
        // Calculate
1625 7
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1626
1627 7
        return $interestAndPrincipal[1];
1628
    }
1629
1630
    public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0)
1631
    {
1632
        $settlement = Functions::flattenSingleValue($settlement);
1633
        $maturity = Functions::flattenSingleValue($maturity);
1634
        $rate = (float) Functions::flattenSingleValue($rate);
1635
        $yield = (float) Functions::flattenSingleValue($yield);
1636
        $redemption = (float) Functions::flattenSingleValue($redemption);
1637
        $frequency = (int) Functions::flattenSingleValue($frequency);
1638
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
1639
1640
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
1641
            return Functions::VALUE();
1642
        }
1643
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
1644
            return Functions::VALUE();
1645
        }
1646
1647
        if (($settlement > $maturity) ||
1648
            (!self::isValidFrequency($frequency)) ||
1649
            (($basis < 0) || ($basis > 4))) {
1650
            return Functions::NAN();
1651
        }
1652
1653
        $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
1654
        $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis);
1655
        $n = self::COUPNUM($settlement, $maturity, $frequency, $basis);
1656
        $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis);
1657
1658
        $baseYF = 1.0 + ($yield / $frequency);
1659
        $rfp = 100 * ($rate / $frequency);
1660
        $de = $dsc / $e;
1661
1662
        $result = $redemption / pow($baseYF, (--$n + $de));
1663
        for ($k = 0; $k <= $n; ++$k) {
1664
            $result += $rfp / (pow($baseYF, ($k + $de)));
1665
        }
1666
        $result -= $rfp * ($a / $e);
1667
1668
        return $result;
1669
    }
1670
1671
    /**
1672
     * PRICEDISC.
1673
     *
1674
     * Returns the price per $100 face value of a discounted security.
1675
     *
1676
     * @param mixed $settlement The security's settlement date.
1677
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1678
     * @param mixed $maturity The security's maturity date.
1679
     *                                The maturity date is the date when the security expires.
1680
     * @param int $discount The security's discount rate
1681
     * @param int $redemption The security's redemption value per $100 face value
1682
     * @param int $basis The type of day count to use.
1683
     *                                        0 or omitted    US (NASD) 30/360
1684
     *                                        1                Actual/actual
1685
     *                                        2                Actual/360
1686
     *                                        3                Actual/365
1687
     *                                        4                European 30/360
1688
     *
1689
     * @return float
1690
     */
1691
    public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0)
1692
    {
1693
        $settlement = Functions::flattenSingleValue($settlement);
1694
        $maturity = Functions::flattenSingleValue($maturity);
1695
        $discount = (float) Functions::flattenSingleValue($discount);
1696
        $redemption = (float) Functions::flattenSingleValue($redemption);
1697
        $basis = (int) Functions::flattenSingleValue($basis);
1698
1699
        //    Validate
1700
        if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) {
0 ignored issues
show
introduced by
The condition is_numeric($discount) &&...) && is_numeric($basis) can never be false.
Loading history...
1701
            if (($discount <= 0) || ($redemption <= 0)) {
1702
                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...
1703
            }
1704
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1705
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
1706
                //    return date error
1707
                return $daysBetweenSettlementAndMaturity;
1708
            }
1709
1710
            return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
1711
        }
1712
1713
        return Functions::VALUE();
1714
    }
1715
1716
    /**
1717
     * PRICEMAT.
1718
     *
1719
     * Returns the price per $100 face value of a security that pays interest at maturity.
1720
     *
1721
     * @param mixed $settlement The security's settlement date.
1722
     *                                The security's settlement date is the date after the issue date when the security is traded to the buyer.
1723
     * @param mixed $maturity The security's maturity date.
1724
     *                                The maturity date is the date when the security expires.
1725
     * @param mixed $issue The security's issue date
1726
     * @param int $rate The security's interest rate at date of issue
1727
     * @param int $yield The security's annual yield
1728
     * @param int $basis The type of day count to use.
1729
     *                                        0 or omitted    US (NASD) 30/360
1730
     *                                        1                Actual/actual
1731
     *                                        2                Actual/360
1732
     *                                        3                Actual/365
1733
     *                                        4                European 30/360
1734
     *
1735
     * @return float
1736
     */
1737
    public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
1738
    {
1739
        $settlement = Functions::flattenSingleValue($settlement);
1740
        $maturity = Functions::flattenSingleValue($maturity);
1741
        $issue = Functions::flattenSingleValue($issue);
1742
        $rate = Functions::flattenSingleValue($rate);
1743
        $yield = Functions::flattenSingleValue($yield);
1744
        $basis = (int) Functions::flattenSingleValue($basis);
1745
1746
        //    Validate
1747
        if (is_numeric($rate) && is_numeric($yield)) {
1748
            if (($rate <= 0) || ($yield <= 0)) {
1749
                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...
1750
            }
1751
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
1752
            if (!is_numeric($daysPerYear)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysPerYear) can never be true.
Loading history...
1753
                return $daysPerYear;
1754
            }
1755
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
1756
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenIssueAndSettlement) can never be true.
Loading history...
1757
                //    return date error
1758
                return $daysBetweenIssueAndSettlement;
1759
            }
1760
            $daysBetweenIssueAndSettlement *= $daysPerYear;
1761
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
1762
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenIssueAndMaturity) can never be true.
Loading history...
1763
                //    return date error
1764
                return $daysBetweenIssueAndMaturity;
1765
            }
1766
            $daysBetweenIssueAndMaturity *= $daysPerYear;
1767
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1768
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
1769
                //    return date error
1770
                return $daysBetweenSettlementAndMaturity;
1771
            }
1772
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
1773
1774
            return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
1775
                   (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
1776
                   (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
1777
        }
1778
1779
        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...
1780
    }
1781
1782
    /**
1783
     * PV.
1784
     *
1785
     * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
1786
     *
1787
     * @param float $rate Interest rate per period
1788
     * @param int $nper Number of periods
1789
     * @param float $pmt Periodic payment (annuity)
1790
     * @param float $fv Future Value
1791
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1792
     *
1793
     * @return float
1794
     */
1795
    public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0)
1796
    {
1797
        $rate = Functions::flattenSingleValue($rate);
1798
        $nper = Functions::flattenSingleValue($nper);
1799
        $pmt = Functions::flattenSingleValue($pmt);
1800
        $fv = Functions::flattenSingleValue($fv);
1801
        $type = Functions::flattenSingleValue($type);
1802
1803
        // Validate parameters
1804
        if ($type != 0 && $type != 1) {
1805
            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...
1806
        }
1807
1808
        // Calculate
1809
        if ($rate !== null && $rate != 0) {
1810
            return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
1811
        }
1812
1813
        return -$fv - $pmt * $nper;
1814
    }
1815
1816
    /**
1817
     * RATE.
1818
     *
1819
     * Returns the interest rate per period of an annuity.
1820
     * RATE is calculated by iteration and can have zero or more solutions.
1821
     * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
1822
     * RATE returns the #NUM! error value.
1823
     *
1824
     * Excel Function:
1825
     *        RATE(nper,pmt,pv[,fv[,type[,guess]]])
1826
     *
1827
     * @category Financial Functions
1828
     *
1829
     * @param float $nper The total number of payment periods in an annuity
1830
     * @param float $pmt The payment made each period and cannot change over the life
1831
     *                                    of the annuity.
1832
     *                                Typically, pmt includes principal and interest but no other
1833
     *                                    fees or taxes.
1834
     * @param float $pv The present value - the total amount that a series of future
1835
     *                                    payments is worth now
1836
     * @param float $fv The future value, or a cash balance you want to attain after
1837
     *                                    the last payment is made. If fv is omitted, it is assumed
1838
     *                                    to be 0 (the future value of a loan, for example, is 0).
1839
     * @param int $type A number 0 or 1 and indicates when payments are due:
1840
     *                                        0 or omitted    At the end of the period.
1841
     *                                        1                At the beginning of the period.
1842
     * @param float $guess Your guess for what the rate will be.
1843
     *                                    If you omit guess, it is assumed to be 10 percent.
1844
     *
1845
     * @return float
1846
     */
1847
    public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1)
1848
    {
1849
        $nper = (int) Functions::flattenSingleValue($nper);
1850
        $pmt = Functions::flattenSingleValue($pmt);
1851
        $pv = Functions::flattenSingleValue($pv);
1852
        $fv = ($fv === null) ? 0.0 : Functions::flattenSingleValue($fv);
0 ignored issues
show
introduced by
The condition $fv === null can never be true.
Loading history...
1853
        $type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type);
0 ignored issues
show
introduced by
The condition $type === null can never be true.
Loading history...
1854
        $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
0 ignored issues
show
introduced by
The condition $guess === null can never be true.
Loading history...
1855
1856
        $rate = $guess;
1857
        if (abs($rate) < self::FINANCIAL_PRECISION) {
1858
            $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...
1859
        } else {
1860
            $f = exp($nper * log(1 + $rate));
1861
            $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
1862
        }
1863
        $y0 = $pv + $pmt * $nper + $fv;
1864
        $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...
1865
1866
        // find root by secant method
1867
        $i = $x0 = 0.0;
1868
        $x1 = $rate;
1869
        while ((abs($y0 - $y1) > self::FINANCIAL_PRECISION) && ($i < self::FINANCIAL_MAX_ITERATIONS)) {
1870
            $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
1871
            $x0 = $x1;
1872
            $x1 = $rate;
1873
            if (($nper * abs($pmt)) > ($pv - $fv)) {
1874
                $x1 = abs($x1);
1875
            }
1876
            if (abs($rate) < self::FINANCIAL_PRECISION) {
1877
                $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
1878
            } else {
1879
                $f = exp($nper * log(1 + $rate));
1880
                $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
1881
            }
1882
1883
            $y0 = $y1;
1884
            $y1 = $y;
1885
            ++$i;
1886
        }
1887
1888
        return $rate;
1889
    }
1890
1891
    /**
1892
     * RECEIVED.
1893
     *
1894
     * Returns the price per $100 face value of a discounted security.
1895
     *
1896
     * @param mixed $settlement The security's settlement date.
1897
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1898
     * @param mixed $maturity The security's maturity date.
1899
     *                                The maturity date is the date when the security expires.
1900
     * @param int $investment The amount invested in the security
1901
     * @param int $discount The security's discount rate
1902
     * @param int $basis The type of day count to use.
1903
     *                                        0 or omitted    US (NASD) 30/360
1904
     *                                        1                Actual/actual
1905
     *                                        2                Actual/360
1906
     *                                        3                Actual/365
1907
     *                                        4                European 30/360
1908
     *
1909
     * @return float
1910
     */
1911
    public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0)
1912
    {
1913
        $settlement = Functions::flattenSingleValue($settlement);
1914
        $maturity = Functions::flattenSingleValue($maturity);
1915
        $investment = (float) Functions::flattenSingleValue($investment);
1916
        $discount = (float) Functions::flattenSingleValue($discount);
1917
        $basis = (int) Functions::flattenSingleValue($basis);
1918
1919
        //    Validate
1920
        if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) {
0 ignored issues
show
introduced by
The condition is_numeric($investment) ...) && is_numeric($basis) can never be false.
Loading history...
1921
            if (($investment <= 0) || ($discount <= 0)) {
1922
                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...
1923
            }
1924
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1925
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
1926
                //    return date error
1927
                return $daysBetweenSettlementAndMaturity;
1928
            }
1929
1930
            return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));
1931
        }
1932
1933
        return Functions::VALUE();
1934
    }
1935
1936
    /**
1937
     * SLN.
1938
     *
1939
     * Returns the straight-line depreciation of an asset for one period
1940
     *
1941
     * @param mixed $cost Initial cost of the asset
1942
     * @param mixed $salvage Value at the end of the depreciation
1943
     * @param mixed $life Number of periods over which the asset is depreciated
1944
     *
1945
     * @return float
1946
     */
1947
    public static function SLN($cost, $salvage, $life)
1948
    {
1949
        $cost = Functions::flattenSingleValue($cost);
1950
        $salvage = Functions::flattenSingleValue($salvage);
1951
        $life = Functions::flattenSingleValue($life);
1952
1953
        // Calculate
1954
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
1955
            if ($life < 0) {
1956
                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...
1957
            }
1958
1959
            return ($cost - $salvage) / $life;
1960
        }
1961
1962
        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...
1963
    }
1964
1965
    /**
1966
     * SYD.
1967
     *
1968
     * Returns the sum-of-years' digits depreciation of an asset for a specified period.
1969
     *
1970
     * @param mixed $cost Initial cost of the asset
1971
     * @param mixed $salvage Value at the end of the depreciation
1972
     * @param mixed $life Number of periods over which the asset is depreciated
1973
     * @param mixed $period Period
1974
     *
1975
     * @return float
1976
     */
1977
    public static function SYD($cost, $salvage, $life, $period)
1978
    {
1979
        $cost = Functions::flattenSingleValue($cost);
1980
        $salvage = Functions::flattenSingleValue($salvage);
1981
        $life = Functions::flattenSingleValue($life);
1982
        $period = Functions::flattenSingleValue($period);
1983
1984
        // Calculate
1985
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
1986
            if (($life < 1) || ($period > $life)) {
1987
                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...
1988
            }
1989
1990
            return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
1991
        }
1992
1993
        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...
1994
    }
1995
1996
    /**
1997
     * TBILLEQ.
1998
     *
1999
     * Returns the bond-equivalent yield for a Treasury bill.
2000
     *
2001
     * @param mixed $settlement The Treasury bill's settlement date.
2002
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2003
     * @param mixed $maturity The Treasury bill's maturity date.
2004
     *                                The maturity date is the date when the Treasury bill expires.
2005
     * @param int $discount The Treasury bill's discount rate
2006
     *
2007
     * @return float
2008
     */
2009
    public static function TBILLEQ($settlement, $maturity, $discount)
2010
    {
2011
        $settlement = Functions::flattenSingleValue($settlement);
2012
        $maturity = Functions::flattenSingleValue($maturity);
2013
        $discount = Functions::flattenSingleValue($discount);
2014
2015
        //    Use TBILLPRICE for validation
2016
        $testValue = self::TBILLPRICE($settlement, $maturity, $discount);
2017
        if (is_string($testValue)) {
0 ignored issues
show
introduced by
The condition is_string($testValue) can never be true.
Loading history...
2018
            return $testValue;
2019
        }
2020
2021
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2022
            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...
2023
        }
2024
2025
        if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2026
            ++$maturity;
2027
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2028
        } else {
2029
            $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
1 ignored issue
show
Bug introduced by
It seems like $maturity can also be of type DateTime; however, parameter $dateValue of PhpOffice\PhpSpreadsheet...ateTime::getDateValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2029
            $daysBetweenSettlementAndMaturity = (DateTime::getDateValue(/** @scrutinizer ignore-type */ $maturity) - DateTime::getDateValue($settlement));
Loading history...
2030
        }
2031
2032
        return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
2033
    }
2034
2035
    /**
2036
     * TBILLPRICE.
2037
     *
2038
     * Returns the yield for a Treasury bill.
2039
     *
2040
     * @param mixed $settlement The Treasury bill's settlement date.
2041
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2042
     * @param mixed $maturity The Treasury bill's maturity date.
2043
     *                                The maturity date is the date when the Treasury bill expires.
2044
     * @param int $discount The Treasury bill's discount rate
2045
     *
2046
     * @return float
2047
     */
2048
    public static function TBILLPRICE($settlement, $maturity, $discount)
2049
    {
2050
        $settlement = Functions::flattenSingleValue($settlement);
2051
        $maturity = Functions::flattenSingleValue($maturity);
2052
        $discount = Functions::flattenSingleValue($discount);
2053
2054
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2055
            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...
2056
        }
2057
2058
        //    Validate
2059
        if (is_numeric($discount)) {
2060
            if ($discount <= 0) {
2061
                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...
2062
            }
2063
2064
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2065
                ++$maturity;
2066
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2067
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
2068
                    //    return date error
2069
                    return $daysBetweenSettlementAndMaturity;
2070
                }
2071
            } else {
2072
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
1 ignored issue
show
Bug introduced by
It seems like $maturity can also be of type DateTime; however, parameter $dateValue of PhpOffice\PhpSpreadsheet...ateTime::getDateValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2072
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue(/** @scrutinizer ignore-type */ $maturity) - DateTime::getDateValue($settlement));
Loading history...
2073
            }
2074
2075
            if ($daysBetweenSettlementAndMaturity > 360) {
2076
                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...
2077
            }
2078
2079
            $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
2080
            if ($price <= 0) {
2081
                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...
2082
            }
2083
2084
            return $price;
2085
        }
2086
2087
        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...
2088
    }
2089
2090
    /**
2091
     * TBILLYIELD.
2092
     *
2093
     * Returns the yield for a Treasury bill.
2094
     *
2095
     * @param mixed $settlement The Treasury bill's settlement date.
2096
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2097
     * @param mixed $maturity The Treasury bill's maturity date.
2098
     *                                The maturity date is the date when the Treasury bill expires.
2099
     * @param int $price The Treasury bill's price per $100 face value
2100
     *
2101
     * @return float
2102
     */
2103
    public static function TBILLYIELD($settlement, $maturity, $price)
2104
    {
2105
        $settlement = Functions::flattenSingleValue($settlement);
2106
        $maturity = Functions::flattenSingleValue($maturity);
2107
        $price = Functions::flattenSingleValue($price);
2108
2109
        //    Validate
2110
        if (is_numeric($price)) {
2111
            if ($price <= 0) {
2112
                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...
2113
            }
2114
2115
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2116
                ++$maturity;
2117
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2118
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
2119
                    //    return date error
2120
                    return $daysBetweenSettlementAndMaturity;
2121
                }
2122
            } else {
2123
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2124
            }
2125
2126
            if ($daysBetweenSettlementAndMaturity > 360) {
2127
                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...
2128
            }
2129
2130
            return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
2131
        }
2132
2133
        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...
2134
    }
2135
2136
    public static function XIRR($values, $dates, $guess = 0.1)
2137
    {
2138
        if ((!is_array($values)) && (!is_array($dates))) {
2139
            return Functions::VALUE();
2140
        }
2141
        $values = Functions::flattenArray($values);
2142
        $dates = Functions::flattenArray($dates);
2143
        $guess = Functions::flattenSingleValue($guess);
2144
        if (count($values) != count($dates)) {
2145
            return Functions::NAN();
2146
        }
2147
2148
        // create an initial range, with a root somewhere between 0 and guess
2149
        $x1 = 0.0;
2150
        $x2 = $guess;
2151
        $f1 = self::XNPV($x1, $values, $dates);
2152
        $f2 = self::XNPV($x2, $values, $dates);
2153
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
2154
            if (($f1 * $f2) < 0.0) {
2155
                break;
2156
            } elseif (abs($f1) < abs($f2)) {
2157
                $f1 = self::XNPV($x1 += 1.6 * ($x1 - $x2), $values, $dates);
2158
            } else {
2159
                $f2 = self::XNPV($x2 += 1.6 * ($x2 - $x1), $values, $dates);
2160
            }
2161
        }
2162
        if (($f1 * $f2) > 0.0) {
2163
            return Functions::VALUE();
2164
        }
2165
2166
        $f = self::XNPV($x1, $values, $dates);
2167
        if ($f < 0.0) {
2168
            $rtb = $x1;
2169
            $dx = $x2 - $x1;
2170
        } else {
2171
            $rtb = $x2;
2172
            $dx = $x1 - $x2;
2173
        }
2174
2175
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
2176
            $dx *= 0.5;
2177
            $x_mid = $rtb + $dx;
2178
            $f_mid = self::XNPV($x_mid, $values, $dates);
2179
            if ($f_mid <= 0.0) {
2180
                $rtb = $x_mid;
2181
            }
2182
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
2183
                return $x_mid;
2184
            }
2185
        }
2186
2187
        return Functions::VALUE();
2188
    }
2189
2190
    /**
2191
     * XNPV.
2192
     *
2193
     * Returns the net present value for a schedule of cash flows that is not necessarily periodic.
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...
2194
     * To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
2195
     *
2196
     * Excel Function:
2197
     *        =XNPV(rate,values,dates)
2198
     *
2199
     * @param float $rate the discount rate to apply to the cash flows
2200
     * @param array of float    $values     A series of cash flows that corresponds to a schedule of payments in dates.
2201
     *                                         The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
2202
     *                                         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.
2203
     *                                         The series of values must contain at least one positive value and one negative value.
2204
     * @param array of mixed    $dates      A schedule of payment dates that corresponds to the cash flow payments.
2205
     *                                         The first payment date indicates the beginning of the schedule of payments.
2206
     *                                         All other dates must be later than this date, but they may occur in any order.
2207
     *
2208
     * @return float
2209
     */
2210
    public static function XNPV($rate, $values, $dates)
2211
    {
2212
        $rate = Functions::flattenSingleValue($rate);
2213
        if (!is_numeric($rate)) {
2214
            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...
2215
        }
2216
        if ((!is_array($values)) || (!is_array($dates))) {
2217
            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...
2218
        }
2219
        $values = Functions::flattenArray($values);
2220
        $dates = Functions::flattenArray($dates);
2221
        $valCount = count($values);
2222
        if ($valCount != count($dates)) {
2223
            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...
2224
        }
2225
        if ((min($values) > 0) || (max($values) < 0)) {
2226
            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...
2227
        }
2228
2229
        $xnpv = 0.0;
2230
        for ($i = 0; $i < $valCount; ++$i) {
2231
            if (!is_numeric($values[$i])) {
2232
                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...
2233
            }
2234
            $xnpv += $values[$i] / pow(1 + $rate, DateTime::DATEDIF($dates[0], $dates[$i], 'd') / 365);
2235
        }
2236
2237
        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...
2238
    }
2239
2240
    /**
2241
     * YIELDDISC.
2242
     *
2243
     * Returns the annual yield of a security that pays interest at maturity.
2244
     *
2245
     * @param mixed $settlement The security's settlement date.
2246
     *                                    The security's settlement date is the date after the issue date when the security is traded to the buyer.
2247
     * @param mixed $maturity The security's maturity date.
2248
     *                                    The maturity date is the date when the security expires.
2249
     * @param int $price The security's price per $100 face value
2250
     * @param int $redemption The security's redemption value per $100 face value
2251
     * @param int $basis The type of day count to use.
2252
     *                                        0 or omitted    US (NASD) 30/360
2253
     *                                        1                Actual/actual
2254
     *                                        2                Actual/360
2255
     *                                        3                Actual/365
2256
     *                                        4                European 30/360
2257
     *
2258
     * @return float
2259
     */
2260
    public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0)
2261
    {
2262
        $settlement = Functions::flattenSingleValue($settlement);
2263
        $maturity = Functions::flattenSingleValue($maturity);
2264
        $price = Functions::flattenSingleValue($price);
2265
        $redemption = Functions::flattenSingleValue($redemption);
2266
        $basis = (int) Functions::flattenSingleValue($basis);
2267
2268
        //    Validate
2269
        if (is_numeric($price) && is_numeric($redemption)) {
2270
            if (($price <= 0) || ($redemption <= 0)) {
2271
                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...
2272
            }
2273
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2274
            if (!is_numeric($daysPerYear)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysPerYear) can never be true.
Loading history...
2275
                return $daysPerYear;
2276
            }
2277
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2278
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
2279
                //    return date error
2280
                return $daysBetweenSettlementAndMaturity;
2281
            }
2282
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2283
2284
            return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
2285
        }
2286
2287
        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...
2288
    }
2289
2290
    /**
2291
     * YIELDMAT.
2292
     *
2293
     * Returns the annual yield of a security that pays interest at maturity.
2294
     *
2295
     * @param mixed $settlement The security's settlement date.
2296
     *                                   The security's settlement date is the date after the issue date when the security is traded to the buyer.
2297
     * @param mixed $maturity The security's maturity date.
2298
     *                                   The maturity date is the date when the security expires.
2299
     * @param mixed $issue The security's issue date
2300
     * @param int $rate The security's interest rate at date of issue
2301
     * @param int $price The security's price per $100 face value
2302
     * @param int $basis The type of day count to use.
2303
     *                                        0 or omitted    US (NASD) 30/360
2304
     *                                        1                Actual/actual
2305
     *                                        2                Actual/360
2306
     *                                        3                Actual/365
2307
     *                                        4                European 30/360
2308
     *
2309
     * @return float
2310
     */
2311
    public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0)
2312
    {
2313
        $settlement = Functions::flattenSingleValue($settlement);
2314
        $maturity = Functions::flattenSingleValue($maturity);
2315
        $issue = Functions::flattenSingleValue($issue);
2316
        $rate = Functions::flattenSingleValue($rate);
2317
        $price = Functions::flattenSingleValue($price);
2318
        $basis = (int) Functions::flattenSingleValue($basis);
2319
2320
        //    Validate
2321
        if (is_numeric($rate) && is_numeric($price)) {
2322
            if (($rate <= 0) || ($price <= 0)) {
2323
                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...
2324
            }
2325
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2326
            if (!is_numeric($daysPerYear)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysPerYear) can never be true.
Loading history...
2327
                return $daysPerYear;
2328
            }
2329
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
2330
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenIssueAndSettlement) can never be true.
Loading history...
2331
                //    return date error
2332
                return $daysBetweenIssueAndSettlement;
2333
            }
2334
            $daysBetweenIssueAndSettlement *= $daysPerYear;
2335
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
2336
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenIssueAndMaturity) can never be true.
Loading history...
2337
                //    return date error
2338
                return $daysBetweenIssueAndMaturity;
2339
            }
2340
            $daysBetweenIssueAndMaturity *= $daysPerYear;
2341
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2342
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition ! is_numeric($daysBetweenSettlementAndMaturity) can never be true.
Loading history...
2343
                //    return date error
2344
                return $daysBetweenSettlementAndMaturity;
2345
            }
2346
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2347
2348
            return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
2349
                   (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
2350
                   ($daysPerYear / $daysBetweenSettlementAndMaturity);
2351
        }
2352
2353
        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...
2354
    }
2355
}
2356