Completed
Push — master ( c4895b...a6c56d )
by Adrien
31:05 queued 25:18
created

MathTrig::SUBTOTAL()   C

Complexity

Conditions 15
Paths 25

Size

Total Lines 41
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 15.0068

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 15
eloc 32
c 1
b 0
f 1
nc 25
nop 1
dl 0
loc 41
rs 5.9166
ccs 31
cts 32
cp 0.9688
crap 15.0068

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use Matrix\Exception as MatrixException;
6
use Matrix\Matrix;
7
8
class MathTrig
9
{
10
    //
11
    //    Private method to return an array of the factors of the input value
12
    //
13 12
    private static function factors($value)
14
    {
15 12
        $startVal = floor(sqrt($value));
16
17 12
        $factorArray = [];
18 12
        for ($i = $startVal; $i > 1; --$i) {
19 11
            if (($value % $i) == 0) {
20 8
                $factorArray = array_merge($factorArray, self::factors($value / $i));
21 8
                $factorArray = array_merge($factorArray, self::factors($i));
22 8
                if ($i <= sqrt($value)) {
23 8
                    break;
24
                }
25
            }
26
        }
27 12
        if (!empty($factorArray)) {
28 8
            rsort($factorArray);
29
30 8
            return $factorArray;
31
        }
32
33 12
        return [(int) $value];
34
    }
35
36 6
    private static function romanCut($num, $n)
37
    {
38 6
        return ($num - ($num % $n)) / $n;
39
    }
40
41
    /**
42
     * ARABIC.
43
     *
44
     * Converts a Roman numeral to an Arabic numeral.
45
     *
46
     * Excel Function:
47
     *        ARABIC(text)
48
     *
49
     * @category Mathematical and Trigonometric Functions
50
     *
51
     * @param string $roman
52
     *
53
     * @return int|string the arabic numberal contrived from the roman numeral
54
     */
55 14
    public static function ARABIC($roman)
56
    {
57
        // An empty string should return 0
58 14
        $roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255);
59 14
        if ($roman === '') {
60 1
            return 0;
61
        }
62
63
        // Convert the roman numeral to an arabic number
64 13
        $negativeNumber = $roman[0] === '-';
65 13
        if ($negativeNumber) {
66 1
            $roman = substr($roman, 1);
67
        }
68
69
        try {
70 13
            $arabic = self::calculateArabic(str_split($roman));
71 1
        } catch (\Exception $e) {
72 1
            return Functions::VALUE(); // Invalid character detected
73
        }
74
75 12
        if ($negativeNumber) {
76 1
            $arabic *= -1; // The number should be negative
77
        }
78
79 12
        return $arabic;
80
    }
81
82
    /**
83
     * Recursively calculate the arabic value of a roman numeral.
84
     *
85
     * @param array $roman
86
     * @param int $sum
87
     * @param int $subtract
88
     *
89
     * @return int
90
     */
91 13
    protected static function calculateArabic(array $roman, &$sum = 0, $subtract = 0)
92
    {
93
        $lookup = [
94 13
            'M' => 1000,
95
            'D' => 500,
96
            'C' => 100,
97
            'L' => 50,
98
            'X' => 10,
99
            'V' => 5,
100
            'I' => 1,
101
        ];
102
103 13
        $numeral = array_shift($roman);
104 13
        if (!isset($lookup[$numeral])) {
105 1
            throw new \Exception('Invalid character detected');
106
        }
107
108 12
        $arabic = $lookup[$numeral];
109 12
        if (count($roman) > 0 && isset($lookup[$roman[0]]) && $arabic < $lookup[$roman[0]]) {
110 8
            $subtract += $arabic;
111
        } else {
112 12
            $sum += ($arabic - $subtract);
113 12
            $subtract = 0;
114
        }
115
116 12
        if (count($roman) > 0) {
117 11
            self::calculateArabic($roman, $sum, $subtract);
118
        }
119
120 12
        return $sum;
121
    }
122
123
    /**
124
     * ATAN2.
125
     *
126
     * This function calculates the arc tangent of the two variables x and y. It is similar to
127
     *        calculating the arc tangent of y ÷ x, except that the signs of both arguments are used
128
     *        to determine the quadrant of the result.
129
     * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a
130
     *        point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between
131
     *        -pi and pi, excluding -pi.
132
     *
133
     * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard
134
     *        PHP atan2() function, so we need to reverse them here before calling the PHP atan() function.
135
     *
136
     * Excel Function:
137
     *        ATAN2(xCoordinate,yCoordinate)
138
     *
139
     * @category Mathematical and Trigonometric Functions
140
     *
141
     * @param float $xCoordinate the x-coordinate of the point
142
     * @param float $yCoordinate the y-coordinate of the point
143
     *
144
     * @return float|string the inverse tangent of the specified x- and y-coordinates, or a string containing an error
145
     */
146 16
    public static function ATAN2($xCoordinate = null, $yCoordinate = null)
147
    {
148 16
        $xCoordinate = Functions::flattenSingleValue($xCoordinate);
149 16
        $yCoordinate = Functions::flattenSingleValue($yCoordinate);
150
151 16
        $xCoordinate = ($xCoordinate !== null) ? $xCoordinate : 0.0;
152 16
        $yCoordinate = ($yCoordinate !== null) ? $yCoordinate : 0.0;
153
154 16
        if (((is_numeric($xCoordinate)) || (is_bool($xCoordinate))) &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (is_numeric($xCoordinate...| is_bool($yCoordinate), Probably Intended Meaning: is_numeric($xCoordinate)... is_bool($yCoordinate))
Loading history...
155 16
            ((is_numeric($yCoordinate))) || (is_bool($yCoordinate))) {
156 15
            $xCoordinate = (float) $xCoordinate;
157 15
            $yCoordinate = (float) $yCoordinate;
158
159 15
            if (($xCoordinate == 0) && ($yCoordinate == 0)) {
160 1
                return Functions::DIV0();
161
            }
162
163 14
            return atan2($yCoordinate, $xCoordinate);
164
        }
165
166 1
        return Functions::VALUE();
167
    }
168
169
    /**
170
     * BASE.
171
     *
172
     * Converts a number into a text representation with the given radix (base).
173
     *
174
     * Excel Function:
175
     *        BASE(Number, Radix [Min_length])
176
     *
177
     * @category Mathematical and Trigonometric Functions
178
     *
179
     * @param float $number
180
     * @param float $radix
181
     * @param int $minLength
182
     *
183
     * @return string the text representation with the given radix (base)
184
     */
185 10
    public static function BASE($number, $radix, $minLength = null)
186
    {
187 10
        $number = Functions::flattenSingleValue($number);
188 10
        $radix = Functions::flattenSingleValue($radix);
189 10
        $minLength = Functions::flattenSingleValue($minLength);
190
191 10
        if (is_numeric($number) && is_numeric($radix) && ($minLength === null || is_numeric($minLength))) {
192
            // Truncate to an integer
193 7
            $number = (int) $number;
194 7
            $radix = (int) $radix;
195 7
            $minLength = (int) $minLength;
196
197 7
            if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) {
198 2
                return Functions::NAN(); // Numeric range constraints
199
            }
200
201 5
            $outcome = strtoupper((string) base_convert($number, 10, $radix));
202 5
            if ($minLength !== null) {
0 ignored issues
show
introduced by
The condition $minLength !== null is always true.
Loading history...
203 5
                $outcome = str_pad($outcome, $minLength, '0', STR_PAD_LEFT); // String padding
204
            }
205
206 5
            return $outcome;
207
        }
208
209 3
        return Functions::VALUE();
210
    }
