Completed
Push — develop ( e95e4d...4fd8e7 )
by Adrien
61:55
created

Financial::daysPerYear()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.392

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 7
nop 2
dl 0
loc 23
ccs 12
cts 15
cp 0.8
crap 7.392
rs 6.7272
c 0
b 0
f 0
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($testDate)
23
    {
24 12
        return $testDate->format('d') == $testDate->format('t');
0 ignored issues
show
Bug introduced by
The method format() does not seem to exist on object<PhpOffice\PhpSpre...t\Calculation\DateTime>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
25
    }
26
27
    /**
28
     * isFirstDayOfMonth.
29
     *
30
     * Returns a boolean TRUE/FALSE indicating if this date is the first date of the month
31
     *
32
     * @param DateTime $testDate The date for testing
33
     *
34
     * @return bool
35
     */
36
    private static function isFirstDayOfMonth($testDate)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
37
    {
38
        return $testDate->format('d') == 1;
0 ignored issues
show
Bug introduced by
The method format() does not seem to exist on object<PhpOffice\PhpSpre...t\Calculation\DateTime>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
39
    }
40
41 12
    private static function couponFirstPeriodDate($settlement, $maturity, $frequency, $next)
42
    {
43 12
        $months = 12 / $frequency;
44
45 12
        $result = Date::excelToDateTimeObject($maturity);
46 12
        $eom = self::isLastDayOfMonth($result);
0 ignored issues
show
Documentation introduced by
$result is of type object<DateTime>, but the function expects a object<PhpOffice\PhpSpre...t\Calculation\DateTime>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
47
48 12
        while ($settlement < Date::PHPToExcel($result)) {
49 12
            $result->modify('-' . $months . ' months');
50
        }
51 12
        if ($next) {
52 8
            $result->modify('+' . $months . ' months');
53
        }
54
55 12
        if ($eom) {
56 1
            $result->modify('-1 day');
57
        }
58
59 12
        return Date::PHPToExcel($result);
60
    }
61
62 19
    private static function isValidFrequency($frequency)
63
    {
64 19
        if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) {
65 13
            return true;
66
        }
67 6
        if ((Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) &&
68 6
            (($frequency == 6) || ($frequency == 12))) {
69
            return true;
70
        }
71
72 6
        return false;
73
    }
74
75
    /**
76
     * daysPerYear.
77
     *
78
     * Returns the number of days in a specified year, as defined by the "basis" value
79
     *
80
     * @param int $year The year against which we're testing
81
     * @param int $basis The type of day count:
82
     *                                    0 or omitted US (NASD)    360
83
     *                                    1                        Actual (365 or 366 in a leap year)
84
     *                                    2                        360
85
     *                                    3                        365
86
     *                                    4                        European 360
87
     *
88
     * @return int
89
     */
90 4
    private static function daysPerYear($year, $basis = 0)
91
    {
92
        switch ($basis) {
93 4
            case 0:
94 2
            case 2:
95 2
            case 4:
96 2
                $daysPerYear = 360;
97 2
98 2
                break;
99
            case 3:
100
                $daysPerYear = 365;
101 2
102 2
                break;
103 2
            case 1:
104
                $daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365;
105
106
                break;
107
            default:
108 4
                return Functions::NAN();
109
        }
110
111 20
        return $daysPerYear;
112
    }
113 20
114 20
    private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
115 20
    {
116 20
        $pmt = self::PMT($rate, $nper, $pv, $fv, $type);
117 20
        $capital = $pv;
118 20
        for ($i = 1; $i <= $per; ++$i) {
119
            $interest = ($type && $i == 1) ? 0 : -$capital * $rate;
120
            $principal = $pmt - $interest;
121 20
            $capital += $principal;
122
        }
123
124
        return [$interest, $principal];
0 ignored issues
show
Bug introduced by
The variable $interest does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $principal does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
125
    }
126
127
    /**
128
     * ACCRINT.
129
     *
130
     * Returns the accrued interest for a security that pays periodic interest.
131
     *
132
     * Excel Function:
133
     *        ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis])
134
     *
135
     * @category Financial Functions
136
     *
137
     * @param mixed $issue the security's issue date
138
     * @param mixed $firstinterest the security's first interest date
139
     * @param mixed $settlement The security's settlement date.
140
     *                                    The security settlement date is the date after the issue date
141
     *                                    when the security is traded to the buyer.
142
     * @param float $rate the security's annual coupon rate
143
     * @param float $par The security's par value.
144
     *                                    If you omit par, ACCRINT uses $1,000.
145
     * @param int $frequency the number of coupon payments per year.
146
     *                                    Valid frequency values are:
147
     *                                        1    Annual
148
     *                                        2    Semi-Annual
149
     *                                        4    Quarterly
150
     *                                    If working in Gnumeric Mode, the following frequency options are
151
     *                                    also available
152
     *                                        6    Bimonthly
153
     *                                        12    Monthly
154
     * @param int $basis The type of day count to use.
155
     *                                        0 or omitted    US (NASD) 30/360
156
     *                                        1                Actual/actual
157
     *                                        2                Actual/360
158
     *                                        3                Actual/365
159
     *                                        4                European 30/360
160 7
     *
161
     * @return float
162 7
     */
163 7
    public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0)
164 7
    {
165 7
        $issue = Functions::flattenSingleValue($issue);
166 7
        $firstinterest = Functions::flattenSingleValue($firstinterest);
0 ignored issues
show
Unused Code introduced by
$firstinterest is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
167 7
        $settlement = Functions::flattenSingleValue($settlement);
168 7
        $rate = Functions::flattenSingleValue($rate);
169
        $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
170
        $frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency);
0 ignored issues
show
Unused Code introduced by
$frequency is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
171 7
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
172 6
173 6
        //    Validate
174 6 View Code Duplication
        if ((is_numeric($rate)) && (is_numeric($par))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175 1
            $rate = (float) $rate;
176
            $par = (float) $par;
177 5
            if (($rate <= 0) || ($par <= 0)) {
178 5
                return Functions::NAN();
179
            }
180 2
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
181
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
182
                //    return date error
183 3
                return $daysBetweenIssueAndSettlement;
184
            }
185
186 1
            return $par * $rate * $daysBetweenIssueAndSettlement;
187
        }
188
189
        return Functions::VALUE();
190
    }
191
192
    /**
193
     * ACCRINTM.
194
     *
195
     * Returns the accrued interest for a security that pays interest at maturity.
196
     *
197
     * Excel Function:
198
     *        ACCRINTM(issue,settlement,rate[,par[,basis]])
199
     *
200
     * @category Financial Functions
201
     *
202
     * @param mixed issue The security's issue date
203
     * @param mixed settlement The security's settlement (or maturity) date
204
     * @param float rate The security's annual coupon rate
205
     * @param float par The security's par value.
206
     *                                    If you omit par, ACCRINT uses $1,000.
207
     * @param int basis The type of day count to use.
208
     *                                        0 or omitted    US (NASD) 30/360
209
     *                                        1                Actual/actual
210
     *                                        2                Actual/360
211
     *                                        3                Actual/365
212
     *                                        4                European 30/360
213
     * @param mixed $issue
214
     * @param mixed $settlement
215
     * @param mixed $rate
216
     * @param mixed $par
217
     * @param mixed $basis
218 5
     *
219
     * @return float
220 5
     */
221 5
    public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0)
222 5
    {
223 5
        $issue = Functions::flattenSingleValue($issue);
224 5
        $settlement = Functions::flattenSingleValue($settlement);
225
        $rate = Functions::flattenSingleValue($rate);
226
        $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
227 5
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
228 4
229 4
        //    Validate
230 4 View Code Duplication
        if ((is_numeric($rate)) && (is_numeric($par))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231 1
            $rate = (float) $rate;
232
            $par = (float) $par;
233 3
            if (($rate <= 0) || ($par <= 0)) {
234 3
                return Functions::NAN();
235
            }
236 1
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
237
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
238
                //    return date error
239 2
                return $daysBetweenIssueAndSettlement;
240
            }
241
242 1
            return $par * $rate * $daysBetweenIssueAndSettlement;
243
        }
244
245
        return Functions::VALUE();
246
    }
247
248
    /**
249
     * AMORDEGRC.
250
     *
251
     * Returns the depreciation for each accounting period.
252
     * This function is provided for the French accounting system. If an asset is purchased in
253
     * the middle of the accounting period, the prorated depreciation is taken into account.
254
     * The function is similar to AMORLINC, except that a depreciation coefficient is applied in
255
     * the calculation depending on the life of the assets.
256
     * This function will return the depreciation until the last period of the life of the assets
257
     * or until the cumulated value of depreciation is greater than the cost of the assets minus
258
     * the salvage value.
259
     *
260
     * Excel Function:
261
     *        AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
262
     *
263
     * @category Financial Functions
264
     *
265
     * @param float cost The cost of the asset
266
     * @param mixed purchased Date of the purchase of the asset
267
     * @param mixed firstPeriod Date of the end of the first period
268
     * @param mixed salvage The salvage value at the end of the life of the asset
269
     * @param float period The period
270
     * @param float rate Rate of depreciation
271
     * @param int basis The type of day count to use.
272
     *                                        0 or omitted    US (NASD) 30/360
273
     *                                        1                Actual/actual
274
     *                                        2                Actual/360
275
     *                                        3                Actual/365
276
     *                                        4                European 30/360
277
     * @param mixed $cost
278
     * @param mixed $purchased
279
     * @param mixed $firstPeriod
280
     * @param mixed $salvage
281
     * @param mixed $period
282
     * @param mixed $rate
283
     * @param mixed $basis
284 2
     *
285
     * @return float
286 2
     */
287 2
    public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
288 2
    {
289 2
        $cost = Functions::flattenSingleValue($cost);
290 2
        $purchased = Functions::flattenSingleValue($purchased);
291 2
        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
292 2
        $salvage = Functions::flattenSingleValue($salvage);
293
        $period = floor(Functions::flattenSingleValue($period));
294
        $rate = Functions::flattenSingleValue($rate);
295
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
296
297
        //    The depreciation coefficients are:
298
        //    Life of assets (1/rate)        Depreciation coefficient
299
        //    Less than 3 years            1
300 2
        //    Between 3 and 4 years        1.5
301 2
        //    Between 5 and 6 years        2
302
        //    More than 6 years            2.5
303 2
        $fUsePer = 1.0 / $rate;
304
        if ($fUsePer < 3.0) {
305 2
            $amortiseCoeff = 1.0;
306 1
        } elseif ($fUsePer < 5.0) {
307
            $amortiseCoeff = 1.5;
308 1
        } elseif ($fUsePer <= 6.0) {
309
            $amortiseCoeff = 2.0;
310
        } else {
311 2
            $amortiseCoeff = 2.5;
312 2
        }
313 2
314 2
        $rate *= $amortiseCoeff;
315
        $fNRate = round(DateTime::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost, 0);
316 2
        $cost -= $fNRate;
317 2
        $fRest = $cost - $salvage;
318 2
319
        for ($n = 0; $n < $period; ++$n) {
320 2
            $fNRate = round($rate * $cost, 0);
321
            $fRest -= $fNRate;
322
323
            if ($fRest < 0.0) {
324
                switch ($period - $n) {
325
                    case 0:
326
                    case 1:
327
                        return round($cost * 0.5, 0);
328
                    default:
329 2
                        return 0.0;
330
                }
331
            }
332 2
            $cost -= $fNRate;
333
        }
334
335
        return $fNRate;
336
    }
