Total Complexity | 324 |
Total Lines | 1843 |
Duplicated Lines | 0 % |
Coverage | 93.99% |
Changes | 1 | ||
Bugs | 0 | Features | 1 |
Complex classes like MathTrig often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MathTrig, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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) |
|
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) |
|
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) |
|
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) { |
|
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) |
|
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) |
|
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); |
|
659 | |||
660 | 69 | if ((!is_numeric($base)) || (!is_numeric($number))) { |
|
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) |
|
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)) { |
|
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)) { |
|
790 | $matrixData1 = [[$matrixData1]]; |
||
791 | } |
||
792 | 9 | if (!is_array($matrixData2)) { |
|
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) { |
|
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 |
||
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
|
|||
956 | 8 | if ($result == self::EVEN($result)) { |
|
1 ignored issue
–
show
|
|||
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); |
|
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 |
||
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) |
|
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) |
|
1851 | } |
||
1852 | } |
||
1853 |