211
212
    /**
213
     * CEILING.
214
     *
215
     * Returns number rounded up, away from zero, to the nearest multiple of significance.
216
     *        For example, if you want to avoid using pennies in your prices and your product is
217
     *        priced at $4.42, use the formula =CEILING(4.42,0.05) to round prices up to the
218
     *        nearest nickel.
219
     *
220
     * Excel Function:
221
     *        CEILING(number[,significance])
222
     *
223
     * @category Mathematical and Trigonometric Functions
224
     *
225
     * @param float $number the number you want to round
226
     * @param float $significance the multiple to which you want to round
227
     *
228
     * @return float|string Rounded Number, or a string containing an error
229
     */
230 43
    public static function CEILING($number, $significance = null)
231
    {
232 43
        $number = Functions::flattenSingleValue($number);
233 43
        $significance = Functions::flattenSingleValue($significance);
234
235 43
        if (($significance === null) &&
236 43
            (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
237
            $significance = $number / abs($number);
238
        }
239
240 43
        if ((is_numeric($number)) && (is_numeric($significance))) {
241 41
            if (($number == 0.0) || ($significance == 0.0)) {
242 3
                return 0.0;
243 38
            } elseif (self::SIGN($number) == self::SIGN($significance)) {
244 35
                return ceil($number / $significance) * $significance;
245
            }
246
247 3
            return Functions::NAN();
248
        }
249
250 2
        return Functions::VALUE();
251
    }
252
253
    /**
254
     * COMBIN.
255
     *
256
     * Returns the number of combinations for a given number of items. Use COMBIN to
257
     *        determine the total possible number of groups for a given number of items.
258
     *
259
     * Excel Function:
260
     *        COMBIN(numObjs,numInSet)
261
     *
262
     * @category Mathematical and Trigonometric Functions
263
     *
264
     * @param int $numObjs Number of different objects
265
     * @param int $numInSet Number of objects in each combination
266
     *
267
     * @return int|string Number of combinations, or a string containing an error
268
     */
269 35
    public static function COMBIN($numObjs, $numInSet)
270
    {
271 35
        $numObjs = Functions::flattenSingleValue($numObjs);
272 35
        $numInSet = Functions::flattenSingleValue($numInSet);
273
274 35
        if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
275 34
            if ($numObjs < $numInSet) {
276 3
                return Functions::NAN();
277 31
            } elseif ($numInSet < 0) {
278 2
                return Functions::NAN();
279
            }
280
281 29
            return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
282
        }
283
284 1
        return Functions::VALUE();
285
    }
286
287
    /**
288
     * EVEN.
289
     *
290
     * Returns number rounded up to the nearest even integer.
291
     * You can use this function for processing items that come in twos. For example,
292
     *        a packing crate accepts rows of one or two items. The crate is full when
293
     *        the number of items, rounded up to the nearest two, matches the crate's
294
     *        capacity.
295
     *
296
     * Excel Function:
297
     *        EVEN(number)
298
     *
299
     * @category Mathematical and Trigonometric Functions
300
     *
301
     * @param float $number Number to round
302
     *
303
     * @return int|string Rounded Number, or a string containing an error
304
     */
305 25
    public static function EVEN($number)
306
    {
307 25
        $number = Functions::flattenSingleValue($number);
308
309 25
        if ($number === null) {
310 1
            return 0;
311 24
        } elseif (is_bool($number)) {
312 2
            $number = (int) $number;
313
        }
314
315 24
        if (is_numeric($number)) {
316 23
            $significance = 2 * self::SIGN($number);
317
318 23
            return (int) self::CEILING($number, $significance);
319
        }
320
321 1
        return Functions::VALUE();
322
    }
323
324
    /**
325
     * FACT.
326
     *
327
     * Returns the factorial of a number.
328
     * The factorial of a number is equal to 1*2*3*...* number.
329
     *
330
     * Excel Function:
331
     *        FACT(factVal)
332
     *
333
     * @category Mathematical and Trigonometric Functions
334
     *
335
     * @param float $factVal Factorial Value
336
     *
337
     * @return int|string Factorial, or a string containing an error
338
     */
339 167
    public static function FACT($factVal)
340
    {
341 167
        $factVal = Functions::flattenSingleValue($factVal);
342
343 167
        if (is_numeric($factVal)) {
344 166
            if ($factVal < 0) {
345 1
                return Functions::NAN();
346
            }
347 165
            $factLoop = floor($factVal);
348 165
            if ((Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) &&
349 165
                ($factVal > $factLoop)) {
350
                return Functions::NAN();
351
            }
352
353 165
            $factorial = 1;
354 165
            while ($factLoop > 1) {
355 93
                $factorial *= $factLoop--;
356
            }
357
358 165
            return $factorial;
359
        }
360
361 1
        return Functions::VALUE();
362
    }
363
364
    /**
365
     * FACTDOUBLE.
366
     *
367
     * Returns the double factorial of a number.
368
     *
369
     * Excel Function:
370
     *        FACTDOUBLE(factVal)
371
     *
372
     * @category Mathematical and Trigonometric Functions
373
     *
374
     * @param float $factVal Factorial Value
375
     *
376
     * @return int|string Double Factorial, or a string containing an error
377
     */
378 8
    public static function FACTDOUBLE($factVal)
379
    {
380 8
        $factLoop = Functions::flattenSingleValue($factVal);
381
382 8
        if (is_numeric($factLoop)) {
383 7
            $factLoop = floor($factLoop);
384 7
            if ($factVal < 0) {
385 1
                return Functions::NAN();
386
            }
387 6
            $factorial = 1;
388 6
            while ($factLoop > 1) {
389 5
                $factorial *= $factLoop--;
390 5
                --$factLoop;
391
            }
392
393 6
            return $factorial;
394
        }
395
396 1
        return Functions::VALUE();
397
    }
398
399
    /**
400
     * FLOOR.
401
     *
402
     * Rounds number down, toward zero, to the nearest multiple of significance.
403
     *
404
     * Excel Function:
405
     *        FLOOR(number[,significance])
406
     *
407
     * @category Mathematical and Trigonometric Functions
408
     *
409
     * @param float $number Number to round
410
     * @param float $significance Significance
411
     *
412
     * @return float|string Rounded Number, or a string containing an error
413
     */
414 11
    public static function FLOOR($number, $significance = null)