337
338
    /**
339
     * AMORLINC.
340
     *
341
     * Returns the depreciation for each accounting period.
342
     * This function is provided for the French accounting system. If an asset is purchased in
343
     * the middle of the accounting period, the prorated depreciation is taken into account.
344
     *
345
     * Excel Function:
346
     *        AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
347
     *
348
     * @category Financial Functions
349
     *
350
     * @param float cost The cost of the asset
351
     * @param mixed purchased Date of the purchase of the asset
352
     * @param mixed firstPeriod Date of the end of the first period
353
     * @param mixed salvage The salvage value at the end of the life of the asset
354
     * @param float period The period
355
     * @param float rate Rate of depreciation
356
     * @param int basis The type of day count to use.
357
     *                                        0 or omitted    US (NASD) 30/360
358
     *                                        1                Actual/actual
359
     *                                        2                Actual/360
360
     *                                        3                Actual/365
361
     *                                        4                European 30/360
362
     * @param mixed $cost
363
     * @param mixed $purchased
364
     * @param mixed $firstPeriod
365
     * @param mixed $salvage
366
     * @param mixed $period
367
     * @param mixed $rate
368
     * @param mixed $basis
369 2
     *
370
     * @return float
371 2
     */
372 2
    public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
373 2
    {
374 2
        $cost = Functions::flattenSingleValue($cost);
375 2
        $purchased = Functions::flattenSingleValue($purchased);
376 2
        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
377 2
        $salvage = Functions::flattenSingleValue($salvage);
378
        $period = Functions::flattenSingleValue($period);
379 2
        $rate = Functions::flattenSingleValue($rate);
380 2
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
381
382 2
        $fOneRate = $cost * $rate;
383 2
        $fCostDelta = $cost - $salvage;
384
        //    Note, quirky variation for leap years on the YEARFRAC for this function
385 2
        $purchasedYear = DateTime::YEAR($purchased);
386 1
        $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
387
388
        if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($purchasedYear))) {
389 2
            $yearFrac *= 365 / 366;
390 2
        }
391
392 2
        $f0Rate = $yearFrac * $rate * $cost;
393
        $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
394 2
395 2
        if ($period == 0) {
396
            return $f0Rate;
397
        } elseif ($period <= $nNumOfFullPeriods) {
398
            return $fOneRate;
399
        } elseif ($period == ($nNumOfFullPeriods + 1)) {
400
            return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
401
        }
402
403
        return 0.0;
404
    }
405
406
    /**
407
     * COUPDAYBS.
408
     *
409
     * Returns the number of days from the beginning of the coupon period to the settlement date.
410
     *
411
     * Excel Function:
412
     *        COUPDAYBS(settlement,maturity,frequency[,basis])
413
     *
414
     * @category Financial Functions
415
     *
416
     * @param mixed settlement The security's settlement date.
417
     *                                The security settlement date is the date after the issue
418
     *                                date when the security is traded to the buyer.
419
     * @param mixed maturity The security's maturity date.
420
     *                                The maturity date is the date when the security expires.
421
     * @param int frequency the number of coupon payments per year.
422
     *                                    Valid frequency values are:
423
     *                                        1    Annual
424
     *                                        2    Semi-Annual
425
     *                                        4    Quarterly
426
     *                                    If working in Gnumeric Mode, the following frequency options are
427
     *                                    also available
428
     *                                        6    Bimonthly
429
     *                                        12    Monthly
430
     * @param int basis The type of day count to use.
431
     *                                        0 or omitted    US (NASD) 30/360
432
     *                                        1                Actual/actual
433
     *                                        2                Actual/360
434
     *                                        3                Actual/365
435
     *                                        4                European 30/360
436
     * @param mixed $settlement
437
     * @param mixed $maturity
438
     * @param mixed $frequency
439
     * @param mixed $basis
440 5
     *
441
     * @return float
442 5
     */
443 5 View Code Duplication
    public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
444 5
    {
445 5
        $settlement = Functions::flattenSingleValue($settlement);
446
        $maturity = Functions::flattenSingleValue($maturity);
447 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
448 1
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
449
450 4
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
451 1
            return Functions::VALUE();
452
        }
453
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
454 3
            return Functions::VALUE();
455 3
        }
456 3
457 1
        if (($settlement > $maturity) ||
458
            (!self::isValidFrequency($frequency)) ||
459
            (($basis < 0) || ($basis > 4))) {
460 2
            return Functions::NAN();
461 2
        }
462
463 2
        $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
464
        $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
465
466
        return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear;
0 ignored issues
show
Documentation introduced by
$prev is of type double|false, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
467
    }
468
469
    /**
470
     * COUPDAYS.
471
     *
472
     * Returns the number of days in the coupon period that contains the settlement date.
473
     *
474
     * Excel Function:
475
     *        COUPDAYS(settlement,maturity,frequency[,basis])
476
     *
477
     * @category Financial Functions
478
     *
479
     * @param mixed settlement The security's settlement date.
480
     *                                The security settlement date is the date after the issue
481
     *                                date when the security is traded to the buyer.
482
     * @param mixed maturity The security's maturity date.
483
     *                                The maturity date is the date when the security expires.
484
     * @param mixed frequency the number of coupon payments per year.
485
     *                                    Valid frequency values are:
486
     *                                        1    Annual
487
     *                                        2    Semi-Annual
488
     *                                        4    Quarterly
489
     *                                    If working in Gnumeric Mode, the following frequency options are
490
     *                                    also available
491
     *                                        6    Bimonthly
492
     *                                        12    Monthly
493
     * @param int basis The type of day count to use.
494
     *                                        0 or omitted    US (NASD) 30/360
495
     *                                        1                Actual/actual
496
     *                                        2                Actual/360
497
     *                                        3                Actual/365
498
     *                                        4                European 30/360
499
     * @param int $frequency
500
     * @param mixed $settlement
501
     * @param mixed $maturity
502
     * @param mixed $basis
503 5
     *
504
     * @return float
505 5
     */
506 5
    public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0)
507 5
    {
508 5
        $settlement = Functions::flattenSingleValue($settlement);
509
        $maturity = Functions::flattenSingleValue($maturity);
510 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
511 1
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
512
513 4
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
514 1
            return Functions::VALUE();
515
        }
516
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
517 3
            return Functions::VALUE();
518 3
        }
519 3
520 1
        if (($settlement > $maturity) ||
521
            (!self::isValidFrequency($frequency)) ||
522
            (($basis < 0) || ($basis > 4))) {
523
            return Functions::NAN();
524 2
        }
525
526
        switch ($basis) {
527 2
            case 3:
528
                // Actual/365
529 1
                return 365 / $frequency;
530
            case 1:
531
                // Actual/actual
532
                if ($frequency == 1) {
533
                    $daysPerYear = self::daysPerYear(DateTime::YEAR($maturity), $basis);
534 1
535 1
                    return $daysPerYear / $frequency;
536
                }
537 1
                $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
538
                $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
539
540 1
                return $next - $prev;
541
            default:
542
                // 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...
543
                return 360 / $frequency;
544
        }
545
546
        return Functions::VALUE();
0 ignored issues
show
Unused Code introduced by
return \PhpOffice\PhpSpr...ion\Functions::VALUE(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
547
    }
548
549
    /**
550
     * COUPDAYSNC.
551
     *
552
     * Returns the number of days from the settlement date to the next coupon date.
553
     *
554
     * Excel Function:
555
     *        COUPDAYSNC(settlement,maturity,frequency[,basis])
556
     *
557
     * @category Financial Functions
558
     *
559
     * @param mixed settlement The security's settlement date.
560
     *                                The security settlement date is the date after the issue
561
     *                                date when the security is traded to the buyer.
562
     * @param mixed maturity The security's maturity date.
563
     *                                The maturity date is the date when the security expires.
564
     * @param mixed frequency the number of coupon payments per year.
565
     *                                    Valid frequency values are:
566
     *                                        1    Annual
567
     *                                        2    Semi-Annual
568
     *                                        4    Quarterly
569
     *                                    If working in Gnumeric Mode, the following frequency options are
570
     *                                    also available
571
     *                                        6    Bimonthly
572
     *                                        12    Monthly
573
     * @param int basis The type of day count to use.
574
     *                                        0 or omitted    US (NASD) 30/360
575
     *                                        1                Actual/actual
576
     *                                        2                Actual/360
577
     *                                        3                Actual/365
578
     *                                        4                European 30/360
579
     * @param mixed $settlement
580
     * @param mixed $maturity
581
     * @param mixed $frequency
582
     * @param mixed $basis
583 5
     *
584
     * @return float
585 5
     */
586 5 View Code Duplication
    public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
587 5
    {
588 5
        $settlement = Functions::flattenSingleValue($settlement);
589
        $maturity = Functions::flattenSingleValue($maturity);
590 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
591 1
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
592
593 4
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
594 1
            return Functions::VALUE();
595
        }
596
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
597 3
            return Functions::VALUE();
598 3
        }
599 3
600 1
        if (($settlement > $maturity) ||
601
            (!self::isValidFrequency($frequency)) ||
602
            (($basis < 0) || ($basis > 4))) {
603 2
            return Functions::NAN();
604 2
        }
605
606 2
        $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
607
        $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
608
609
        return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear;
0 ignored issues
show
Documentation introduced by
$next is of type double|false, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
610
    }
611
612
    /**
613
     * COUPNCD.
614
     *
615
     * Returns the next coupon date after the settlement date.
616
     *
617
     * Excel Function:
618
     *        COUPNCD(settlement,maturity,frequency[,basis])
619
     *
620
     * @category Financial Functions
621
     *
622
     * @param mixed settlement The security's settlement date.
623
     *                                The security settlement date is the date after the issue
624
     *                                date when the security is traded to the buyer.
625
     * @param mixed maturity The security's maturity date.
626
     *                                The maturity date is the date when the security expires.
627
     * @param mixed frequency the number of coupon payments per year.
628
     *                                    Valid frequency values are:
629
     *                                        1    Annual
630
     *                                        2    Semi-Annual
631
     *                                        4    Quarterly
632
     *                                    If working in Gnumeric Mode, the following frequency options are
633
     *                                    also available
634
     *                                        6    Bimonthly
635
     *                                        12    Monthly
636
     * @param int basis The type of day count to use.
637
     *                                        0 or omitted    US (NASD) 30/360
638
     *                                        1                Actual/actual
639
     *                                        2                Actual/360
640
     *                                        3                Actual/365
641
     *                                        4                European 30/360
642
     * @param mixed $settlement
643
     * @param mixed $maturity
644
     * @param mixed $frequency
645
     * @param mixed $basis
646
     *
647 5
     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
648
     *                        depending on the value of the ReturnDateType flag
649 5
     */
