Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Calculation; 4 5 use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend; 6 7 class Statistical 8 { 9 const LOG_GAMMA_X_MAX_VALUE = 2.55e305; 10 const XMININ = 2.23e-308; 11 const EPS = 2.22e-16; 12 const MAX_VALUE = 1.2e308; 13 const MAX_ITERATIONS = 256; 14 const SQRT2PI = 2.5066282746310005024157652848110452530069867406099; 15 16 private static function checkTrendArrays(&$array1, &$array2) 17 { 18 if (!is_array($array1)) { 19 $array1 = [$array1]; 20 } 21 if (!is_array($array2)) { 22 $array2 = [$array2]; 23 } 24 25 $array1 = Functions::flattenArray($array1); 26 $array2 = Functions::flattenArray($array2); 27 foreach ($array1 as $key => $value) { 28 if ((is_bool($value)) || (is_string($value)) || ($value === null)) { 29 unset($array1[$key], $array2[$key]); 30 } 31 } 32 foreach ($array2 as $key => $value) { 33 if ((is_bool($value)) || (is_string($value)) || ($value === null)) { 34 unset($array1[$key], $array2[$key]); 35 } 36 } 37 $array1 = array_merge($array1); 38 $array2 = array_merge($array2); 39 40 return true; 41 } 42 43 /** 44 * Incomplete beta function. 45 * 46 * @author Jaco van Kooten 47 * @author Paul Meagher 48 * 49 * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). 50 * 51 * @param mixed $x require 0<=x<=1 52 * @param mixed $p require p>0 53 * @param mixed $q require q>0 54 * 55 * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow 56 */ 57 private static function incompleteBeta($x, $p, $q) 58 { 59 if ($x <= 0.0) { 60 return 0.0; 61 } elseif ($x >= 1.0) { 62 return 1.0; 63 } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { 64 return 0.0; 65 } 66 $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); 67 if ($x < ($p + 1.0) / ($p + $q + 2.0)) { 68 return $beta_gam * self::betaFraction($x, $p, $q) / $p; 69 } 70 71 return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); 72 } 73 74 // Function cache for logBeta function 75 private static $logBetaCacheP = 0.0; 76 77 private static $logBetaCacheQ = 0.0; 78 79 private static $logBetaCacheResult = 0.0; 80 81 /** 82 * The natural logarithm of the beta function. 83 * 84 * @param mixed $p require p>0 85 * @param mixed $q require q>0 86 * 87 * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow 88 * 89 * @author Jaco van Kooten 90 */ 91 private static function logBeta($p, $q) 92 { 93 if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { 94 self::$logBetaCacheP = $p; 95 self::$logBetaCacheQ = $q; 96 if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { 97 self::$logBetaCacheResult = 0.0; 98 } else { 99 self::$logBetaCacheResult = self::logGamma($p) + self::logGamma($q) - self::logGamma($p + $q); 100 } 101 } 102 103 return self::$logBetaCacheResult; 104 } 105 106 /** 107 * Evaluates of continued fraction part of incomplete beta function. 108 * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). 109 * 110 * @author Jaco van Kooten 111 * 112 * @param mixed $x 113 * @param mixed $p 114 * @param mixed $q 115 * 116 * @return float 117 */ 118 private static function betaFraction($x, $p, $q) 119 { 120 $c = 1.0; 121 $sum_pq = $p + $q; 122 $p_plus = $p + 1.0; 123 $p_minus = $p - 1.0; 124 $h = 1.0 - $sum_pq * $x / $p_plus; 125 if (abs($h) < self::XMININ) { 126 $h = self::XMININ; 127 } 128 $h = 1.0 / $h; 129 $frac = $h; 130 $m = 1; 131 $delta = 0.0; 132 while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { 133 $m2 = 2 * $m; 134 // even index for d 135 $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); 136 $h = 1.0 + $d * $h; 137 if (abs($h) < self::XMININ) { 138 $h = self::XMININ; 139 } 140 $h = 1.0 / $h; 141 $c = 1.0 + $d / $c; 142 if (abs($c) < self::XMININ) { 143 $c = self::XMININ; 144 } 145 $frac *= $h * $c; 146 // odd index for d 147 $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); 148 $h = 1.0 + $d * $h; 149 if (abs($h) < self::XMININ) { 150 $h = self::XMININ; 151 } 152 $h = 1.0 / $h; 153 $c = 1.0 + $d / $c; 154 if (abs($c) < self::XMININ) { 155 $c = self::XMININ; 156 } 157 $delta = $h * $c; 158 $frac *= $delta; 159 ++$m; 160 } 161 162 return $frac; 163 } 164 165 /** 166 * logGamma function. 167 * 168 * @version 1.1 169 * 170 * @author Jaco van Kooten 171 * 172 * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. 173 * 174 * The natural logarithm of the gamma function. <br /> 175 * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br /> 176 * Applied Mathematics Division <br /> 177 * Argonne National Laboratory <br /> 178 * Argonne, IL 60439 <br /> 179 * <p> 180 * References: 181 * <ol> 182 * <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural 183 * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li> 184 * <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li> 185 * <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li> 186 * </ol> 187 * </p> 188 * <p> 189 * From the original documentation: 190 * </p> 191 * <p> 192 * This routine calculates the LOG(GAMMA) function for a positive real argument X. 193 * Computation is based on an algorithm outlined in references 1 and 2. 194 * The program uses rational functions that theoretically approximate LOG(GAMMA) 195 * to at least 18 significant decimal digits. The approximation for X > 12 is from 196 * reference 3, while approximations for X < 12.0 are similar to those in reference 197 * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, 198 * the compiler, the intrinsic functions, and proper selection of the 199 * machine-dependent constants. 200 * </p> 201 * <p> 202 * Error returns: <br /> 203 * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. 204 * The computation is believed to be free of underflow and overflow. 205 * </p> 206 * 207 * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 208 */ 209 210 // Function cache for logGamma 211 private static $logGammaCacheResult = 0.0; 212 213 private static $logGammaCacheX = 0.0; 214 215 private static function logGamma($x) 216 { 217 // Log Gamma related constants 218 static $lg_d1 = -0.5772156649015328605195174; 219 static $lg_d2 = 0.4227843350984671393993777; 220 static $lg_d4 = 1.791759469228055000094023; 221 222 static $lg_p1 = [ 223 4.945235359296727046734888, 224 201.8112620856775083915565, 225 2290.838373831346393026739, 226 11319.67205903380828685045, 227 28557.24635671635335736389, 228 38484.96228443793359990269, 229 26377.48787624195437963534, 230 7225.813979700288197698961, 231 ]; 232 static $lg_p2 = [ 233 4.974607845568932035012064, 234 542.4138599891070494101986, 235 15506.93864978364947665077, 236 184793.2904445632425417223, 237 1088204.76946882876749847, 238 3338152.967987029735917223, 239 5106661.678927352456275255, 240 3074109.054850539556250927, 241 ]; 242 static $lg_p4 = [ 243 14745.02166059939948905062, 244 2426813.369486704502836312, 245 121475557.4045093227939592, 246 2663432449.630976949898078, 247 29403789566.34553899906876, 248 170266573776.5398868392998, 249 492612579337.743088758812, 250 560625185622.3951465078242, 251 ]; 252 static $lg_q1 = [ 253 67.48212550303777196073036, 254 1113.332393857199323513008, 255 7738.757056935398733233834, 256 27639.87074403340708898585, 257 54993.10206226157329794414, 258 61611.22180066002127833352, 259 36351.27591501940507276287, 260 8785.536302431013170870835, 261 ]; 262 static $lg_q2 = [ 263 183.0328399370592604055942, 264 7765.049321445005871323047, 265 133190.3827966074194402448, 266 1136705.821321969608938755, 267 5267964.117437946917577538, 268 13467014.54311101692290052, 269 17827365.30353274213975932, 270 9533095.591844353613395747, 271 ]; 272 static $lg_q4 = [ 273 2690.530175870899333379843, 274 639388.5654300092398984238, 275 41355999.30241388052042842, 276 1120872109.61614794137657, 277 14886137286.78813811542398, 278 101680358627.2438228077304, 279 341747634550.7377132798597, 280 446315818741.9713286462081, 281 ]; 282 static $lg_c = [ 283 -0.001910444077728, 284 8.4171387781295e-4, 285 -5.952379913043012e-4, 286 7.93650793500350248e-4, 287 -0.002777777777777681622553, 288 0.08333333333333333331554247, 289 0.0057083835261, 290 ]; 291 292 // Rough estimate of the fourth root of logGamma_xBig 293 static $lg_frtbig = 2.25e76; 294 static $pnt68 = 0.6796875; 295 296 if ($x == self::$logGammaCacheX) { 297 return self::$logGammaCacheResult; 298 } 299 $y = $x; 300 if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { 301 if ($y <= self::EPS) { 302 $res = -log($y); 303 } elseif ($y <= 1.5) { 304 // --------------------- 305 // EPS .LT. X .LE. 1.5 306 // --------------------- 307 if ($y < $pnt68) { 308 $corr = -log($y); 309 $xm1 = $y; 310 } else { 311 $corr = 0.0; 312 $xm1 = $y - 1.0; 313 } 314 if ($y <= 0.5 || $y >= $pnt68) { 315 $xden = 1.0; 316 $xnum = 0.0; 317 for ($i = 0; $i < 8; ++$i) { 318 $xnum = $xnum * $xm1 + $lg_p1[$i]; 319 $xden = $xden * $xm1 + $lg_q1[$i]; 320 } 321 $res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden)); 322 } else { 323 $xm2 = $y - 1.0; 324 $xden = 1.0; 325 $xnum = 0.0; 326 for ($i = 0; $i < 8; ++$i) { 327 $xnum = $xnum * $xm2 + $lg_p2[$i]; 328 $xden = $xden * $xm2 + $lg_q2[$i]; 329 } 330 $res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden)); 331 } 332 } elseif ($y <= 4.0) { 333 // --------------------- 334 // 1.5 .LT. X .LE. 4.0 335 // --------------------- 336 $xm2 = $y - 2.0; 337 $xden = 1.0; 338 $xnum = 0.0; 339 for ($i = 0; $i < 8; ++$i) { 340 $xnum = $xnum * $xm2 + $lg_p2[$i]; 341 $xden = $xden * $xm2 + $lg_q2[$i]; 342 } 343 $res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden)); 344 } elseif ($y <= 12.0) { 345 // ---------------------- 346 // 4.0 .LT. X .LE. 12.0 347 // ---------------------- 348 $xm4 = $y - 4.0; 349 $xden = -1.0; 350 $xnum = 0.0; 351 for ($i = 0; $i < 8; ++$i) { 352 $xnum = $xnum * $xm4 + $lg_p4[$i]; 353 $xden = $xden * $xm4 + $lg_q4[$i]; 354 } 355 $res = $lg_d4 + $xm4 * ($xnum / $xden); 356 } else { 357 // --------------------------------- 358 // Evaluate for argument .GE. 12.0 359 // --------------------------------- 360 $res = 0.0; 361 if ($y <= $lg_frtbig) { 362 $res = $lg_c[6]; 363 $ysq = $y * $y; 364 for ($i = 0; $i < 6; ++$i) { 365 $res = $res / $ysq + $lg_c[$i]; 366 } 367 $res /= $y; 368 $corr = log($y); 369 $res = $res + log(self::SQRT2PI) - 0.5 * $corr; 370 $res += $y * ($corr - 1.0); 371 } 372 } 373 } else { 374 // -------------------------- 375 // Return for bad arguments 376 // -------------------------- 377 $res = self::MAX_VALUE; 378 } 379 // ------------------------------ 380 // Final adjustments and return 381 // ------------------------------ 382 self::$logGammaCacheX = $x; 383 self::$logGammaCacheResult = $res; 384 385 return $res; 386 } 387 388 // 389 // Private implementation of the incomplete Gamma function 390 // 391 private static function incompleteGamma($a, $x) 392 { 393 static $max = 32; 394 $summer = 0; 395 for ($n = 0; $n <= $max; ++$n) { 396 $divisor = $a; 397 for ($i = 1; $i <= $n; ++$i) { 398 $divisor *= ($a + $i); 399 } 400 $summer += ($x ** $n / $divisor); 401 } 402 403 return $x ** $a * exp(0 - $x) * $summer; 404 } 405 406 // 407 // Private implementation of the Gamma function 408 // 409 private static function gamma($data) 410 { 411 if ($data == 0.0) { 412 return 0; 413 } 414 415 static $p0 = 1.000000000190015; 416 static $p = [ 417 1 => 76.18009172947146, 418 2 => -86.50532032941677, 419 3 => 24.01409824083091, 420 4 => -1.231739572450155, 421 5 => 1.208650973866179e-3, 422 6 => -5.395239384953e-6, 423 ]; 424 425 $y = $x = $data; 426 $tmp = $x + 5.5; 427 $tmp -= ($x + 0.5) * log($tmp); 428 429 $summer = $p0; 430 for ($j = 1; $j <= 6; ++$j) { 431 $summer += ($p[$j] / ++$y); 432 } 433 434 return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); 435 } 436 437 /* 438 * inverse_ncdf.php 439 * ------------------- 440 * begin : Friday, January 16, 2004 441 * copyright : (C) 2004 Michael Nickerson 442 * email : nickersonm@yahoo.com 443 * 444 */ 445 private static function inverseNcdf($p) 446 { 447 // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to 448 // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as 449 // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html 450 // I have not checked the accuracy of this implementation. Be aware that PHP 451 // will truncate the coeficcients to 14 digits. 452 453 // You have permission to use and distribute this function freely for 454 // whatever purpose you want, but please show common courtesy and give credit 455 // where credit is due. 456 457 // Input paramater is $p - probability - where 0 < p < 1. 458 459 // Coefficients in rational approximations 460 static $a = [ 461 1 => -3.969683028665376e+01, 462 2 => 2.209460984245205e+02, 463 3 => -2.759285104469687e+02, 464 4 => 1.383577518672690e+02, 465 5 => -3.066479806614716e+01, 466 6 => 2.506628277459239e+00, 467 ]; 468 469 static $b = [ 470 1 => -5.447609879822406e+01, 471 2 => 1.615858368580409e+02, 472 3 => -1.556989798598866e+02, 473 4 => 6.680131188771972e+01, 474 5 => -1.328068155288572e+01, 475 ]; 476 477 static $c = [ 478 1 => -7.784894002430293e-03, 479 2 => -3.223964580411365e-01, 480 3 => -2.400758277161838e+00, 481 4 => -2.549732539343734e+00, 482 5 => 4.374664141464968e+00, 483 6 => 2.938163982698783e+00, 484 ]; 485 486 static $d = [ 487 1 => 7.784695709041462e-03, 488 2 => 3.224671290700398e-01, 489 3 => 2.445134137142996e+00, 490 4 => 3.754408661907416e+00, 491 ]; 492 493 // Define lower and upper region break-points. 494 $p_low = 0.02425; //Use lower region approx. below this 495 $p_high = 1 - $p_low; //Use upper region approx. above this 496 497 if (0 < $p && $p < $p_low) { 498 // Rational approximation for lower region. 499 $q = sqrt(-2 * log($p)); 500 501 return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / 502 (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); 503 } elseif ($p_low <= $p && $p <= $p_high) { 504 // Rational approximation for central region. 505 $q = $p - 0.5; 506 $r = $q * $q; 507 508 return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / 509 ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); 510 } elseif ($p_high < $p && $p < 1) { 511 // Rational approximation for upper region. 512 $q = sqrt(-2 * log(1 - $p)); 513 514 return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) / 515 (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1); 516 } 517 // If 0 < p < 1, return a null value 518 return Functions::NULL(); 519 } 520 521 /** 522 * MS Excel does not count Booleans if passed as cell values, but they are counted if passed as literals. 523 * OpenOffice Calc always counts Booleans. 524 * Gnumeric never counts Booleans. 525 * 526 * @param mixed $arg 527 * @param mixed $k 528 * 529 * @return int|mixed 530 */ 531 private static function testAcceptedBoolean($arg, $k) 532 { 533 if ( 534 (is_bool($arg)) && 535 ((!Functions::isCellValue($k) && (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL)) || 536 (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE)) 537 ) { 538 $arg = (int) $arg; 539 } 540 541 return $arg; 542 } 543 544 /** 545 * @param mixed $arg 546 * @param mixed $k 547 * 548 * @return bool 549 */ 550 private static function isAcceptedCountable($arg, $k) 551 { 552 if ( 553 ((is_numeric($arg)) && (!is_string($arg))) || 554 ((is_numeric($arg)) && (!Functions::isCellValue($k)) && 555 (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC)) 556 ) { 557 return true; 558 } 559 560 return false; 561 } 562 563 /** 564 * AVEDEV. 565 * 566 * Returns the average of the absolute deviations of data points from their mean. 567 * AVEDEV is a measure of the variability in a data set. 568 * 569 * Excel Function: 570 * AVEDEV(value1[,value2[, ...]]) 571 * 572 * @param mixed ...$args Data values 573 * 574 * @return float|string 575 */ 576 public static function AVEDEV(...$args) 577 { 578 $aArgs = Functions::flattenArrayIndexed($args); 579 580 // Return value 581 $returnValue = 0; 582 583 $aMean = self::AVERAGE(...$args); 584 if ($aMean === Functions::DIV0()) { 585 return Functions::NAN(); 586 } elseif ($aMean === Functions::VALUE()) { 587 return Functions::VALUE(); 588 } 589 590 $aCount = 0; 591 foreach ($aArgs as $k => $arg) { 592 $arg = self::testAcceptedBoolean($arg, $k); 593 // Is it a numeric value? 594 // Strings containing numeric values are only counted if they are string literals (not cell values) 595 // and then only in MS Excel and in Open Office, not in Gnumeric 596 if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { 597 return Functions::VALUE(); 598 } 599 if (self::isAcceptedCountable($arg, $k)) { 600 $returnValue += abs($arg - $aMean); 601 ++$aCount; 602 } 603 } 604 605 // Return 606 if ($aCount === 0) { 607 return Functions::DIV0(); 608 } 609 610 return $returnValue / $aCount; 611 } 612 613 /** 614 * AVERAGE. 615 * 616 * Returns the average (arithmetic mean) of the arguments 617 * 618 * Excel Function: 619 * AVERAGE(value1[,value2[, ...]]) 620 * 621 * @param mixed ...$args Data values 622 * 623 * @return float|string 624 */ 625 public static function AVERAGE(...$args) 626 { 627 $returnValue = $aCount = 0; 628 629 // Loop through arguments 630 foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { 631 $arg = self::testAcceptedBoolean($arg, $k); 632 // Is it a numeric value? 633 // Strings containing numeric values are only counted if they are string literals (not cell values) 634 // and then only in MS Excel and in Open Office, not in Gnumeric 635 if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { 636 return Functions::VALUE(); 637 } 638 if (self::isAcceptedCountable($arg, $k)) { 639 $returnValue += $arg; 640 ++$aCount; 641 } 642 } 643 644 // Return 645 if ($aCount > 0) { 646 return $returnValue / $aCount; 647 } 648 649 return Functions::DIV0(); 650 } 651 652 /** 653 * AVERAGEA. 654 * 655 * Returns the average of its arguments, including numbers, text, and logical values 656 * 657 * Excel Function: 658 * AVERAGEA(value1[,value2[, ...]]) 659 * 660 * @param mixed ...$args Data values 661 * 662 * @return float|string 663 */ 664 public static function AVERAGEA(...$args) 665 { 666 $returnValue = null; 667 668 $aCount = 0; 669 // Loop through arguments 670 foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { 671 if ( 672 (is_bool($arg)) && 673 (!Functions::isMatrixValue($k)) 674 ) { 675 } else { 676 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { 677 if (is_bool($arg)) { 678 $arg = (int) $arg; 679 } elseif (is_string($arg)) { 680 $arg = 0; 681 } 682 $returnValue += $arg; 683 ++$aCount; 684 } 685 } 686 } 687 688 if ($aCount > 0) { 689 return $returnValue / $aCount; 690 } 691 692 return Functions::DIV0(); 693 } 694 695 /** 696 * AVERAGEIF. 697 * 698 * Returns the average value from a range of cells that contain numbers within the list of arguments 699 * 700 * Excel Function: 701 * AVERAGEIF(value1[,value2[, ...]],condition) 702 * 703 * @param mixed $aArgs Data values 704 * @param string $condition the criteria that defines which cells will be checked 705 * @param mixed[] $averageArgs Data values 706 * 707 * @return float|string 708 */ 709 public static function AVERAGEIF($aArgs, $condition, $averageArgs = []) 710 { 711 $returnValue = 0; 712 713 $aArgs = Functions::flattenArray($aArgs); 714 $averageArgs = Functions::flattenArray($averageArgs); 715 if (empty($averageArgs)) { 716 $averageArgs = $aArgs; 717 } 718 $condition = Functions::ifCondition($condition); 719 $conditionIsNumeric = strpos($condition, '"') === false; 720 721 // Loop through arguments 722 $aCount = 0; 723 foreach ($aArgs as $key => $arg) { 724 if (!is_numeric($arg)) { 725 if ($conditionIsNumeric) { 726 continue; 727 } 728 $arg = Calculation::wrapResult(strtoupper($arg)); 729 } elseif (!$conditionIsNumeric) { 730 continue; 731 } 732 $testCondition = '=' . $arg . $condition; 733 if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) { 734 $returnValue += $averageArgs[$key]; 735 ++$aCount; 736 } 737 } 738 739 if ($aCount > 0) { 740 return $returnValue / $aCount; 741 } 742 743 return Functions::DIV0(); 744 } 745 746 /** 747 * BETADIST. 748 * 749 * Returns the beta distribution. 750 * 751 * @param float $value Value at which you want to evaluate the distribution 752 * @param float $alpha Parameter to the distribution 753 * @param float $beta Parameter to the distribution 754 * @param mixed $rMin 755 * @param mixed $rMax 756 * 757 * @return float|string 758 */ 759 public static function BETADIST($value, $alpha, $beta, $rMin = 0, $rMax = 1) 760 { 761 $value = Functions::flattenSingleValue($value); 762 $alpha = Functions::flattenSingleValue($alpha); 763 $beta = Functions::flattenSingleValue($beta); 764 $rMin = Functions::flattenSingleValue($rMin); 765 $rMax = Functions::flattenSingleValue($rMax); 766 767 if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) { 768 if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { 769 return Functions::NAN(); 770 } 771 if ($rMin > $rMax) { 772 $tmp = $rMin; 773 $rMin = $rMax; 774 $rMax = $tmp; 775 } 776 $value -= $rMin; 777 $value /= ($rMax - $rMin); 778 779 return self::incompleteBeta($value, $alpha, $beta); 780 } 781 782 return Functions::VALUE(); 783 } 784 785 /** 786 * BETAINV. 787 * 788 * Returns the inverse of the Beta distribution. 789 * 790 * @param float $probability Probability at which you want to evaluate the distribution 791 * @param float $alpha Parameter to the distribution 792 * @param float $beta Parameter to the distribution 793 * @param float $rMin Minimum value 794 * @param float $rMax Maximum value 795 * 796 * @return float|string 797 */ 798 public static function BETAINV($probability, $alpha, $beta, $rMin = 0, $rMax = 1) 799 { 800 $probability = Functions::flattenSingleValue($probability); 801 $alpha = Functions::flattenSingleValue($alpha); 802 $beta = Functions::flattenSingleValue($beta); 803 $rMin = Functions::flattenSingleValue($rMin); 804 $rMax = Functions::flattenSingleValue($rMax); 805 806 if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) { 807 if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) { 808 return Functions::NAN(); 809 } 810 if ($rMin > $rMax) { 811 $tmp = $rMin; 812 $rMin = $rMax; 813 $rMax = $tmp; 814 } 815 $a = 0; 816 $b = 2; 817 818 $i = 0; 819 while ((($b - $a) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { 820 $guess = ($a + $b) / 2; 821 $result = self::BETADIST($guess, $alpha, $beta); 822 if (($result == $probability) || ($result == 0)) { 823 $b = $a; 824 } elseif ($result > $probability) { 825 $b = $guess; 826 } else { 827 $a = $guess; 828 } 829 } 830 if ($i == self::MAX_ITERATIONS) { 831 return Functions::NA(); 832 } 833 834 return round($rMin + $guess * ($rMax - $rMin), 12); 835 } 836 837 return Functions::VALUE(); 838 } 839 840 /** 841 * BINOMDIST. 842 * 843 * Returns the individual term binomial distribution probability. Use BINOMDIST in problems with 844 * a fixed number of tests or trials, when the outcomes of any trial are only success or failure, 845 * when trials are independent, and when the probability of success is constant throughout the 846 * experiment. For example, BINOMDIST can calculate the probability that two of the next three 847 * babies born are male. 848 * 849 * @param float $value Number of successes in trials 850 * @param float $trials Number of trials 851 * @param float $probability Probability of success on each trial 852 * @param bool $cumulative 853 * 854 * @return float|string 855 */ 856 public static function BINOMDIST($value, $trials, $probability, $cumulative) 857 { 858 $value = Functions::flattenSingleValue($value); 859 $trials = Functions::flattenSingleValue($trials); 860 $probability = Functions::flattenSingleValue($probability); 861 862 if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) { 863 $value = floor($value); 864 $trials = floor($trials); 865 if (($value < 0) || ($value > $trials)) { 866 return Functions::NAN(); 867 } 868 if (($probability < 0) || ($probability > 1)) { 869 return Functions::NAN(); 870 } 871 if ((is_numeric($cumulative)) || (is_bool($cumulative))) { 872 if ($cumulative) { 873 $summer = 0; 874 for ($i = 0; $i <= $value; ++$i) { 875 $summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i); 876 } 877 878 return $summer; 879 } 880 881 return MathTrig::COMBIN($trials, $value) * $probability ** $value * (1 - $probability) ** ($trials - $value); 882 } 883 } 884 885 return Functions::VALUE(); 886 } 887 888 /** 889 * CHIDIST. 890 * 891 * Returns the one-tailed probability of the chi-squared distribution. 892 * 893 * @param float $value Value for the function 894 * @param float $degrees degrees of freedom 895 * 896 * @return float|string 897 */ 898 public static function CHIDIST($value, $degrees) 899 { 900 $value = Functions::flattenSingleValue($value); 901 $degrees = Functions::flattenSingleValue($degrees); 902 903 if ((is_numeric($value)) && (is_numeric($degrees))) { 904 $degrees = floor($degrees); 905 if ($degrees < 1) { 906 return Functions::NAN(); 907 } 908 if ($value < 0) { 909 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { 910 return 1; 911 } 912 913 return Functions::NAN(); 914 } 915 916 return 1 - (self::incompleteGamma($degrees / 2, $value / 2) / self::gamma($degrees / 2)); 917 } 918 919 return Functions::VALUE(); 920 } 921 922 /** 923 * CHIINV. 924 * 925 * Returns the one-tailed probability of the chi-squared distribution. 926 * 927 * @param float $probability Probability for the function 928 * @param float $degrees degrees of freedom 929 * 930 * @return float|string 931 */ 932 public static function CHIINV($probability, $degrees) 933 { 934 $probability = Functions::flattenSingleValue($probability); 935 $degrees = Functions::flattenSingleValue($degrees); 936 937 if ((is_numeric($probability)) && (is_numeric($degrees))) { 938 $degrees = floor($degrees); 939 940 $xLo = 100; 941 $xHi = 0; 942 943 $x = $xNew = 1; 944 $dx = 1; 945 $i = 0; 946 947 while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { 948 // Apply Newton-Raphson step 949 $result = 1 - (self::incompleteGamma($degrees / 2, $x / 2) / self::gamma($degrees / 2)); 950 $error = $result - $probability; 951 if ($error == 0.0) { 952 $dx = 0; 953 } elseif ($error < 0.0) { 954 $xLo = $x; 955 } else { 956 $xHi = $x; 957 } 958 // Avoid division by zero 959 if ($result != 0.0) { 960 $dx = $error / $result; 961 $xNew = $x - $dx; 962 } 963 // If the NR fails to converge (which for example may be the 964 // case if the initial guess is too rough) we apply a bisection 965 // step to determine a more narrow interval around the root. 966 if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { 967 $xNew = ($xLo + $xHi) / 2; 968 $dx = $xNew - $x; 969 } 970 $x = $xNew; 971 } 972 if ($i == self::MAX_ITERATIONS) { 973 return Functions::NA(); 974 } 975 976 return round($x, 12); 977 } 978 979 return Functions::VALUE(); 980 } 981 982 /** 983 * CONFIDENCE. 984 * 985 * Returns the confidence interval for a population mean 986 * 987 * @param float $alpha 988 * @param float $stdDev Standard Deviation 989 * @param float $size 990 * 991 * @return float|string 992 */ 993 public static function CONFIDENCE($alpha, $stdDev, $size) 994 { 995 $alpha = Functions::flattenSingleValue($alpha); 996 $stdDev = Functions::flattenSingleValue($stdDev); 997 $size = Functions::flattenSingleValue($size); 998 999 if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) { 1000 $size = floor($size); 1001 if (($alpha <= 0) || ($alpha >= 1)) { 1002 return Functions::NAN(); 1003 } 1004 if (($stdDev <= 0) || ($size < 1)) { 1005 return Functions::NAN(); 1006 } 1007 1008 return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); 1009 } 1010 1011 return Functions::VALUE(); 1012 } 1013 1014 /** 1015 * CORREL. 1016 * 1017 * Returns covariance, the average of the products of deviations for each data point pair. 1018 * 1019 * @param mixed $yValues array of mixed Data Series Y 1020 * @param null|mixed $xValues array of mixed Data Series X 1021 * 1022 * @return float|string 1023 */ 1024 public static function CORREL($yValues, $xValues = null) 1025 { 1026 if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { 1027 return Functions::VALUE(); 1028 } 1029 if (!self::checkTrendArrays($yValues, $xValues)) { 1030 return Functions::VALUE(); 1031 } 1032 $yValueCount = count($yValues); 1033 $xValueCount = count($xValues); 1034 1035 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 1036 return Functions::NA(); 1037 } elseif ($yValueCount == 1) { 1038 return Functions::DIV0(); 1039 } 1040 1041 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 1042 1043 return $bestFitLinear->getCorrelation(); 1044 } 1045 1046 /** 1047 * COUNT. 1048 * 1049 * Counts the number of cells that contain numbers within the list of arguments 1050 * 1051 * Excel Function: 1052 * COUNT(value1[,value2[, ...]]) 1053 * 1054 * @param mixed ...$args Data values 1055 * 1056 * @return int 1057 */ 1058 public static function COUNT(...$args) 1059 { 1060 $returnValue = 0; 1061 1062 // Loop through arguments 1063 $aArgs = Functions::flattenArrayIndexed($args); 1064 foreach ($aArgs as $k => $arg) { 1065 $arg = self::testAcceptedBoolean($arg, $k); 1066 // Is it a numeric value? 1067 // Strings containing numeric values are only counted if they are string literals (not cell values) 1068 // and then only in MS Excel and in Open Office, not in Gnumeric 1069 if (self::isAcceptedCountable($arg, $k)) { 1070 ++$returnValue; 1071 } 1072 } 1073 1074 return $returnValue; 1075 } 1076 1077 /** 1078 * COUNTA. 1079 * 1080 * Counts the number of cells that are not empty within the list of arguments 1081 * 1082 * Excel Function: 1083 * COUNTA(value1[,value2[, ...]]) 1084 * 1085 * @param mixed ...$args Data values 1086 * 1087 * @return int 1088 */ 1089 public static function COUNTA(...$args) 1090 { 1091 $returnValue = 0; 1092 1093 // Loop through arguments 1094 $aArgs = Functions::flattenArrayIndexed($args); 1095 foreach ($aArgs as $k => $arg) { 1096 // Nulls are counted if literals, but not if cell values 1097 if ($arg !== null || (!Functions::isCellValue($k))) { 1098 ++$returnValue; 1099 } 1100 } 1101 1102 return $returnValue; 1103 } 1104 1105 /** 1106 * COUNTBLANK. 1107 * 1108 * Counts the number of empty cells within the list of arguments 1109 * 1110 * Excel Function: 1111 * COUNTBLANK(value1[,value2[, ...]]) 1112 * 1113 * @param mixed ...$args Data values 1114 * 1115 * @return int 1116 */ 1117 public static function COUNTBLANK(...$args) 1118 { 1119 $returnValue = 0; 1120 1121 // Loop through arguments 1122 $aArgs = Functions::flattenArray($args); 1123 foreach ($aArgs as $arg) { 1124 // Is it a blank cell? 1125 if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { 1126 ++$returnValue; 1127 } 1128 } 1129 1130 return $returnValue; 1131 } 1132 1133 /** 1134 * COUNTIF. 1135 * 1136 * Counts the number of cells that contain numbers within the list of arguments 1137 * 1138 * Excel Function: 1139 * COUNTIF(value1[,value2[, ...]],condition) 1140 * 1141 * @param mixed $aArgs Data values 1142 * @param string $condition the criteria that defines which cells will be counted 1143 * 1144 * @return int 1145 */ 1146 public static function COUNTIF($aArgs, $condition) 1147 { 1148 $returnValue = 0; 1149 1150 $aArgs = Functions::flattenArray($aArgs); 1151 $condition = Functions::ifCondition($condition); 1152 $conditionIsNumeric = strpos($condition, '"') === false; 1153 // Loop through arguments 1154 foreach ($aArgs as $arg) { 1155 if (!is_numeric($arg)) { 1156 if ($conditionIsNumeric) { 1157 continue; 1158 } 1159 $arg = Calculation::wrapResult(strtoupper($arg)); 1160 } elseif (!$conditionIsNumeric) { 1161 continue; 1162 } 1163 $testCondition = '=' . $arg . $condition; 1164 if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) { 1165 // Is it a value within our criteria 1166 ++$returnValue; 1167 } 1168 } 1169 1170 return $returnValue; 1171 } 1172 1173 /** 1174 * COUNTIFS. 1175 * 1176 * Counts the number of cells that contain numbers within the list of arguments 1177 * 1178 * Excel Function: 1179 * COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…) 1180 * 1181 * @param mixed $args Criterias 1182 * 1183 * @return int 1184 */ 1185 public static function COUNTIFS(...$args) 1186 { 1187 $arrayList = $args; 1188 1189 // Return value 1190 $returnValue = 0; 1191 1192 if (empty($arrayList)) { 1193 return $returnValue; 1194 } 1195 1196 $aArgsArray = []; 1197 $conditions = []; 1198 1199 while (count($arrayList) > 0) { 1200 $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); 1201 $conditions[] = Functions::ifCondition(array_shift($arrayList)); 1202 } 1203 1204 // Loop through each arg and see if arguments and conditions are true 1205 foreach (array_keys($aArgsArray[0]) as $index) { 1206 $valid = true; 1207 1208 foreach ($conditions as $cidx => $condition) { 1209 $conditionIsNumeric = strpos($condition, '"') === false; 1210 $arg = $aArgsArray[$cidx][$index]; 1211 1212 // Loop through arguments 1213 if (!is_numeric($arg)) { 1214 if ($conditionIsNumeric) { 1215 $valid = false; 1216 1217 break; // if false found, don't need to check other conditions 1218 } 1219 $arg = Calculation::wrapResult(strtoupper($arg)); 1220 } elseif (!$conditionIsNumeric) { 1221 $valid = false; 1222 1223 break; // if false found, don't need to check other conditions 1224 } 1225 $testCondition = '=' . $arg . $condition; 1226 if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { 1227 // Is not a value within our criteria 1228 $valid = false; 1229 1230 break; // if false found, don't need to check other conditions 1231 } 1232 } 1233 1234 if ($valid) { 1235 ++$returnValue; 1236 } 1237 } 1238 1239 // Return 1240 return $returnValue; 1241 } 1242 1243 /** 1244 * COVAR. 1245 * 1246 * Returns covariance, the average of the products of deviations for each data point pair. 1247 * 1248 * @param mixed $yValues array of mixed Data Series Y 1249 * @param mixed $xValues array of mixed Data Series X 1250 * 1251 * @return float|string 1252 */ 1253 public static function COVAR($yValues, $xValues) 1254 { 1255 if (!self::checkTrendArrays($yValues, $xValues)) { 1256 return Functions::VALUE(); 1257 } 1258 $yValueCount = count($yValues); 1259 $xValueCount = count($xValues); 1260 1261 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 1262 return Functions::NA(); 1263 } elseif ($yValueCount == 1) { 1264 return Functions::DIV0(); 1265 } 1266 1267 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 1268 1269 return $bestFitLinear->getCovariance(); 1270 } 1271 1272 /** 1273 * CRITBINOM. 1274 * 1275 * Returns the smallest value for which the cumulative binomial distribution is greater 1276 * than or equal to a criterion value 1277 * 1278 * See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used 1279 * 1280 * @param float $trials number of Bernoulli trials 1281 * @param float $probability probability of a success on each trial 1282 * @param float $alpha criterion value 1283 * 1284 * @return int|string 1285 * 1286 * @TODO Warning. This implementation differs from the algorithm detailed on the MS 1287 * web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess 1288 * This eliminates a potential endless loop error, but may have an adverse affect on the 1289 * accuracy of the function (although all my tests have so far returned correct results). 1290 */ 1291 public static function CRITBINOM($trials, $probability, $alpha) 1292 { 1293 $trials = floor(Functions::flattenSingleValue($trials)); 1294 $probability = Functions::flattenSingleValue($probability); 1295 $alpha = Functions::flattenSingleValue($alpha); 1296 1297 if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) { 1298 $trials = (int) $trials; 1299 if ($trials < 0) { 1300 return Functions::NAN(); 1301 } elseif (($probability < 0.0) || ($probability > 1.0)) { 1302 return Functions::NAN(); 1303 } elseif (($alpha < 0.0) || ($alpha > 1.0)) { 1304 return Functions::NAN(); 1305 } 1306 1307 if ($alpha <= 0.5) { 1308 $t = sqrt(log(1 / ($alpha * $alpha))); 1309 $trialsApprox = 0 - ($t + (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t)); 1310 } else { 1311 $t = sqrt(log(1 / (1 - $alpha) ** 2)); 1312 $trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t); 1313 } 1314 1315 $Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability))); 1316 if ($Guess < 0) { 1317 $Guess = 0; 1318 } elseif ($Guess > $trials) { 1319 $Guess = $trials; 1320 } 1321 1322 $TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0; 1323 $EssentiallyZero = 10e-12; 1324 1325 $m = floor($trials * $probability); 1326 ++$TotalUnscaledProbability; 1327 if ($m == $Guess) { 1328 ++$UnscaledPGuess; 1329 } 1330 if ($m <= $Guess) { 1331 ++$UnscaledCumPGuess; 1332 } 1333 1334 $PreviousValue = 1; 1335 $Done = false; 1336 $k = $m + 1; 1337 while ((!$Done) && ($k <= $trials)) { 1338 $CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability)); 1339 $TotalUnscaledProbability += $CurrentValue; 1340 if ($k == $Guess) { 1341 $UnscaledPGuess += $CurrentValue; 1342 } 1343 if ($k <= $Guess) { 1344 $UnscaledCumPGuess += $CurrentValue; 1345 } 1346 if ($CurrentValue <= $EssentiallyZero) { 1347 $Done = true; 1348 } 1349 $PreviousValue = $CurrentValue; 1350 ++$k; 1351 } 1352 1353 $PreviousValue = 1; 1354 $Done = false; 1355 $k = $m - 1; 1356 while ((!$Done) && ($k >= 0)) { 1357 $CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability); 1358 $TotalUnscaledProbability += $CurrentValue; 1359 if ($k == $Guess) { 1360 $UnscaledPGuess += $CurrentValue; 1361 } 1362 if ($k <= $Guess) { 1363 $UnscaledCumPGuess += $CurrentValue; 1364 } 1365 if ($CurrentValue <= $EssentiallyZero) { 1366 $Done = true; 1367 } 1368 $PreviousValue = $CurrentValue; 1369 --$k; 1370 } 1371 1372 $PGuess = $UnscaledPGuess / $TotalUnscaledProbability; 1373 $CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability; 1374 1375 $CumPGuessMinus1 = $CumPGuess - 1; 1376 1377 while (true) { 1378 if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) { 1379 return $Guess; 1380 } elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) { 1381 $PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability); 1382 $CumPGuessMinus1 = $CumPGuess; 1383 $CumPGuess = $CumPGuess + $PGuessPlus1; 1384 $PGuess = $PGuessPlus1; 1385 ++$Guess; 1386 } elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) { 1387 $PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability; 1388 $CumPGuess = $CumPGuessMinus1; 1389 $CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess; 1390 $PGuess = $PGuessMinus1; 1391 --$Guess; 1392 } 1393 } 1394 } 1395 1396 return Functions::VALUE(); 1397 } 1398 1399 /** 1400 * DEVSQ. 1401 * 1402 * Returns the sum of squares of deviations of data points from their sample mean. 1403 * 1404 * Excel Function: 1405 * DEVSQ(value1[,value2[, ...]]) 1406 * 1407 * @param mixed ...$args Data values 1408 * 1409 * @return float|string 1410 */ 1411 public static function DEVSQ(...$args) 1412 { 1413 $aArgs = Functions::flattenArrayIndexed($args); 1414 1415 // Return value 1416 $returnValue = null; 1417 1418 $aMean = self::AVERAGE($aArgs); 1419 if ($aMean != Functions::DIV0()) { 1420 $aCount = -1; 1421 foreach ($aArgs as $k => $arg) { 1422 // Is it a numeric value? 1423 if ( 1424 (is_bool($arg)) && 1425 ((!Functions::isCellValue($k)) || 1426 (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) 1427 ) { 1428 $arg = (int) $arg; 1429 } 1430 if ((is_numeric($arg)) && (!is_string($arg))) { 1431 if ($returnValue === null) { 1432 $returnValue = ($arg - $aMean) ** 2; 1433 } else { 1434 $returnValue += ($arg - $aMean) ** 2; 1435 } 1436 ++$aCount; 1437 } 1438 } 1439 1440 // Return 1441 if ($returnValue === null) { 1442 return Functions::NAN(); 1443 } 1444 1445 return $returnValue; 1446 } 1447 1448 return Functions::NA(); 1449 } 1450 1451 /** 1452 * EXPONDIST. 1453 * 1454 * Returns the exponential distribution. Use EXPONDIST to model the time between events, 1455 * such as how long an automated bank teller takes to deliver cash. For example, you can 1456 * use EXPONDIST to determine the probability that the process takes at most 1 minute. 1457 * 1458 * @param float $value Value of the function 1459 * @param float $lambda The parameter value 1460 * @param bool $cumulative 1461 * 1462 * @return float|string 1463 */ 1464 public static function EXPONDIST($value, $lambda, $cumulative) 1465 { 1466 $value = Functions::flattenSingleValue($value); 1467 $lambda = Functions::flattenSingleValue($lambda); 1468 $cumulative = Functions::flattenSingleValue($cumulative); 1469 1470 if ((is_numeric($value)) && (is_numeric($lambda))) { 1471 if (($value < 0) || ($lambda < 0)) { 1472 return Functions::NAN(); 1473 } 1474 if ((is_numeric($cumulative)) || (is_bool($cumulative))) { 1475 if ($cumulative) { 1476 return 1 - exp(0 - $value * $lambda); 1477 } 1478 1479 return $lambda * exp(0 - $value * $lambda); 1480 } 1481 } 1482 1483 return Functions::VALUE(); 1484 } 1485 1486 private static function betaFunction($a, $b) 1487 { 1488 return (self::gamma($a) * self::gamma($b)) / self::gamma($a + $b); 1489 } 1490 1491 private static function regularizedIncompleteBeta($value, $a, $b) 1492 { 1493 return self::incompleteBeta($value, $a, $b) / self::betaFunction($a, $b); 1494 } 1495 1496 /** 1497 * F.DIST. 1498 * 1499 * Returns the F probability distribution. 1500 * You can use this function to determine whether two data sets have different degrees of diversity. 1501 * For example, you can examine the test scores of men and women entering high school, and determine 1502 * if the variability in the females is different from that found in the males. 1503 * 1504 * @param float $value Value of the function 1505 * @param int $u The numerator degrees of freedom 1506 * @param int $v The denominator degrees of freedom 1507 * @param bool $cumulative If cumulative is TRUE, F.DIST returns the cumulative distribution function; 1508 * if FALSE, it returns the probability density function. 1509 * 1510 * @return float|string 1511 */ 1512 public static function FDIST2($value, $u, $v, $cumulative) 1513 { 1514 $value = Functions::flattenSingleValue($value); 1515 $u = Functions::flattenSingleValue($u); 1516 $v = Functions::flattenSingleValue($v); 1517 $cumulative = Functions::flattenSingleValue($cumulative); 1518 1519 if (is_numeric($value) && is_numeric($u) && is_numeric($v)) { 1520 if ($value < 0 || $u < 1 || $v < 1) { 1521 return Functions::NAN(); 1522 } 1523 1524 $cumulative = (bool) $cumulative; 1525 $u = (int) $u; 1526 $v = (int) $v; 1527 1528 if ($cumulative) { 1529 $adjustedValue = ($u * $value) / ($u * $value + $v); 1530 1531 return self::incompleteBeta($adjustedValue, $u / 2, $v / 2); 1532 } 1533 1534 return (self::gamma(($v + $u) / 2) / (self::gamma($u / 2) * self::gamma($v / 2))) * 1535 (($u / $v) ** ($u / 2)) * 1536 (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2))); 1537 } 1538 1539 return Functions::VALUE(); 1540 } 1541 1542 /** 1543 * FISHER. 1544 * 1545 * Returns the Fisher transformation at x. This transformation produces a function that 1546 * is normally distributed rather than skewed. Use this function to perform hypothesis 1547 * testing on the correlation coefficient. 1548 * 1549 * @param float $value 1550 * 1551 * @return float|string 1552 */ 1553 public static function FISHER($value) 1554 { 1555 $value = Functions::flattenSingleValue($value); 1556 1557 if (is_numeric($value)) { 1558 if (($value <= -1) || ($value >= 1)) { 1559 return Functions::NAN(); 1560 } 1561 1562 return 0.5 * log((1 + $value) / (1 - $value)); 1563 } 1564 1565 return Functions::VALUE(); 1566 } 1567 1568 /** 1569 * FISHERINV. 1570 * 1571 * Returns the inverse of the Fisher transformation. Use this transformation when 1572 * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then 1573 * FISHERINV(y) = x. 1574 * 1575 * @param float $value 1576 * 1577 * @return float|string 1578 */ 1579 public static function FISHERINV($value) 1580 { 1581 $value = Functions::flattenSingleValue($value); 1582 1583 if (is_numeric($value)) { 1584 return (exp(2 * $value) - 1) / (exp(2 * $value) + 1); 1585 } 1586 1587 return Functions::VALUE(); 1588 } 1589 1590 /** 1591 * FORECAST. 1592 * 1593 * Calculates, or predicts, a future value by using existing values. The predicted value is a y-value for a given x-value. 1594 * 1595 * @param float $xValue Value of X for which we want to find Y 1596 * @param mixed $yValues array of mixed Data Series Y 1597 * @param mixed $xValues of mixed Data Series X 1598 * 1599 * @return bool|float|string 1600 */ 1601 public static function FORECAST($xValue, $yValues, $xValues) 1602 { 1603 $xValue = Functions::flattenSingleValue($xValue); 1604 if (!is_numeric($xValue)) { 1605 return Functions::VALUE(); 1606 } elseif (!self::checkTrendArrays($yValues, $xValues)) { 1607 return Functions::VALUE(); 1608 } 1609 $yValueCount = count($yValues); 1610 $xValueCount = count($xValues); 1611 1612 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 1613 return Functions::NA(); 1614 } elseif ($yValueCount == 1) { 1615 return Functions::DIV0(); 1616 } 1617 1618 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 1619 1620 return $bestFitLinear->getValueOfYForX($xValue); 1621 } 1622 1623 /** 1624 * GAMMA. 1625 * 1626 * Return the gamma function value. 1627 * 1628 * @param float $value 1629 * 1630 * @return float|string The result, or a string containing an error 1631 */ 1632 public static function GAMMAFunction($value) 1633 { 1634 $value = Functions::flattenSingleValue($value); 1635 if (!is_numeric($value)) { 1636 return Functions::VALUE(); 1637 } elseif ((((int) $value) == ((float) $value)) && $value <= 0.0) { 1638 return Functions::NAN(); 1639 } 1640 1641 return self::gamma($value); 1642 } 1643 1644 /** 1645 * GAMMADIST. 1646 * 1647 * Returns the gamma distribution. 1648 * 1649 * @param float $value Value at which you want to evaluate the distribution 1650 * @param float $a Parameter to the distribution 1651 * @param float $b Parameter to the distribution 1652 * @param bool $cumulative 1653 * 1654 * @return float|string 1655 */ 1656 public static function GAMMADIST($value, $a, $b, $cumulative) 1657 { 1658 $value = Functions::flattenSingleValue($value); 1659 $a = Functions::flattenSingleValue($a); 1660 $b = Functions::flattenSingleValue($b); 1661 1662 if ((is_numeric($value)) && (is_numeric($a)) && (is_numeric($b))) { 1663 if (($value < 0) || ($a <= 0) || ($b <= 0)) { 1664 return Functions::NAN(); 1665 } 1666 if ((is_numeric($cumulative)) || (is_bool($cumulative))) { 1667 if ($cumulative) { 1668 return self::incompleteGamma($a, $value / $b) / self::gamma($a); 1669 } 1670 1671 return (1 / ($b ** $a * self::gamma($a))) * $value ** ($a - 1) * exp(0 - ($value / $b)); 1672 } 1673 } 1674 1675 return Functions::VALUE(); 1676 } 1677 1678 /** 1679 * GAMMAINV. 1680 * 1681 * Returns the inverse of the Gamma distribution. 1682 * 1683 * @param float $probability Probability at which you want to evaluate the distribution 1684 * @param float $alpha Parameter to the distribution 1685 * @param float $beta Parameter to the distribution 1686 * 1687 * @return float|string 1688 */ 1689 public static function GAMMAINV($probability, $alpha, $beta) 1690 { 1691 $probability = Functions::flattenSingleValue($probability); 1692 $alpha = Functions::flattenSingleValue($alpha); 1693 $beta = Functions::flattenSingleValue($beta); 1694 1695 if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta))) { 1696 if (($alpha <= 0) || ($beta <= 0) || ($probability < 0) || ($probability > 1)) { 1697 return Functions::NAN(); 1698 } 1699 1700 $xLo = 0; 1701 $xHi = $alpha * $beta * 5; 1702 1703 $x = $xNew = 1; 1704 $dx = 1024; 1705 $i = 0; 1706 1707 while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { 1708 // Apply Newton-Raphson step 1709 $error = self::GAMMADIST($x, $alpha, $beta, true) - $probability; 1710 if ($error < 0.0) { 1711 $xLo = $x; 1712 } else { 1713 $xHi = $x; 1714 } 1715 $pdf = self::GAMMADIST($x, $alpha, $beta, false); 1716 // Avoid division by zero 1717 if ($pdf != 0.0) { 1718 $dx = $error / $pdf; 1719 $xNew = $x - $dx; 1720 } 1721 // If the NR fails to converge (which for example may be the 1722 // case if the initial guess is too rough) we apply a bisection 1723 // step to determine a more narrow interval around the root. 1724 if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { 1725 $xNew = ($xLo + $xHi) / 2; 1726 $dx = $xNew - $x; 1727 } 1728 $x = $xNew; 1729 } 1730 if ($i == self::MAX_ITERATIONS) { 1731 return Functions::NA(); 1732 } 1733 1734 return $x; 1735 } 1736 1737 return Functions::VALUE(); 1738 } 1739 1740 /** 1741 * GAMMALN. 1742 * 1743 * Returns the natural logarithm of the gamma function. 1744 * 1745 * @param float $value 1746 * 1747 * @return float|string 1748 */ 1749 public static function GAMMALN($value) 1750 { 1751 $value = Functions::flattenSingleValue($value); 1752 1753 if (is_numeric($value)) { 1754 if ($value <= 0) { 1755 return Functions::NAN(); 1756 } 1757 1758 return log(self::gamma($value)); 1759 } 1760 1761 return Functions::VALUE(); 1762 } 1763 1764 /** 1765 * GAUSS. 1766 * 1767 * Calculates the probability that a member of a standard normal population will fall between 1768 * the mean and z standard deviations from the mean. 1769 * 1770 * @param float $value 1771 * 1772 * @return float|string The result, or a string containing an error 1773 */ 1774 public static function GAUSS($value) 1775 { 1776 $value = Functions::flattenSingleValue($value); 1777 if (!is_numeric($value)) { 1778 return Functions::VALUE(); 1779 } 1780 1781 return self::NORMDIST($value, 0, 1, true) - 0.5; 1782 } 1783 1784 /** 1785 * GEOMEAN. 1786 * 1787 * Returns the geometric mean of an array or range of positive data. For example, you 1788 * can use GEOMEAN to calculate average growth rate given compound interest with 1789 * variable rates. 1790 * 1791 * Excel Function: 1792 * GEOMEAN(value1[,value2[, ...]]) 1793 * 1794 * @param mixed ...$args Data values 1795 * 1796 * @return float|string 1797 */ 1798 public static function GEOMEAN(...$args) 1799 { 1800 $aArgs = Functions::flattenArray($args); 1801 1802 $aMean = MathTrig::PRODUCT($aArgs); 1803 if (is_numeric($aMean) && ($aMean > 0)) { 1804 $aCount = self::COUNT($aArgs); 1805 if (self::MIN($aArgs) > 0) { 1806 return $aMean ** (1 / $aCount); 1807 } 1808 } 1809 1810 return Functions::NAN(); 1811 } 1812 1813 /** 1814 * GROWTH. 1815 * 1816 * Returns values along a predicted exponential Trend 1817 * 1818 * @param mixed[] $yValues Data Series Y 1819 * @param mixed[] $xValues Data Series X 1820 * @param mixed[] $newValues Values of X for which we want to find Y 1821 * @param bool $const a logical value specifying whether to force the intersect to equal 0 1822 * 1823 * @return array of float 1824 */ 1825 public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) 1826 { 1827 $yValues = Functions::flattenArray($yValues); 1828 $xValues = Functions::flattenArray($xValues); 1829 $newValues = Functions::flattenArray($newValues); 1830 $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); 1831 1832 $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); 1833 if (empty($newValues)) { 1834 $newValues = $bestFitExponential->getXValues(); 1835 } 1836 1837 $returnArray = []; 1838 foreach ($newValues as $xValue) { 1839 $returnArray[0][] = $bestFitExponential->getValueOfYForX($xValue); 1840 } 1841 1842 return $returnArray; 1843 } 1844 1845 /** 1846 * HARMEAN. 1847 * 1848 * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the 1849 * arithmetic mean of reciprocals. 1850 * 1851 * Excel Function: 1852 * HARMEAN(value1[,value2[, ...]]) 1853 * 1854 * @param mixed ...$args Data values 1855 * 1856 * @return float|string 1857 */ 1858 public static function HARMEAN(...$args) 1859 { 1860 // Return value 1861 $returnValue = 0; 1862 1863 // Loop through arguments 1864 $aArgs = Functions::flattenArray($args); 1865 if (self::MIN($aArgs) < 0) { 1866 return Functions::NAN(); 1867 } 1868 $aCount = 0; 1869 foreach ($aArgs as $arg) { 1870 // Is it a numeric value? 1871 if ((is_numeric($arg)) && (!is_string($arg))) { 1872 if ($arg <= 0) { 1873 return Functions::NAN(); 1874 } 1875 $returnValue += (1 / $arg); 1876 ++$aCount; 1877 } 1878 } 1879 1880 // Return 1881 if ($aCount > 0) { 1882 return 1 / ($returnValue / $aCount); 1883 } 1884 1885 return Functions::NA(); 1886 } 1887 1888 /** 1889 * HYPGEOMDIST. 1890 * 1891 * Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of 1892 * sample successes, given the sample size, population successes, and population size. 1893 * 1894 * @param float $sampleSuccesses Number of successes in the sample 1895 * @param float $sampleNumber Size of the sample 1896 * @param float $populationSuccesses Number of successes in the population 1897 * @param float $populationNumber Population size 1898 * 1899 * @return float|string 1900 */ 1901 public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) 1902 { 1903 $sampleSuccesses = Functions::flattenSingleValue($sampleSuccesses); 1904 $sampleNumber = Functions::flattenSingleValue($sampleNumber); 1905 $populationSuccesses = Functions::flattenSingleValue($populationSuccesses); 1906 $populationNumber = Functions::flattenSingleValue($populationNumber); 1907 1908 if ((is_numeric($sampleSuccesses)) && (is_numeric($sampleNumber)) && (is_numeric($populationSuccesses)) && (is_numeric($populationNumber))) { 1909 $sampleSuccesses = floor($sampleSuccesses); 1910 $sampleNumber = floor($sampleNumber); 1911 $populationSuccesses = floor($populationSuccesses); 1912 $populationNumber = floor($populationNumber); 1913 1914 if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { 1915 return Functions::NAN(); 1916 } 1917 if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { 1918 return Functions::NAN(); 1919 } 1920 if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { 1921 return Functions::NAN(); 1922 } 1923 1924 return MathTrig::COMBIN($populationSuccesses, $sampleSuccesses) * 1925 MathTrig::COMBIN($populationNumber - $populationSuccesses, $sampleNumber - $sampleSuccesses) / 1926 MathTrig::COMBIN($populationNumber, $sampleNumber); 1927 } 1928 1929 return Functions::VALUE(); 1930 } 1931 1932 /** 1933 * INTERCEPT. 1934 * 1935 * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. 1936 * 1937 * @param mixed[] $yValues Data Series Y 1938 * @param mixed[] $xValues Data Series X 1939 * 1940 * @return float|string 1941 */ 1942 public static function INTERCEPT($yValues, $xValues) 1943 { 1944 if (!self::checkTrendArrays($yValues, $xValues)) { 1945 return Functions::VALUE(); 1946 } 1947 $yValueCount = count($yValues); 1948 $xValueCount = count($xValues); 1949 1950 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 1951 return Functions::NA(); 1952 } elseif ($yValueCount == 1) { 1953 return Functions::DIV0(); 1954 } 1955 1956 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 1957 1958 return $bestFitLinear->getIntersect(); 1959 } 1960 1961 /** 1962 * KURT. 1963 * 1964 * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness 1965 * or flatness of a distribution compared with the normal distribution. Positive 1966 * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a 1967 * relatively flat distribution. 1968 * 1969 * @param array ...$args Data Series 1970 * 1971 * @return float|string 1972 */ 1973 public static function KURT(...$args) 1974 { 1975 $aArgs = Functions::flattenArrayIndexed($args); 1976 $mean = self::AVERAGE($aArgs); 1977 $stdDev = self::STDEV($aArgs); 1978 1979 if ($stdDev > 0) { 1980 $count = $summer = 0; 1981 // Loop through arguments 1982 foreach ($aArgs as $k => $arg) { 1983 if ( 1984 (is_bool($arg)) && 1985 (!Functions::isMatrixValue($k)) 1986 ) { 1987 } else { 1988 // Is it a numeric value? 1989 if ((is_numeric($arg)) && (!is_string($arg))) { 1990 $summer += (($arg - $mean) / $stdDev) ** 4; 1991 ++$count; 1992 } 1993 } 1994 } 1995 1996 // Return 1997 if ($count > 3) { 1998 return $summer * ($count * ($count + 1) / (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2 / (($count - 2) * ($count - 3))); 1999 } 2000 } 2001 2002 return Functions::DIV0(); 2003 } 2004 2005 /** 2006 * LARGE. 2007 * 2008 * Returns the nth largest value in a data set. You can use this function to 2009 * select a value based on its relative standing. 2010 * 2011 * Excel Function: 2012 * LARGE(value1[,value2[, ...]],entry) 2013 * 2014 * @param mixed $args Data values 2015 * 2016 * @return float|string The result, or a string containing an error 2017 */ 2018 public static function LARGE(...$args) 2019 { 2020 $aArgs = Functions::flattenArray($args); 2021 $entry = array_pop($aArgs); 2022 2023 if ((is_numeric($entry)) && (!is_string($entry))) { 2024 $entry = (int) floor($entry); 2025 2026 // Calculate 2027 $mArgs = []; 2028 foreach ($aArgs as $arg) { 2029 // Is it a numeric value? 2030 if ((is_numeric($arg)) && (!is_string($arg))) { 2031 $mArgs[] = $arg; 2032 } 2033 } 2034 $count = self::COUNT($mArgs); 2035 --$entry; 2036 if (($entry < 0) || ($entry >= $count) || ($count == 0)) { 2037 return Functions::NAN(); 2038 } 2039 rsort($mArgs); 2040 2041 return $mArgs[$entry]; 2042 } 2043 2044 return Functions::VALUE(); 2045 } 2046 2047 /** 2048 * LINEST. 2049 * 2050 * Calculates the statistics for a line by using the "least squares" method to calculate a straight line that best fits your data, 2051 * and then returns an array that describes the line. 2052 * 2053 * @param mixed[] $yValues Data Series Y 2054 * @param null|mixed[] $xValues Data Series X 2055 * @param bool $const a logical value specifying whether to force the intersect to equal 0 2056 * @param bool $stats a logical value specifying whether to return additional regression statistics 2057 * 2058 * @return array|int|string The result, or a string containing an error 2059 */ 2060 public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) 2061 { 2062 $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); 2063 $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); 2064 if ($xValues === null) { 2065 $xValues = range(1, count(Functions::flattenArray($yValues))); 2066 } 2067 2068 if (!self::checkTrendArrays($yValues, $xValues)) { 2069 return Functions::VALUE(); 2070 } 2071 $yValueCount = count($yValues); 2072 $xValueCount = count($xValues); 2073 2074 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 2075 return Functions::NA(); 2076 } elseif ($yValueCount == 1) { 2077 return 0; 2078 } 2079 2080 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); 2081 if ($stats) { 2082 return [ 2083 [ 2084 $bestFitLinear->getSlope(), 2085 $bestFitLinear->getSlopeSE(), 2086 $bestFitLinear->getGoodnessOfFit(), 2087 $bestFitLinear->getF(), 2088 $bestFitLinear->getSSRegression(), 2089 ], 2090 [ 2091 $bestFitLinear->getIntersect(), 2092 $bestFitLinear->getIntersectSE(), 2093 $bestFitLinear->getStdevOfResiduals(), 2094 $bestFitLinear->getDFResiduals(), 2095 $bestFitLinear->getSSResiduals(), 2096 ], 2097 ]; 2098 } 2099 2100 return [ 2101 $bestFitLinear->getSlope(), 2102 $bestFitLinear->getIntersect(), 2103 ]; 2104 } 2105 2106 /** 2107 * LOGEST. 2108 * 2109 * Calculates an exponential curve that best fits the X and Y data series, 2110 * and then returns an array that describes the line. 2111 * 2112 * @param mixed[] $yValues Data Series Y 2113 * @param null|mixed[] $xValues Data Series X 2114 * @param bool $const a logical value specifying whether to force the intersect to equal 0 2115 * @param bool $stats a logical value specifying whether to return additional regression statistics 2116 * 2117 * @return array|int|string The result, or a string containing an error 2118 */ 2119 public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) 2120 { 2121 $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); 2122 $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); 2123 if ($xValues === null) { 2124 $xValues = range(1, count(Functions::flattenArray($yValues))); 2125 } 2126 2127 if (!self::checkTrendArrays($yValues, $xValues)) { 2128 return Functions::VALUE(); 2129 } 2130 $yValueCount = count($yValues); 2131 $xValueCount = count($xValues); 2132 2133 foreach ($yValues as $value) { 2134 if ($value <= 0.0) { 2135 return Functions::NAN(); 2136 } 2137 } 2138 2139 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 2140 return Functions::NA(); 2141 } elseif ($yValueCount == 1) { 2142 return 1; 2143 } 2144 2145 $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); 2146 if ($stats) { 2147 return [ 2148 [ 2149 $bestFitExponential->getSlope(), 2150 $bestFitExponential->getSlopeSE(), 2151 $bestFitExponential->getGoodnessOfFit(), 2152 $bestFitExponential->getF(), 2153 $bestFitExponential->getSSRegression(), 2154 ], 2155 [ 2156 $bestFitExponential->getIntersect(), 2157 $bestFitExponential->getIntersectSE(), 2158 $bestFitExponential->getStdevOfResiduals(), 2159 $bestFitExponential->getDFResiduals(), 2160 $bestFitExponential->getSSResiduals(), 2161 ], 2162 ]; 2163 } 2164 2165 return [ 2166 $bestFitExponential->getSlope(), 2167 $bestFitExponential->getIntersect(), 2168 ]; 2169 } 2170 2171 /** 2172 * LOGINV. 2173 * 2174 * Returns the inverse of the normal cumulative distribution 2175 * 2176 * @param float $probability 2177 * @param float $mean 2178 * @param float $stdDev 2179 * 2180 * @return float|string The result, or a string containing an error 2181 * 2182 * @TODO Try implementing P J Acklam's refinement algorithm for greater 2183 * accuracy if I can get my head round the mathematics 2184 * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ 2185 */ 2186 public static function LOGINV($probability, $mean, $stdDev) 2187 { 2188 $probability = Functions::flattenSingleValue($probability); 2189 $mean = Functions::flattenSingleValue($mean); 2190 $stdDev = Functions::flattenSingleValue($stdDev); 2191 2192 if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { 2193 if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) { 2194 return Functions::NAN(); 2195 } 2196 2197 return exp($mean + $stdDev * self::NORMSINV($probability)); 2198 } 2199 2200 return Functions::VALUE(); 2201 } 2202 2203 /** 2204 * LOGNORMDIST. 2205 * 2206 * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed 2207 * with parameters mean and standard_dev. 2208 * 2209 * @param float $value 2210 * @param float $mean 2211 * @param float $stdDev 2212 * 2213 * @return float|string The result, or a string containing an error 2214 */ 2215 public static function LOGNORMDIST($value, $mean, $stdDev) 2216 { 2217 $value = Functions::flattenSingleValue($value); 2218 $mean = Functions::flattenSingleValue($mean); 2219 $stdDev = Functions::flattenSingleValue($stdDev); 2220 2221 if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { 2222 if (($value <= 0) || ($stdDev <= 0)) { 2223 return Functions::NAN(); 2224 } 2225 2226 return self::NORMSDIST((log($value) - $mean) / $stdDev); 2227 } 2228 2229 return Functions::VALUE(); 2230 } 2231 2232 /** 2233 * LOGNORM.DIST. 2234 * 2235 * Returns the lognormal distribution of x, where ln(x) is normally distributed 2236 * with parameters mean and standard_dev. 2237 * 2238 * @param float $value 2239 * @param float $mean 2240 * @param float $stdDev 2241 * @param bool $cumulative 2242 * 2243 * @return float|string The result, or a string containing an error 2244 */ 2245 public static function LOGNORMDIST2($value, $mean, $stdDev, $cumulative = false) 2246 { 2247 $value = Functions::flattenSingleValue($value); 2248 $mean = Functions::flattenSingleValue($mean); 2249 $stdDev = Functions::flattenSingleValue($stdDev); 2250 $cumulative = (bool) Functions::flattenSingleValue($cumulative); 2251 2252 if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { 2253 if (($value <= 0) || ($stdDev <= 0)) { 2254 return Functions::NAN(); 2255 } 2256 2257 if ($cumulative === true) { 2258 return self::NORMSDIST2((log($value) - $mean) / $stdDev, true); 2259 } 2260 2261 return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * 2262 exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); 2263 } 2264 2265 return Functions::VALUE(); 2266 } 2267 2268 /** 2269 * MAX. 2270 * 2271 * MAX returns the value of the element of the values passed that has the highest value, 2272 * with negative numbers considered smaller than positive numbers. 2273 * 2274 * Excel Function: 2275 * MAX(value1[,value2[, ...]]) 2276 * 2277 * @param mixed ...$args Data values 2278 * 2279 * @return float 2280 */ 2281 public static function MAX(...$args) 2282 { 2283 $returnValue = null; 2284 2285 // Loop through arguments 2286 $aArgs = Functions::flattenArray($args); 2287 foreach ($aArgs as $arg) { 2288 // Is it a numeric value? 2289 if ((is_numeric($arg)) && (!is_string($arg))) { 2290 if (($returnValue === null) || ($arg > $returnValue)) { 2291 $returnValue = $arg; 2292 } 2293 } 2294 } 2295 2296 if ($returnValue === null) { 2297 return 0; 2298 } 2299 2300 return $returnValue; 2301 } 2302 2303 /** 2304 * MAXA. 2305 * 2306 * Returns the greatest value in a list of arguments, including numbers, text, and logical values 2307 * 2308 * Excel Function: 2309 * MAXA(value1[,value2[, ...]]) 2310 * 2311 * @param mixed ...$args Data values 2312 * 2313 * @return float 2314 */ 2315 public static function MAXA(...$args) 2316 { 2317 $returnValue = null; 2318 2319 // Loop through arguments 2320 $aArgs = Functions::flattenArray($args); 2321 foreach ($aArgs as $arg) { 2322 // Is it a numeric value? 2323 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { 2324 if (is_bool($arg)) { 2325 $arg = (int) $arg; 2326 } elseif (is_string($arg)) { 2327 $arg = 0; 2328 } 2329 if (($returnValue === null) || ($arg > $returnValue)) { 2330 $returnValue = $arg; 2331 } 2332 } 2333 } 2334 2335 if ($returnValue === null) { 2336 return 0; 2337 } 2338 2339 return $returnValue; 2340 } 2341 2342 /** 2343 * MAXIFS. 2344 * 2345 * Counts the maximum value within a range of cells that contain numbers within the list of arguments 2346 * 2347 * Excel Function: 2348 * MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) 2349 * 2350 * @param mixed $args Data range and criterias 2351 * 2352 * @return float 2353 */ 2354 public static function MAXIFS(...$args) 2355 { 2356 $arrayList = $args; 2357 2358 // Return value 2359 $returnValue = null; 2360 2361 $maxArgs = Functions::flattenArray(array_shift($arrayList)); 2362 $aArgsArray = []; 2363 $conditions = []; 2364 2365 while (count($arrayList) > 0) { 2366 $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); 2367 $conditions[] = Functions::ifCondition(array_shift($arrayList)); 2368 } 2369 2370 // Loop through each arg and see if arguments and conditions are true 2371 foreach ($maxArgs as $index => $value) { 2372 $valid = true; 2373 2374 foreach ($conditions as $cidx => $condition) { 2375 $arg = $aArgsArray[$cidx][$index]; 2376 2377 // Loop through arguments 2378 if (!is_numeric($arg)) { 2379 $arg = Calculation::wrapResult(strtoupper($arg)); 2380 } 2381 $testCondition = '=' . $arg . $condition; 2382 if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { 2383 // Is not a value within our criteria 2384 $valid = false; 2385 2386 break; // if false found, don't need to check other conditions 2387 } 2388 } 2389 2390 if ($valid) { 2391 $returnValue = $returnValue === null ? $value : max($value, $returnValue); 2392 } 2393 } 2394 2395 // Return 2396 return $returnValue; 2397 } 2398 2399 /** 2400 * MEDIAN. 2401 * 2402 * Returns the median of the given numbers. The median is the number in the middle of a set of numbers. 2403 * 2404 * Excel Function: 2405 * MEDIAN(value1[,value2[, ...]]) 2406 * 2407 * @param mixed ...$args Data values 2408 * 2409 * @return float|string The result, or a string containing an error 2410 */ 2411 public static function MEDIAN(...$args) 2412 { 2413 $returnValue = Functions::NAN(); 2414 2415 $mArgs = []; 2416 // Loop through arguments 2417 $aArgs = Functions::flattenArray($args); 2418 foreach ($aArgs as $arg) { 2419 // Is it a numeric value? 2420 if ((is_numeric($arg)) && (!is_string($arg))) { 2421 $mArgs[] = $arg; 2422 } 2423 } 2424 2425 $mValueCount = count($mArgs); 2426 if ($mValueCount > 0) { 2427 sort($mArgs, SORT_NUMERIC); 2428 $mValueCount = $mValueCount / 2; 2429 if ($mValueCount == floor($mValueCount)) { 2430 $returnValue = ($mArgs[$mValueCount--] + $mArgs[$mValueCount]) / 2; 2431 } else { 2432 $mValueCount = floor($mValueCount); 2433 $returnValue = $mArgs[$mValueCount]; 2434 } 2435 } 2436 2437 return $returnValue; 2438 } 2439 2440 /** 2441 * MIN. 2442 * 2443 * MIN returns the value of the element of the values passed that has the smallest value, 2444 * with negative numbers considered smaller than positive numbers. 2445 * 2446 * Excel Function: 2447 * MIN(value1[,value2[, ...]]) 2448 * 2449 * @param mixed ...$args Data values 2450 * 2451 * @return float 2452 */ 2453 public static function MIN(...$args) 2454 { 2455 $returnValue = null; 2456 2457 // Loop through arguments 2458 $aArgs = Functions::flattenArray($args); 2459 foreach ($aArgs as $arg) { 2460 // Is it a numeric value? 2461 if ((is_numeric($arg)) && (!is_string($arg))) { 2462 if (($returnValue === null) || ($arg < $returnValue)) { 2463 $returnValue = $arg; 2464 } 2465 } 2466 } 2467 2468 if ($returnValue === null) { 2469 return 0; 2470 } 2471 2472 return $returnValue; 2473 } 2474 2475 /** 2476 * MINA. 2477 * 2478 * Returns the smallest value in a list of arguments, including numbers, text, and logical values 2479 * 2480 * Excel Function: 2481 * MINA(value1[,value2[, ...]]) 2482 * 2483 * @param mixed ...$args Data values 2484 * 2485 * @return float 2486 */ 2487 public static function MINA(...$args) 2488 { 2489 $returnValue = null; 2490 2491 // Loop through arguments 2492 $aArgs = Functions::flattenArray($args); 2493 foreach ($aArgs as $arg) { 2494 // Is it a numeric value? 2495 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { 2496 if (is_bool($arg)) { 2497 $arg = (int) $arg; 2498 } elseif (is_string($arg)) { 2499 $arg = 0; 2500 } 2501 if (($returnValue === null) || ($arg < $returnValue)) { 2502 $returnValue = $arg; 2503 } 2504 } 2505 } 2506 2507 if ($returnValue === null) { 2508 return 0; 2509 } 2510 2511 return $returnValue; 2512 } 2513 2514 /** 2515 * MINIFS. 2516 * 2517 * Returns the minimum value within a range of cells that contain numbers within the list of arguments 2518 * 2519 * Excel Function: 2520 * MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) 2521 * 2522 * @param mixed $args Data range and criterias 2523 * 2524 * @return float 2525 */ 2526 public static function MINIFS(...$args) 2527 { 2528 $arrayList = $args; 2529 2530 // Return value 2531 $returnValue = null; 2532 2533 $minArgs = Functions::flattenArray(array_shift($arrayList)); 2534 $aArgsArray = []; 2535 $conditions = []; 2536 2537 while (count($arrayList) > 0) { 2538 $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); 2539 $conditions[] = Functions::ifCondition(array_shift($arrayList)); 2540 } 2541 2542 // Loop through each arg and see if arguments and conditions are true 2543 foreach ($minArgs as $index => $value) { 2544 $valid = true; 2545 2546 foreach ($conditions as $cidx => $condition) { 2547 $arg = $aArgsArray[$cidx][$index]; 2548 2549 // Loop through arguments 2550 if (!is_numeric($arg)) { 2551 $arg = Calculation::wrapResult(strtoupper($arg)); 2552 } 2553 $testCondition = '=' . $arg . $condition; 2554 if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { 2555 // Is not a value within our criteria 2556 $valid = false; 2557 2558 break; // if false found, don't need to check other conditions 2559 } 2560 } 2561 2562 if ($valid) { 2563 $returnValue = $returnValue === null ? $value : min($value, $returnValue); 2564 } 2565 } 2566 2567 // Return 2568 return $returnValue; 2569 } 2570 2571 // 2572 // Special variant of array_count_values that isn't limited to strings and integers, 2573 // but can work with floating point numbers as values 2574 // 2575 private static function modeCalc($data) 2576 { 2577 $frequencyArray = []; 2578 $index = 0; 2579 $maxfreq = 0; 2580 $maxfreqkey = ''; 2581 $maxfreqdatum = ''; 2582 foreach ($data as $datum) { 2583 $found = false; 2584 ++$index; 2585 foreach ($frequencyArray as $key => $value) { 2586 if ((string) $value['value'] == (string) $datum) { 2587 ++$frequencyArray[$key]['frequency']; 2588 $freq = $frequencyArray[$key]['frequency']; 2589 if ($freq > $maxfreq) { 2590 $maxfreq = $freq; 2591 $maxfreqkey = $key; 2592 $maxfreqdatum = $datum; 2593 } elseif ($freq == $maxfreq) { 2594 if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { 2595 $maxfreqkey = $key; 2596 $maxfreqdatum = $datum; 2597 } 2598 } 2599 $found = true; 2600 2601 break; 2602 } 2603 } 2604 if (!$found) { 2605 $frequencyArray[] = [ 2606 'value' => $datum, 2607 'frequency' => 1, 2608 'index' => $index, 2609 ]; 2610 } 2611 } 2612 2613 if ($maxfreq <= 1) { 2614 return Functions::NA(); 2615 } 2616 2617 return $maxfreqdatum; 2618 } 2619 2620 /** 2621 * MODE. 2622 * 2623 * Returns the most frequently occurring, or repetitive, value in an array or range of data 2624 * 2625 * Excel Function: 2626 * MODE(value1[,value2[, ...]]) 2627 * 2628 * @param mixed ...$args Data values 2629 * 2630 * @return float|string The result, or a string containing an error 2631 */ 2632 public static function MODE(...$args) 2633 { 2634 $returnValue = Functions::NA(); 2635 2636 // Loop through arguments 2637 $aArgs = Functions::flattenArray($args); 2638 2639 $mArgs = []; 2640 foreach ($aArgs as $arg) { 2641 // Is it a numeric value? 2642 if ((is_numeric($arg)) && (!is_string($arg))) { 2643 $mArgs[] = $arg; 2644 } 2645 } 2646 2647 if (!empty($mArgs)) { 2648 return self::modeCalc($mArgs); 2649 } 2650 2651 return $returnValue; 2652 } 2653 2654 /** 2655 * NEGBINOMDIST. 2656 * 2657 * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that 2658 * there will be number_f failures before the number_s-th success, when the constant 2659 * probability of a success is probability_s. This function is similar to the binomial 2660 * distribution, except that the number of successes is fixed, and the number of trials is 2661 * variable. Like the binomial, trials are assumed to be independent. 2662 * 2663 * @param float $failures Number of Failures 2664 * @param float $successes Threshold number of Successes 2665 * @param float $probability Probability of success on each trial 2666 * 2667 * @return float|string The result, or a string containing an error 2668 */ 2669 public static function NEGBINOMDIST($failures, $successes, $probability) 2670 { 2671 $failures = floor(Functions::flattenSingleValue($failures)); 2672 $successes = floor(Functions::flattenSingleValue($successes)); 2673 $probability = Functions::flattenSingleValue($probability); 2674 2675 if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) { 2676 if (($failures < 0) || ($successes < 1)) { 2677 return Functions::NAN(); 2678 } elseif (($probability < 0) || ($probability > 1)) { 2679 return Functions::NAN(); 2680 } 2681 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { 2682 if (($failures + $successes - 1) <= 0) { 2683 return Functions::NAN(); 2684 } 2685 } 2686 2687 return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) * ($probability ** $successes) * ((1 - $probability) ** $failures); 2688 } 2689 2690 return Functions::VALUE(); 2691 } 2692 2693 /** 2694 * NORMDIST. 2695 * 2696 * Returns the normal distribution for the specified mean and standard deviation. This 2697 * function has a very wide range of applications in statistics, including hypothesis 2698 * testing. 2699 * 2700 * @param float $value 2701 * @param float $mean Mean Value 2702 * @param float $stdDev Standard Deviation 2703 * @param bool $cumulative 2704 * 2705 * @return float|string The result, or a string containing an error 2706 */ 2707 public static function NORMDIST($value, $mean, $stdDev, $cumulative) 2708 { 2709 $value = Functions::flattenSingleValue($value); 2710 $mean = Functions::flattenSingleValue($mean); 2711 $stdDev = Functions::flattenSingleValue($stdDev); 2712 2713 if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { 2714 if ($stdDev < 0) { 2715 return Functions::NAN(); 2716 } 2717 if ((is_numeric($cumulative)) || (is_bool($cumulative))) { 2718 if ($cumulative) { 2719 return 0.5 * (1 + Engineering::erfVal(($value - $mean) / ($stdDev * sqrt(2)))); 2720 } 2721 2722 return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); 2723 } 2724 } 2725 2726 return Functions::VALUE(); 2727 } 2728 2729 /** 2730 * NORMINV. 2731 * 2732 * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. 2733 * 2734 * @param float $probability 2735 * @param float $mean Mean Value 2736 * @param float $stdDev Standard Deviation 2737 * 2738 * @return float|string The result, or a string containing an error 2739 */ 2740 public static function NORMINV($probability, $mean, $stdDev) 2741 { 2742 $probability = Functions::flattenSingleValue($probability); 2743 $mean = Functions::flattenSingleValue($mean); 2744 $stdDev = Functions::flattenSingleValue($stdDev); 2745 2746 if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { 2747 if (($probability < 0) || ($probability > 1)) { 2748 return Functions::NAN(); 2749 } 2750 if ($stdDev < 0) { 2751 return Functions::NAN(); 2752 } 2753 2754 return (self::inverseNcdf($probability) * $stdDev) + $mean; 2755 } 2756 2757 return Functions::VALUE(); 2758 } 2759 2760 /** 2761 * NORMSDIST. 2762 * 2763 * Returns the standard normal cumulative distribution function. The distribution has 2764 * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a 2765 * table of standard normal curve areas. 2766 * 2767 * @param float $value 2768 * 2769 * @return float|string The result, or a string containing an error 2770 */ 2771 public static function NORMSDIST($value) 2772 { 2773 $value = Functions::flattenSingleValue($value); 2774 if (!is_numeric($value)) { 2775 return Functions::VALUE(); 2776 } 2777 2778 return self::NORMDIST($value, 0, 1, true); 2779 } 2780 2781 /** 2782 * NORM.S.DIST. 2783 * 2784 * Returns the standard normal cumulative distribution function. The distribution has 2785 * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a 2786 * table of standard normal curve areas. 2787 * 2788 * @param float $value 2789 * @param bool $cumulative 2790 * 2791 * @return float|string The result, or a string containing an error 2792 */ 2793 public static function NORMSDIST2($value, $cumulative) 2794 { 2795 $value = Functions::flattenSingleValue($value); 2796 if (!is_numeric($value)) { 2797 return Functions::VALUE(); 2798 } 2799 $cumulative = (bool) Functions::flattenSingleValue($cumulative); 2800 2801 return self::NORMDIST($value, 0, 1, $cumulative); 2802 } 2803 2804 /** 2805 * NORMSINV. 2806 * 2807 * Returns the inverse of the standard normal cumulative distribution 2808 * 2809 * @param float $value 2810 * 2811 * @return float|string The result, or a string containing an error 2812 */ 2813 public static function NORMSINV($value) 2814 { 2815 return self::NORMINV($value, 0, 1); 2816 } 2817 2818 /** 2819 * PERCENTILE. 2820 * 2821 * Returns the nth percentile of values in a range.. 2822 * 2823 * Excel Function: 2824 * PERCENTILE(value1[,value2[, ...]],entry) 2825 * 2826 * @param mixed $args Data values 2827 * 2828 * @return float|string The result, or a string containing an error 2829 */ 2830 public static function PERCENTILE(...$args) 2831 { 2832 $aArgs = Functions::flattenArray($args); 2833 2834 // Calculate 2835 $entry = array_pop($aArgs); 2836 2837 if ((is_numeric($entry)) && (!is_string($entry))) { 2838 if (($entry < 0) || ($entry > 1)) { 2839 return Functions::NAN(); 2840 } 2841 $mArgs = []; 2842 foreach ($aArgs as $arg) { 2843 // Is it a numeric value? 2844 if ((is_numeric($arg)) && (!is_string($arg))) { 2845 $mArgs[] = $arg; 2846 } 2847 } 2848 $mValueCount = count($mArgs); 2849 if ($mValueCount > 0) { 2850 sort($mArgs); 2851 $count = self::COUNT($mArgs); 2852 $index = $entry * ($count - 1); 2853 $iBase = floor($index); 2854 if ($index == $iBase) { 2855 return $mArgs[$index]; 2856 } 2857 $iNext = $iBase + 1; 2858 $iProportion = $index - $iBase; 2859 2860 return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); 2861 } 2862 } 2863 2864 return Functions::VALUE(); 2865 } 2866 2867 /** 2868 * PERCENTRANK. 2869 * 2870 * Returns the rank of a value in a data set as a percentage of the data set. 2871 * 2872 * @param float[] $valueSet An array of, or a reference to, a list of numbers 2873 * @param int $value the number whose rank you want to find 2874 * @param int $significance the number of significant digits for the returned percentage value 2875 * 2876 * @return float|string (string if result is an error) 2877 */ 2878 public static function PERCENTRANK($valueSet, $value, $significance = 3) 2879 { 2880 $valueSet = Functions::flattenArray($valueSet); 2881 $value = Functions::flattenSingleValue($value); 2882 $significance = ($significance === null) ? 3 : (int) Functions::flattenSingleValue($significance); 2883 2884 foreach ($valueSet as $key => $valueEntry) { 2885 if (!is_numeric($valueEntry)) { 2886 unset($valueSet[$key]); 2887 } 2888 } 2889 sort($valueSet, SORT_NUMERIC); 2890 $valueCount = count($valueSet); 2891 if ($valueCount == 0) { 2892 return Functions::NAN(); 2893 } 2894 2895 $valueAdjustor = $valueCount - 1; 2896 if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { 2897 return Functions::NA(); 2898 } 2899 2900 $pos = array_search($value, $valueSet); 2901 if ($pos === false) { 2902 $pos = 0; 2903 $testValue = $valueSet[0]; 2904 while ($testValue < $value) { 2905 $testValue = $valueSet[++$pos]; 2906 } 2907 --$pos; 2908 $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); 2909 } 2910 2911 return round($pos / $valueAdjustor, $significance); 2912 } 2913 2914 /** 2915 * PERMUT. 2916 * 2917 * Returns the number of permutations for a given number of objects that can be 2918 * selected from number objects. A permutation is any set or subset of objects or 2919 * events where internal order is significant. Permutations are different from 2920 * combinations, for which the internal order is not significant. Use this function 2921 * for lottery-style probability calculations. 2922 * 2923 * @param int $numObjs Number of different objects 2924 * @param int $numInSet Number of objects in each permutation 2925 * 2926 * @return int|string Number of permutations, or a string containing an error 2927 */ 2928 public static function PERMUT($numObjs, $numInSet) 2929 { 2930 $numObjs = Functions::flattenSingleValue($numObjs); 2931 $numInSet = Functions::flattenSingleValue($numInSet); 2932 2933 if ((is_numeric($numObjs)) && (is_numeric($numInSet))) { 2934 $numInSet = floor($numInSet); 2935 if ($numObjs < $numInSet) { 2936 return Functions::NAN(); 2937 } 2938 2939 return round(MathTrig::FACT($numObjs) / MathTrig::FACT($numObjs - $numInSet)); 2940 } 2941 2942 return Functions::VALUE(); 2943 } 2944 2945 /** 2946 * POISSON. 2947 * 2948 * Returns the Poisson distribution. A common application of the Poisson distribution 2949 * is predicting the number of events over a specific time, such as the number of 2950 * cars arriving at a toll plaza in 1 minute. 2951 * 2952 * @param float $value 2953 * @param float $mean Mean Value 2954 * @param bool $cumulative 2955 * 2956 * @return float|string The result, or a string containing an error 2957 */ 2958 public static function POISSON($value, $mean, $cumulative) 2959 { 2960 $value = Functions::flattenSingleValue($value); 2961 $mean = Functions::flattenSingleValue($mean); 2962 2963 if ((is_numeric($value)) && (is_numeric($mean))) { 2964 if (($value < 0) || ($mean <= 0)) { 2965 return Functions::NAN(); 2966 } 2967 if ((is_numeric($cumulative)) || (is_bool($cumulative))) { 2968 if ($cumulative) { 2969 $summer = 0; 2970 $floor = floor($value); 2971 for ($i = 0; $i <= $floor; ++$i) { 2972 $summer += $mean ** $i / MathTrig::FACT($i); 2973 } 2974 2975 return exp(0 - $mean) * $summer; 2976 } 2977 2978 return (exp(0 - $mean) * $mean ** $value) / MathTrig::FACT($value); 2979 } 2980 } 2981 2982 return Functions::VALUE(); 2983 } 2984 2985 /** 2986 * QUARTILE. 2987 * 2988 * Returns the quartile of a data set. 2989 * 2990 * Excel Function: 2991 * QUARTILE(value1[,value2[, ...]],entry) 2992 * 2993 * @param mixed $args Data values 2994 * 2995 * @return float|string The result, or a string containing an error 2996 */ 2997 public static function QUARTILE(...$args) 2998 { 2999 $aArgs = Functions::flattenArray($args); 3000 3001 // Calculate 3002 $entry = floor(array_pop($aArgs)); 3003 3004 if ((is_numeric($entry)) && (!is_string($entry))) { 3005 $entry /= 4; 3006 if (($entry < 0) || ($entry > 1)) { 3007 return Functions::NAN(); 3008 } 3009 3010 return self::PERCENTILE($aArgs, $entry); 3011 } 3012 3013 return Functions::VALUE(); 3014 } 3015 3016 /** 3017 * RANK. 3018 * 3019 * Returns the rank of a number in a list of numbers. 3020 * 3021 * @param int $value the number whose rank you want to find 3022 * @param float[] $valueSet An array of, or a reference to, a list of numbers 3023 * @param int $order Order to sort the values in the value set 3024 * 3025 * @return float|string The result, or a string containing an error 3026 */ 3027 public static function RANK($value, $valueSet, $order = 0) 3028 { 3029 $value = Functions::flattenSingleValue($value); 3030 $valueSet = Functions::flattenArray($valueSet); 3031 $order = ($order === null) ? 0 : (int) Functions::flattenSingleValue($order); 3032 3033 foreach ($valueSet as $key => $valueEntry) { 3034 if (!is_numeric($valueEntry)) { 3035 unset($valueSet[$key]); 3036 } 3037 } 3038 3039 if ($order == 0) { 3040 rsort($valueSet, SORT_NUMERIC); 3041 } else { 3042 sort($valueSet, SORT_NUMERIC); 3043 } 3044 $pos = array_search($value, $valueSet); 3045 if ($pos === false) { 3046 return Functions::NA(); 3047 } 3048 3049 return ++$pos; 3050 } 3051 3052 /** 3053 * RSQ. 3054 * 3055 * Returns the square of the Pearson product moment correlation coefficient through data points in known_y's and known_x's. 3056 * 3057 * @param mixed[] $yValues Data Series Y 3058 * @param mixed[] $xValues Data Series X 3059 * 3060 * @return float|string The result, or a string containing an error 3061 */ 3062 public static function RSQ($yValues, $xValues) 3063 { 3064 if (!self::checkTrendArrays($yValues, $xValues)) { 3065 return Functions::VALUE(); 3066 } 3067 $yValueCount = count($yValues); 3068 $xValueCount = count($xValues); 3069 3070 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 3071 return Functions::NA(); 3072 } elseif ($yValueCount == 1) { 3073 return Functions::DIV0(); 3074 } 3075 3076 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 3077 3078 return $bestFitLinear->getGoodnessOfFit(); 3079 } 3080 3081 /** 3082 * SKEW. 3083 * 3084 * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry 3085 * of a distribution around its mean. Positive skewness indicates a distribution with an 3086 * asymmetric tail extending toward more positive values. Negative skewness indicates a 3087 * distribution with an asymmetric tail extending toward more negative values. 3088 * 3089 * @param array ...$args Data Series 3090 * 3091 * @return float|string The result, or a string containing an error 3092 */ 3093 public static function SKEW(...$args) 3094 { 3095 $aArgs = Functions::flattenArrayIndexed($args); 3096 $mean = self::AVERAGE($aArgs); 3097 $stdDev = self::STDEV($aArgs); 3098 3099 $count = $summer = 0; 3100 // Loop through arguments 3101 foreach ($aArgs as $k => $arg) { 3102 if ( 3103 (is_bool($arg)) && 3104 (!Functions::isMatrixValue($k)) 3105 ) { 3106 } else { 3107 // Is it a numeric value? 3108 if ((is_numeric($arg)) && (!is_string($arg))) { 3109 $summer += (($arg - $mean) / $stdDev) ** 3; 3110 ++$count; 3111 } 3112 } 3113 } 3114 3115 if ($count > 2) { 3116 return $summer * ($count / (($count - 1) * ($count - 2))); 3117 } 3118 3119 return Functions::DIV0(); 3120 } 3121 3122 /** 3123 * SLOPE. 3124 * 3125 * Returns the slope of the linear regression line through data points in known_y's and known_x's. 3126 * 3127 * @param mixed[] $yValues Data Series Y 3128 * @param mixed[] $xValues Data Series X 3129 * 3130 * @return float|string The result, or a string containing an error 3131 */ 3132 public static function SLOPE($yValues, $xValues) 3133 { 3134 if (!self::checkTrendArrays($yValues, $xValues)) { 3135 return Functions::VALUE(); 3136 } 3137 $yValueCount = count($yValues); 3138 $xValueCount = count($xValues); 3139 3140 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 3141 return Functions::NA(); 3142 } elseif ($yValueCount == 1) { 3143 return Functions::DIV0(); 3144 } 3145 3146 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 3147 3148 return $bestFitLinear->getSlope(); 3149 } 3150 3151 /** 3152 * SMALL. 3153 * 3154 * Returns the nth smallest value in a data set. You can use this function to 3155 * select a value based on its relative standing. 3156 * 3157 * Excel Function: 3158 * SMALL(value1[,value2[, ...]],entry) 3159 * 3160 * @param mixed $args Data values 3161 * 3162 * @return float|string The result, or a string containing an error 3163 */ 3164 public static function SMALL(...$args) 3165 { 3166 $aArgs = Functions::flattenArray($args); 3167 3168 // Calculate 3169 $entry = array_pop($aArgs); 3170 3171 if ((is_numeric($entry)) && (!is_string($entry))) { 3172 $entry = (int) floor($entry); 3173 3174 $mArgs = []; 3175 foreach ($aArgs as $arg) { 3176 // Is it a numeric value? 3177 if ((is_numeric($arg)) && (!is_string($arg))) { 3178 $mArgs[] = $arg; 3179 } 3180 } 3181 $count = self::COUNT($mArgs); 3182 --$entry; 3183 if (($entry < 0) || ($entry >= $count) || ($count == 0)) { 3184 return Functions::NAN(); 3185 } 3186 sort($mArgs); 3187 3188 return $mArgs[$entry]; 3189 } 3190 3191 return Functions::VALUE(); 3192 } 3193 3194 /** 3195 * STANDARDIZE. 3196 * 3197 * Returns a normalized value from a distribution characterized by mean and standard_dev. 3198 * 3199 * @param float $value Value to normalize 3200 * @param float $mean Mean Value 3201 * @param float $stdDev Standard Deviation 3202 * 3203 * @return float|string Standardized value, or a string containing an error 3204 */ 3205 public static function STANDARDIZE($value, $mean, $stdDev) 3206 { 3207 $value = Functions::flattenSingleValue($value); 3208 $mean = Functions::flattenSingleValue($mean); 3209 $stdDev = Functions::flattenSingleValue($stdDev); 3210 3211 if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { 3212 if ($stdDev <= 0) { 3213 return Functions::NAN(); 3214 } 3215 3216 return ($value - $mean) / $stdDev; 3217 } 3218 3219 return Functions::VALUE(); 3220 } 3221 3222 /** 3223 * STDEV. 3224 * 3225 * Estimates standard deviation based on a sample. The standard deviation is a measure of how 3226 * widely values are dispersed from the average value (the mean). 3227 * 3228 * Excel Function: 3229 * STDEV(value1[,value2[, ...]]) 3230 * 3231 * @param mixed ...$args Data values 3232 * 3233 * @return float|string The result, or a string containing an error 3234 */ 3235 public static function STDEV(...$args) 3236 { 3237 $aArgs = Functions::flattenArrayIndexed($args); 3238 3239 // Return value 3240 $returnValue = null; 3241 3242 $aMean = self::AVERAGE($aArgs); 3243 if ($aMean !== null) { 3244 $aCount = -1; 3245 foreach ($aArgs as $k => $arg) { 3246 if ( 3247 (is_bool($arg)) && 3248 ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) 3249 ) { 3250 $arg = (int) $arg; 3251 } 3252 // Is it a numeric value? 3253 if ((is_numeric($arg)) && (!is_string($arg))) { 3254 if ($returnValue === null) { 3255 $returnValue = ($arg - $aMean) ** 2; 3256 } else { 3257 $returnValue += ($arg - $aMean) ** 2; 3258 } 3259 ++$aCount; 3260 } 3261 } 3262 3263 // Return 3264 if (($aCount > 0) && ($returnValue >= 0)) { 3265 return sqrt($returnValue / $aCount); 3266 } 3267 } 3268 3269 return Functions::DIV0(); 3270 } 3271 3272 /** 3273 * STDEVA. 3274 * 3275 * Estimates standard deviation based on a sample, including numbers, text, and logical values 3276 * 3277 * Excel Function: 3278 * STDEVA(value1[,value2[, ...]]) 3279 * 3280 * @param mixed ...$args Data values 3281 * 3282 * @return float|string 3283 */ 3284 public static function STDEVA(...$args) 3285 { 3286 $aArgs = Functions::flattenArrayIndexed($args); 3287 3288 $returnValue = null; 3289 3290 $aMean = self::AVERAGEA($aArgs); 3291 if ($aMean !== null) { 3292 $aCount = -1; 3293 foreach ($aArgs as $k => $arg) { 3294 if ( 3295 (is_bool($arg)) && 3296 (!Functions::isMatrixValue($k)) 3297 ) { 3298 } else { 3299 // Is it a numeric value? 3300 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { 3301 if (is_bool($arg)) { 3302 $arg = (int) $arg; 3303 } elseif (is_string($arg)) { 3304 $arg = 0; 3305 } 3306 if ($returnValue === null) { 3307 $returnValue = ($arg - $aMean) ** 2; 3308 } else { 3309 $returnValue += ($arg - $aMean) ** 2; 3310 } 3311 ++$aCount; 3312 } 3313 } 3314 } 3315 3316 if (($aCount > 0) && ($returnValue >= 0)) { 3317 return sqrt($returnValue / $aCount); 3318 } 3319 } 3320 3321 return Functions::DIV0(); 3322 } 3323 3324 /** 3325 * STDEVP. 3326 * 3327 * Calculates standard deviation based on the entire population 3328 * 3329 * Excel Function: 3330 * STDEVP(value1[,value2[, ...]]) 3331 * 3332 * @param mixed ...$args Data values 3333 * 3334 * @return float|string 3335 */ 3336 public static function STDEVP(...$args) 3337 { 3338 $aArgs = Functions::flattenArrayIndexed($args); 3339 3340 $returnValue = null; 3341 3342 $aMean = self::AVERAGE($aArgs); 3343 if ($aMean !== null) { 3344 $aCount = 0; 3345 foreach ($aArgs as $k => $arg) { 3346 if ( 3347 (is_bool($arg)) && 3348 ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) 3349 ) { 3350 $arg = (int) $arg; 3351 } 3352 // Is it a numeric value? 3353 if ((is_numeric($arg)) && (!is_string($arg))) { 3354 if ($returnValue === null) { 3355 $returnValue = ($arg - $aMean) ** 2; 3356 } else { 3357 $returnValue += ($arg - $aMean) ** 2; 3358 } 3359 ++$aCount; 3360 } 3361 } 3362 3363 if (($aCount > 0) && ($returnValue >= 0)) { 3364 return sqrt($returnValue / $aCount); 3365 } 3366 } 3367 3368 return Functions::DIV0(); 3369 } 3370 3371 /** 3372 * STDEVPA. 3373 * 3374 * Calculates standard deviation based on the entire population, including numbers, text, and logical values 3375 * 3376 * Excel Function: 3377 * STDEVPA(value1[,value2[, ...]]) 3378 * 3379 * @param mixed ...$args Data values 3380 * 3381 * @return float|string 3382 */ 3383 public static function STDEVPA(...$args) 3384 { 3385 $aArgs = Functions::flattenArrayIndexed($args); 3386 3387 $returnValue = null; 3388 3389 $aMean = self::AVERAGEA($aArgs); 3390 if ($aMean !== null) { 3391 $aCount = 0; 3392 foreach ($aArgs as $k => $arg) { 3393 if ( 3394 (is_bool($arg)) && 3395 (!Functions::isMatrixValue($k)) 3396 ) { 3397 } else { 3398 // Is it a numeric value? 3399 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { 3400 if (is_bool($arg)) { 3401 $arg = (int) $arg; 3402 } elseif (is_string($arg)) { 3403 $arg = 0; 3404 } 3405 if ($returnValue === null) { 3406 $returnValue = ($arg - $aMean) ** 2; 3407 } else { 3408 $returnValue += ($arg - $aMean) ** 2; 3409 } 3410 ++$aCount; 3411 } 3412 } 3413 } 3414 3415 if (($aCount > 0) && ($returnValue >= 0)) { 3416 return sqrt($returnValue / $aCount); 3417 } 3418 } 3419 3420 return Functions::DIV0(); 3421 } 3422 3423 /** 3424 * STEYX. 3425 * 3426 * Returns the standard error of the predicted y-value for each x in the regression. 3427 * 3428 * @param mixed[] $yValues Data Series Y 3429 * @param mixed[] $xValues Data Series X 3430 * 3431 * @return float|string 3432 */ 3433 public static function STEYX($yValues, $xValues) 3434 { 3435 if (!self::checkTrendArrays($yValues, $xValues)) { 3436 return Functions::VALUE(); 3437 } 3438 $yValueCount = count($yValues); 3439 $xValueCount = count($xValues); 3440 3441 if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { 3442 return Functions::NA(); 3443 } elseif ($yValueCount == 1) { 3444 return Functions::DIV0(); 3445 } 3446 3447 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); 3448 3449 return $bestFitLinear->getStdevOfResiduals(); 3450 } 3451 3452 /** 3453 * TDIST. 3454 * 3455 * Returns the probability of Student's T distribution. 3456 * 3457 * @param float $value Value for the function 3458 * @param float $degrees degrees of freedom 3459 * @param float $tails number of tails (1 or 2) 3460 * 3461 * @return float|string The result, or a string containing an error 3462 */ 3463 public static function TDIST($value, $degrees, $tails) 3464 { 3465 $value = Functions::flattenSingleValue($value); 3466 $degrees = floor(Functions::flattenSingleValue($degrees)); 3467 $tails = floor(Functions::flattenSingleValue($tails)); 3468 3469 if ((is_numeric($value)) && (is_numeric($degrees)) && (is_numeric($tails))) { 3470 if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { 3471 return Functions::NAN(); 3472 } 3473 // tdist, which finds the probability that corresponds to a given value 3474 // of t with k degrees of freedom. This algorithm is translated from a 3475 // pascal function on p81 of "Statistical Computing in Pascal" by D 3476 // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: 3477 // London). The above Pascal algorithm is itself a translation of the 3478 // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer 3479 // Laboratory as reported in (among other places) "Applied Statistics 3480 // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis 3481 // Horwood Ltd.; W. Sussex, England). 3482 $tterm = $degrees; 3483 $ttheta = atan2($value, sqrt($tterm)); 3484 $tc = cos($ttheta); 3485 $ts = sin($ttheta); 3486 3487 if (($degrees % 2) == 1) { 3488 $ti = 3; 3489 $tterm = $tc; 3490 } else { 3491 $ti = 2; 3492 $tterm = 1; 3493 } 3494 3495 $tsum = $tterm; 3496 while ($ti < $degrees) { 3497 $tterm *= $tc * $tc * ($ti - 1) / $ti; 3498 $tsum += $tterm; 3499 $ti += 2; 3500 } 3501 $tsum *= $ts; 3502 if (($degrees % 2) == 1) { 3503 $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); 3504 } 3505 $tValue = 0.5 * (1 + $tsum); 3506 if ($tails == 1) { 3507 return 1 - abs($tValue); 3508 } 3509 3510 return 1 - abs((1 - $tValue) - $tValue); 3511 } 3512 3513 return Functions::VALUE(); 3514 } 3515 3516 /** 3517 * TINV. 3518 * 3519 * Returns the one-tailed probability of the chi-squared distribution. 3520 * 3521 * @param float $probability Probability for the function 3522 * @param float $degrees degrees of freedom 3523 * 3524 * @return float|string The result, or a string containing an error 3525 */ 3526 public static function TINV($probability, $degrees) 3527 { 3528 $probability = Functions::flattenSingleValue($probability); 3529 $degrees = floor(Functions::flattenSingleValue($degrees)); 3530 3531 if ((is_numeric($probability)) && (is_numeric($degrees))) { 3532 $xLo = 100; 3533 $xHi = 0; 3534 3535 $x = $xNew = 1; 3536 $dx = 1; 3537 $i = 0; 3538 3539 while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { 3540 // Apply Newton-Raphson step 3541 $result = self::TDIST($x, $degrees, 2); 3542 $error = $result - $probability; 3543 if ($error == 0.0) { 3544 $dx = 0; 3545 } elseif ($error < 0.0) { 3546 $xLo = $x; 3547 } else { 3548 $xHi = $x; 3549 } 3550 // Avoid division by zero 3551 if ($result != 0.0) { 3552 $dx = $error / $result; 3553 $xNew = $x - $dx; 3554 } 3555 // If the NR fails to converge (which for example may be the 3556 // case if the initial guess is too rough) we apply a bisection 3557 // step to determine a more narrow interval around the root. 3558 if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { 3559 $xNew = ($xLo + $xHi) / 2; 3560 $dx = $xNew - $x; 3561 } 3562 $x = $xNew; 3563 } 3564 if ($i == self::MAX_ITERATIONS) { 3565 return Functions::NA(); 3566 } 3567 3568 return round($x, 12); 3569 } 3570 3571 return Functions::VALUE(); 3572 } 3573 3574 /** 3575 * TREND. 3576 * 3577 * Returns values along a linear Trend 3578 * 3579 * @param mixed[] $yValues Data Series Y 3580 * @param mixed[] $xValues Data Series X 3581 * @param mixed[] $newValues Values of X for which we want to find Y 3582 * @param bool $const a logical value specifying whether to force the intersect to equal 0 3583 * 3584 * @return array of float 3585 */ 3586 public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) 3587 { 3588 $yValues = Functions::flattenArray($yValues); 3589 $xValues = Functions::flattenArray($xValues); 3590 $newValues = Functions::flattenArray($newValues); 3591 $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); 3592 3593 $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); 3594 if (empty($newValues)) { 3595 $newValues = $bestFitLinear->getXValues(); 3596 } 3597 3598 $returnArray = []; 3599 foreach ($newValues as $xValue) { 3600 $returnArray[0][] = $bestFitLinear->getValueOfYForX($xValue); 3601 } 3602 3603 return $returnArray; 3604 } 3605 3606 /** 3607 * TRIMMEAN. 3608 * 3609 * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean 3610 * taken by excluding a percentage of data points from the top and bottom tails 3611 * of a data set. 3612 * 3613 * Excel Function: 3614 * TRIMEAN(value1[,value2[, ...]], $discard) 3615 * 3616 * @param mixed $args Data values 3617 * 3618 * @return float|string 3619 */ 3620 public static function TRIMMEAN(...$args) 3621 { 3622 $aArgs = Functions::flattenArray($args); 3623 3624 // Calculate 3625 $percent = array_pop($aArgs); 3626 3627 if ((is_numeric($percent)) && (!is_string($percent))) { 3628 if (($percent < 0) || ($percent > 1)) { 3629 return Functions::NAN(); 3630 } 3631 $mArgs = []; 3632 foreach ($aArgs as $arg) { 3633 // Is it a numeric value? 3634 if ((is_numeric($arg)) && (!is_string($arg))) { 3635 $mArgs[] = $arg; 3636 } 3637 } 3638 $discard = floor(self::COUNT($mArgs) * $percent / 2); 3639 sort($mArgs); 3640 for ($i = 0; $i < $discard; ++$i) { 3641 array_pop($mArgs); 3642 array_shift($mArgs); 3643 } 3644 3645 return self::AVERAGE($mArgs); 3646 } 3647 3648 return Functions::VALUE(); 3649 } 3650 3651 /** 3652 * VARFunc. 3653 * 3654 * Estimates variance based on a sample. 3655 * 3656 * Excel Function: 3657 * VAR(value1[,value2[, ...]]) 3658 * 3659 * @param mixed ...$args Data values 3660 * 3661 * @return float|string (string if result is an error) 3662 */ 3663 public static function VARFunc(...$args) 3664 { 3665 $returnValue = Functions::DIV0(); 3666 3667 $summerA = $summerB = 0; 3668 3669 // Loop through arguments 3670 $aArgs = Functions::flattenArray($args); 3671 $aCount = 0; 3672 foreach ($aArgs as $arg) { 3673 if (is_bool($arg)) { 3674 $arg = (int) $arg; 3675 } 3676 // Is it a numeric value? 3677 if ((is_numeric($arg)) && (!is_string($arg))) { 3678 $summerA += ($arg * $arg); 3679 $summerB += $arg; 3680 ++$aCount; 3681 } 3682 } 3683 3684 if ($aCount > 1) { 3685 $summerA *= $aCount; 3686 $summerB *= $summerB; 3687 $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1)); 3688 } 3689 3690 return $returnValue; 3691 } 3692 3693 /** 3694 * VARA. 3695 * 3696 * Estimates variance based on a sample, including numbers, text, and logical values 3697 * 3698 * Excel Function: 3699 * VARA(value1[,value2[, ...]]) 3700 * 3701 * @param mixed ...$args Data values 3702 * 3703 * @return float|string (string if result is an error) 3704 */ 3705 public static function VARA(...$args) 3706 { 3707 $returnValue = Functions::DIV0(); 3708 3709 $summerA = $summerB = 0; 3710 3711 // Loop through arguments 3712 $aArgs = Functions::flattenArrayIndexed($args); 3713 $aCount = 0; 3714 foreach ($aArgs as $k => $arg) { 3715 if ( 3716 (is_string($arg)) && 3717 (Functions::isValue($k)) 3718 ) { 3719 return Functions::VALUE(); 3720 } elseif ( 3721 (is_string($arg)) && 3722 (!Functions::isMatrixValue($k)) 3723 ) { 3724 } else { 3725 // Is it a numeric value? 3726 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { 3727 if (is_bool($arg)) { 3728 $arg = (int) $arg; 3729 } elseif (is_string($arg)) { 3730 $arg = 0; 3731 } 3732 $summerA += ($arg * $arg); 3733 $summerB += $arg; 3734 ++$aCount; 3735 } 3736 } 3737 } 3738 3739 if ($aCount > 1) { 3740 $summerA *= $aCount; 3741 $summerB *= $summerB; 3742 $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1)); 3743 } 3744 3745 return $returnValue; 3746 } 3747 3748 /** 3749 * VARP. 3750 * 3751 * Calculates variance based on the entire population 3752 * 3753 * Excel Function: 3754 * VARP(value1[,value2[, ...]]) 3755 * 3756 * @param mixed ...$args Data values 3757 * 3758 * @return float|string (string if result is an error) 3759 */ 3760 public static function VARP(...$args) 3761 { 3762 // Return value 3763 $returnValue = Functions::DIV0(); 3764 3765 $summerA = $summerB = 0; 3766 3767 // Loop through arguments 3768 $aArgs = Functions::flattenArray($args); 3769 $aCount = 0; 3770 foreach ($aArgs as $arg) { 3771 if (is_bool($arg)) { 3772 $arg = (int) $arg; 3773 } 3774 // Is it a numeric value? 3775 if ((is_numeric($arg)) && (!is_string($arg))) { 3776 $summerA += ($arg * $arg); 3777 $summerB += $arg; 3778 ++$aCount; 3779 } 3780 } 3781 3782 if ($aCount > 0) { 3783 $summerA *= $aCount; 3784 $summerB *= $summerB; 3785 $returnValue = ($summerA - $summerB) / ($aCount * $aCount); 3786 } 3787 3788 return $returnValue; 3789 } 3790 3791 /** 3792 * VARPA. 3793 * 3794 * Calculates variance based on the entire population, including numbers, text, and logical values 3795 * 3796 * Excel Function: 3797 * VARPA(value1[,value2[, ...]]) 3798 * 3799 * @param mixed ...$args Data values 3800 * 3801 * @return float|string (string if result is an error) 3802 */ 3803 public static function VARPA(...$args) 3804 { 3805 $returnValue = Functions::DIV0(); 3806 3807 $summerA = $summerB = 0; 3808 3809 // Loop through arguments 3810 $aArgs = Functions::flattenArrayIndexed($args); 3811 $aCount = 0; 3812 foreach ($aArgs as $k => $arg) { 3813 if ( 3814 (is_string($arg)) && 3815 (Functions::isValue($k)) 3816 ) { 3817 return Functions::VALUE(); 3818 } elseif ( 3819 (is_string($arg)) && 3820 (!Functions::isMatrixValue($k)) 3821 ) { 3822 } else { 3823 // Is it a numeric value? 3824 if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { 3825 if (is_bool($arg)) { 3826 $arg = (int) $arg; 3827 } elseif (is_string($arg)) { 3828 $arg = 0; 3829 } 3830 $summerA += ($arg * $arg); 3831 $summerB += $arg; 3832 ++$aCount; 3833 } 3834 } 3835 } 3836 3837 if ($aCount > 0) { 3838 $summerA *= $aCount; 3839 $summerB *= $summerB; 3840 $returnValue = ($summerA - $summerB) / ($aCount * $aCount); 3841 } 3842 3843 return $returnValue; 3844 } 3845 3846 /** 3847 * WEIBULL. 3848 * 3849 * Returns the Weibull distribution. Use this distribution in reliability 3850 * analysis, such as calculating a device's mean time to failure. 3851 * 3852 * @param float $value 3853 * @param float $alpha Alpha Parameter 3854 * @param float $beta Beta Parameter 3855 * @param bool $cumulative 3856 * 3857 * @return float|string (string if result is an error) 3858 */ 3859 public static function WEIBULL($value, $alpha, $beta, $cumulative) 3860 { 3861 $value = Functions::flattenSingleValue($value); 3862 $alpha = Functions::flattenSingleValue($alpha); 3863 $beta = Functions::flattenSingleValue($beta); 3864 3865 if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta))) { 3866 if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { 3867 return Functions::NAN(); 3868 } 3869 if ((is_numeric($cumulative)) || (is_bool($cumulative))) { 3870 if ($cumulative) { 3871 return 1 - exp(0 - ($value / $beta) ** $alpha); 3872 } 3873 3874 return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha); 3875 } 3876 } 3877 3878 return Functions::VALUE(); 3879 } 3880 3881 /** 3882 * ZTEST. 3883 * 3884 * Returns the Weibull distribution. Use this distribution in reliability 3885 * analysis, such as calculating a device's mean time to failure. 3886 * 3887 * @param float $dataSet 3888 * @param float $m0 Alpha Parameter 3889 * @param float $sigma Beta Parameter 3890 * 3891 * @return float|string (string if result is an error) 3892 */ 3893 public static function ZTEST($dataSet, $m0, $sigma = null) 3894 { 3895 $dataSet = Functions::flattenArrayIndexed($dataSet); 3896 $m0 = Functions::flattenSingleValue($m0); 3897 $sigma = Functions::flattenSingleValue($sigma); 3898 3899 if ($sigma === null) { 3900 $sigma = self::STDEV($dataSet); 3901 } 3902 $n = count($dataSet); 3903 3904 return 1 - self::NORMSDIST((self::AVERAGE($dataSet) - $m0) / ($sigma / sqrt($n))); 3905 } 3906 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body