415
    {
416 11
        $number = Functions::flattenSingleValue($number);
417 11
        $significance = Functions::flattenSingleValue($significance);
418
419 11
        if (($significance === null) &&
420 11
            (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) {
421
            $significance = $number / abs($number);
422
        }
423
424 11
        if ((is_numeric($number)) && (is_numeric($significance))) {
425 9
            if ($significance == 0.0) {
426 1
                return Functions::DIV0();
427 8
            } elseif ($number == 0.0) {
428
                return 0.0;
429 8
            } elseif (self::SIGN($significance) == 1) {
430 6
                return floor($number / $significance) * $significance;
431 2
            } elseif (self::SIGN($number) == -1 && self::SIGN($significance) == -1) {
432 1
                return floor($number / $significance) * $significance;
433
            }
434
435 1
            return Functions::NAN();
436
        }
437
438 2
        return Functions::VALUE();
439
    }
440
441
    /**
442
     * FLOOR.MATH.
443
     *
444
     * Round a number down to the nearest integer or to the nearest multiple of significance.
445
     *
446
     * Excel Function:
447
     *        FLOOR.MATH(number[,significance[,mode]])
448
     *
449
     * @category Mathematical and Trigonometric Functions
450
     *
451
     * @param float $number Number to round
452
     * @param float $significance Significance
453
     * @param int $mode direction to round negative numbers
454
     *
455
     * @return float|string Rounded Number, or a string containing an error
456
     */
457 17
    public static function FLOORMATH($number, $significance = null, $mode = 0)
458
    {
459 17
        $number = Functions::flattenSingleValue($number);
460 17
        $significance = Functions::flattenSingleValue($significance);
461 17
        $mode = Functions::flattenSingleValue($mode);
462
463 17
        if (is_numeric($number) && $significance === null) {
464 2
            $significance = $number / abs($number);
465
        }
466
467 17
        if (is_numeric($number) && is_numeric($significance) && is_numeric($mode)) {
468 16
            if ($significance == 0.0) {
469 1
                return Functions::DIV0();
470 15
            } elseif ($number == 0.0) {
471
                return 0.0;
472 15
            } elseif (self::SIGN($significance) == -1 || (self::SIGN($number) == -1 && !empty($mode))) {
473 4
                return ceil($number / $significance) * $significance;
474
            }
475
476 11
            return floor($number / $significance) * $significance;
477
        }
478
479 1
        return Functions::VALUE();
480
    }
481
482
    /**
483
     * FLOOR.PRECISE.
484
     *
485
     * Rounds number down, toward zero, to the nearest multiple of significance.
486
     *
487
     * Excel Function:
488
     *        FLOOR.PRECISE(number[,significance])
489
     *
490
     * @category Mathematical and Trigonometric Functions
491
     *
492
     * @param float $number Number to round
493
     * @param float $significance Significance
494
     *
495
     * @return float|string Rounded Number, or a string containing an error
496
     */
497 11
    public static function FLOORPRECISE($number, $significance = 1)
498
    {
499 11
        $number = Functions::flattenSingleValue($number);
500 11
        $significance = Functions::flattenSingleValue($significance);
501
502 11
        if ((is_numeric($number)) && (is_numeric($significance))) {
503 10
            if ($significance == 0.0) {
504 1
                return Functions::DIV0();
505 9
            } elseif ($number == 0.0) {
506
                return 0.0;
507
            }
508
509 9
            return floor($number / abs($significance)) * abs($significance);
510
        }
511
512 1
        return Functions::VALUE();
513
    }
514
515 25
    private static function evaluateGCD($a, $b)
516
    {
517 25
        return $b ? self::evaluateGCD($b, $a % $b) : $a;
518
    }
519
520
    /**
521
     * GCD.
522
     *
523
     * Returns the greatest common divisor of a series of numbers.
524
     * The greatest common divisor is the largest integer that divides both
525
     *        number1 and number2 without a remainder.
526
     *
527
     * Excel Function:
528
     *        GCD(number1[,number2[, ...]])
529
     *
530
     * @category Mathematical and Trigonometric Functions
531
     *
532
     * @param mixed ...$args Data values
533
     *
534
     * @return int|mixed|string Greatest Common Divisor, or a string containing an error
535
     */
536 27
    public static function GCD(...$args)
537
    {
538 27
        $args = Functions::flattenArray($args);
539
        // Loop through arguments
540 27
        foreach (Functions::flattenArray($args) as $value) {
541 27
            if (!is_numeric($value)) {
542 1
                return Functions::VALUE();
543 27
            } elseif ($value < 0) {
544 1
                return Functions::NAN();
545
            }
546
        }
547
548 25
        $gcd = (int) array_pop($args);
549
        do {
550 25
            $gcd = self::evaluateGCD($gcd, (int) array_pop($args));
551 25
        } while (!empty($args));
552
553 25
        return $gcd;
554
    }
555
556
    /**
557
     * INT.
558
     *
559
     * Casts a floating point value to an integer
560
     *
561
     * Excel Function:
562
     *        INT(number)
563
     *
564
     * @category Mathematical and Trigonometric Functions
565
     *
566
     * @param float $number Number to cast to an integer
567
     *
568
     * @return int|string Integer value, or a string containing an error
569
     */
570 19
    public static function INT($number)
571
    {
572 19
        $number = Functions::flattenSingleValue($number);
573
574 19
        if ($number === null) {
575 1
            return 0;
576 18
        } elseif (is_bool($number)) {
577 2
            return (int) $number;
578
        }
579 16
        if (is_numeric($number)) {
580 15
            return (int) floor($number);
581
        }
582
583 1
        return Functions::VALUE();
584
    }
585
586
    /**
587
     * LCM.
588
     *
589
     * Returns the lowest common multiplier of a series of numbers
590
     * The least common multiple is the smallest positive integer that is a multiple
591
     * of all integer arguments number1, number2, and so on. Use LCM to add fractions
592
     * with different denominators.
593
     *
594
     * Excel Function:
595
     *        LCM(number1[,number2[, ...]])
596
     *
597
     * @category Mathematical and Trigonometric Functions
598
     *
599
     * @param mixed ...$args Data values
600
     *
601
     * @return int|string Lowest Common Multiplier, or a string containing an error
602
     */
603 12
    public static function LCM(...$args)
604
    {
605 12
        $returnValue = 1;
606 12
        $allPoweredFactors = [];
607
        // Loop through arguments
608 12
        foreach (Functions::flattenArray($args) as $value) {
609 12
            if (!is_numeric($value)) {
610 1
                return Functions::VALUE();
611
            }
612 12
            if ($value == 0) {
613 1
                return 0;
614 12
            } elseif ($value < 0) {
615 1
                return Functions::NAN();
616
            }
617 12
            $myFactors = self::factors(floor($value));
618 12
            $myCountedFactors = array_count_values($myFactors);
619 12
            $myPoweredFactors = [];
620 12
            foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) {
621 12
                $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor, $myCountedPower);
622
            }
623 12
            foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
624 12
                if (isset($allPoweredFactors[$myPoweredValue])) {
625 6
                    if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
626 6
                        $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
627
                    }
628
                } else {
629 12
                    $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
630
                }
631
            }
632
        }
633 9
        foreach ($allPoweredFactors as $allPoweredFactor) {
634 9
            $returnValue *= (int) $allPoweredFactor;
635
        }
636
637 9
        return $returnValue;
638
    }
639
640
    /**
641
     * LOG_BASE.
642
     *
643
     * Returns the logarithm of a number to a specified base. The default base is 10.
644
     *
645
     * Excel Function:
646
     *        LOG(number[,base])
647
     *
648
     * @category Mathematical and Trigonometric Functions
649
     *
650
     * @param float $number The positive real number for which you want the logarithm
651
     * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10.
652
     *
653
     * @return float|string The result, or a string containing an error
654
     */
655 69
    public static function logBase($number = null, $base = 10)
656
    {
657 69
        $number = Functions::flattenSingleValue($number);
658 69
        $base = ($base === null) ? 10 : (float) Functions::flattenSingleValue($base);
0 ignored issues
show
introduced by
The condition $base === null is always false.
Loading history...
659
660 69
        if ((!is_numeric($base)) || (!is_numeric($number))) {
0 ignored issues
show
introduced by
The condition is_numeric($base) is always true.
Loading history...
661 2
            return Functions::VALUE();
662
        }
663 67
        if (($base <= 0) || ($number <= 0)) {
664 18
            return Functions::NAN();
665
        }
666
667 49
        return log($number, $base);
668
    }
669
670
    /**
671
     * MDETERM.
672
     *
673
     * Returns the matrix determinant of an array.
674
     *
675
     * Excel Function:
676
     *        MDETERM(array)
677
     *
678
     * @category Mathematical and Trigonometric Functions
679
     *
680
     * @param array $matrixValues A matrix of values
681
     *
682
     * @return float|string The result, or a string containing an error
683
     */
684 15
    public static function MDETERM($matrixValues)