650 5 View Code Duplication
    public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
651 5
    {
652 5
        $settlement = Functions::flattenSingleValue($settlement);
653
        $maturity = Functions::flattenSingleValue($maturity);
654 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
655 1
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
656
657 4
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
658 1
            return Functions::VALUE();
659
        }
660
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
661 3
            return Functions::VALUE();
662 3
        }
663 3
664 1
        if (($settlement > $maturity) ||
665
            (!self::isValidFrequency($frequency)) ||
666
            (($basis < 0) || ($basis > 4))) {
667 2
            return Functions::NAN();
668
        }
669
670
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
671
    }
672
673
    /**
674
     * COUPNUM.
675
     *
676
     * Returns the number of coupons payable between the settlement date and maturity date,
677
     * rounded up to the nearest whole coupon.
678
     *
679
     * Excel Function:
680
     *        COUPNUM(settlement,maturity,frequency[,basis])
681
     *
682
     * @category Financial Functions
683
     *
684
     * @param mixed settlement The security's settlement date.
685
     *                                The security settlement date is the date after the issue
686
     *                                date when the security is traded to the buyer.
687
     * @param mixed maturity The security's maturity date.
688
     *                                The maturity date is the date when the security expires.
689
     * @param mixed frequency the number of coupon payments per year.
690
     *                                    Valid frequency values are:
691
     *                                        1    Annual
692
     *                                        2    Semi-Annual
693
     *                                        4    Quarterly
694
     *                                    If working in Gnumeric Mode, the following frequency options are
695
     *                                    also available
696
     *                                        6    Bimonthly
697
     *                                        12    Monthly
698
     * @param int basis The type of day count to use.
699
     *                                        0 or omitted    US (NASD) 30/360
700
     *                                        1                Actual/actual
701
     *                                        2                Actual/360
702
     *                                        3                Actual/365
703
     *                                        4                European 30/360
704
     * @param mixed $settlement
705
     * @param mixed $maturity
706
     * @param mixed $frequency
707
     * @param mixed $basis
708 6
     *
709
     * @return int
710 6
     */
711 6
    public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0)
712 6
    {
713 6
        $settlement = Functions::flattenSingleValue($settlement);
714
        $maturity = Functions::flattenSingleValue($maturity);
715 6
        $frequency = (int) Functions::flattenSingleValue($frequency);
716 1
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
717
718 5
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
719 1
            return Functions::VALUE();
720
        }
721
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
722 4
            return Functions::VALUE();
723 4
        }
724 4
725 1
        if (($settlement > $maturity) ||
726
            (!self::isValidFrequency($frequency)) ||
727
            (($basis < 0) || ($basis > 4))) {
728 3
            return Functions::NAN();
729 3
        }
730
731
        $settlement = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
732 3
        $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * 365;
0 ignored issues
show
Documentation introduced by
$settlement is of type double|false, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
733 1
734 2
        switch ($frequency) {
735 1
            case 1: // annual payments
736 1
                return ceil($daysBetweenSettlementAndMaturity / 360);
737 1
            case 2: // half-yearly
738
                return ceil($daysBetweenSettlementAndMaturity / 180);
739
            case 4: // quarterly
740
                return ceil($daysBetweenSettlementAndMaturity / 90);
741
            case 6: // bimonthly
742
                return ceil($daysBetweenSettlementAndMaturity / 60);
743
            case 12: // monthly
744
                return ceil($daysBetweenSettlementAndMaturity / 30);
745
        }
746
747
        return Functions::VALUE();
748
    }
749
750
    /**
751
     * COUPPCD.
752
     *
753
     * Returns the previous coupon date before the settlement date.
754
     *
755
     * Excel Function:
756
     *        COUPPCD(settlement,maturity,frequency[,basis])
757
     *
758
     * @category Financial Functions
759
     *
760
     * @param mixed settlement The security's settlement date.
761
     *                                The security settlement date is the date after the issue
762
     *                                date when the security is traded to the buyer.
763
     * @param mixed maturity The security's maturity date.
764
     *                                The maturity date is the date when the security expires.
765
     * @param mixed frequency the number of coupon payments per year.
766
     *                                    Valid frequency values are:
767
     *                                        1    Annual
768
     *                                        2    Semi-Annual
769
     *                                        4    Quarterly
770
     *                                    If working in Gnumeric Mode, the following frequency options are
771
     *                                    also available
772
     *                                        6    Bimonthly
773
     *                                        12    Monthly
774
     * @param int basis The type of day count to use.
775
     *                                        0 or omitted    US (NASD) 30/360
776
     *                                        1                Actual/actual
777
     *                                        2                Actual/360
778
     *                                        3                Actual/365
779
     *                                        4                European 30/360
780
     * @param mixed $settlement
781
     * @param mixed $maturity
782
     * @param mixed $frequency
783
     * @param mixed $basis
784
     *
785 5
     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
786
     *                        depending on the value of the ReturnDateType flag
787 5
     */
788 5 View Code Duplication
    public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
789 5
    {
790 5
        $settlement = Functions::flattenSingleValue($settlement);
791
        $maturity = Functions::flattenSingleValue($maturity);
792 5
        $frequency = (int) Functions::flattenSingleValue($frequency);
793 1
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
794
795 4
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
796 1
            return Functions::VALUE();
797
        }
798
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
799 3
            return Functions::VALUE();
800 3
        }
801 3
802 1
        if (($settlement > $maturity) ||
803
            (!self::isValidFrequency($frequency)) ||
804
            (($basis < 0) || ($basis > 4))) {
805 2
            return Functions::NAN();
806
        }
807
808
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
809
    }
810
811
    /**
812
     * CUMIPMT.
813
     *
814
     * Returns the cumulative interest paid on a loan between the start and end periods.
815
     *
816
     * Excel Function:
817
     *        CUMIPMT(rate,nper,pv,start,end[,type])
818
     *
819
     * @category Financial Functions
820
     *
821
     * @param float $rate The Interest rate
822
     * @param int $nper The total number of payment periods
823
     * @param float $pv Present Value
824
     * @param int $start The first period in the calculation.
825
     *                            Payment periods are numbered beginning with 1.
826
     * @param int $end the last period in the calculation
827
     * @param int $type A number 0 or 1 and indicates when payments are due:
828
     *                                0 or omitted    At the end of the period.
829
     *                                1                At the beginning of the period.
830 9
     *
831
     * @return float
832 9
     */
833 9 View Code Duplication
    public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
834 9
    {
835 9
        $rate = Functions::flattenSingleValue($rate);
836 9
        $nper = (int) Functions::flattenSingleValue($nper);
837 9
        $pv = Functions::flattenSingleValue($pv);
838
        $start = (int) Functions::flattenSingleValue($start);
839
        $end = (int) Functions::flattenSingleValue($end);
840 9
        $type = (int) Functions::flattenSingleValue($type);
841 1
842
        // Validate parameters
843 8
        if ($type != 0 && $type != 1) {
844 1
            return Functions::NAN();
845
        }
846
        if ($start < 1 || $start > $end) {
847
            return Functions::VALUE();
848 7
        }
849 7
850 7
        // Calculate
851
        $interest = 0;
852
        for ($per = $start; $per <= $end; ++$per) {
853 7
            $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type);
854
        }
855
856
        return $interest;
857
    }
858
859
    /**
860
     * CUMPRINC.
861
     *
862
     * Returns the cumulative principal paid on a loan between the start and end periods.
863
     *
864
     * Excel Function:
865
     *        CUMPRINC(rate,nper,pv,start,end[,type])
866
     *
867
     * @category Financial Functions
868
     *
869
     * @param float $rate The Interest rate
870
     * @param int $nper The total number of payment periods
871
     * @param float $pv Present Value
872
     * @param int $start The first period in the calculation.
873
     *                            Payment periods are numbered beginning with 1.
874
     * @param int $end the last period in the calculation
875
     * @param int $type A number 0 or 1 and indicates when payments are due:
876
     *                                0 or omitted    At the end of the period.
877
     *                                1                At the beginning of the period.
878 9
     *
879
     * @return float
880 9
     */
881 9 View Code Duplication
    public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
882 9
    {
883 9
        $rate = Functions::flattenSingleValue($rate);
884 9
        $nper = (int) Functions::flattenSingleValue($nper);
885 9
        $pv = Functions::flattenSingleValue($pv);
886
        $start = (int) Functions::flattenSingleValue($start);
887
        $end = (int) Functions::flattenSingleValue($end);
888 9
        $type = (int) Functions::flattenSingleValue($type);
889 1
890
        // Validate parameters
891 8
        if ($type != 0 && $type != 1) {
892 1
            return Functions::NAN();
893
        }
894
        if ($start < 1 || $start > $end) {
895
            return Functions::VALUE();
896 7
        }
897 7
898 7
        // Calculate
899
        $principal = 0;
900
        for ($per = $start; $per <= $end; ++$per) {
901 7
            $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type);
902
        }
903
904
        return $principal;
905
    }
906
907
    /**
908
     * DB.
909
     *
910
     * Returns the depreciation of an asset for a specified period using the
911
     * fixed-declining balance method.
912
     * This form of depreciation is used if you want to get a higher depreciation value
913
     * at the beginning of the depreciation (as opposed to linear depreciation). The
914
     * depreciation value is reduced with every depreciation period by the depreciation
915
     * already deducted from the initial cost.
916
     *
917
     * Excel Function:
918
     *        DB(cost,salvage,life,period[,month])
919
     *
920
     * @category Financial Functions
921
     *
922
     * @param float cost Initial cost of the asset
923
     * @param float salvage Value at the end of the depreciation.
924
     *                                (Sometimes called the salvage value of the asset)
925
     * @param int life Number of periods over which the asset is depreciated.
926
     *                                (Sometimes called the useful life of the asset)
927
     * @param int period The period for which you want to calculate the
928
     *                                depreciation. Period must use the same units as life.
929
     * @param int month Number of months in the first year. If month is omitted,
930
     *                                it defaults to 12.
931
     * @param mixed $cost
932
     * @param mixed $salvage
933
     * @param mixed $life
934
     * @param mixed $period
935
     * @param mixed $month
936 16
     *
937
     * @return float
938 16
     */
939 16
    public static function DB($cost, $salvage, $life, $period, $month = 12)
