Total Complexity | 308 |
Total Lines | 1767 |
Duplicated Lines | 0 % |
Coverage | 94.07% |
Changes | 2 | ||
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) |
|
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 | 25 | private static function evaluateGCD($a, $b) |
|
442 | { |
||
443 | 25 | return $b ? self::evaluateGCD($b, $a % $b) : $a; |
|
444 | } |
||
445 | |||
446 | /** |
||
447 | * GCD. |
||
448 | * |
||
449 | * Returns the greatest common divisor of a series of numbers. |
||
450 | * The greatest common divisor is the largest integer that divides both |
||
451 | * number1 and number2 without a remainder. |
||
452 | * |
||
453 | * Excel Function: |
||
454 | * GCD(number1[,number2[, ...]]) |
||
455 | * |
||
456 | * @category Mathematical and Trigonometric Functions |
||
457 | * |
||
458 | * @param mixed ...$args Data values |
||
459 | * |
||
460 | * @return int|mixed|string Greatest Common Divisor, or a string containing an error |
||
461 | */ |
||
462 | 27 | public static function GCD(...$args) |
|
463 | { |
||
464 | 27 | $args = Functions::flattenArray($args); |
|
465 | // Loop through arguments |
||
466 | 27 | foreach (Functions::flattenArray($args) as $value) { |
|
467 | 27 | if (!is_numeric($value)) { |
|
468 | 1 | return Functions::VALUE(); |
|
469 | 27 | } elseif ($value < 0) { |
|
470 | 1 | return Functions::NAN(); |
|
471 | } |
||
472 | } |
||
473 | |||
474 | 25 | $gcd = (int) array_pop($args); |
|
475 | do { |
||
476 | 25 | $gcd = self::evaluateGCD($gcd, (int) array_pop($args)); |
|
477 | 25 | } while (!empty($args)); |
|
478 | |||
479 | 25 | return $gcd; |
|
480 | } |
||
481 | |||
482 | /** |
||
483 | * INT. |
||
484 | * |
||
485 | * Casts a floating point value to an integer |
||
486 | * |
||
487 | * Excel Function: |
||
488 | * INT(number) |
||
489 | * |
||
490 | * @category Mathematical and Trigonometric Functions |
||
491 | * |
||
492 | * @param float $number Number to cast to an integer |
||
493 | * |
||
494 | * @return int|string Integer value, or a string containing an error |
||
495 | */ |
||
496 | 19 | public static function INT($number) |
|
497 | { |
||
498 | 19 | $number = Functions::flattenSingleValue($number); |
|
499 | |||
500 | 19 | if ($number === null) { |
|
501 | 1 | return 0; |
|
502 | 18 | } elseif (is_bool($number)) { |
|
503 | 2 | return (int) $number; |
|
504 | } |
||
505 | 16 | if (is_numeric($number)) { |
|
506 | 15 | return (int) floor($number); |
|
507 | } |
||
508 | |||
509 | 1 | return Functions::VALUE(); |
|
510 | } |
||
511 | |||
512 | /** |
||
513 | * LCM. |
||
514 | * |
||
515 | * Returns the lowest common multiplier of a series of numbers |
||
516 | * The least common multiple is the smallest positive integer that is a multiple |
||
517 | * of all integer arguments number1, number2, and so on. Use LCM to add fractions |
||
518 | * with different denominators. |
||
519 | * |
||
520 | * Excel Function: |
||
521 | * LCM(number1[,number2[, ...]]) |
||
522 | * |
||
523 | * @category Mathematical and Trigonometric Functions |
||
524 | * |
||
525 | * @param mixed ...$args Data values |
||
526 | * |
||
527 | * @return int|string Lowest Common Multiplier, or a string containing an error |
||
528 | */ |
||
529 | 12 | public static function LCM(...$args) |
|
530 | { |
||
531 | 12 | $returnValue = 1; |
|
532 | 12 | $allPoweredFactors = []; |
|
533 | // Loop through arguments |
||
534 | 12 | foreach (Functions::flattenArray($args) as $value) { |
|
535 | 12 | if (!is_numeric($value)) { |
|
536 | 1 | return Functions::VALUE(); |
|
537 | } |
||
538 | 12 | if ($value == 0) { |
|
539 | 1 | return 0; |
|
540 | 12 | } elseif ($value < 0) { |
|
541 | 1 | return Functions::NAN(); |
|
542 | } |
||
543 | 12 | $myFactors = self::factors(floor($value)); |
|
544 | 12 | $myCountedFactors = array_count_values($myFactors); |
|
545 | 12 | $myPoweredFactors = []; |
|
546 | 12 | foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { |
|
547 | 12 | $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor, $myCountedPower); |
|
548 | } |
||
549 | 12 | foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { |
|
550 | 12 | if (isset($allPoweredFactors[$myPoweredValue])) { |
|
551 | 6 | if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { |
|
552 | 6 | $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; |
|
553 | } |
||
554 | } else { |
||
555 | 12 | $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; |
|
556 | } |
||
557 | } |
||
558 | } |
||
559 | 9 | foreach ($allPoweredFactors as $allPoweredFactor) { |
|
560 | 9 | $returnValue *= (int) $allPoweredFactor; |
|
561 | } |
||
562 | |||
563 | 9 | return $returnValue; |
|
564 | } |
||
565 | |||
566 | /** |
||
567 | * LOG_BASE. |
||
568 | * |
||
569 | * Returns the logarithm of a number to a specified base. The default base is 10. |
||
570 | * |
||
571 | * Excel Function: |
||
572 | * LOG(number[,base]) |
||
573 | * |
||
574 | * @category Mathematical and Trigonometric Functions |
||
575 | * |
||
576 | * @param float $number The positive real number for which you want the logarithm |
||
577 | * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10. |
||
578 | * |
||
579 | * @return float|string The result, or a string containing an error |
||
580 | */ |
||
581 | 69 | public static function logBase($number = null, $base = 10) |
|
582 | { |
||
583 | 69 | $number = Functions::flattenSingleValue($number); |
|
584 | 69 | $base = ($base === null) ? 10 : (float) Functions::flattenSingleValue($base); |
|
585 | |||
586 | 69 | if ((!is_numeric($base)) || (!is_numeric($number))) { |
|
587 | 2 | return Functions::VALUE(); |
|
588 | } |
||
589 | 67 | if (($base <= 0) || ($number <= 0)) { |
|
590 | 18 | return Functions::NAN(); |
|
591 | } |
||
592 | |||
593 | 49 | return log($number, $base); |
|
594 | } |
||
595 | |||
596 | /** |
||
597 | * MDETERM. |
||
598 | * |
||
599 | * Returns the matrix determinant of an array. |
||
600 | * |
||
601 | * Excel Function: |
||
602 | * MDETERM(array) |
||
603 | * |
||
604 | * @category Mathematical and Trigonometric Functions |
||
605 | * |
||
606 | * @param array $matrixValues A matrix of values |
||
607 | * |
||
608 | * @return float|string The result, or a string containing an error |
||
609 | */ |
||
610 | 14 | public static function MDETERM($matrixValues) |
|
645 | } |
||
646 | } |
||
647 | |||
648 | /** |
||
649 | * MINVERSE. |
||
650 | * |
||
651 | * Returns the inverse matrix for the matrix stored in an array. |
||
652 | * |
||
653 | * Excel Function: |
||
654 | * MINVERSE(array) |
||
655 | * |
||
656 | * @category Mathematical and Trigonometric Functions |
||
657 | * |
||
658 | * @param array $matrixValues A matrix of values |
||
659 | * |
||
660 | * @return array|string The result, or a string containing an error |
||
661 | */ |
||
662 | 10 | public static function MINVERSE($matrixValues) |
|
663 | { |
||
664 | 10 | $matrixData = []; |
|
665 | 10 | if (!is_array($matrixValues)) { |
|
666 | $matrixValues = [[$matrixValues]]; |
||
667 | } |
||
668 | |||
669 | 10 | $row = $maxColumn = 0; |
|
670 | 10 | foreach ($matrixValues as $matrixRow) { |
|
671 | 10 | if (!is_array($matrixRow)) { |
|
672 | $matrixRow = [$matrixRow]; |
||
673 | } |
||
674 | 10 | $column = 0; |
|
675 | 10 | foreach ($matrixRow as $matrixCell) { |
|
676 | 10 | if ((is_string($matrixCell)) || ($matrixCell === null)) { |
|
677 | return Functions::VALUE(); |
||
678 | } |
||
679 | 10 | $matrixData[$row][$column] = $matrixCell; |
|
680 | 10 | ++$column; |
|
681 | } |
||
682 | 10 | if ($column > $maxColumn) { |
|
683 | 10 | $maxColumn = $column; |
|
684 | } |
||
685 | 10 | ++$row; |
|
686 | } |
||
687 | |||
688 | 10 | $matrix = new Matrix($matrixData); |
|
689 | 10 | if (!$matrix->isSquare()) { |
|
690 | return Functions::VALUE(); |
||
691 | } |
||
692 | |||
693 | 10 | if ($matrix->determinant() == 0.0) { |
|
694 | return Functions::NAN(); |
||
695 | } |
||
696 | |||
697 | try { |
||
698 | 10 | return $matrix->inverse()->toArray(); |
|
699 | } catch (MatrixException $ex) { |
||
700 | return Functions::VALUE(); |
||
701 | } |
||
702 | } |
||
703 | |||
704 | /** |
||
705 | * MMULT. |
||
706 | * |
||
707 | * @param array $matrixData1 A matrix of values |
||
708 | * @param array $matrixData2 A matrix of values |
||
709 | * |
||
710 | * @return array|string The result, or a string containing an error |
||
711 | */ |
||
712 | 8 | public static function MMULT($matrixData1, $matrixData2) |
|
713 | { |
||
714 | 8 | $matrixAData = $matrixBData = []; |
|
715 | 8 | if (!is_array($matrixData1)) { |
|
716 | $matrixData1 = [[$matrixData1]]; |
||
717 | } |
||
718 | 8 | if (!is_array($matrixData2)) { |
|
719 | $matrixData2 = [[$matrixData2]]; |
||
720 | } |
||
721 | |||
722 | try { |
||
723 | 8 | $rowA = 0; |
|
724 | 8 | foreach ($matrixData1 as $matrixRow) { |
|
725 | 8 | if (!is_array($matrixRow)) { |
|
726 | $matrixRow = [$matrixRow]; |
||
727 | } |
||
728 | 8 | $columnA = 0; |
|
729 | 8 | foreach ($matrixRow as $matrixCell) { |
|
730 | 8 | if ((!is_numeric($matrixCell)) || ($matrixCell === null)) { |
|
731 | return Functions::VALUE(); |
||
732 | } |
||
733 | 8 | $matrixAData[$rowA][$columnA] = $matrixCell; |
|
734 | 8 | ++$columnA; |
|
735 | } |
||
736 | 8 | ++$rowA; |
|
737 | } |
||
738 | 8 | $matrixA = new Matrix($matrixAData); |
|
739 | 8 | $rowB = 0; |
|
740 | 8 | foreach ($matrixData2 as $matrixRow) { |
|
741 | 8 | if (!is_array($matrixRow)) { |
|
742 | 1 | $matrixRow = [$matrixRow]; |
|
743 | } |
||
744 | 8 | $columnB = 0; |
|
745 | 8 | foreach ($matrixRow as $matrixCell) { |
|
746 | 8 | if ((!is_numeric($matrixCell)) || ($matrixCell === null)) { |
|
747 | return Functions::VALUE(); |
||
748 | } |
||
749 | 8 | $matrixBData[$rowB][$columnB] = $matrixCell; |
|
750 | 8 | ++$columnB; |
|
751 | } |
||
752 | 8 | ++$rowB; |
|
753 | } |
||
754 | 8 | $matrixB = new Matrix($matrixBData); |
|
755 | |||
756 | 8 | if ($columnA != $rowB) { |
|
757 | 2 | return Functions::VALUE(); |
|
758 | } |
||
759 | |||
760 | 6 | return $matrixA->multiply($matrixB)->toArray(); |
|
761 | } catch (MatrixException $ex) { |
||
762 | return Functions::VALUE(); |
||
763 | } |
||
764 | } |
||
765 | |||
766 | /** |
||
767 | * MOD. |
||
768 | * |
||
769 | * @param int $a Dividend |
||
770 | * @param int $b Divisor |
||
771 | * |
||
772 | * @return int|string Remainder, or a string containing an error |
||
773 | */ |
||
774 | 10 | public static function MOD($a = 1, $b = 1) |
|
775 | { |
||
776 | 10 | $a = (float) Functions::flattenSingleValue($a); |
|
777 | 10 | $b = (float) Functions::flattenSingleValue($b); |
|
778 | |||
779 | 10 | if ($b == 0.0) { |
|
780 | 1 | return Functions::DIV0(); |
|
781 | 9 | } elseif (($a < 0.0) && ($b > 0.0)) { |
|
782 | 1 | return $b - fmod(abs($a), $b); |
|
783 | 8 | } elseif (($a > 0.0) && ($b < 0.0)) { |
|
784 | 2 | return $b + fmod($a, abs($b)); |
|
785 | } |
||
786 | |||
787 | 6 | return fmod($a, $b); |
|
788 | } |
||
789 | |||
790 | /** |
||
791 | * MROUND. |
||
792 | * |
||
793 | * Rounds a number to the nearest multiple of a specified value |
||
794 | * |
||
795 | * @param float $number Number to round |
||
796 | * @param int $multiple Multiple to which you want to round $number |
||
797 | * |
||
798 | * @return float|string Rounded Number, or a string containing an error |
||
799 | */ |
||
800 | 13 | public static function MROUND($number, $multiple) |
|
801 | { |
||
802 | 13 | $number = Functions::flattenSingleValue($number); |
|
803 | 13 | $multiple = Functions::flattenSingleValue($multiple); |
|
804 | |||
805 | 13 | if ((is_numeric($number)) && (is_numeric($multiple))) { |
|
806 | 11 | if ($multiple == 0) { |
|
807 | 1 | return 0; |
|
808 | } |
||
809 | 10 | if ((self::SIGN($number)) == (self::SIGN($multiple))) { |
|
810 | 9 | $multiplier = 1 / $multiple; |
|
811 | |||
812 | 9 | return round($number * $multiplier) / $multiplier; |
|
813 | } |
||
814 | |||
815 | 1 | return Functions::NAN(); |
|
816 | } |
||
817 | |||
818 | 2 | return Functions::VALUE(); |
|
819 | } |
||
820 | |||
821 | /** |
||
822 | * MULTINOMIAL. |
||
823 | * |
||
824 | * Returns the ratio of the factorial of a sum of values to the product of factorials. |
||
825 | * |
||
826 | * @param array of mixed Data Series |
||
827 | * |
||
828 | * @return float|string The result, or a string containing an error |
||
829 | */ |
||
830 | 2 | public static function MULTINOMIAL(...$args) |
|
831 | { |
||
832 | 2 | $summer = 0; |
|
833 | 2 | $divisor = 1; |
|
834 | // Loop through arguments |
||
835 | 2 | foreach (Functions::flattenArray($args) as $arg) { |
|
836 | // Is it a numeric value? |
||
837 | 2 | if (is_numeric($arg)) { |
|
838 | 2 | if ($arg < 1) { |
|
839 | return Functions::NAN(); |
||
840 | } |
||
841 | 2 | $summer += floor($arg); |
|
842 | 2 | $divisor *= self::FACT($arg); |
|
843 | } else { |
||
844 | return Functions::VALUE(); |
||
845 | } |
||
846 | } |
||
847 | |||
848 | // Return |
||
849 | 2 | if ($summer > 0) { |
|
850 | 2 | $summer = self::FACT($summer); |
|
851 | |||
852 | 2 | return $summer / $divisor; |
|
853 | } |
||
854 | |||
855 | return 0; |
||
856 | } |
||
857 | |||
858 | /** |
||
859 | * ODD. |
||
860 | * |
||
861 | * Returns number rounded up to the nearest odd integer. |
||
862 | * |
||
863 | * @param float $number Number to round |
||
864 | * |
||
865 | * @return int|string Rounded Number, or a string containing an error |
||
866 | */ |
||
867 | 13 | public static function ODD($number) |
|
868 | { |
||
869 | 13 | $number = Functions::flattenSingleValue($number); |
|
870 | |||
871 | 13 | if ($number === null) { |
|
872 | 1 | return 1; |
|
873 | 12 | } elseif (is_bool($number)) { |
|
874 | 2 | return 1; |
|
875 | 10 | } elseif (is_numeric($number)) { |
|
876 | 9 | $significance = self::SIGN($number); |
|
877 | 9 | if ($significance == 0) { |
|
878 | 1 | return 1; |
|
879 | } |
||
880 | |||
881 | 8 | $result = self::CEILING($number, $significance); |
|
1 ignored issue
–
show
|
|||
882 | 8 | if ($result == self::EVEN($result)) { |
|
1 ignored issue
–
show
|
|||
883 | 5 | $result += $significance; |
|
884 | } |
||
885 | |||
886 | 8 | return (int) $result; |
|
887 | } |
||
888 | |||
889 | 1 | return Functions::VALUE(); |
|
890 | } |
||
891 | |||
892 | /** |
||
893 | * POWER. |
||
894 | * |
||
895 | * Computes x raised to the power y. |
||
896 | * |
||
897 | * @param float $x |
||
898 | * @param float $y |
||
899 | * |
||
900 | * @return float|string The result, or a string containing an error |
||
901 | */ |
||
902 | 81 | public static function POWER($x = 0, $y = 2) |
|
903 | { |
||
904 | 81 | $x = Functions::flattenSingleValue($x); |
|
905 | 81 | $y = Functions::flattenSingleValue($y); |
|
906 | |||
907 | // Validate parameters |
||
908 | 81 | if ($x == 0.0 && $y == 0.0) { |
|
909 | 1 | return Functions::NAN(); |
|
910 | 80 | } elseif ($x == 0.0 && $y < 0.0) { |
|
911 | 2 | return Functions::DIV0(); |
|
912 | } |
||
913 | |||
914 | // Return |
||
915 | 78 | $result = pow($x, $y); |
|
916 | |||
917 | 78 | return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN(); |
|
918 | } |
||
919 | |||
920 | /** |
||
921 | * PRODUCT. |
||
922 | * |
||
923 | * PRODUCT returns the product of all the values and cells referenced in the argument list. |
||
924 | * |
||
925 | * Excel Function: |
||
926 | * PRODUCT(value1[,value2[, ...]]) |
||
927 | * |
||
928 | * @category Mathematical and Trigonometric Functions |
||
929 | * |
||
930 | * @param mixed ...$args Data values |
||
931 | * |
||
932 | * @return float |
||
933 | */ |
||
934 | 11 | public static function PRODUCT(...$args) |
|
935 | { |
||
936 | // Return value |
||
937 | 11 | $returnValue = null; |
|
938 | |||
939 | // Loop through arguments |
||
940 | 11 | foreach (Functions::flattenArray($args) as $arg) { |
|
941 | // Is it a numeric value? |
||
942 | 11 | if ((is_numeric($arg)) && (!is_string($arg))) { |
|
943 | 11 | if ($returnValue === null) { |
|
944 | 11 | $returnValue = $arg; |
|
945 | } else { |
||
946 | 11 | $returnValue *= $arg; |
|
947 | } |
||
948 | } |
||
949 | } |
||
950 | |||
951 | // Return |
||
952 | 11 | if ($returnValue === null) { |
|
953 | return 0; |
||
954 | } |
||
955 | |||
956 | 11 | return $returnValue; |
|
957 | } |
||
958 | |||
959 | /** |
||
960 | * QUOTIENT. |
||
961 | * |
||
962 | * QUOTIENT function returns the integer portion of a division. Numerator is the divided number |
||
963 | * and denominator is the divisor. |
||
964 | * |
||
965 | * Excel Function: |
||
966 | * QUOTIENT(value1[,value2[, ...]]) |
||
967 | * |
||
968 | * @category Mathematical and Trigonometric Functions |
||
969 | * |
||
970 | * @param mixed ...$args Data values |
||
971 | * |
||
972 | * @return float |
||
973 | */ |
||
974 | 6 | public static function QUOTIENT(...$args) |
|
975 | { |
||
976 | // Return value |
||
977 | 6 | $returnValue = null; |
|
978 | |||
979 | // Loop through arguments |
||
980 | 6 | foreach (Functions::flattenArray($args) as $arg) { |
|
981 | // Is it a numeric value? |
||
982 | 6 | if ((is_numeric($arg)) && (!is_string($arg))) { |
|
983 | 6 | if ($returnValue === null) { |
|
984 | 6 | $returnValue = ($arg == 0) ? 0 : $arg; |
|
985 | } else { |
||
986 | 6 | if (($returnValue == 0) || ($arg == 0)) { |
|
987 | $returnValue = 0; |
||
988 | } else { |
||
989 | 6 | $returnValue /= $arg; |
|
990 | } |
||
991 | } |
||
992 | } |
||
993 | } |
||
994 | |||
995 | // Return |
||
996 | 6 | return (int) $returnValue; |
|
997 | } |
||
998 | |||
999 | /** |
||
1000 | * RAND. |
||
1001 | * |
||
1002 | * @param int $min Minimal value |
||
1003 | * @param int $max Maximal value |
||
1004 | * |
||
1005 | * @return int Random number |
||
1006 | */ |
||
1007 | 3 | public static function RAND($min = 0, $max = 0) |
|
1008 | { |
||
1009 | 3 | $min = Functions::flattenSingleValue($min); |
|
1010 | 3 | $max = Functions::flattenSingleValue($max); |
|
1011 | |||
1012 | 3 | if ($min == 0 && $max == 0) { |
|
1013 | 1 | return (mt_rand(0, 10000000)) / 10000000; |
|
1014 | } |
||
1015 | |||
1016 | 3 | return mt_rand($min, $max); |
|
1017 | } |
||
1018 | |||
1019 | 6 | public static function ROMAN($aValue, $style = 0) |
|
1020 | { |
||
1021 | 6 | $aValue = Functions::flattenSingleValue($aValue); |
|
1022 | 6 | $style = ($style === null) ? 0 : (int) Functions::flattenSingleValue($style); |
|
1023 | 6 | if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) { |
|
1024 | return Functions::VALUE(); |
||
1025 | } |
||
1026 | 6 | $aValue = (int) $aValue; |
|
1027 | 6 | if ($aValue == 0) { |
|
1028 | return ''; |
||
1029 | } |
||
1030 | |||
1031 | 6 | $mill = ['', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM']; |
|
1032 | 6 | $cent = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; |
|
1033 | 6 | $tens = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; |
|
1034 | 6 | $ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; |
|
1035 | |||
1036 | 6 | $roman = ''; |
|
1037 | 6 | while ($aValue > 5999) { |
|
1038 | $roman .= 'M'; |
||
1039 | $aValue -= 1000; |
||
1040 | } |
||
1041 | 6 | $m = self::romanCut($aValue, 1000); |
|
1042 | 6 | $aValue %= 1000; |
|
1043 | 6 | $c = self::romanCut($aValue, 100); |
|
1044 | 6 | $aValue %= 100; |
|
1045 | 6 | $t = self::romanCut($aValue, 10); |
|
1046 | 6 | $aValue %= 10; |
|
1047 | |||
1048 | 6 | return $roman . $mill[$m] . $cent[$c] . $tens[$t] . $ones[$aValue]; |
|
1049 | } |
||
1050 | |||
1051 | /** |
||
1052 | * ROUNDUP. |
||
1053 | * |
||
1054 | * Rounds a number up to a specified number of decimal places |
||
1055 | * |
||
1056 | * @param float $number Number to round |
||
1057 | * @param int $digits Number of digits to which you want to round $number |
||
1058 | * |
||
1059 | * @return float|string Rounded Number, or a string containing an error |
||
1060 | */ |
||
1061 | 14 | public static function ROUNDUP($number, $digits) |
|
1062 | { |
||
1063 | 14 | $number = Functions::flattenSingleValue($number); |
|
1064 | 14 | $digits = Functions::flattenSingleValue($digits); |
|
1065 | |||
1066 | 14 | if ((is_numeric($number)) && (is_numeric($digits))) { |
|
1067 | 12 | $significance = pow(10, (int) $digits); |
|
1068 | 12 | if ($number < 0.0) { |
|
1069 | 2 | return floor($number * $significance) / $significance; |
|
1070 | } |
||
1071 | |||
1072 | 10 | return ceil($number * $significance) / $significance; |
|
1073 | } |
||
1074 | |||
1075 | 2 | return Functions::VALUE(); |
|
1076 | } |
||
1077 | |||
1078 | /** |
||
1079 | * ROUNDDOWN. |
||
1080 | * |
||
1081 | * Rounds a number down to a specified number of decimal places |
||
1082 | * |
||
1083 | * @param float $number Number to round |
||
1084 | * @param int $digits Number of digits to which you want to round $number |
||
1085 | * |
||
1086 | * @return float|string Rounded Number, or a string containing an error |
||
1087 | */ |
||
1088 | 14 | public static function ROUNDDOWN($number, $digits) |
|
1089 | { |
||
1090 | 14 | $number = Functions::flattenSingleValue($number); |
|
1091 | 14 | $digits = Functions::flattenSingleValue($digits); |
|
1092 | |||
1093 | 14 | if ((is_numeric($number)) && (is_numeric($digits))) { |
|
1094 | 12 | $significance = pow(10, (int) $digits); |
|
1095 | 12 | if ($number < 0.0) { |
|
1096 | 2 | return ceil($number * $significance) / $significance; |
|
1097 | } |
||
1098 | |||
1099 | 10 | return floor($number * $significance) / $significance; |
|
1100 | } |
||
1101 | |||
1102 | 2 | return Functions::VALUE(); |
|
1103 | } |
||
1104 | |||
1105 | /** |
||
1106 | * SERIESSUM. |
||
1107 | * |
||
1108 | * Returns the sum of a power series |
||
1109 | * |
||
1110 | * @param float $x Input value to the power series |
||
1111 | * @param float $n Initial power to which you want to raise $x |
||
1112 | * @param float $m Step by which to increase $n for each term in the series |
||
1113 | * @param array of mixed Data Series |
||
1114 | * |
||
1115 | * @return float|string The result, or a string containing an error |
||
1116 | */ |
||
1117 | 2 | public static function SERIESSUM(...$args) |
|
1118 | { |
||
1119 | 2 | $returnValue = 0; |
|
1120 | |||
1121 | // Loop through arguments |
||
1122 | 2 | $aArgs = Functions::flattenArray($args); |
|
1123 | |||
1124 | 2 | $x = array_shift($aArgs); |
|
1125 | 2 | $n = array_shift($aArgs); |
|
1126 | 2 | $m = array_shift($aArgs); |
|
1127 | |||
1128 | 2 | if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) { |
|
1129 | // Calculate |
||
1130 | 2 | $i = 0; |
|
1131 | 2 | foreach ($aArgs as $arg) { |
|
1132 | // Is it a numeric value? |
||
1133 | 2 | if ((is_numeric($arg)) && (!is_string($arg))) { |
|
1134 | 2 | $returnValue += $arg * pow($x, $n + ($m * $i++)); |
|
1135 | } else { |
||
1136 | return Functions::VALUE(); |
||
1137 | } |
||
1138 | } |
||
1139 | |||
1140 | 2 | return $returnValue; |
|
1141 | } |
||
1142 | |||
1143 | return Functions::VALUE(); |
||
1144 | } |
||
1145 | |||
1146 | /** |
||
1147 | * SIGN. |
||
1148 | * |
||
1149 | * Determines the sign of a number. Returns 1 if the number is positive, zero (0) |
||
1150 | * if the number is 0, and -1 if the number is negative. |
||
1151 | * |
||
1152 | * @param float $number Number to round |
||
1153 | * |
||
1154 | * @return int|string sign value, or a string containing an error |
||
1155 | */ |
||
1156 | 72 | public static function SIGN($number) |
|
1157 | { |
||
1158 | 72 | $number = Functions::flattenSingleValue($number); |
|
1159 | |||
1160 | 72 | if (is_bool($number)) { |
|
1161 | 2 | return (int) $number; |
|
1162 | } |
||
1163 | 70 | if (is_numeric($number)) { |
|
1164 | 69 | if ($number == 0.0) { |
|
1165 | 4 | return 0; |
|
1166 | } |
||
1167 | |||
1168 | 65 | return $number / abs($number); |
|
1169 | } |
||
1170 | |||
1171 | 1 | return Functions::VALUE(); |
|
1172 | } |
||
1173 | |||
1174 | /** |
||
1175 | * SQRTPI. |
||
1176 | * |
||
1177 | * Returns the square root of (number * pi). |
||
1178 | * |
||
1179 | * @param float $number Number |
||
1180 | * |
||
1181 | * @return float|string Square Root of Number * Pi, or a string containing an error |
||
1182 | */ |
||
1183 | 15 | public static function SQRTPI($number) |
|
1184 | { |
||
1185 | 15 | $number = Functions::flattenSingleValue($number); |
|
1186 | |||
1187 | 15 | if (is_numeric($number)) { |
|
1188 | 14 | if ($number < 0) { |
|
1189 | 3 | return Functions::NAN(); |
|
1190 | } |
||
1191 | |||
1192 | 11 | return sqrt($number * M_PI); |
|
1193 | } |
||
1194 | |||
1195 | 1 | return Functions::VALUE(); |
|
1196 | } |
||
1197 | |||
1198 | 11 | protected static function filterHiddenArgs($cellReference, $args) |
|
1199 | { |
||
1200 | 11 | return array_filter( |
|
1201 | $args, |
||
1202 | function ($index) use ($cellReference) { |
||
1203 | 11 | [, $row, $column] = explode('.', $index); |
|
1204 | |||
1205 | 11 | return $cellReference->getWorksheet()->getRowDimension($row)->getVisible() && |
|
1206 | 11 | $cellReference->getWorksheet()->getColumnDimension($column)->getVisible(); |
|
1207 | 11 | }, |
|
1208 | 11 | ARRAY_FILTER_USE_KEY |
|
1209 | ); |
||
1210 | } |
||
1211 | |||
1212 | 23 | protected static function filterFormulaArgs($cellReference, $args) |
|
1213 | { |
||
1214 | 23 | return array_filter( |
|
1215 | $args, |
||
1216 | function ($index) use ($cellReference) { |
||
1217 | 23 | [, $row, $column] = explode('.', $index); |
|
1218 | 23 | if ($cellReference->getWorksheet()->cellExists($column . $row)) { |
|
1219 | //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula |
||
1220 | 23 | $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); |
|
1221 | 23 | $cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue()); |
|
1222 | |||
1223 | 23 | return !$isFormula || $cellFormula; |
|
1224 | } |
||
1225 | |||
1226 | return true; |
||
1227 | 23 | }, |
|
1228 | 23 | ARRAY_FILTER_USE_KEY |
|
1229 | ); |
||
1230 | } |
||
1231 | |||
1232 | /** |
||
1233 | * SUBTOTAL. |
||
1234 | * |
||
1235 | * Returns a subtotal in a list or database. |
||
1236 | * |
||
1237 | * @param int the number 1 to 11 that specifies which function to |
||
1238 | * use in calculating subtotals within a range |
||
1239 | * list |
||
1240 | * Numbers 101 to 111 shadow the functions of 1 to 11 |
||
1241 | * but ignore any values in the range that are |
||
1242 | * in hidden rows or columns |
||
1243 | * @param array of mixed Data Series |
||
1244 | * |
||
1245 | * @return float|string |
||
1246 | */ |
||
1247 | 23 | public static function SUBTOTAL(...$args) |
|
1248 | { |
||
1249 | 23 | $cellReference = array_pop($args); |
|
1250 | 23 | $aArgs = Functions::flattenArrayIndexed($args); |
|
1251 | 23 | $subtotal = array_shift($aArgs); |
|
1252 | |||
1253 | // Calculate |
||
1254 | 23 | if ((is_numeric($subtotal)) && (!is_string($subtotal))) { |
|
1255 | 23 | if ($subtotal > 100) { |
|
1256 | 11 | $aArgs = self::filterHiddenArgs($cellReference, $aArgs); |
|
1257 | 11 | $subtotal -= 100; |
|
1258 | } |
||
1259 | |||
1260 | 23 | $aArgs = self::filterFormulaArgs($cellReference, $aArgs); |
|
1261 | switch ($subtotal) { |
||
1262 | 23 | case 1: |
|
1263 | 2 | return Statistical::AVERAGE($aArgs); |
|
1264 | 21 | case 2: |
|
1265 | 2 | return Statistical::COUNT($aArgs); |
|
1266 | 19 | case 3: |
|
1267 | 2 | return Statistical::COUNTA($aArgs); |
|
1268 | 17 | case 4: |
|
1269 | 2 | return Statistical::MAX($aArgs); |
|
1270 | 15 | case 5: |
|
1271 | 2 | return Statistical::MIN($aArgs); |
|
1272 | 13 | case 6: |
|
1273 | 2 | return self::PRODUCT($aArgs); |
|
1274 | 11 | case 7: |
|
1275 | 2 | return Statistical::STDEV($aArgs); |
|
1276 | 9 | case 8: |
|
1277 | 2 | return Statistical::STDEVP($aArgs); |
|
1278 | 7 | case 9: |
|
1279 | 3 | return self::SUM($aArgs); |
|
1280 | 4 | case 10: |
|
1281 | 2 | return Statistical::VARFunc($aArgs); |
|
1282 | 2 | case 11: |
|
1283 | 2 | return Statistical::VARP($aArgs); |
|
1284 | } |
||
1285 | } |
||
1286 | |||
1287 | return Functions::VALUE(); |
||
1288 | } |
||
1289 | |||
1290 | /** |
||
1291 | * SUM. |
||
1292 | * |
||
1293 | * SUM computes the sum of all the values and cells referenced in the argument list. |
||
1294 | * |
||
1295 | * Excel Function: |
||
1296 | * SUM(value1[,value2[, ...]]) |
||
1297 | * |
||
1298 | * @category Mathematical and Trigonometric Functions |
||
1299 | * |
||
1300 | * @param mixed ...$args Data values |
||
1301 | * |
||
1302 | * @return float |
||
1303 | */ |
||
1304 | 34 | public static function SUM(...$args) |
|
1305 | { |
||
1306 | 34 | $returnValue = 0; |
|
1307 | |||
1308 | // Loop through the arguments |
||
1309 | 34 | foreach (Functions::flattenArray($args) as $arg) { |
|
1310 | // Is it a numeric value? |
||
1311 | 34 | if ((is_numeric($arg)) && (!is_string($arg))) { |
|
1312 | 34 | $returnValue += $arg; |
|
1313 | } |
||
1314 | } |
||
1315 | |||
1316 | 34 | return $returnValue; |
|
1317 | } |
||
1318 | |||
1319 | /** |
||
1320 | * SUMIF. |
||
1321 | * |
||
1322 | * Counts the number of cells that contain numbers within the list of arguments |
||
1323 | * |
||
1324 | * Excel Function: |
||
1325 | * SUMIF(value1[,value2[, ...]],condition) |
||
1326 | * |
||
1327 | * @category Mathematical and Trigonometric Functions |
||
1328 | * |
||
1329 | * @param mixed $aArgs Data values |
||
1330 | * @param string $condition the criteria that defines which cells will be summed |
||
1331 | * @param mixed $sumArgs |
||
1332 | * |
||
1333 | * @return float |
||
1334 | */ |
||
1335 | 10 | public static function SUMIF($aArgs, $condition, $sumArgs = []) |
|
1336 | { |
||
1337 | 10 | $returnValue = 0; |
|
1338 | |||
1339 | 10 | $aArgs = Functions::flattenArray($aArgs); |
|
1340 | 10 | $sumArgs = Functions::flattenArray($sumArgs); |
|
1341 | 10 | if (empty($sumArgs)) { |
|
1342 | 1 | $sumArgs = $aArgs; |
|
1343 | } |
||
1344 | 10 | $condition = Functions::ifCondition($condition); |
|
1345 | // Loop through arguments |
||
1346 | 10 | foreach ($aArgs as $key => $arg) { |
|
1347 | 10 | if (!is_numeric($arg)) { |
|
1348 | 6 | $arg = str_replace('"', '""', $arg); |
|
1349 | 6 | $arg = Calculation::wrapResult(strtoupper($arg)); |
|
1350 | } |
||
1351 | |||
1352 | 10 | $testCondition = '=' . $arg . $condition; |
|
1353 | 10 | $sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0; |
|
1354 | |||
1355 | 10 | if (is_numeric($sumValue) && |
|
1356 | 10 | Calculation::getInstance()->_calculateFormulaValue($testCondition)) { |
|
1357 | // Is it a value within our criteria and only numeric can be added to the result |
||
1358 | 10 | $returnValue += $sumValue; |
|
1359 | } |
||
1360 | } |
||
1361 | |||
1362 | 10 | return $returnValue; |
|
1363 | } |
||
1364 | |||
1365 | /** |
||
1366 | * SUMIFS. |
||
1367 | * |
||
1368 | * Counts the number of cells that contain numbers within the list of arguments |
||
1369 | * |
||
1370 | * Excel Function: |
||
1371 | * SUMIFS(value1[,value2[, ...]],condition) |
||
1372 | * |
||
1373 | * @category Mathematical and Trigonometric Functions |
||
1374 | * |
||
1375 | * @param mixed $args Data values |
||
1376 | * @param string $condition the criteria that defines which cells will be summed |
||
1377 | * |
||
1378 | * @return float |
||
1379 | */ |
||
1380 | 2 | public static function SUMIFS(...$args) |
|
1381 | { |
||
1382 | 2 | $arrayList = $args; |
|
1383 | |||
1384 | // Return value |
||
1385 | 2 | $returnValue = 0; |
|
1386 | |||
1387 | 2 | $sumArgs = Functions::flattenArray(array_shift($arrayList)); |
|
1388 | 2 | $aArgsArray = []; |
|
1389 | 2 | $conditions = []; |
|
1390 | |||
1391 | 2 | while (count($arrayList) > 0) { |
|
1392 | 2 | $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); |
|
1393 | 2 | $conditions[] = Functions::ifCondition(array_shift($arrayList)); |
|
1394 | } |
||
1395 | |||
1396 | // Loop through each sum and see if arguments and conditions are true |
||
1397 | 2 | foreach ($sumArgs as $index => $value) { |
|
1398 | 2 | $valid = true; |
|
1399 | |||
1400 | 2 | foreach ($conditions as $cidx => $condition) { |
|
1401 | 2 | $arg = $aArgsArray[$cidx][$index]; |
|
1402 | |||
1403 | // Loop through arguments |
||
1404 | 2 | if (!is_numeric($arg)) { |
|
1405 | 2 | $arg = Calculation::wrapResult(strtoupper($arg)); |
|
1406 | } |
||
1407 | 2 | $testCondition = '=' . $arg . $condition; |
|
1408 | 2 | if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { |
|
1409 | // Is not a value within our criteria |
||
1410 | 2 | $valid = false; |
|
1411 | |||
1412 | 2 | break; // if false found, don't need to check other conditions |
|
1413 | } |
||
1414 | } |
||
1415 | |||
1416 | 2 | if ($valid) { |
|
1417 | 2 | $returnValue += $value; |
|
1418 | } |
||
1419 | } |
||
1420 | |||
1421 | // Return |
||
1422 | 2 | return $returnValue; |
|
1423 | } |
||
1424 | |||
1425 | /** |
||
1426 | * SUMPRODUCT. |
||
1427 | * |
||
1428 | * Excel Function: |
||
1429 | * SUMPRODUCT(value1[,value2[, ...]]) |
||
1430 | * |
||
1431 | * @category Mathematical and Trigonometric Functions |
||
1432 | * |
||
1433 | * @param mixed ...$args Data values |
||
1434 | * |
||
1435 | * @return float|string The result, or a string containing an error |
||
1436 | */ |
||
1437 | 3 | public static function SUMPRODUCT(...$args) |
|
1438 | { |
||
1439 | 3 | $arrayList = $args; |
|
1440 | |||
1441 | 3 | $wrkArray = Functions::flattenArray(array_shift($arrayList)); |
|
1442 | 3 | $wrkCellCount = count($wrkArray); |
|
1443 | |||
1444 | 3 | for ($i = 0; $i < $wrkCellCount; ++$i) { |
|
1445 | 3 | if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) { |
|
1446 | $wrkArray[$i] = 0; |
||
1447 | } |
||
1448 | } |
||
1449 | |||
1450 | 3 | foreach ($arrayList as $matrixData) { |
|
1451 | 3 | $array2 = Functions::flattenArray($matrixData); |
|
1452 | 3 | $count = count($array2); |
|
1453 | 3 | if ($wrkCellCount != $count) { |
|
1454 | return Functions::VALUE(); |
||
1455 | } |
||
1456 | |||
1457 | 3 | foreach ($array2 as $i => $val) { |
|
1458 | 3 | if ((!is_numeric($val)) || (is_string($val))) { |
|
1459 | $val = 0; |
||
1460 | } |
||
1461 | 3 | $wrkArray[$i] *= $val; |
|
1462 | } |
||
1463 | } |
||
1464 | |||
1465 | 3 | return array_sum($wrkArray); |
|
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * SUMSQ. |
||
1470 | * |
||
1471 | * SUMSQ returns the sum of the squares of the arguments |
||
1472 | * |
||
1473 | * Excel Function: |
||
1474 | * SUMSQ(value1[,value2[, ...]]) |
||
1475 | * |
||
1476 | * @category Mathematical and Trigonometric Functions |
||
1477 | * |
||
1478 | * @param mixed ...$args Data values |
||
1479 | * |
||
1480 | * @return float |
||
1481 | */ |
||
1482 | 7 | public static function SUMSQ(...$args) |
|
1483 | { |
||
1484 | 7 | $returnValue = 0; |
|
1485 | |||
1486 | // Loop through arguments |
||
1487 | 7 | foreach (Functions::flattenArray($args) as $arg) { |
|
1488 | // Is it a numeric value? |
||
1489 | 7 | if ((is_numeric($arg)) && (!is_string($arg))) { |
|
1490 | 7 | $returnValue += ($arg * $arg); |
|
1491 | } |
||
1492 | } |
||
1493 | |||
1494 | 7 | return $returnValue; |
|
1495 | } |
||
1496 | |||
1497 | /** |
||
1498 | * SUMX2MY2. |
||
1499 | * |
||
1500 | * @param mixed[] $matrixData1 Matrix #1 |
||
1501 | * @param mixed[] $matrixData2 Matrix #2 |
||
1502 | * |
||
1503 | * @return float |
||
1504 | */ |
||
1505 | 3 | public static function SUMX2MY2($matrixData1, $matrixData2) |
|
1506 | { |
||
1507 | 3 | $array1 = Functions::flattenArray($matrixData1); |
|
1508 | 3 | $array2 = Functions::flattenArray($matrixData2); |
|
1509 | 3 | $count = min(count($array1), count($array2)); |
|
1510 | |||
1511 | 3 | $result = 0; |
|
1512 | 3 | for ($i = 0; $i < $count; ++$i) { |
|
1513 | 3 | if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && |
|
1514 | 3 | ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) { |
|
1515 | 3 | $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); |
|
1516 | } |
||
1517 | } |
||
1518 | |||
1519 | 3 | return $result; |
|
1520 | } |
||
1521 | |||
1522 | /** |
||
1523 | * SUMX2PY2. |
||
1524 | * |
||
1525 | * @param mixed[] $matrixData1 Matrix #1 |
||
1526 | * @param mixed[] $matrixData2 Matrix #2 |
||
1527 | * |
||
1528 | * @return float |
||
1529 | */ |
||
1530 | 3 | public static function SUMX2PY2($matrixData1, $matrixData2) |
|
1531 | { |
||
1532 | 3 | $array1 = Functions::flattenArray($matrixData1); |
|
1533 | 3 | $array2 = Functions::flattenArray($matrixData2); |
|
1534 | 3 | $count = min(count($array1), count($array2)); |
|
1535 | |||
1536 | 3 | $result = 0; |
|
1537 | 3 | for ($i = 0; $i < $count; ++$i) { |
|
1538 | 3 | if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && |
|
1539 | 3 | ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) { |
|
1540 | 3 | $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); |
|
1541 | } |
||
1542 | } |
||
1543 | |||
1544 | 3 | return $result; |
|
1545 | } |
||
1546 | |||
1547 | /** |
||
1548 | * SUMXMY2. |
||
1549 | * |
||
1550 | * @param mixed[] $matrixData1 Matrix #1 |
||
1551 | * @param mixed[] $matrixData2 Matrix #2 |
||
1552 | * |
||
1553 | * @return float |
||
1554 | */ |
||
1555 | 3 | public static function SUMXMY2($matrixData1, $matrixData2) |
|
1556 | { |
||
1557 | 3 | $array1 = Functions::flattenArray($matrixData1); |
|
1558 | 3 | $array2 = Functions::flattenArray($matrixData2); |
|
1559 | 3 | $count = min(count($array1), count($array2)); |
|
1560 | |||
1561 | 3 | $result = 0; |
|
1562 | 3 | for ($i = 0; $i < $count; ++$i) { |
|
1563 | 3 | if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && |
|
1564 | 3 | ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) { |
|
1565 | 3 | $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); |
|
1566 | } |
||
1567 | } |
||
1568 | |||
1569 | 3 | return $result; |
|
1570 | } |
||
1571 | |||
1572 | /** |
||
1573 | * TRUNC. |
||
1574 | * |
||
1575 | * Truncates value to the number of fractional digits by number_digits. |
||
1576 | * |
||
1577 | * @param float $value |
||
1578 | * @param int $digits |
||
1579 | * |
||
1580 | * @return float|string Truncated value, or a string containing an error |
||
1581 | */ |
||
1582 | 19 | public static function TRUNC($value = 0, $digits = 0) |
|
1583 | { |
||
1584 | 19 | $value = Functions::flattenSingleValue($value); |
|
1585 | 19 | $digits = Functions::flattenSingleValue($digits); |
|
1586 | |||
1587 | // Validate parameters |
||
1588 | 19 | if ((!is_numeric($value)) || (!is_numeric($digits))) { |
|
1589 | 2 | return Functions::VALUE(); |
|
1590 | } |
||
1591 | 17 | $digits = floor($digits); |
|
1592 | |||
1593 | // Truncate |
||
1594 | 17 | $adjust = pow(10, $digits); |
|
1595 | |||
1596 | 17 | if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { |
|
1597 | 2 | return $value; |
|
1598 | } |
||
1599 | |||
1600 | 15 | return ((int) ($value * $adjust)) / $adjust; |
|
1601 | } |
||
1602 | |||
1603 | /** |
||
1604 | * SEC. |
||
1605 | * |
||
1606 | * Returns the secant of an angle. |
||
1607 | * |
||
1608 | * @param float $angle Number |
||
1609 | * |
||
1610 | * @return float|string The secant of the angle |
||
1611 | */ |
||
1612 | 14 | public static function SEC($angle) |
|
1613 | { |
||
1614 | 14 | $angle = Functions::flattenSingleValue($angle); |
|
1615 | |||
1616 | 14 | if (!is_numeric($angle)) { |
|
1617 | 1 | return Functions::VALUE(); |
|
1618 | } |
||
1619 | |||
1620 | 13 | $result = cos($angle); |
|
1621 | |||
1622 | 13 | return ($result == 0.0) ? Functions::DIV0() : 1 / $result; |
|
1623 | } |
||
1624 | |||
1625 | /** |
||
1626 | * SECH. |
||
1627 | * |
||
1628 | * Returns the hyperbolic secant of an angle. |
||
1629 | * |
||
1630 | * @param float $angle Number |
||
1631 | * |
||
1632 | * @return float|string The hyperbolic secant of the angle |
||
1633 | */ |
||
1634 | 14 | public static function SECH($angle) |
|
1635 | { |
||
1636 | 14 | $angle = Functions::flattenSingleValue($angle); |
|
1637 | |||
1638 | 14 | if (!is_numeric($angle)) { |
|
1639 | 1 | return Functions::VALUE(); |
|
1640 | } |
||
1641 | |||
1642 | 13 | $result = cosh($angle); |
|
1643 | |||
1644 | 13 | return ($result == 0.0) ? Functions::DIV0() : 1 / $result; |
|
1645 | } |
||
1646 | |||
1647 | /** |
||
1648 | * CSC. |
||
1649 | * |
||
1650 | * Returns the cosecant of an angle. |
||
1651 | * |
||
1652 | * @param float $angle Number |
||
1653 | * |
||
1654 | * @return float|string The cosecant of the angle |
||
1655 | */ |
||
1656 | 10 | public static function CSC($angle) |
|
1657 | { |
||
1658 | 10 | $angle = Functions::flattenSingleValue($angle); |
|
1659 | |||
1660 | 10 | if (!is_numeric($angle)) { |
|
1661 | 1 | return Functions::VALUE(); |
|
1662 | } |
||
1663 | |||
1664 | 9 | $result = sin($angle); |
|
1665 | |||
1666 | 9 | return ($result == 0.0) ? Functions::DIV0() : 1 / $result; |
|
1667 | } |
||
1668 | |||
1669 | /** |
||
1670 | * CSCH. |
||
1671 | * |
||
1672 | * Returns the hyperbolic cosecant of an angle. |
||
1673 | * |
||
1674 | * @param float $angle Number |
||
1675 | * |
||
1676 | * @return float|string The hyperbolic cosecant of the angle |
||
1677 | */ |
||
1678 | 14 | public static function CSCH($angle) |
|
1679 | { |
||
1680 | 14 | $angle = Functions::flattenSingleValue($angle); |
|
1681 | |||
1682 | 14 | if (!is_numeric($angle)) { |
|
1683 | 1 | return Functions::VALUE(); |
|
1684 | } |
||
1685 | |||
1686 | 13 | $result = sinh($angle); |
|
1687 | |||
1688 | 13 | return ($result == 0.0) ? Functions::DIV0() : 1 / $result; |
|
1689 | } |
||
1690 | |||
1691 | /** |
||
1692 | * COT. |
||
1693 | * |
||
1694 | * Returns the cotangent of an angle. |
||
1695 | * |
||
1696 | * @param float $angle Number |
||
1697 | * |
||
1698 | * @return float|string The cotangent of the angle |
||
1699 | */ |
||
1700 | 10 | public static function COT($angle) |
|
1701 | { |
||
1702 | 10 | $angle = Functions::flattenSingleValue($angle); |
|
1703 | |||
1704 | 10 | if (!is_numeric($angle)) { |
|
1705 | 1 | return Functions::VALUE(); |
|
1706 | } |
||
1707 | |||
1708 | 9 | $result = tan($angle); |
|
1709 | |||
1710 | 9 | return ($result == 0.0) ? Functions::DIV0() : 1 / $result; |
|
1711 | } |
||
1712 | |||
1713 | /** |
||
1714 | * COTH. |
||
1715 | * |
||
1716 | * Returns the hyperbolic cotangent of an angle. |
||
1717 | * |
||
1718 | * @param float $angle Number |
||
1719 | * |
||
1720 | * @return float|string The hyperbolic cotangent of the angle |
||
1721 | */ |
||
1722 | 14 | public static function COTH($angle) |
|
1723 | { |
||
1724 | 14 | $angle = Functions::flattenSingleValue($angle); |
|
1725 | |||
1726 | 14 | if (!is_numeric($angle)) { |
|
1727 | 1 | return Functions::VALUE(); |
|
1728 | } |
||
1729 | |||
1730 | 13 | $result = tanh($angle); |
|
1731 | |||
1732 | 13 | return ($result == 0.0) ? Functions::DIV0() : 1 / $result; |
|
1733 | } |
||
1734 | |||
1735 | /** |
||
1736 | * ACOT. |
||
1737 | * |
||
1738 | * Returns the arccotangent of a number. |
||
1739 | * |
||
1740 | * @param float $number Number |
||
1741 | * |
||
1742 | * @return float|string The arccotangent of the number |
||
1743 | */ |
||
1744 | 14 | public static function ACOT($number) |
|
1753 | } |
||
1754 | |||
1755 | /** |
||
1756 | * ACOTH. |
||
1757 | * |
||
1758 | * Returns the hyperbolic arccotangent of a number. |
||
1759 | * |
||
1760 | * @param float $number Number |
||
1761 | * |
||
1762 | * @return float|string The hyperbolic arccotangent of the number |
||
1763 | */ |
||
1764 | 14 | public static function ACOTH($number) |
|
1775 | } |
||
1776 | } |
||
1777 |