685
    {
686 15
        $matrixData = [];
687 15
        if (!is_array($matrixValues)) {
0 ignored issues
show
introduced by
The condition is_array($matrixValues) is always true.
Loading history...
688
            $matrixValues = [[$matrixValues]];
689
        }
690
691 15
        $row = $maxColumn = 0;
692 15
        foreach ($matrixValues as $matrixRow) {
693 15
            if (!is_array($matrixRow)) {
694
                $matrixRow = [$matrixRow];
695
            }
696 15
            $column = 0;
697 15
            foreach ($matrixRow as $matrixCell) {
698 15
                if ((is_string($matrixCell)) || ($matrixCell === null)) {
699 1
                    return Functions::VALUE();
700
                }
701 15
                $matrixData[$row][$column] = $matrixCell;
702 15
                ++$column;
703
            }
704 15
            if ($column > $maxColumn) {
705 15
                $maxColumn = $column;
706
            }
707 15
            ++$row;
708
        }
709
710 14
        $matrix = new Matrix($matrixData);
711 14
        if (!$matrix->isSquare()) {
712 1
            return Functions::VALUE();
713
        }
714
715
        try {
716 13
            return $matrix->determinant();
717
        } catch (MatrixException $ex) {
718
            return Functions::VALUE();
719
        }
720
    }
721
722
    /**
723
     * MINVERSE.
724
     *
725
     * Returns the inverse matrix for the matrix stored in an array.
726
     *
727
     * Excel Function:
728
     *        MINVERSE(array)
729
     *
730
     * @category Mathematical and Trigonometric Functions
731
     *
732
     * @param array $matrixValues A matrix of values
733
     *
734
     * @return array|string The result, or a string containing an error
735
     */
736 11
    public static function MINVERSE($matrixValues)
737
    {
738 11
        $matrixData = [];
739 11
        if (!is_array($matrixValues)) {
0 ignored issues
show
introduced by
The condition is_array($matrixValues) is always true.
Loading history...
740
            $matrixValues = [[$matrixValues]];
741
        }
742
743 11
        $row = $maxColumn = 0;
744 11
        foreach ($matrixValues as $matrixRow) {
745 11
            if (!is_array($matrixRow)) {
746
                $matrixRow = [$matrixRow];
747
            }
748 11
            $column = 0;
749 11
            foreach ($matrixRow as $matrixCell) {
750 11
                if ((is_string($matrixCell)) || ($matrixCell === null)) {
751
                    return Functions::VALUE();
752
                }
753 11
                $matrixData[$row][$column] = $matrixCell;
754 11
                ++$column;
755
            }
756 11
            if ($column > $maxColumn) {
757 11
                $maxColumn = $column;
758
            }
759 11
            ++$row;
760
        }
761
762 11
        $matrix = new Matrix($matrixData);
763 11
        if (!$matrix->isSquare()) {
764
            return Functions::VALUE();
765
        }
766
767 11
        if ($matrix->determinant() == 0.0) {
768
            return Functions::NAN();
769
        }
770
771
        try {
772 11
            return $matrix->inverse()->toArray();
773
        } catch (MatrixException $ex) {
774
            return Functions::VALUE();
775
        }
776
    }
777
778
    /**
779
     * MMULT.
780
     *
781
     * @param array $matrixData1 A matrix of values
782
     * @param array $matrixData2 A matrix of values
783
     *
784
     * @return array|string The result, or a string containing an error
785
     */
786 9
    public static function MMULT($matrixData1, $matrixData2)
787
    {
788 9
        $matrixAData = $matrixBData = [];
789 9
        if (!is_array($matrixData1)) {
0 ignored issues
show
introduced by
The condition is_array($matrixData1) is always true.
Loading history...
790
            $matrixData1 = [[$matrixData1]];
791
        }
792 9
        if (!is_array($matrixData2)) {
0 ignored issues
show
introduced by
The condition is_array($matrixData2) is always true.
Loading history...
793
            $matrixData2 = [[$matrixData2]];
794
        }
795
796
        try {
797 9
            $rowA = 0;
798 9
            foreach ($matrixData1 as $matrixRow) {
799 9
                if (!is_array($matrixRow)) {
800
                    $matrixRow = [$matrixRow];
801
                }
802 9
                $columnA = 0;
803 9
                foreach ($matrixRow as $matrixCell) {
804 9
                    if ((!is_numeric($matrixCell)) || ($matrixCell === null)) {
805
                        return Functions::VALUE();
806
                    }
807 9
                    $matrixAData[$rowA][$columnA] = $matrixCell;
808 9
                    ++$columnA;
809
                }
810 9
                ++$rowA;
811
            }
812 9
            $matrixA = new Matrix($matrixAData);
813 9
            $rowB = 0;
814 9
            foreach ($matrixData2 as $matrixRow) {
815 9
                if (!is_array($matrixRow)) {
816 1
                    $matrixRow = [$matrixRow];
817
                }
818 9
                $columnB = 0;
819 9
                foreach ($matrixRow as $matrixCell) {
820 9
                    if ((!is_numeric($matrixCell)) || ($matrixCell === null)) {
821
                        return Functions::VALUE();
822
                    }
823 9
                    $matrixBData[$rowB][$columnB] = $matrixCell;
824 9
                    ++$columnB;
825
                }
826 9
                ++$rowB;
827
            }
828 9
            $matrixB = new Matrix($matrixBData);
829
830 9
            if ($columnA != $rowB) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $columnA seems to be defined by a foreach iteration on line 798. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
831 2
                return Functions::VALUE();
832
            }
833
834 7
            return $matrixA->multiply($matrixB)->toArray();
835
        } catch (MatrixException $ex) {
836
            return Functions::VALUE();
837
        }
838
    }
839
840
    /**
841
     * MOD.
842
     *
843
     * @param int $a Dividend
844
     * @param int $b Divisor
845
     *
846
     * @return int|string Remainder, or a string containing an error
847
     */
848 10
    public static function MOD($a = 1, $b = 1)
849
    {
850 10
        $a = (float) Functions::flattenSingleValue($a);
851 10
        $b = (float) Functions::flattenSingleValue($b);
852
853 10
        if ($b == 0.0) {
854 1
            return Functions::DIV0();
855 9
        } elseif (($a < 0.0) && ($b > 0.0)) {
856 1
            return $b - fmod(abs($a), $b);
857 8
        } elseif (($a > 0.0) && ($b < 0.0)) {
858 2
            return $b + fmod($a, abs($b));
859
        }
860
861 6
        return fmod($a, $b);
862
    }
863
864
    /**
865
     * MROUND.
866
     *
867
     * Rounds a number to the nearest multiple of a specified value
868
     *
869
     * @param float $number Number to round
870
     * @param int $multiple Multiple to which you want to round $number
871
     *
872
     * @return float|string Rounded Number, or a string containing an error
873
     */
874 13
    public static function MROUND($number, $multiple)
875
    {
876 13
        $number = Functions::flattenSingleValue($number);
877 13
        $multiple = Functions::flattenSingleValue($multiple);
878
879 13
        if ((is_numeric($number)) && (is_numeric($multiple))) {
880 11
            if ($multiple == 0) {
881 1
                return 0;
882
            }
883 10
            if ((self::SIGN($number)) == (self::SIGN($multiple))) {
884 9
                $multiplier = 1 / $multiple;
885
886 9
                return round($number * $multiplier) / $multiplier;
887
            }
888
889 1
            return Functions::NAN();
890
        }
891
892 2
        return Functions::VALUE();
893
    }
894
895
    /**
896
     * MULTINOMIAL.
897
     *
898
     * Returns the ratio of the factorial of a sum of values to the product of factorials.
899
     *
900
     * @param array of mixed Data Series
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\Calculation\of was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
901
     *
902
     * @return float|string The result, or a string containing an error
903
     */
904 2
    public static function MULTINOMIAL(...$args)