940 16
    {
941 16
        $cost = Functions::flattenSingleValue($cost);
942 16
        $salvage = Functions::flattenSingleValue($salvage);
943
        $life = Functions::flattenSingleValue($life);
944
        $period = Functions::flattenSingleValue($period);
945 16
        $month = Functions::flattenSingleValue($month);
946 15
947 15
        //    Validate
948 15
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) {
949 15
            $cost = (float) $cost;
950 15
            $salvage = (float) $salvage;
951 15
            $life = (int) $life;
952 1
            $period = (int) $period;
953 14
            $month = (int) $month;
954 1
            if ($cost == 0) {
955
                return 0.0;
956
            } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) {
957 13
                return Functions::NAN();
958 13
            }
959
            //    Set Fixed Depreciation Rate
960
            $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
961 13
            $fixedDepreciationRate = round($fixedDepreciationRate, 3);
962 13
963 13
            //    Loop through each period calculating the depreciation
964 13
            $previousDepreciation = 0;
965 11
            for ($per = 1; $per <= $period; ++$per) {
966 2
                if ($per == 1) {
967
                    $depreciation = $cost * $fixedDepreciationRate * $month / 12;
968 11
                } elseif ($per == ($life + 1)) {
969
                    $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
970 13
                } else {
971
                    $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
972 13
                }
973
                $previousDepreciation += $depreciation;
974
            }
975
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
976 13
                $depreciation = round($depreciation, 2);
0 ignored issues
show
Bug introduced by
The variable $depreciation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
977
            }
978
979 1
            return $depreciation;
980
        }
981
982
        return Functions::VALUE();
983
    }
984
985
    /**
986
     * DDB.
987
     *
988
     * Returns the depreciation of an asset for a specified period using the
989
     * double-declining balance method or some other method you specify.
990
     *
991
     * Excel Function:
992
     *        DDB(cost,salvage,life,period[,factor])
993
     *
994
     * @category Financial Functions
995
     *
996
     * @param float cost Initial cost of the asset
997
     * @param float salvage Value at the end of the depreciation.
998
     *                                (Sometimes called the salvage value of the asset)
999
     * @param int life Number of periods over which the asset is depreciated.
1000
     *                                (Sometimes called the useful life of the asset)
1001
     * @param int period The period for which you want to calculate the
1002
     *                                depreciation. Period must use the same units as life.
1003
     * @param float factor The rate at which the balance declines.
1004
     *                                If factor is omitted, it is assumed to be 2 (the
1005
     *                                double-declining balance method).
1006
     * @param mixed $cost
1007
     * @param mixed $salvage
1008
     * @param mixed $life
1009
     * @param mixed $period
1010
     * @param mixed $factor
1011 15
     *
1012
     * @return float
1013 15
     */
1014 15
    public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
1015 15
    {
1016 15
        $cost = Functions::flattenSingleValue($cost);
1017 15
        $salvage = Functions::flattenSingleValue($salvage);
1018
        $life = Functions::flattenSingleValue($life);
1019
        $period = Functions::flattenSingleValue($period);
1020 15
        $factor = Functions::flattenSingleValue($factor);
1021 14
1022 14
        //    Validate
1023 14
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) {
1024 14
            $cost = (float) $cost;
1025 14
            $salvage = (float) $salvage;
1026 14
            $life = (int) $life;
1027 1
            $period = (int) $period;
1028
            $factor = (float) $factor;
1029
            if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) {
1030 13
                return Functions::NAN();
1031 13
            }
1032
            //    Set Fixed Depreciation Rate
1033
            $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
1034 13
            $fixedDepreciationRate = round($fixedDepreciationRate, 3);
0 ignored issues
show
Unused Code introduced by
$fixedDepreciationRate is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1035 13
1036 13
            //    Loop through each period calculating the depreciation
1037 13
            $previousDepreciation = 0;
1038
            for ($per = 1; $per <= $period; ++$per) {
1039 13
                $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation));
1040
                $previousDepreciation += $depreciation;
1041
            }
1042
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
1043 13
                $depreciation = round($depreciation, 2);
0 ignored issues
show
Bug introduced by
The variable $depreciation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1044
            }
1045
1046 1
            return $depreciation;
1047
        }
1048
1049
        return Functions::VALUE();
1050
    }
1051
1052
    /**
1053
     * DISC.
1054
     *
1055
     * Returns the discount rate for a security.
1056
     *
1057
     * Excel Function:
1058
     *        DISC(settlement,maturity,price,redemption[,basis])
1059
     *
1060
     * @category Financial Functions
1061
     *
1062
     * @param mixed settlement The security's settlement date.
1063
     *                                The security settlement date is the date after the issue
1064
     *                                date when the security is traded to the buyer.
1065
     * @param mixed maturity The security's maturity date.
1066
     *                                The maturity date is the date when the security expires.
1067
     * @param int price The security's price per $100 face value
1068
     * @param int redemption The security's redemption value per $100 face value
1069
     * @param int basis The type of day count to use.
1070
     *                                        0 or omitted    US (NASD) 30/360
1071
     *                                        1                Actual/actual
1072
     *                                        2                Actual/360
1073
     *                                        3                Actual/365
1074
     *                                        4                European 30/360
1075
     * @param mixed $settlement
1076
     * @param mixed $maturity
1077
     * @param mixed $price
1078
     * @param mixed $redemption
1079
     * @param mixed $basis
1080 5
     *
1081
     * @return float
1082 5
     */
1083 5 View Code Duplication
    public static function DISC($settlement, $maturity, $price, $redemption, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1084 5
    {
1085 5
        $settlement = Functions::flattenSingleValue($settlement);
1086 5
        $maturity = Functions::flattenSingleValue($maturity);
1087
        $price = Functions::flattenSingleValue($price);
1088
        $redemption = Functions::flattenSingleValue($redemption);
1089 5
        $basis = Functions::flattenSingleValue($basis);
1090 4
1091 4
        //    Validate
1092 4
        if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) {
1093 4
            $price = (float) $price;
1094 1
            $redemption = (float) $redemption;
1095
            $basis = (int) $basis;
1096 3
            if (($price <= 0) || ($redemption <= 0)) {
1097 3
                return Functions::NAN();
1098
            }
1099 1
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1100
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1101
                //    return date error
1102 2
                return $daysBetweenSettlementAndMaturity;
1103
            }
1104
1105 1
            return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity;
1106
        }
1107
1108
        return Functions::VALUE();
1109
    }
1110
1111
    /**
1112
     * DOLLARDE.
1113
     *
1114
     * Converts a dollar price expressed as an integer part and a fraction
1115
     *        part into a dollar price expressed as a decimal number.
1116
     * Fractional dollar numbers are sometimes used for security prices.
1117
     *
1118
     * Excel Function:
1119
     *        DOLLARDE(fractional_dollar,fraction)
1120
     *
1121
     * @category Financial Functions
1122
     *
1123
     * @param float $fractional_dollar Fractional Dollar
1124
     * @param int $fraction Fraction
1125 9
     *
1126
     * @return float
1127 9
     */
1128 9 View Code Duplication
    public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1129
    {
1130
        $fractional_dollar = Functions::flattenSingleValue($fractional_dollar);
1131 9
        $fraction = (int) Functions::flattenSingleValue($fraction);
1132 1
1133
        // Validate parameters
1134 8
        if ($fractional_dollar === null || $fraction < 0) {
1135 1
            return Functions::NAN();
1136
        }
1137
        if ($fraction == 0) {
1138 7
            return Functions::DIV0();
1139 7
        }
1140 7
1141 7
        $dollars = floor($fractional_dollar);
1142
        $cents = fmod($fractional_dollar, 1);
1143 7
        $cents /= $fraction;
1144
        $cents *= pow(10, ceil(log10($fraction)));
1145
1146
        return $dollars + $cents;
1147
    }
1148
1149
    /**
1150
     * DOLLARFR.
1151
     *
1152
     * Converts a dollar price expressed as a decimal number into a dollar price
1153
     *        expressed as a fraction.
1154
     * Fractional dollar numbers are sometimes used for security prices.
1155
     *
1156
     * Excel Function:
1157
     *        DOLLARFR(decimal_dollar,fraction)
1158
     *
1159
     * @category Financial Functions
1160
     *
1161
     * @param float $decimal_dollar Decimal Dollar
1162
     * @param int $fraction Fraction
1163 9
     *
1164
     * @return float
1165 9
     */
1166 9 View Code Duplication
    public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1167
    {
1168
        $decimal_dollar = Functions::flattenSingleValue($decimal_dollar);
1169 9
        $fraction = (int) Functions::flattenSingleValue($fraction);
1170 1
1171
        // Validate parameters
1172 8
        if ($decimal_dollar === null || $fraction < 0) {
1173 1
            return Functions::NAN();
1174
        }
1175
        if ($fraction == 0) {
1176 7
            return Functions::DIV0();
1177 7
        }
1178 7
1179 7
        $dollars = floor($decimal_dollar);
1180
        $cents = fmod($decimal_dollar, 1);
1181 7
        $cents *= $fraction;
1182
        $cents *= pow(10, -ceil(log10($fraction)));
1183
1184
        return $dollars + $cents;
1185
    }
1186
1187
    /**
1188
     * EFFECT.
1189
     *
1190
     * Returns the effective interest rate given the nominal rate and the number of
1191
     *        compounding payments per year.
1192
     *
1193
     * Excel Function:
1194
     *        EFFECT(nominal_rate,npery)
1195
     *
1196
     * @category Financial Functions
1197
     *
1198
     * @param float $nominal_rate Nominal interest rate
1199
     * @param int $npery Number of compounding payments per year
1200 5
     *
1201
     * @return float
1202 5
     */