905
    {
906 2
        $summer = 0;
907 2
        $divisor = 1;
908
        // Loop through arguments
909 2
        foreach (Functions::flattenArray($args) as $arg) {
910
            // Is it a numeric value?
911 2
            if (is_numeric($arg)) {
912 2
                if ($arg < 1) {
913
                    return Functions::NAN();
914
                }
915 2
                $summer += floor($arg);
916 2
                $divisor *= self::FACT($arg);
917
            } else {
918
                return Functions::VALUE();
919
            }
920
        }
921
922
        // Return
923 2
        if ($summer > 0) {
924 2
            $summer = self::FACT($summer);
925
926 2
            return $summer / $divisor;
927
        }
928
929
        return 0;
930
    }
931
932
    /**
933
     * ODD.
934
     *
935
     * Returns number rounded up to the nearest odd integer.
936
     *
937
     * @param float $number Number to round
938
     *
939
     * @return int|string Rounded Number, or a string containing an error
940
     */
941 13
    public static function ODD($number)
942
    {
943 13
        $number = Functions::flattenSingleValue($number);
944
945 13
        if ($number === null) {
946 1
            return 1;
947 12
        } elseif (is_bool($number)) {
948 2
            return 1;
949 10
        } elseif (is_numeric($number)) {
950 9
            $significance = self::SIGN($number);
951 9
            if ($significance == 0) {
952 1
                return 1;
953
            }
954
955 8
            $result = self::CEILING($number, $significance);
1 ignored issue
show
Bug introduced by
It seems like $significance can also be of type string; however, parameter $significance of PhpOffice\PhpSpreadsheet...ion\MathTrig::CEILING() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

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

955
            $result = self::CEILING($number, /** @scrutinizer ignore-type */ $significance);
Loading history...
956 8
            if ($result == self::EVEN($result)) {
1 ignored issue
show
Bug introduced by
It seems like $result can also be of type string; however, parameter $number of PhpOffice\PhpSpreadsheet...lation\MathTrig::EVEN() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

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

956
            if ($result == self::EVEN(/** @scrutinizer ignore-type */ $result)) {
Loading history...
957 5
                $result += $significance;
958
            }
959
960 8
            return (int) $result;
961
        }
962
963 1
        return Functions::VALUE();
964
    }
965
966
    /**
967
     * POWER.
968
     *
969
     * Computes x raised to the power y.
970
     *
971
     * @param float $x
972
     * @param float $y
973
     *
974
     * @return float|string The result, or a string containing an error
975
     */
976 81
    public static function POWER($x = 0, $y = 2)
977
    {
978 81
        $x = Functions::flattenSingleValue($x);
979 81
        $y = Functions::flattenSingleValue($y);
980
981
        // Validate parameters
982 81
        if ($x == 0.0 && $y == 0.0) {
983 1
            return Functions::NAN();
984 80
        } elseif ($x == 0.0 && $y < 0.0) {
985 2
            return Functions::DIV0();
986
        }
987
988
        // Return
989 78
        $result = pow($x, $y);
990
991 78
        return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN();
992
    }
993
994
    /**
995
     * PRODUCT.
996
     *
997
     * PRODUCT returns the product of all the values and cells referenced in the argument list.
998
     *
999
     * Excel Function:
1000
     *        PRODUCT(value1[,value2[, ...]])
1001
     *
1002
     * @category Mathematical and Trigonometric Functions
1003
     *
1004
     * @param mixed ...$args Data values
1005
     *
1006
     * @return float
1007
     */
1008 11
    public static function PRODUCT(...$args)
1009
    {
1010
        // Return value
1011 11
        $returnValue = null;
1012
1013
        // Loop through arguments
1014 11
        foreach (Functions::flattenArray($args) as $arg) {
1015
            // Is it a numeric value?
1016 11
            if ((is_numeric($arg)) && (!is_string($arg))) {
1017 11
                if ($returnValue === null) {
1018 11
                    $returnValue = $arg;
1019
                } else {
1020 11
                    $returnValue *= $arg;
1021
                }
1022
            }
1023
        }
1024
1025
        // Return
1026 11
        if ($returnValue === null) {
1027
            return 0;
1028
        }
1029
1030 11
        return $returnValue;
1031
    }
1032
1033
    /**
1034
     * QUOTIENT.
1035
     *
1036
     * QUOTIENT function returns the integer portion of a division. Numerator is the divided number
1037
     *        and denominator is the divisor.
1038
     *
1039
     * Excel Function:
1040
     *        QUOTIENT(value1[,value2[, ...]])
1041
     *
1042
     * @category Mathematical and Trigonometric Functions
1043
     *
1044
     * @param mixed ...$args Data values
1045
     *
1046
     * @return float
1047
     */
1048 6
    public static function QUOTIENT(...$args)
1049
    {
1050
        // Return value
1051 6
        $returnValue = null;
1052
1053
        // Loop through arguments
1054 6
        foreach (Functions::flattenArray($args) as $arg) {
1055
            // Is it a numeric value?
1056 6
            if ((is_numeric($arg)) && (!is_string($arg))) {
1057 6
                if ($returnValue === null) {
1058 6
                    $returnValue = ($arg == 0) ? 0 : $arg;
1059
                } else {
1060 6
                    if (($returnValue == 0) || ($arg == 0)) {
1061
                        $returnValue = 0;
1062
                    } else {
1063 6
                        $returnValue /= $arg;
1064
                    }
1065
                }
1066
            }
1067
        }
1068
1069
        // Return
1070 6
        return (int) $returnValue;
1071
    }
1072
1073
    /**
1074
     * RAND.
1075
     *
1076
     * @param int $min Minimal value
1077
     * @param int $max Maximal value
1078
     *
1079
     * @return int Random number
1080
     */
1081 3
    public static function RAND($min = 0, $max = 0)
1082
    {
1083 3
        $min = Functions::flattenSingleValue($min);
1084 3
        $max = Functions::flattenSingleValue($max);
1085
1086 3
        if ($min == 0 && $max == 0) {
1087 1
            return (mt_rand(0, 10000000)) / 10000000;
1088
        }
1089
1090 3
        return mt_rand($min, $max);
1091
    }
1092
1093 6
    public static function ROMAN($aValue, $style = 0)
1094
    {
1095 6
        $aValue = Functions::flattenSingleValue($aValue);
1096 6
        $style = ($style === null) ? 0 : (int) Functions::flattenSingleValue($style);
0 ignored issues
show
Unused Code introduced by
The assignment to $style is dead and can be removed.
Loading history...
1097 6
        if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) {
1098
            return Functions::VALUE();
1099
        }
1100 6
        $aValue = (int) $aValue;
1101 6
        if ($aValue == 0) {
1102
            return '';
1103
        }
1104
1105 6
        $mill = ['', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM'];
1106 6
        $cent = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'];
1107 6
        $tens = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'];
1108 6
        $ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'];
1109
1110 6
        $roman = '';
1111 6
        while ($aValue > 5999) {
1112
            $roman .= 'M';
1113
            $aValue -= 1000;
1114
        }
1115 6
        $m = self::romanCut($aValue, 1000);
1116 6
        $aValue %= 1000;
1117 6
        $c = self::romanCut($aValue, 100);
1118 6
        $aValue %= 100;
1119 6
        $t = self::romanCut($aValue, 10);
1120 6
        $aValue %= 10;
1121
1122 6
        return $roman . $mill[$m] . $cent[$c] . $tens[$t] . $ones[$aValue];
1123
    }
1124
1125
    /**
1126
     * ROUNDUP.
1127
     *
1128
     * Rounds a number up to a specified number of decimal places
1129
     *
1130
     * @param float $number Number to round
1131
     * @param int $digits Number of digits to which you want to round $number
1132
     *
1133
     * @return float|string Rounded Number, or a string containing an error
1134
     */
1135 16
    public static function ROUNDUP($number, $digits)
1136
    {
1137 16
        $number = Functions::flattenSingleValue($number);
1138 16
        $digits = Functions::flattenSingleValue($digits);
1139
1140 16
        if ((is_numeric($number)) && (is_numeric($digits))) {
1141 14
            if ($number < 0.0) {
1142 2
                $significance = pow(10, (int) $digits);
1143
1144 2
                return floor($number * $significance) / $significance;
1145
            }
1146
1147 12
            return round($number + 0.5 * pow(0.1, $digits), $digits, PHP_ROUND_HALF_DOWN);
1148
        }
1149
1150 2
        return Functions::VALUE();
1151
    }
1152
1153
    /**
1154
     * ROUNDDOWN.
1155
     *
1156
     * Rounds a number down to a specified number of decimal places
1157
     *
1158
     * @param float $number Number to round
1159
     * @param int $digits Number of digits to which you want to round $number
1160
     *
1161
     * @return float|string Rounded Number, or a string containing an error
1162
     */
1163 16
    public static function ROUNDDOWN($number, $digits)
1164
    {
1165 16
        $number = Functions::flattenSingleValue($number);
1166 16
        $digits = Functions::flattenSingleValue($digits);
1167
1168 16
        if ((is_numeric($number)) && (is_numeric($digits))) {
1169 14
            if ($number < 0.0) {
1170 2
                $significance = pow(10, (int) $digits);
1171
1172 2
                return ceil($number * $significance) / $significance;
1173
            }
1174
1175 12
            return round($number - 0.5 * pow(0.1, $digits), $digits, PHP_ROUND_HALF_UP);
1176
        }
1177
1178 2
        return Functions::VALUE();
1179
    }
1180
1181
    /**
1182
     * SERIESSUM.
1183
     *
1184
     * Returns the sum of a power series
1185
     *
1186
     * @param float $x Input value to the power series
1187
     * @param float $n Initial power to which you want to raise $x
1188
     * @param float $m Step by which to increase $n for each term in the series
1189
     * @param array of mixed Data Series
1190
     *
1191
     * @return float|string The result, or a string containing an error
1192
     */
1193 2
    public static function SERIESSUM(...$args)
1194
    {
1195 2
        $returnValue = 0;
1196
1197
        // Loop through arguments
1198 2
        $aArgs = Functions::flattenArray($args);
1199
1200 2
        $x = array_shift($aArgs);
1201 2
        $n = array_shift($aArgs);
1202 2
        $m = array_shift($aArgs);
1203
1204 2
        if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) {
1205
            // Calculate
1206 2
            $i = 0;
1207 2
            foreach ($aArgs as $arg) {
1208
                // Is it a numeric value?
1209 2
                if ((is_numeric($arg)) && (!is_string($arg))) {
1210 2
                    $returnValue += $arg * pow($x, $n + ($m * $i++));
1211
                } else {
1212
                    return Functions::VALUE();
1213
                }
1214
            }
1215
1216 2
            return $returnValue;
1217
        }
1218
1219
        return Functions::VALUE();
1220
    }
1221
1222
    /**
1223
     * SIGN.
1224
     *
1225
     * Determines the sign of a number. Returns 1 if the number is positive, zero (0)
1226
     *        if the number is 0, and -1 if the number is negative.
1227
     *
1228
     * @param float $number Number to round
1229
     *
1230
     * @return int|string sign value, or a string containing an error
1231
     */
1232 87
    public static function SIGN($number)
1233
    {
1234 87
        $number = Functions::flattenSingleValue($number);
1235
1236 87
        if (is_bool($number)) {
1237 2
            return (int) $number;
1238
        }
1239 85
        if (is_numeric($number)) {
1240 84
            if ($number == 0.0) {
1241 4
                return 0;
1242
            }
1243
1244 80
            return $number / abs($number);
1245
        }
1246
1247 1
        return Functions::VALUE();
1248
    }
1249
1250
    /**
1251
     * SQRTPI.
1252
     *
1253
     * Returns the square root of (number * pi).
1254
     *
1255
     * @param float $number Number
1256
     *
1257
     * @return float|string Square Root of Number * Pi, or a string containing an error
1258
     */
1259 15
    public static function SQRTPI($number)
1260
    {
1261 15
        $number = Functions::flattenSingleValue($number);
1262
1263 15
        if (is_numeric($number)) {
1264 14
            if ($number < 0) {
1265 3
                return Functions::NAN();
1266
            }
1267
1268 11
            return sqrt($number * M_PI);
1269
        }
1270
1271 1
        return Functions::VALUE();
1272
    }
1273
1274 11
    protected static function filterHiddenArgs($cellReference, $args)
1275
    {
1276 11
        return array_filter(
1277
            $args,
1278
            function ($index) use ($cellReference) {
1279 11
                [, $row, $column] = explode('.', $index);
1280
1281 11
                return $cellReference->getWorksheet()->getRowDimension($row)->getVisible() &&
1282 11
                    $cellReference->getWorksheet()->getColumnDimension($column)->getVisible();
1283 11
            },
1284 11
            ARRAY_FILTER_USE_KEY
1285
        );
1286
    }
1287
1288 23
    protected static function filterFormulaArgs($cellReference, $args)
1289
    {
1290 23
        return array_filter(
1291
            $args,
1292
            function ($index) use ($cellReference) {
1293 23
                [, $row, $column] = explode('.', $index);
1294 23
                if ($cellReference->getWorksheet()->cellExists($column . $row)) {
1295
                    //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula
1296 23
                    $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula();
1297 23
                    $cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue());
1298
1299 23
                    return !$isFormula || $cellFormula;
1300
                }
1301
1302
                return true;
1303 23
            },
1304 23
            ARRAY_FILTER_USE_KEY
1305
        );
1306
    }
1307
1308
    /**
1309
     * SUBTOTAL.
1310
     *
1311
     * Returns a subtotal in a list or database.
1312
     *
1313
     * @param int the number 1 to 11 that specifies which function to
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\Calculation\the was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
1314
     *                    use in calculating subtotals within a range
1315
     *                    list
1316
     *            Numbers 101 to 111 shadow the functions of 1 to 11
1317
     *                    but ignore any values in the range that are
1318
     *                    in hidden rows or columns
1319
     * @param array of mixed Data Series
1320
     *
1321
     * @return float|string
1322
     */
1323 23
    public static function SUBTOTAL(...$args)
1324
    {
1325 23
        $cellReference = array_pop($args);
1326 23
        $aArgs = Functions::flattenArrayIndexed($args);
1327 23
        $subtotal = array_shift($aArgs);
1328
1329
        // Calculate
1330 23
        if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
1331 23
            if ($subtotal > 100) {
1332 11
                $aArgs = self::filterHiddenArgs($cellReference, $aArgs);
1333 11
                $subtotal -= 100;
1334
            }
1335
1336 23
            $aArgs = self::filterFormulaArgs($cellReference, $aArgs);
1337
            switch ($subtotal) {
1338 23
                case 1:
1339 2
                    return Statistical::AVERAGE($aArgs);
1340 21
                case 2:
1341 2
                    return Statistical::COUNT($aArgs);
1342 19
                case 3:
1343 2
                    return Statistical::COUNTA($aArgs);
1344 17
                case 4:
1345 2
                    return Statistical::MAX($aArgs);
1346 15
                case 5:
1347 2
                    return Statistical::MIN($aArgs);
1348 13
                case 6:
1349 2
                    return self::PRODUCT($aArgs);
1350 11
                case 7:
1351 2
                    return Statistical::STDEV($aArgs);
1352 9
                case 8:
1353 2
                    return Statistical::STDEVP($aArgs);
1354 7
                case 9:
1355 3
                    return self::SUM($aArgs);
1356 4
                case 10:
1357 2
                    return Statistical::VARFunc($aArgs);
1358 2
                case 11:
1359 2
                    return Statistical::VARP($aArgs);
1360
            }
1361
        }