1203 5 View Code Duplication
    public static function EFFECT($nominal_rate = 0, $npery = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1204
    {
1205
        $nominal_rate = Functions::flattenSingleValue($nominal_rate);
1206 5
        $npery = (int) Functions::flattenSingleValue($npery);
1207 1
1208
        // Validate parameters
1209
        if ($nominal_rate <= 0 || $npery < 1) {
1210 4
            return Functions::NAN();
1211
        }
1212
1213
        return pow((1 + $nominal_rate / $npery), $npery) - 1;
1214
    }
1215
1216
    /**
1217
     * FV.
1218
     *
1219
     * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
1220
     *
1221
     * Excel Function:
1222
     *        FV(rate,nper,pmt[,pv[,type]])
1223
     *
1224
     * @category Financial Functions
1225
     *
1226
     * @param float $rate The interest rate per period
1227
     * @param int $nper Total number of payment periods in an annuity
1228
     * @param float $pmt The payment made each period: it cannot change over the
1229
     *                            life of the annuity. Typically, pmt contains principal
1230
     *                            and interest but no other fees or taxes.
1231
     * @param float $pv present Value, or the lump-sum amount that a series of
1232
     *                            future payments is worth right now
1233
     * @param int $type A number 0 or 1 and indicates when payments are due:
1234
     *                                0 or omitted    At the end of the period.
1235
     *                                1                At the beginning of the period.
1236 8
     *
1237
     * @return float
1238 8
     */
1239 8 View Code Duplication
    public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1240 8
    {
1241 8
        $rate = Functions::flattenSingleValue($rate);
1242 8
        $nper = Functions::flattenSingleValue($nper);
1243
        $pmt = Functions::flattenSingleValue($pmt);
1244
        $pv = Functions::flattenSingleValue($pv);
1245 8
        $type = Functions::flattenSingleValue($type);
1246 1
1247
        // Validate parameters
1248
        if ($type != 0 && $type != 1) {
1249
            return Functions::NAN();
1250 7
        }
1251 6
1252
        // Calculate
1253
        if ($rate !== null && $rate != 0) {
1254 1
            return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
1255
        }
1256
1257
        return -$pv - $pmt * $nper;
1258
    }
1259
1260
    /**
1261
     * FVSCHEDULE.
1262
     *
1263
     * Returns the future value of an initial principal after applying a series of compound interest rates.
1264
     * Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
1265
     *
1266
     * Excel Function:
1267
     *        FVSCHEDULE(principal,schedule)
1268
     *
1269
     * @param float $principal the present value
1270
     * @param float[] $schedule an array of interest rates to apply
1271 3
     *
1272
     * @return float
1273 3
     */
1274 3
    public static function FVSCHEDULE($principal, $schedule)
1275
    {
1276 3
        $principal = Functions::flattenSingleValue($principal);
1277 3
        $schedule = Functions::flattenArray($schedule);
1278
1279
        foreach ($schedule as $rate) {
1280 3
            $principal *= 1 + $rate;
1281
        }
1282
1283
        return $principal;
1284
    }
1285
1286
    /**
1287
     * INTRATE.
1288
     *
1289
     * Returns the interest rate for a fully invested security.
1290
     *
1291
     * Excel Function:
1292
     *        INTRATE(settlement,maturity,investment,redemption[,basis])
1293
     *
1294
     * @param mixed $settlement The security's settlement date.
1295
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1296
     * @param mixed $maturity The security's maturity date.
1297
     *                                The maturity date is the date when the security expires.
1298
     * @param int $investment the amount invested in the security
1299
     * @param int $redemption the amount to be received at maturity
1300
     * @param int $basis The type of day count to use.
1301
     *                                        0 or omitted    US (NASD) 30/360
1302
     *                                        1                Actual/actual
1303
     *                                        2                Actual/360
1304
     *                                        3                Actual/365
1305
     *                                        4                European 30/360
1306 5
     *
1307
     * @return float
1308 5
     */
1309 5 View Code Duplication
    public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1310 5
    {
1311 5
        $settlement = Functions::flattenSingleValue($settlement);
1312 5
        $maturity = Functions::flattenSingleValue($maturity);
1313
        $investment = Functions::flattenSingleValue($investment);
1314
        $redemption = Functions::flattenSingleValue($redemption);
1315 5
        $basis = Functions::flattenSingleValue($basis);
1316 4
1317 4
        //    Validate
1318 4
        if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) {
1319 4
            $investment = (float) $investment;
1320 1
            $redemption = (float) $redemption;
1321
            $basis = (int) $basis;
1322 3
            if (($investment <= 0) || ($redemption <= 0)) {
1323 3
                return Functions::NAN();
1324
            }
1325 1
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1326
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1327
                //    return date error
1328 2
                return $daysBetweenSettlementAndMaturity;
1329
            }
1330
1331 1
            return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);
1332
        }
1333
1334
        return Functions::VALUE();
1335
    }
1336
1337
    /**
1338
     * IPMT.
1339
     *
1340
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1341
     *
1342
     * Excel Function:
1343
     *        IPMT(rate,per,nper,pv[,fv][,type])
1344
     *
1345
     * @param float $rate Interest rate per period
1346
     * @param int $per Period for which we want to find the interest
1347
     * @param int $nper Number of periods
1348
     * @param float $pv Present Value
1349
     * @param float $fv Future Value
1350
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1351 15
     *
1352
     * @return float
1353 15
     */
1354 15 View Code Duplication
    public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1355 15
    {
1356 15
        $rate = Functions::flattenSingleValue($rate);
1357 15
        $per = (int) Functions::flattenSingleValue($per);
1358 15
        $nper = (int) Functions::flattenSingleValue($nper);
1359
        $pv = Functions::flattenSingleValue($pv);
1360
        $fv = Functions::flattenSingleValue($fv);
1361 15
        $type = (int) Functions::flattenSingleValue($type);
1362 1
1363
        // Validate parameters
1364 14
        if ($type != 0 && $type != 1) {
1365 1
            return Functions::NAN();
1366
        }
1367
        if ($per <= 0 || $per > $nper) {
1368
            return Functions::VALUE();
1369 13
        }
1370
1371 13
        // Calculate
1372
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1373
1374
        return $interestAndPrincipal[0];
1375
    }
1376
1377
    /**
1378
     * IRR.
1379
     *
1380
     * Returns the internal rate of return for a series of cash flows represented by the numbers in values.
1381
     * These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
1382
     * at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
1383
     * for an investment consisting of payments (negative values) and income (positive values) that occur at regular
1384
     * periods.
1385
     *
1386
     * Excel Function:
1387
     *        IRR(values[,guess])
1388
     *
1389
     * @param float[] $values An array or a reference to cells that contain numbers for which you want
1390
     *                                    to calculate the internal rate of return.
1391
     *                                Values must contain at least one positive value and one negative value to
1392
     *                                    calculate the internal rate of return.
1393
     * @param float $guess A number that you guess is close to the result of IRR
1394 5
     *
1395
     * @return float
1396 5
     */
1397
    public static function IRR($values, $guess = 0.1)
1398
    {
1399 5
        if (!is_array($values)) {
1400 5
            return Functions::VALUE();
1401
        }
1402
        $values = Functions::flattenArray($values);
1403 5
        $guess = Functions::flattenSingleValue($guess);
1404 5
1405 5
        // create an initial range, with a root somewhere between 0 and guess
1406 5
        $x1 = 0.0;
1407 5
        $x2 = $guess;
1408 5
        $f1 = self::NPV($x1, $values);
1409 5
        $f2 = self::NPV($x2, $values);
1410 View Code Duplication
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1411 4
            if (($f1 * $f2) < 0.0) {
1412 3
                break;
1413
            }
1414 1
            if (abs($f1) < abs($f2)) {
1415
                $f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values);
1416
            } else {
1417 5
                $f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values);
1418
            }
1419
        }
1420
        if (($f1 * $f2) > 0.0) {
1421 5
            return Functions::VALUE();
1422 5
        }
1423
1424
        $f = self::NPV($x1, $values);
1425 View Code Duplication
        if ($f < 0.0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1426 5
            $rtb = $x1;
1427 5
            $dx = $x2 - $x1;
1428
        } else {
1429
            $rtb = $x2;
1430 5
            $dx = $x1 - $x2;
1431 5
        }
1432 5
1433 5 View Code Duplication
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1434 5
            $dx *= 0.5;
1435 5
            $x_mid = $rtb + $dx;
1436
            $f_mid = self::NPV($x_mid, $values);
1437 5
            if ($f_mid <= 0.0) {
1438 5
                $rtb = $x_mid;
1439
            }
1440
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
1441
                return $x_mid;
1442
            }
1443
        }
1444
1445
        return Functions::VALUE();
1446
    }
1447
1448
    /**
1449
     * ISPMT.
1450
     *
1451
     * Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
1452
     *
1453
     * Excel Function:
1454
     *     =ISPMT(interest_rate, period, number_payments, PV)
1455
     *
1456
     * interest_rate is the interest rate for the investment
1457
     *
1458
     * period is the period to calculate the interest rate.  It must be betweeen 1 and number_payments.
1459
     *
1460
     * number_payments is the number of payments for the annuity
1461 4
     *
1462
     * PV is the loan amount or present value of the payments
1463
     */
1464 4
    public static function ISPMT(...$args)
1465
    {
1466
        // Return value
1467 4
        $returnValue = 0;
1468 4
1469 4
        // Get the parameters
1470 4
        $aArgs = Functions::flattenArray($args);
1471 4
        $interestRate = array_shift($aArgs);
1472
        $period = array_shift($aArgs);
1473
        $numberPeriods = array_shift($aArgs);
1474 4
        $principleRemaining = array_shift($aArgs);
1475 4
1476 4
        // Calculate
1477 4
        $principlePayment = ($principleRemaining * 1.0) / ($numberPeriods * 1.0);
1478
        for ($i = 0; $i <= $period; ++$i) {
1479 4
            $returnValue = $interestRate * $principleRemaining * -1;
1480
            $principleRemaining -= $principlePayment;
1481
            // principle needs to be 0 after the last payment, don't let floating point screw it up
1482
            if ($i == $numberPeriods) {
1483
                $returnValue = 0;
1484 4
            }
1485
        }
1486
1487
        return $returnValue;
1488
    }
1489
1490
    /**
1491
     * MIRR.
1492
     *
1493
     * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
1494
     *        the cost of the investment and the interest received on reinvestment of cash.
1495
     *
1496
     * Excel Function:
1497
     *        MIRR(values,finance_rate, reinvestment_rate)
1498
     *
1499
     * @param float[] $values An array or a reference to cells that contain a series of payments and
1500
     *                                            income occurring at regular intervals.
1501
     *                                        Payments are negative value, income is positive values.
1502
     * @param float $finance_rate The interest rate you pay on the money used in the cash flows
1503
     * @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them
1504 5
     *
1505
     * @return float
1506 5
     */
1507
    public static function MIRR($values, $finance_rate, $reinvestment_rate)
1508
    {
1509 5
        if (!is_array($values)) {
1510 5
            return Functions::VALUE();
1511 5
        }
1512 5
        $values = Functions::flattenArray($values);
1513
        $finance_rate = Functions::flattenSingleValue($finance_rate);
1514 5
        $reinvestment_rate = Functions::flattenSingleValue($reinvestment_rate);
1515 5
        $n = count($values);
1516
1517 5
        $rr = 1.0 + $reinvestment_rate;
1518 5
        $fr = 1.0 + $finance_rate;
1519 5
1520 5
        $npv_pos = $npv_neg = 0.0;
1521
        foreach ($values as $i => $v) {
1522 5
            if ($v >= 0) {
1523
                $npv_pos += $v / pow($rr, $i);
1524
            } else {
1525
                $npv_neg += $v / pow($fr, $i);
1526 5
            }
1527
        }
1528
1529
        if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) {
1530 5
            return Functions::VALUE();
1531 5
        }
1532
1533 5
        $mirr = pow((-$npv_pos * pow($rr, $n))
1534
                / ($npv_neg * ($rr)), (1.0 / ($n - 1))) - 1.0;
1535
1536
        return is_finite($mirr) ? $mirr : Functions::VALUE();
1537
    }
1538
1539
    /**
1540
     * NOMINAL.
1541
     *
1542
     * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
1543
     *
1544
     * @param float $effect_rate Effective interest rate
1545
     * @param int $npery Number of compounding payments per year
1546 5
     *
1547
     * @return float
1548 5
     */
1549 5 View Code Duplication
    public static function NOMINAL($effect_rate = 0, $npery = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1550
    {
1551
        $effect_rate = Functions::flattenSingleValue($effect_rate);
1552 5
        $npery = (int) Functions::flattenSingleValue($npery);
1553 1
1554
        // Validate parameters
1555
        if ($effect_rate <= 0 || $npery < 1) {
1556
            return Functions::NAN();
1557 4
        }
1558
1559
        // Calculate
1560
        return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
1561
    }
1562
1563
    /**
1564
     * NPER.
1565
     *
1566
     * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
1567
     *
1568
     * @param float $rate Interest rate per period
1569
     * @param int $pmt Periodic payment (annuity)
1570
     * @param float $pv Present Value
1571
     * @param float $fv Future Value
1572
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1573 9
     *
1574
     * @return float
1575 9
     */
1576 9
    public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0)
1577 9
    {
1578 9
        $rate = Functions::flattenSingleValue($rate);
1579 9
        $pmt = Functions::flattenSingleValue($pmt);
1580
        $pv = Functions::flattenSingleValue($pv);
1581
        $fv = Functions::flattenSingleValue($fv);
1582 9
        $type = Functions::flattenSingleValue($type);
1583 1
1584
        // Validate parameters
1585
        if ($type != 0 && $type != 1) {
1586
            return Functions::NAN();
1587 8
        }
1588 6
1589 1
        // Calculate
1590
        if ($rate !== null && $rate != 0) {
1591
            if ($pmt == 0 && $pv == 0) {
1592 5
                return Functions::NAN();
1593
            }
1594 2
1595 1
            return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
1596
        }
1597
        if ($pmt == 0) {
1598 1
            return Functions::NAN();
1599
        }
1600
1601
        return (-$pv - $fv) / $pmt;
1602
    }
1603
1604
    /**
1605
     * NPV.
1606
     *
1607
     * Returns the Net Present Value of a cash flow series given a discount rate.
1608 9
     *
1609
     * @return float
1610
     */
1611 9
    public static function NPV(...$args)
1612
    {
1613
        // Return value
1614 9
        $returnValue = 0;
1615
1616
        // Loop through arguments
1617 9
        $aArgs = Functions::flattenArray($args);
1618 9
1619
        // Calculate
1620 9
        $rate = array_shift($aArgs);
1621 9
        for ($i = 1; $i <= count($aArgs); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1622
            // Is it a numeric value?
1623
            if (is_numeric($aArgs[$i - 1])) {
1624
                $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
1625
            }
1626 9
        }
1627
1628
        // Return
1629
        return $returnValue;
1630
    }
1631
1632
    /**
1633
     * PMT.
1634
     *
1635
     * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
1636
     *
1637
     * @param float $rate Interest rate per period
1638
     * @param int $nper Number of periods
1639
     * @param float $pv Present Value
1640
     * @param float $fv Future Value
1641
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1642 20
     *
1643
     * @return float
1644 20
     */
1645 20
    public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
1646 20
    {
1647 20
        $rate = Functions::flattenSingleValue($rate);
1648 20
        $nper = Functions::flattenSingleValue($nper);
1649
        $pv = Functions::flattenSingleValue($pv);
1650
        $fv = Functions::flattenSingleValue($fv);
1651 20
        $type = Functions::flattenSingleValue($type);
1652
1653
        // Validate parameters
1654
        if ($type != 0 && $type != 1) {
1655
            return Functions::NAN();
1656 20
        }
1657 20
1658
        // Calculate
1659
        if ($rate !== null && $rate != 0) {
1660
            return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
1661
        }
1662
1663
        return (-$pv - $fv) / $nper;
1664
    }
1665
1666
    /**
1667
     * PPMT.
1668
     *
1669
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1670
     *
1671
     * @param float $rate Interest rate per period
1672
     * @param int $per Period for which we want to find the interest
1673
     * @param int $nper Number of periods
1674
     * @param float $pv Present Value
1675
     * @param float $fv Future Value
1676
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1677 7
     *
1678
     * @return float
1679 7
     */
1680 7 View Code Duplication
    public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1681 7
    {
1682 7
        $rate = Functions::flattenSingleValue($rate);
1683 7
        $per = (int) Functions::flattenSingleValue($per);
1684 7
        $nper = (int) Functions::flattenSingleValue($nper);
1685
        $pv = Functions::flattenSingleValue($pv);
1686
        $fv = Functions::flattenSingleValue($fv);
1687 7
        $type = (int) Functions::flattenSingleValue($type);
1688
1689
        // Validate parameters
1690 7
        if ($type != 0 && $type != 1) {
1691
            return Functions::NAN();
1692
        }
1693
        if ($per <= 0 || $per > $nper) {
1694
            return Functions::VALUE();
1695 7
        }
1696
1697 7
        // Calculate
1698
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1699
1700
        return $interestAndPrincipal[1];
1701
    }
1702
1703
    public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0)
1704
    {
1705
        $settlement = Functions::flattenSingleValue($settlement);
1706
        $maturity = Functions::flattenSingleValue($maturity);
1707
        $rate = (float) Functions::flattenSingleValue($rate);
1708
        $yield = (float) Functions::flattenSingleValue($yield);
1709
        $redemption = (float) Functions::flattenSingleValue($redemption);
1710
        $frequency = (int) Functions::flattenSingleValue($frequency);
1711
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
1712
1713
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
1714
            return Functions::VALUE();
1715
        }
1716
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
1717
            return Functions::VALUE();
1718
        }
1719
1720
        if (($settlement > $maturity) ||
1721
            (!self::isValidFrequency($frequency)) ||
1722
            (($basis < 0) || ($basis > 4))) {
1723
            return Functions::NAN();
1724
        }
1725
1726
        $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
1727
        $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis);
1728
        $n = self::COUPNUM($settlement, $maturity, $frequency, $basis);
1729
        $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis);
1730
1731
        $baseYF = 1.0 + ($yield / $frequency);
1732
        $rfp = 100 * ($rate / $frequency);
1733
        $de = $dsc / $e;
1734
1735
        $result = $redemption / pow($baseYF, (--$n + $de));
1736
        for ($k = 0; $k <= $n; ++$k) {
1737
            $result += $rfp / (pow($baseYF, ($k + $de)));
1738
        }
1739
        $result -= $rfp * ($a / $e);
1740
1741
        return $result;
1742
    }
1743
1744
    /**
1745
     * PRICEDISC.
1746
     *
1747
     * Returns the price per $100 face value of a discounted security.
1748
     *
1749
     * @param mixed settlement The security's settlement date.
1750
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1751
     * @param mixed maturity The security's maturity date.
1752
     *                                The maturity date is the date when the security expires.
1753
     * @param int discount The security's discount rate
1754
     * @param int redemption The security's redemption value per $100 face value
1755
     * @param int basis The type of day count to use.
1756
     *                                        0 or omitted    US (NASD) 30/360
1757
     *                                        1                Actual/actual
1758
     *                                        2                Actual/360
1759
     *                                        3                Actual/365
1760
     *                                        4                European 30/360
1761
     * @param mixed $settlement
1762
     * @param mixed $maturity
1763
     * @param mixed $discount
1764
     * @param mixed $redemption
1765
     * @param mixed $basis
1766
     *
1767
     * @return float
1768
     */
1769 View Code Duplication
    public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1770
    {
1771
        $settlement = Functions::flattenSingleValue($settlement);
1772
        $maturity = Functions::flattenSingleValue($maturity);
1773
        $discount = (float) Functions::flattenSingleValue($discount);
1774
        $redemption = (float) Functions::flattenSingleValue($redemption);
1775
        $basis = (int) Functions::flattenSingleValue($basis);
1776
1777
        //    Validate
1778
        if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) {
1779
            if (($discount <= 0) || ($redemption <= 0)) {
1780
                return Functions::NAN();
1781
            }
1782
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1783
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1784
                //    return date error
1785
                return $daysBetweenSettlementAndMaturity;
1786
            }
1787
1788
            return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
1789
        }
1790
1791
        return Functions::VALUE();
1792
    }
1793
1794
    /**
1795
     * PRICEMAT.
1796
     *
1797
     * Returns the price per $100 face value of a security that pays interest at maturity.
1798
     *
1799
     * @param mixed settlement The security's settlement date.
1800
     *                                The security's settlement date is the date after the issue date when the security is traded to the buyer.
1801
     * @param mixed maturity The security's maturity date.
1802
     *                                The maturity date is the date when the security expires.
1803
     * @param mixed issue The security's issue date
1804
     * @param int rate The security's interest rate at date of issue
1805
     * @param int yield The security's annual yield
1806
     * @param int basis The type of day count to use.
1807
     *                                        0 or omitted    US (NASD) 30/360
1808
     *                                        1                Actual/actual
1809
     *                                        2                Actual/360
1810
     *                                        3                Actual/365
1811
     *                                        4                European 30/360
1812
     * @param mixed $settlement
1813
     * @param mixed $maturity
1814
     * @param mixed $issue
1815
     * @param mixed $rate
1816
     * @param mixed $yield
1817
     * @param mixed $basis
1818
     *
1819
     * @return float
1820
     */
1821
    public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
1822
    {
1823
        $settlement = Functions::flattenSingleValue($settlement);
1824
        $maturity = Functions::flattenSingleValue($maturity);
1825
        $issue = Functions::flattenSingleValue($issue);
1826
        $rate = Functions::flattenSingleValue($rate);
1827
        $yield = Functions::flattenSingleValue($yield);
1828
        $basis = (int) Functions::flattenSingleValue($basis);
1829
1830
        //    Validate
1831
        if (is_numeric($rate) && is_numeric($yield)) {
1832
            if (($rate <= 0) || ($yield <= 0)) {
1833
                return Functions::NAN();
1834
            }
1835
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
1836
            if (!is_numeric($daysPerYear)) {
1837
                return $daysPerYear;
1838
            }
1839
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
1840
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
1841
                //    return date error
1842
                return $daysBetweenIssueAndSettlement;
1843
            }
1844
            $daysBetweenIssueAndSettlement *= $daysPerYear;
1845
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
1846
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
1847
                //    return date error
1848
                return $daysBetweenIssueAndMaturity;
1849
            }