1362
1363
        return Functions::VALUE();
1364
    }
1365
1366
    /**
1367
     * SUM.
1368
     *
1369
     * SUM computes the sum of all the values and cells referenced in the argument list.
1370
     *
1371
     * Excel Function:
1372
     *        SUM(value1[,value2[, ...]])
1373
     *
1374
     * @category Mathematical and Trigonometric Functions
1375
     *
1376
     * @param mixed ...$args Data values
1377
     *
1378
     * @return float
1379
     */
1380 40
    public static function SUM(...$args)
1381
    {
1382 40
        $returnValue = 0;
1383
1384
        // Loop through the arguments
1385 40
        foreach (Functions::flattenArray($args) as $arg) {
1386
            // Is it a numeric value?
1387 40
            if ((is_numeric($arg)) && (!is_string($arg))) {
1388 40
                $returnValue += $arg;
1389
            }
1390
        }
1391
1392 40
        return $returnValue;
1393
    }
1394
1395
    /**
1396
     * SUMIF.
1397
     *
1398
     * Counts the number of cells that contain numbers within the list of arguments
1399
     *
1400
     * Excel Function:
1401
     *        SUMIF(value1[,value2[, ...]],condition)
1402
     *
1403
     * @category Mathematical and Trigonometric Functions
1404
     *
1405
     * @param mixed $aArgs Data values
1406
     * @param string $condition the criteria that defines which cells will be summed
1407
     * @param mixed $sumArgs
1408
     *
1409
     * @return float
1410
     */
1411 10
    public static function SUMIF($aArgs, $condition, $sumArgs = [])
1412
    {
1413 10
        $returnValue = 0;
1414
1415 10
        $aArgs = Functions::flattenArray($aArgs);
1416 10
        $sumArgs = Functions::flattenArray($sumArgs);
1417 10
        if (empty($sumArgs)) {
1418 1
            $sumArgs = $aArgs;
1419
        }
1420 10
        $condition = Functions::ifCondition($condition);
1421
        // Loop through arguments
1422 10
        foreach ($aArgs as $key => $arg) {
1423 10
            if (!is_numeric($arg)) {
1424 6
                $arg = str_replace('"', '""', $arg);
1425 6
                $arg = Calculation::wrapResult(strtoupper($arg));
1426
            }
1427
1428 10
            $testCondition = '=' . $arg . $condition;
1429 10
            $sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0;
1430
1431 10
            if (is_numeric($sumValue) &&
1432 10
                Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
1433
                // Is it a value within our criteria and only numeric can be added to the result
1434 10
                $returnValue += $sumValue;
1435
            }
1436
        }
1437
1438 10
        return $returnValue;
1439
    }
1440
1441
    /**
1442
     * SUMIFS.
1443
     *
1444
     *    Counts the number of cells that contain numbers within the list of arguments
1445
     *
1446
     *    Excel Function:
1447
     *        SUMIFS(value1[,value2[, ...]],condition)
1448
     *
1449
     *    @category Mathematical and Trigonometric Functions
1450
     *
1451
     * @param mixed $args Data values
1452
     * @param string $condition the criteria that defines which cells will be summed
1453
     *
1454
     * @return float
1455
     */
1456 2
    public static function SUMIFS(...$args)
1457
    {
1458 2
        $arrayList = $args;
1459
1460
        // Return value
1461 2
        $returnValue = 0;
1462
1463 2
        $sumArgs = Functions::flattenArray(array_shift($arrayList));
1464 2
        $aArgsArray = [];
1465 2
        $conditions = [];
1466
1467 2
        while (count($arrayList) > 0) {
1468 2
            $aArgsArray[] = Functions::flattenArray(array_shift($arrayList));
1469 2
            $conditions[] = Functions::ifCondition(array_shift($arrayList));
1470
        }
1471
1472
        // Loop through each sum and see if arguments and conditions are true
1473 2
        foreach ($sumArgs as $index => $value) {
1474 2
            $valid = true;
1475
1476 2
            foreach ($conditions as $cidx => $condition) {
1477 2
                $arg = $aArgsArray[$cidx][$index];
1478
1479
                // Loop through arguments
1480 2
                if (!is_numeric($arg)) {
1481 2
                    $arg = Calculation::wrapResult(strtoupper($arg));
1482
                }
1483 2
                $testCondition = '=' . $arg . $condition;
1484 2
                if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
1485
                    // Is not a value within our criteria
1486 2
                    $valid = false;
1487
1488 2
                    break; // if false found, don't need to check other conditions
1489
                }
1490
            }
1491
1492 2
            if ($valid) {
1493 2
                $returnValue += $value;
1494
            }
1495
        }
1496
1497
        // Return
1498 2
        return $returnValue;
1499
    }
1500
1501
    /**
1502
     * SUMPRODUCT.
1503
     *
1504
     * Excel Function:
1505
     *        SUMPRODUCT(value1[,value2[, ...]])
1506
     *
1507
     * @category Mathematical and Trigonometric Functions
1508
     *
1509
     * @param mixed ...$args Data values
1510
     *
1511
     * @return float|string The result, or a string containing an error
1512
     */
1513 3
    public static function SUMPRODUCT(...$args)
1514
    {
1515 3
        $arrayList = $args;
1516
1517 3
        $wrkArray = Functions::flattenArray(array_shift($arrayList));
1518 3
        $wrkCellCount = count($wrkArray);
1519
1520 3
        for ($i = 0; $i < $wrkCellCount; ++$i) {
1521 3
            if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) {
1522
                $wrkArray[$i] = 0;
1523
            }
1524
        }
1525
1526 3
        foreach ($arrayList as $matrixData) {
1527 3
            $array2 = Functions::flattenArray($matrixData);
1528 3
            $count = count($array2);
1529 3
            if ($wrkCellCount != $count) {
1530
                return Functions::VALUE();
1531
            }
1532
1533 3
            foreach ($array2 as $i => $val) {
1534 3
                if ((!is_numeric($val)) || (is_string($val))) {
1535
                    $val = 0;
1536
                }
1537 3
                $wrkArray[$i] *= $val;
1538
            }
1539
        }
1540
1541 3
        return array_sum($wrkArray);
1542
    }
1543
1544
    /**
1545
     * SUMSQ.
1546
     *
1547
     * SUMSQ returns the sum of the squares of the arguments
1548
     *
1549
     * Excel Function:
1550
     *        SUMSQ(value1[,value2[, ...]])
1551
     *
1552
     * @category Mathematical and Trigonometric Functions
1553
     *
1554
     * @param mixed ...$args Data values
1555
     *
1556
     * @return float
1557
     */
1558 7
    public static function SUMSQ(...$args)
1559
    {
1560 7
        $returnValue = 0;
1561
1562
        // Loop through arguments
1563 7
        foreach (Functions::flattenArray($args) as $arg) {
1564
            // Is it a numeric value?
1565 7
            if ((is_numeric($arg)) && (!is_string($arg))) {
1566 7
                $returnValue += ($arg * $arg);
1567
            }
1568
        }
1569
1570 7
        return $returnValue;
1571
    }
1572
1573
    /**
1574
     * SUMX2MY2.
1575
     *
1576
     * @param mixed[] $matrixData1 Matrix #1
1577
     * @param mixed[] $matrixData2 Matrix #2
1578
     *
1579
     * @return float
1580
     */
1581 3
    public static function SUMX2MY2($matrixData1, $matrixData2)