1850
            $daysBetweenIssueAndMaturity *= $daysPerYear;
1851
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1852
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1853
                //    return date error
1854
                return $daysBetweenSettlementAndMaturity;
1855
            }
1856
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
1857
1858
            return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
1859
                   (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
1860
                   (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
1861
        }
1862
1863
        return Functions::VALUE();
1864
    }
1865
1866
    /**
1867
     * PV.
1868
     *
1869
     * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
1870
     *
1871
     * @param float $rate Interest rate per period
1872
     * @param int $nper Number of periods
1873
     * @param float $pmt Periodic payment (annuity)
1874
     * @param float $fv Future Value
1875
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1876
     *
1877
     * @return float
1878
     */
1879 View Code Duplication
    public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1880
    {
1881
        $rate = Functions::flattenSingleValue($rate);
1882
        $nper = Functions::flattenSingleValue($nper);
1883
        $pmt = Functions::flattenSingleValue($pmt);
1884
        $fv = Functions::flattenSingleValue($fv);
1885
        $type = Functions::flattenSingleValue($type);
1886
1887
        // Validate parameters
1888
        if ($type != 0 && $type != 1) {
1889
            return Functions::NAN();
1890
        }
1891
1892
        // Calculate
1893
        if ($rate !== null && $rate != 0) {
1894
            return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
1895
        }
1896
1897
        return -$fv - $pmt * $nper;
1898
    }
1899
1900
    /**
1901
     * RATE.
1902
     *
1903
     * Returns the interest rate per period of an annuity.
1904
     * RATE is calculated by iteration and can have zero or more solutions.
1905
     * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
1906
     * RATE returns the #NUM! error value.
1907
     *
1908
     * Excel Function:
1909
     *        RATE(nper,pmt,pv[,fv[,type[,guess]]])
1910
     *
1911
     * @category Financial Functions
1912
     *
1913
     * @param float nper The total number of payment periods in an annuity
1914
     * @param float pmt The payment made each period and cannot change over the life
1915
     *                                    of the annuity.
1916
     *                                Typically, pmt includes principal and interest but no other
1917
     *                                    fees or taxes.
1918
     * @param float pv The present value - the total amount that a series of future
1919
     *                                    payments is worth now
1920
     * @param float fv The future value, or a cash balance you want to attain after
1921
     *                                    the last payment is made. If fv is omitted, it is assumed
1922
     *                                    to be 0 (the future value of a loan, for example, is 0).
1923
     * @param int type A number 0 or 1 and indicates when payments are due:
1924
     *                                        0 or omitted    At the end of the period.
1925
     *                                        1                At the beginning of the period.
1926
     * @param float guess Your guess for what the rate will be.
1927
     *                                    If you omit guess, it is assumed to be 10 percent.
1928
     * @param mixed $nper
1929
     * @param mixed $pmt
1930
     * @param mixed $pv
1931
     * @param mixed $fv
1932
     * @param mixed $type
1933
     * @param mixed $guess
1934
     *
1935
     * @return float
1936
     **/
1937
    public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1)
1938
    {
1939
        $nper = (int) Functions::flattenSingleValue($nper);
1940
        $pmt = Functions::flattenSingleValue($pmt);
1941
        $pv = Functions::flattenSingleValue($pv);
1942
        $fv = ($fv === null) ? 0.0 : Functions::flattenSingleValue($fv);
1943
        $type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type);
1944
        $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
1945
1946
        $rate = $guess;
1947 View Code Duplication
        if (abs($rate) < self::FINANCIAL_PRECISION) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1948
            $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
0 ignored issues
show
Unused Code introduced by
$y is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1949
        } else {
1950
            $f = exp($nper * log(1 + $rate));
1951
            $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
0 ignored issues
show
Unused Code introduced by
$y is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1952
        }
1953
        $y0 = $pv + $pmt * $nper + $fv;
1954
        $y1 = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
0 ignored issues
show
Bug introduced by
The variable $f does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1955
1956
        // find root by secant method
1957
        $i = $x0 = 0.0;
1958
        $x1 = $rate;
1959
        while ((abs($y0 - $y1) > self::FINANCIAL_PRECISION) && ($i < self::FINANCIAL_MAX_ITERATIONS)) {
1960
            $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
1961
            $x0 = $x1;
1962
            $x1 = $rate;
1963
            if (($nper * abs($pmt)) > ($pv - $fv)) {
1964
                $x1 = abs($x1);
1965
            }
1966 View Code Duplication
            if (abs($rate) < self::FINANCIAL_PRECISION) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1967
                $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
1968
            } else {
1969
                $f = exp($nper * log(1 + $rate));
1970
                $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
1971
            }
1972
1973
            $y0 = $y1;
1974
            $y1 = $y;
1975
            ++$i;
1976
        }
1977
1978
        return $rate;
1979
    }
1980
1981
    /**
1982
     * RECEIVED.
1983
     *
1984
     * Returns the price per $100 face value of a discounted security.
1985
     *
1986
     * @param mixed settlement The security's settlement date.
1987
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1988
     * @param mixed maturity The security's maturity date.
1989
     *                                The maturity date is the date when the security expires.
1990
     * @param int investment The amount invested in the security
1991
     * @param int discount The security's discount rate
1992
     * @param int basis The type of day count to use.
1993
     *                                        0 or omitted    US (NASD) 30/360
1994
     *                                        1                Actual/actual
1995
     *                                        2                Actual/360
1996
     *                                        3                Actual/365
1997
     *                                        4                European 30/360
1998
     * @param mixed $settlement
1999
     * @param mixed $maturity
2000
     * @param mixed $investment
2001
     * @param mixed $discount
2002
     * @param mixed $basis
2003
     *
2004
     * @return float
2005
     */
2006 View Code Duplication
    public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2007
    {
2008
        $settlement = Functions::flattenSingleValue($settlement);
2009
        $maturity = Functions::flattenSingleValue($maturity);
2010
        $investment = (float) Functions::flattenSingleValue($investment);
2011
        $discount = (float) Functions::flattenSingleValue($discount);
2012
        $basis = (int) Functions::flattenSingleValue($basis);
2013
2014
        //    Validate
2015
        if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) {
2016
            if (($investment <= 0) || ($discount <= 0)) {
2017
                return Functions::NAN();
2018
            }
2019
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2020
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2021
                //    return date error
2022
                return $daysBetweenSettlementAndMaturity;
2023
            }
2024
2025
            return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));
2026
        }
2027
2028
        return Functions::VALUE();
2029
    }
2030
2031
    /**
2032
     * SLN.
2033
     *
2034
     * Returns the straight-line depreciation of an asset for one period
2035
     *
2036
     * @param cost Initial cost of the asset
2037
     * @param salvage Value at the end of the depreciation
2038
     * @param life Number of periods over which the asset is depreciated
2039
     * @param mixed $cost
2040
     * @param mixed $salvage
2041
     * @param mixed $life
2042
     *
2043
     * @return float
2044
     */
2045 View Code Duplication
    public static function SLN($cost, $salvage, $life)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2046
    {
2047
        $cost = Functions::flattenSingleValue($cost);
2048
        $salvage = Functions::flattenSingleValue($salvage);
2049
        $life = Functions::flattenSingleValue($life);
2050
2051
        // Calculate
2052
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
2053
            if ($life < 0) {
2054
                return Functions::NAN();
2055
            }
2056
2057
            return ($cost - $salvage) / $life;
2058
        }
2059
2060
        return Functions::VALUE();
2061
    }
2062
2063
    /**
2064
     * SYD.
2065
     *
2066
     * Returns the sum-of-years' digits depreciation of an asset for a specified period.
2067
     *
2068
     * @param cost Initial cost of the asset
2069
     * @param salvage Value at the end of the depreciation
2070
     * @param life Number of periods over which the asset is depreciated
2071
     * @param period Period
2072
     * @param mixed $cost
2073
     * @param mixed $salvage
2074
     * @param mixed $life
2075
     * @param mixed $period
2076
     *
2077
     * @return float
2078
     */
2079
    public static function SYD($cost, $salvage, $life, $period)
2080
    {
2081
        $cost = Functions::flattenSingleValue($cost);
2082
        $salvage = Functions::flattenSingleValue($salvage);
2083
        $life = Functions::flattenSingleValue($life);
2084
        $period = Functions::flattenSingleValue($period);
2085
2086
        // Calculate
2087
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
2088
            if (($life < 1) || ($period > $life)) {
2089
                return Functions::NAN();
2090
            }
2091
2092
            return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
2093
        }
2094
2095
        return Functions::VALUE();
2096
    }
2097
2098
    /**
2099
     * TBILLEQ.
2100
     *
2101
     * Returns the bond-equivalent yield for a Treasury bill.
2102
     *
2103
     * @param mixed settlement The Treasury bill's settlement date.
2104
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2105
     * @param mixed maturity The Treasury bill's maturity date.
2106
     *                                The maturity date is the date when the Treasury bill expires.
2107
     * @param int discount The Treasury bill's discount rate
2108
     * @param mixed $settlement
2109
     * @param mixed $maturity
2110
     * @param mixed $discount
2111
     *
2112
     * @return float
2113
     */
2114
    public static function TBILLEQ($settlement, $maturity, $discount)
2115
    {
2116
        $settlement = Functions::flattenSingleValue($settlement);
2117
        $maturity = Functions::flattenSingleValue($maturity);
2118
        $discount = Functions::flattenSingleValue($discount);
2119
2120
        //    Use TBILLPRICE for validation
2121
        $testValue = self::TBILLPRICE($settlement, $maturity, $discount);
2122
        if (is_string($testValue)) {
2123
            return $testValue;
2124
        }
2125
2126
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2127
            return Functions::VALUE();
2128
        }
2129
2130
        if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2131
            ++$maturity;
2132
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2133
        } else {
2134
            $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2135
        }
2136
2137
        return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
2138
    }
2139
2140
    /**
2141
     * TBILLPRICE.
2142
     *
2143
     * Returns the yield for a Treasury bill.
2144
     *
2145
     * @param mixed settlement The Treasury bill's settlement date.
2146
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2147
     * @param mixed maturity The Treasury bill's maturity date.
2148
     *                                The maturity date is the date when the Treasury bill expires.
2149
     * @param int discount The Treasury bill's discount rate
2150
     * @param mixed $settlement
2151
     * @param mixed $maturity
2152
     * @param mixed $discount
2153
     *
2154
     * @return float
2155
     */
2156
    public static function TBILLPRICE($settlement, $maturity, $discount)
2157
    {
2158
        $settlement = Functions::flattenSingleValue($settlement);
2159
        $maturity = Functions::flattenSingleValue($maturity);
2160
        $discount = Functions::flattenSingleValue($discount);
2161
2162
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2163
            return Functions::VALUE();
2164
        }
2165
2166
        //    Validate