1582
    {
1583 3
        $array1 = Functions::flattenArray($matrixData1);
1584 3
        $array2 = Functions::flattenArray($matrixData2);
1585 3
        $count = min(count($array1), count($array2));
1586
1587 3
        $result = 0;
1588 3
        for ($i = 0; $i < $count; ++$i) {
1589 3
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1590 3
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1591 3
                $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]);
1592
            }
1593
        }
1594
1595 3
        return $result;
1596
    }
1597
1598
    /**
1599
     * SUMX2PY2.
1600
     *
1601
     * @param mixed[] $matrixData1 Matrix #1
1602
     * @param mixed[] $matrixData2 Matrix #2
1603
     *
1604
     * @return float
1605
     */
1606 3
    public static function SUMX2PY2($matrixData1, $matrixData2)
1607
    {
1608 3
        $array1 = Functions::flattenArray($matrixData1);
1609 3
        $array2 = Functions::flattenArray($matrixData2);
1610 3
        $count = min(count($array1), count($array2));
1611
1612 3
        $result = 0;
1613 3
        for ($i = 0; $i < $count; ++$i) {
1614 3
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1615 3
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1616 3
                $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]);
1617
            }
1618
        }
1619
1620 3
        return $result;
1621
    }
1622
1623
    /**
1624
     * SUMXMY2.
1625
     *
1626
     * @param mixed[] $matrixData1 Matrix #1
1627
     * @param mixed[] $matrixData2 Matrix #2
1628
     *
1629
     * @return float
1630
     */
1631 3
    public static function SUMXMY2($matrixData1, $matrixData2)
1632
    {
1633 3
        $array1 = Functions::flattenArray($matrixData1);
1634 3
        $array2 = Functions::flattenArray($matrixData2);
1635 3
        $count = min(count($array1), count($array2));
1636
1637 3
        $result = 0;
1638 3
        for ($i = 0; $i < $count; ++$i) {
1639 3
            if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
1640 3
                ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
1641 3
                $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]);
1642
            }
1643
        }
1644
1645 3
        return $result;
1646
    }
1647
1648
    /**
1649
     * TRUNC.
1650
     *
1651
     * Truncates value to the number of fractional digits by number_digits.
1652
     *
1653
     * @param float $value
1654
     * @param int $digits
1655
     *
1656
     * @return float|string Truncated value, or a string containing an error
1657
     */
1658 19
    public static function TRUNC($value = 0, $digits = 0)
1659
    {
1660 19
        $value = Functions::flattenSingleValue($value);
1661 19
        $digits = Functions::flattenSingleValue($digits);
1662
1663
        // Validate parameters
1664 19
        if ((!is_numeric($value)) || (!is_numeric($digits))) {
1665 2
            return Functions::VALUE();
1666
        }
1667 17
        $digits = floor($digits);
1668
1669
        // Truncate
1670 17
        $adjust = pow(10, $digits);
1671
1672 17
        if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) {
1673 2
            return $value;
1674
        }
1675
1676 15
        return ((int) ($value * $adjust)) / $adjust;
1677
    }
1678
1679
    /**
1680
     * SEC.
1681
     *
1682
     * Returns the secant of an angle.
1683
     *
1684
     * @param float $angle Number
1685
     *
1686
     * @return float|string The secant of the angle
1687
     */
1688 14
    public static function SEC($angle)
1689
    {
1690 14
        $angle = Functions::flattenSingleValue($angle);
1691
1692 14
        if (!is_numeric($angle)) {
1693 1
            return Functions::VALUE();
1694
        }
1695
1696 13
        $result = cos($angle);
1697
1698 13
        return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
1699
    }
1700
1701
    /**
1702
     * SECH.
1703
     *
1704
     * Returns the hyperbolic secant of an angle.
1705
     *
1706
     * @param float $angle Number
1707
     *
1708
     * @return float|string The hyperbolic secant of the angle
1709
     */
1710 14
    public static function SECH($angle)
1711
    {
1712 14
        $angle = Functions::flattenSingleValue($angle);
1713
1714 14
        if (!is_numeric($angle)) {
1715 1
            return Functions::VALUE();
1716
        }
1717
1718 13
        $result = cosh($angle);
1719
1720 13
        return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
1721
    }
1722
1723
    /**
1724
     * CSC.
1725
     *
1726
     * Returns the cosecant of an angle.
1727
     *
1728
     * @param float $angle Number
1729
     *
1730
     * @return float|string The cosecant of the angle
1731
     */
1732 10
    public static function CSC($angle)
1733
    {
1734 10
        $angle = Functions::flattenSingleValue($angle);
1735
1736 10
        if (!is_numeric($angle)) {
1737 1
            return Functions::VALUE();
1738
        }
1739
1740 9
        $result = sin($angle);
1741
1742 9
        return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
1743
    }
1744
1745
    /**
1746
     * CSCH.
1747
     *
1748
     * Returns the hyperbolic cosecant of an angle.
1749
     *
1750
     * @param float $angle Number
1751
     *
1752
     * @return float|string The hyperbolic cosecant of the angle
1753
     */
1754 14
    public static function CSCH($angle)
1755
    {
1756 14
        $angle = Functions::flattenSingleValue($angle);
1757
1758 14
        if (!is_numeric($angle)) {
1759 1
            return Functions::VALUE();
1760
        }
1761
1762 13
        $result = sinh($angle);
1763
1764 13
        return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
1765
    }
1766
1767
    /**
1768
     * COT.
1769
     *
1770
     * Returns the cotangent of an angle.
1771
     *
1772
     * @param float $angle Number
1773
     *
1774
     * @return float|string The cotangent of the angle
1775
     */
1776 10
    public static function COT($angle)
1777
    {
1778 10
        $angle = Functions::flattenSingleValue($angle);
1779
1780 10
        if (!is_numeric($angle)) {
1781 1
            return Functions::VALUE();
1782
        }
1783
1784 9
        $result = tan($angle);
1785
1786 9
        return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
1787
    }
1788
1789
    /**
1790
     * COTH.
1791
     *
1792
     * Returns the hyperbolic cotangent of an angle.
1793
     *
1794
     * @param float $angle Number
1795
     *
1796
     * @return float|string The hyperbolic cotangent of the angle
1797
     */
1798 14
    public static function COTH($angle)
1799
    {
1800 14
        $angle = Functions::flattenSingleValue($angle);
1801
1802 14
        if (!is_numeric($angle)) {
1803 1
            return Functions::VALUE();
1804
        }
1805
1806 13
        $result = tanh($angle);
1807
1808 13
        return ($result == 0.0) ? Functions::DIV0() : 1 / $result;
1809
    }
1810
1811
    /**
1812
     * ACOT.
1813
     *
1814
     * Returns the arccotangent of a number.
1815
     *
1816
     * @param float $number Number
1817
     *
1818
     * @return float|string The arccotangent of the number
1819
     */
1820 14
    public static function ACOT($number)
1821
    {
1822 14
        $number = Functions::flattenSingleValue($number);
1823
1824 14
        if (!is_numeric($number)) {
1825 1
            return Functions::VALUE();
1826
        }
1827
1828 13
        return (M_PI / 2) - atan($number);
1829
    }
1830
1831
    /**
1832
     * ACOTH.
1833
     *
1834
     * Returns the hyperbolic arccotangent of a number.
1835
     *
1836
     * @param float $number Number
1837
     *
1838
     * @return float|string The hyperbolic arccotangent of the number
1839
     */
1840 14
    public static function ACOTH($number)
1841
    {
1842 14
        $number = Functions::flattenSingleValue($number);
1843
1844 14
        if (!is_numeric($number)) {
1845 1
            return Functions::VALUE();
1846
        }
1847
1848 13
        $result = log(($number + 1) / ($number - 1)) / 2;
1849
1850 13
        return is_nan($result) ? Functions::NAN() : $result;
1851
    }
1852
}
1853