2167
        if (is_numeric($discount)) {
2168
            if ($discount <= 0) {
2169
                return Functions::NAN();
2170
            }
2171
2172 View Code Duplication
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2173
                ++$maturity;
2174
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2175
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2176
                    //    return date error
2177
                    return $daysBetweenSettlementAndMaturity;
2178
                }
2179
            } else {
2180
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2181
            }
2182
2183
            if ($daysBetweenSettlementAndMaturity > 360) {
2184
                return Functions::NAN();
2185
            }
2186
2187
            $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
2188
            if ($price <= 0) {
2189
                return Functions::NAN();
2190
            }
2191
2192
            return $price;
2193
        }
2194
2195
        return Functions::VALUE();
2196
    }
2197
2198
    /**
2199
     * TBILLYIELD.
2200
     *
2201
     * Returns the yield for a Treasury bill.
2202
     *
2203
     * @param mixed settlement The Treasury bill's settlement date.
2204
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2205
     * @param mixed maturity The Treasury bill's maturity date.
2206
     *                                The maturity date is the date when the Treasury bill expires.
2207
     * @param int price The Treasury bill's price per $100 face value
2208
     * @param mixed $settlement
2209
     * @param mixed $maturity
2210
     * @param mixed $price
2211
     *
2212
     * @return float
2213
     */
2214
    public static function TBILLYIELD($settlement, $maturity, $price)
2215
    {
2216
        $settlement = Functions::flattenSingleValue($settlement);
2217
        $maturity = Functions::flattenSingleValue($maturity);
2218
        $price = Functions::flattenSingleValue($price);
2219
2220
        //    Validate
2221
        if (is_numeric($price)) {
2222
            if ($price <= 0) {
2223
                return Functions::NAN();
2224
            }
2225
2226 View Code Duplication
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2227
                ++$maturity;
2228
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2229
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2230
                    //    return date error
2231
                    return $daysBetweenSettlementAndMaturity;
2232
                }
2233
            } else {
2234
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2235
            }
2236
2237
            if ($daysBetweenSettlementAndMaturity > 360) {
2238
                return Functions::NAN();
2239
            }
2240
2241
            return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
2242
        }
2243
2244
        return Functions::VALUE();
2245
    }
2246
2247
    public static function XIRR($values, $dates, $guess = 0.1)
2248
    {
2249
        if ((!is_array($values)) && (!is_array($dates))) {
2250
            return Functions::VALUE();
2251
        }
2252
        $values = Functions::flattenArray($values);
2253
        $dates = Functions::flattenArray($dates);
2254
        $guess = Functions::flattenSingleValue($guess);
2255
        if (count($values) != count($dates)) {
2256
            return Functions::NAN();
2257
        }
2258
2259
        // create an initial range, with a root somewhere between 0 and guess
2260
        $x1 = 0.0;
2261
        $x2 = $guess;
2262
        $f1 = self::XNPV($x1, $values, $dates);
2263
        $f2 = self::XNPV($x2, $values, $dates);
2264 View Code Duplication
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2265
            if (($f1 * $f2) < 0.0) {
2266
                break;
2267
            } elseif (abs($f1) < abs($f2)) {
2268
                $f1 = self::XNPV($x1 += 1.6 * ($x1 - $x2), $values, $dates);
2269
            } else {
2270
                $f2 = self::XNPV($x2 += 1.6 * ($x2 - $x1), $values, $dates);
2271
            }
2272
        }
2273
        if (($f1 * $f2) > 0.0) {
2274
            return Functions::VALUE();
2275
        }
2276
2277
        $f = self::XNPV($x1, $values, $dates);
2278 View Code Duplication
        if ($f < 0.0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2279
            $rtb = $x1;
2280
            $dx = $x2 - $x1;
2281
        } else {
2282
            $rtb = $x2;
2283
            $dx = $x1 - $x2;
2284
        }
2285
2286 View Code Duplication
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2287
            $dx *= 0.5;
2288
            $x_mid = $rtb + $dx;
2289
            $f_mid = self::XNPV($x_mid, $values, $dates);
2290
            if ($f_mid <= 0.0) {
2291
                $rtb = $x_mid;
2292
            }
2293
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
2294
                return $x_mid;
2295
            }
2296
        }
2297
2298
        return Functions::VALUE();
2299
    }
2300
2301
    /**
2302
     * XNPV.
2303
     *
2304
     * Returns the net present value for a schedule of cash flows that is not necessarily periodic.
2305
     * To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
2306
     *
2307
     * Excel Function:
2308
     *        =XNPV(rate,values,dates)
2309
     *
2310
     * @param float $rate the discount rate to apply to the cash flows
2311
     * @param array of float    $values     A series of cash flows that corresponds to a schedule of payments in dates.
2312
     *                                         The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
2313
     *                                         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.
2314
     *                                         The series of values must contain at least one positive value and one negative value.
2315
     * @param array of mixed    $dates      A schedule of payment dates that corresponds to the cash flow payments.
2316
     *                                         The first payment date indicates the beginning of the schedule of payments.
2317
     *                                         All other dates must be later than this date, but they may occur in any order.
2318
     *
2319
     * @return float
2320
     */
2321
    public static function XNPV($rate, $values, $dates)
2322
    {
2323
        $rate = Functions::flattenSingleValue($rate);
2324
        if (!is_numeric($rate)) {
2325
            return Functions::VALUE();
2326
        }
2327
        if ((!is_array($values)) || (!is_array($dates))) {
2328
            return Functions::VALUE();
2329
        }
2330
        $values = Functions::flattenArray($values);
2331
        $dates = Functions::flattenArray($dates);
2332
        $valCount = count($values);
2333
        if ($valCount != count($dates)) {
2334
            return Functions::NAN();
2335
        }
2336
        if ((min($values) > 0) || (max($values) < 0)) {
2337
            return Functions::VALUE();
2338
        }
2339
2340
        $xnpv = 0.0;
2341
        for ($i = 0; $i < $valCount; ++$i) {
2342
            if (!is_numeric($values[$i])) {
2343
                return Functions::VALUE();
2344
            }
2345
            $xnpv += $values[$i] / pow(1 + $rate, DateTime::DATEDIF($dates[0], $dates[$i], 'd') / 365);
2346
        }
2347
2348
        return (is_finite($xnpv)) ? $xnpv : Functions::VALUE();
2349
    }
2350
2351
    /**
2352
     * YIELDDISC.
2353
     *
2354
     * Returns the annual yield of a security that pays interest at maturity.
2355
     *
2356
     * @param mixed settlement The security's settlement date.
2357
     *                                    The security's settlement date is the date after the issue date when the security is traded to the buyer.
2358
     * @param mixed maturity The security's maturity date.
2359
     *                                    The maturity date is the date when the security expires.
2360
     * @param int price The security's price per $100 face value
2361
     * @param int redemption The security's redemption value per $100 face value
2362
     * @param int basis The type of day count to use.
2363
     *                                        0 or omitted    US (NASD) 30/360
2364
     *                                        1                Actual/actual
2365
     *                                        2                Actual/360
2366
     *                                        3                Actual/365
2367
     *                                        4                European 30/360
2368
     * @param mixed $settlement
2369
     * @param mixed $maturity
2370
     * @param mixed $price
2371
     * @param mixed $redemption
2372
     * @param mixed $basis
2373
     *
2374
     * @return float
2375
     */
2376
    public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0)
2377
    {
2378
        $settlement = Functions::flattenSingleValue($settlement);
2379
        $maturity = Functions::flattenSingleValue($maturity);
2380
        $price = Functions::flattenSingleValue($price);
2381
        $redemption = Functions::flattenSingleValue($redemption);
2382
        $basis = (int) Functions::flattenSingleValue($basis);
2383
2384
        //    Validate
2385
        if (is_numeric($price) && is_numeric($redemption)) {
2386
            if (($price <= 0) || ($redemption <= 0)) {
2387
                return Functions::NAN();
2388
            }
2389
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2390
            if (!is_numeric($daysPerYear)) {
2391
                return $daysPerYear;
2392
            }
2393
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2394
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2395
                //    return date error
2396
                return $daysBetweenSettlementAndMaturity;
2397
            }
2398
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2399
2400
            return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
2401
        }
2402
2403
        return Functions::VALUE();
2404
    }
2405
2406
    /**
2407
     * YIELDMAT.
2408
     *
2409
     * Returns the annual yield of a security that pays interest at maturity.
2410
     *
2411
     * @param mixed settlement The security's settlement date.
2412
     *                                   The security's settlement date is the date after the issue date when the security is traded to the buyer.
2413
     * @param mixed maturity The security's maturity date.
2414
     *                                   The maturity date is the date when the security expires.
2415
     * @param mixed issue The security's issue date
2416
     * @param int rate The security's interest rate at date of issue
2417
     * @param int price The security's price per $100 face value
2418
     * @param int basis The type of day count to use.
2419
     *                                        0 or omitted    US (NASD) 30/360
2420
     *                                        1                Actual/actual
2421
     *                                        2                Actual/360
2422
     *                                        3                Actual/365
2423
     *                                        4                European 30/360
2424
     * @param mixed $settlement
2425
     * @param mixed $maturity
2426
     * @param mixed $issue
2427
     * @param mixed $rate
2428
     * @param mixed $price
2429
     * @param mixed $basis
2430
     *
2431
     * @return float
2432
     */
2433
    public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0)
2434
    {
2435
        $settlement = Functions::flattenSingleValue($settlement);
2436
        $maturity = Functions::flattenSingleValue($maturity);
2437
        $issue = Functions::flattenSingleValue($issue);
2438
        $rate = Functions::flattenSingleValue($rate);
2439
        $price = Functions::flattenSingleValue($price);
2440
        $basis = (int) Functions::flattenSingleValue($basis);
2441
2442
        //    Validate
2443
        if (is_numeric($rate) && is_numeric($price)) {
2444
            if (($rate <= 0) || ($price <= 0)) {
2445
                return Functions::NAN();
2446
            }
2447
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2448
            if (!is_numeric($daysPerYear)) {
2449
                return $daysPerYear;
2450
            }
2451
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
2452
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
2453
                //    return date error
2454
                return $daysBetweenIssueAndSettlement;
2455
            }
2456
            $daysBetweenIssueAndSettlement *= $daysPerYear;
2457
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
2458
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
2459
                //    return date error
2460
                return $daysBetweenIssueAndMaturity;
2461
            }
2462
            $daysBetweenIssueAndMaturity *= $daysPerYear;
2463
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2464
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2465
                //    return date error
2466
                return $daysBetweenSettlementAndMaturity;
2467
            }
2468
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2469
2470
            return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
2471
                   (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
2472
                   ($daysPerYear / $daysBetweenSettlementAndMaturity);
2473
        }
2474
2475
        return Functions::VALUE();
2476
    }
2477
}
2478