PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/branches/v1.6.5/Classes/PHPExcel/Calculation/Functions.php

#
PHP | 7142 lines | 4199 code | 675 blank | 2268 comment | 1299 complexity | 78eaacab02c64a14509049ef66dcdc67 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-2.0, LGPL-2.1, GPL-3.0, LGPL-3.0
  1. <?php
  2. /**
  3. * PHPExcel
  4. *
  5. * Copyright (c) 2006 - 2009 PHPExcel
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPExcel
  22. * @package PHPExcel_Calculation
  23. * @copyright Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
  24. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  25. * @version ##VERSION##, ##DATE##
  26. */
  27. define('EPS', 2.22e-16);
  28. define('MAX_VALUE', 1.2e308);
  29. define('LOG_GAMMA_X_MAX_VALUE', 2.55e305);
  30. define('SQRT2PI', 2.5066282746310005024157652848110452530069867406099);
  31. define('XMININ', 2.23e-308);
  32. define('MAX_ITERATIONS', 150);
  33. define('PRECISION', 8.88E-016);
  34. define('EULER', 2.71828182845904523536);
  35. $savedPrecision = ini_get('precision');
  36. if ($savedPrecision < 15) {
  37. ini_set('precision',15);
  38. }
  39. /** PHPExcel_Cell */
  40. require_once 'PHPExcel/Cell.php';
  41. /** PHPExcel_Cell_DataType */
  42. require_once 'PHPExcel/Cell/DataType.php';
  43. /** PHPExcel_Shared_Date */
  44. require_once 'PHPExcel/Shared/Date.php';
  45. /** PHPExcel_Shared_Date */
  46. require_once 'PHPExcel/Shared/JAMA/Matrix.php';
  47. /**
  48. * PHPExcel_Calculation_Functions
  49. *
  50. * @category PHPExcel
  51. * @package PHPExcel_Calculation
  52. * @copyright Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
  53. */
  54. class PHPExcel_Calculation_Functions {
  55. /** constants */
  56. const COMPATIBILITY_EXCEL = 'Excel';
  57. const COMPATIBILITY_GNUMERIC = 'Gnumeric';
  58. const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
  59. const RETURNDATE_PHP_NUMERIC = 'P';
  60. const RETURNDATE_PHP_OBJECT = 'O';
  61. const RETURNDATE_EXCEL = 'E';
  62. /**
  63. * Compatibility mode to use for error checking and responses
  64. *
  65. * @var string
  66. */
  67. private static $compatibilityMode = self::COMPATIBILITY_EXCEL;
  68. /**
  69. * Data Type to use when returning date values
  70. *
  71. * @var integer
  72. */
  73. private static $ReturnDateType = self::RETURNDATE_PHP_NUMERIC;
  74. /**
  75. * List of error codes
  76. *
  77. * @var array
  78. */
  79. private static $_errorCodes = array( 'null' => '#NULL!',
  80. 'divisionbyzero' => '#DIV/0!',
  81. 'value' => '#VALUE!',
  82. 'reference' => '#REF!',
  83. 'name' => '#NAME?',
  84. 'num' => '#NUM!',
  85. 'na' => '#N/A',
  86. 'gettingdata' => '#GETTING_DATA'
  87. );
  88. /**
  89. * Set the Compatibility Mode
  90. *
  91. * @param string $compatibilityMode Compatibility Mode
  92. * @return boolean (Success or Failure)
  93. */
  94. public static function setCompatibilityMode($compatibilityMode) {
  95. if (($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
  96. ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
  97. ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  98. self::$compatibilityMode = $compatibilityMode;
  99. return True;
  100. }
  101. return False;
  102. }
  103. /**
  104. * Return the current Compatibility Mode
  105. *
  106. * @return string $compatibilityMode Compatibility Mode
  107. */
  108. public static function getCompatibilityMode() {
  109. return self::$compatibilityMode;
  110. }
  111. /**
  112. * Set the Return Date Format (Excel, PHP Serialized or PHP Object)
  113. *
  114. * @param integer $returnDateType Return Date Format
  115. * @return boolean Success or failure
  116. */
  117. public static function setReturnDateType($returnDateType) {
  118. if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) ||
  119. ($returnDateType == self::RETURNDATE_PHP_OBJECT) ||
  120. ($returnDateType == self::RETURNDATE_EXCEL)) {
  121. self::$ReturnDateType = $returnDateType;
  122. return True;
  123. }
  124. return False;
  125. } // function setReturnDateType()
  126. /**
  127. * Return the Return Date Format (Excel, PHP Serialized or PHP Object)
  128. *
  129. * @return integer $returnDateType Return Date Format
  130. */
  131. public static function getReturnDateType() {
  132. return self::$ReturnDateType;
  133. } // function getReturnDateType()
  134. /**
  135. * DUMMY
  136. *
  137. * @return string #NAME?
  138. */
  139. public static function DUMMY() {
  140. return self::$_errorCodes['name'];
  141. }
  142. /**
  143. * NA
  144. *
  145. * @return string #N/A!
  146. */
  147. public static function NA() {
  148. return self::$_errorCodes['na'];
  149. }
  150. /**
  151. * LOGICAL_AND
  152. *
  153. * Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
  154. *
  155. * Booleans arguments are treated as True or False as appropriate
  156. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  157. * If any argument value is a string, or a Null, it is ignored
  158. *
  159. * Quirk of Excel:
  160. * String values passed directly to the function rather than through a cell reference
  161. * e.g.=AND(1,"A",1)
  162. * will return a #VALUE! error, _not_ ignoring the string.
  163. * This behaviour is not replicated
  164. *
  165. * @param array of mixed Data Series
  166. * @return boolean
  167. */
  168. public static function LOGICAL_AND() {
  169. // Return value
  170. $returnValue = True;
  171. // Loop through the arguments
  172. $aArgs = self::flattenArray(func_get_args());
  173. $argCount = 0;
  174. foreach ($aArgs as $arg) {
  175. // Is it a boolean value?
  176. if (is_bool($arg)) {
  177. $returnValue = $returnValue && $arg;
  178. ++$argCount;
  179. } elseif ((is_numeric($arg)) && (!is_string($arg))) {
  180. $returnValue = $returnValue && ($arg != 0);
  181. ++$argCount;
  182. }
  183. }
  184. // Return
  185. if ($argCount == 0) {
  186. return self::$_errorCodes['value'];
  187. }
  188. return $returnValue;
  189. }
  190. /**
  191. * LOGICAL_OR
  192. *
  193. * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
  194. *
  195. * Booleans arguments are treated as True or False as appropriate
  196. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  197. * If any argument value is a string, or a Null, it is ignored
  198. *
  199. * @param array of mixed Data Series
  200. * @return boolean
  201. */
  202. public static function LOGICAL_OR() {
  203. // Return value
  204. $returnValue = False;
  205. // Loop through the arguments
  206. $aArgs = self::flattenArray(func_get_args());
  207. $argCount = 0;
  208. foreach ($aArgs as $arg) {
  209. // Is it a boolean value?
  210. if (is_bool($arg)) {
  211. $returnValue = $returnValue || $arg;
  212. ++$argCount;
  213. } elseif ((is_numeric($arg)) && (!is_string($arg))) {
  214. $returnValue = $returnValue || ($arg != 0);
  215. ++$argCount;
  216. }
  217. }
  218. // Return
  219. if ($argCount == 0) {
  220. return self::$_errorCodes['value'];
  221. }
  222. return $returnValue;
  223. }
  224. /**
  225. * LOGICAL_FALSE
  226. *
  227. * Returns FALSE.
  228. *
  229. * @return boolean
  230. */
  231. public static function LOGICAL_FALSE() {
  232. return False;
  233. }
  234. /**
  235. * LOGICAL_TRUE
  236. *
  237. * Returns TRUE.
  238. *
  239. * @return boolean
  240. */
  241. public static function LOGICAL_TRUE() {
  242. return True;
  243. }
  244. /**
  245. * ATAN2
  246. *
  247. * This function calculates the arc tangent of the two variables x and y. It is similar to
  248. * calculating the arc tangent of y / x, except that the signs of both arguments are used
  249. * to determine the quadrant of the result.
  250. * Note that Excel reverses the arguments, so we need to reverse them here before calling the
  251. * standard PHP atan() function
  252. *
  253. * @param float $x Number
  254. * @param float $y Number
  255. * @return float Square Root of Number * Pi
  256. */
  257. public static function REVERSE_ATAN2($x, $y) {
  258. $x = self::flattenSingleValue($x);
  259. $y = self::flattenSingleValue($y);
  260. return atan2($y, $x);
  261. }
  262. /**
  263. * SUM
  264. *
  265. * SUM computes the sum of all the values and cells referenced in the argument list.
  266. *
  267. * @param array of mixed Data Series
  268. * @return float
  269. */
  270. public static function SUM() {
  271. // Return value
  272. $returnValue = 0;
  273. // Loop through the arguments
  274. $aArgs = self::flattenArray(func_get_args());
  275. foreach ($aArgs as $arg) {
  276. // Is it a numeric value?
  277. if ((is_numeric($arg)) && (!is_string($arg))) {
  278. $returnValue += $arg;
  279. }
  280. }
  281. // Return
  282. return $returnValue;
  283. }
  284. /**
  285. * SUMSQ
  286. *
  287. * Returns the sum of the squares of the arguments
  288. *
  289. * @param array of mixed Data Series
  290. * @return float
  291. */
  292. public static function SUMSQ() {
  293. // Return value
  294. $returnValue = 0;
  295. // Loop trough arguments
  296. $aArgs = self::flattenArray(func_get_args());
  297. foreach ($aArgs as $arg) {
  298. // Is it a numeric value?
  299. if ((is_numeric($arg)) && (!is_string($arg))) {
  300. $returnValue += pow($arg,2);
  301. }
  302. }
  303. // Return
  304. return $returnValue;
  305. }
  306. /**
  307. * PRODUCT
  308. *
  309. * PRODUCT returns the product of all the values and cells referenced in the argument list.
  310. *
  311. * @param array of mixed Data Series
  312. * @return float
  313. */
  314. public static function PRODUCT() {
  315. // Return value
  316. $returnValue = null;
  317. // Loop trough arguments
  318. $aArgs = self::flattenArray(func_get_args());
  319. foreach ($aArgs as $arg) {
  320. // Is it a numeric value?
  321. if ((is_numeric($arg)) && (!is_string($arg))) {
  322. if (is_null($returnValue)) {
  323. $returnValue = $arg;
  324. } else {
  325. $returnValue *= $arg;
  326. }
  327. }
  328. }
  329. // Return
  330. if (is_null($returnValue)) {
  331. return 0;
  332. }
  333. return $returnValue;
  334. }
  335. /**
  336. * QUOTIENT
  337. *
  338. * QUOTIENT function returns the integer portion of a division.numerator is the divided number
  339. * and denominator is the divisor.
  340. *
  341. * @param array of mixed Data Series
  342. * @return float
  343. */
  344. public static function QUOTIENT() {
  345. // Return value
  346. $returnValue = null;
  347. // Loop trough arguments
  348. $aArgs = self::flattenArray(func_get_args());
  349. foreach ($aArgs as $arg) {
  350. // Is it a numeric value?
  351. if ((is_numeric($arg)) && (!is_string($arg))) {
  352. if (is_null($returnValue)) {
  353. if (($returnValue == 0) || ($arg == 0)) {
  354. $returnValue = 0;
  355. } else {
  356. $returnValue = $arg;
  357. }
  358. } else {
  359. if (($returnValue == 0) || ($arg == 0)) {
  360. $returnValue = 0;
  361. } else {
  362. $returnValue /= $arg;
  363. }
  364. }
  365. }
  366. }
  367. // Return
  368. return intval($returnValue);
  369. }
  370. /**
  371. * MIN
  372. *
  373. * MIN returns the value of the element of the values passed that has the smallest value,
  374. * with negative numbers considered smaller than positive numbers.
  375. *
  376. * @param array of mixed Data Series
  377. * @return float
  378. */
  379. public static function MIN() {
  380. // Return value
  381. $returnValue = null;
  382. // Loop trough arguments
  383. $aArgs = self::flattenArray(func_get_args());
  384. foreach ($aArgs as $arg) {
  385. // Is it a numeric value?
  386. if ((is_numeric($arg)) && (!is_string($arg))) {
  387. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  388. $returnValue = $arg;
  389. }
  390. }
  391. }
  392. // Return
  393. if(is_null($returnValue)) {
  394. return 0;
  395. }
  396. return $returnValue;
  397. }
  398. /**
  399. * MINA
  400. *
  401. * Returns the smallest value in a list of arguments, including numbers, text, and logical values
  402. *
  403. * @param array of mixed Data Series
  404. * @return float
  405. */
  406. public static function MINA() {
  407. // Return value
  408. $returnValue = null;
  409. // Loop through arguments
  410. $aArgs = self::flattenArray(func_get_args());
  411. foreach ($aArgs as $arg) {
  412. // Is it a numeric value?
  413. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  414. if (is_bool($arg)) {
  415. $arg = (integer) $arg;
  416. } elseif (is_string($arg)) {
  417. $arg = 0;
  418. }
  419. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  420. $returnValue = $arg;
  421. }
  422. }
  423. }
  424. // Return
  425. if(is_null($returnValue)) {
  426. return 0;
  427. }
  428. return $returnValue;
  429. }
  430. /**
  431. * SMALL
  432. *
  433. * Returns the nth smallest value in a data set. You can use this function to
  434. * select a value based on its relative standing.
  435. *
  436. * @param array of mixed Data Series
  437. * @param float Entry in the series to return
  438. * @return float
  439. */
  440. public static function SMALL() {
  441. $aArgs = self::flattenArray(func_get_args());
  442. // Calculate
  443. $n = array_pop($aArgs);
  444. if ((is_numeric($n)) && (!is_string($n))) {
  445. $mArgs = array();
  446. foreach ($aArgs as $arg) {
  447. // Is it a numeric value?
  448. if ((is_numeric($arg)) && (!is_string($arg))) {
  449. $mArgs[] = $arg;
  450. }
  451. }
  452. $count = self::COUNT($mArgs);
  453. $n = floor(--$n);
  454. if (($n < 0) || ($n >= $count) || ($count == 0)) {
  455. return self::$_errorCodes['num'];
  456. }
  457. sort($mArgs);
  458. return $mArgs[$n];
  459. }
  460. return self::$_errorCodes['value'];
  461. }
  462. /**
  463. * MAX
  464. *
  465. * MAX returns the value of the element of the values passed that has the highest value,
  466. * with negative numbers considered smaller than positive numbers.
  467. *
  468. * @param array of mixed Data Series
  469. * @return float
  470. */
  471. public static function MAX() {
  472. // Return value
  473. $returnValue = null;
  474. // Loop trough arguments
  475. $aArgs = self::flattenArray(func_get_args());
  476. foreach ($aArgs as $arg) {
  477. // Is it a numeric value?
  478. if ((is_numeric($arg)) && (!is_string($arg))) {
  479. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  480. $returnValue = $arg;
  481. }
  482. }
  483. }
  484. // Return
  485. if(is_null($returnValue)) {
  486. return 0;
  487. }
  488. return $returnValue;
  489. }
  490. /**
  491. * MAXA
  492. *
  493. * Returns the greatest value in a list of arguments, including numbers, text, and logical values
  494. *
  495. * @param array of mixed Data Series
  496. * @return float
  497. */
  498. public static function MAXA() {
  499. // Return value
  500. $returnValue = null;
  501. // Loop through arguments
  502. $aArgs = self::flattenArray(func_get_args());
  503. foreach ($aArgs as $arg) {
  504. // Is it a numeric value?
  505. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  506. if (is_bool($arg)) {
  507. $arg = (integer) $arg;
  508. } elseif (is_string($arg)) {
  509. $arg = 0;
  510. }
  511. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  512. $returnValue = $arg;
  513. }
  514. }
  515. }
  516. // Return
  517. if(is_null($returnValue)) {
  518. return 0;
  519. }
  520. return $returnValue;
  521. }
  522. /**
  523. * LARGE
  524. *
  525. * Returns the nth largest value in a data set. You can use this function to
  526. * select a value based on its relative standing.
  527. *
  528. * @param array of mixed Data Series
  529. * @param float Entry in the series to return
  530. * @return float
  531. *
  532. */
  533. public static function LARGE() {
  534. $aArgs = self::flattenArray(func_get_args());
  535. // Calculate
  536. $n = floor(array_pop($aArgs));
  537. if ((is_numeric($n)) && (!is_string($n))) {
  538. $mArgs = array();
  539. foreach ($aArgs as $arg) {
  540. // Is it a numeric value?
  541. if ((is_numeric($arg)) && (!is_string($arg))) {
  542. $mArgs[] = $arg;
  543. }
  544. }
  545. $count = self::COUNT($mArgs);
  546. $n = floor(--$n);
  547. if (($n < 0) || ($n >= $count) || ($count == 0)) {
  548. return self::$_errorCodes['num'];
  549. }
  550. rsort($mArgs);
  551. return $mArgs[$n];
  552. }
  553. return self::$_errorCodes['value'];
  554. }
  555. /**
  556. * PERCENTILE
  557. *
  558. * Returns the nth percentile of values in a range..
  559. *
  560. * @param array of mixed Data Series
  561. * @param float $entry Entry in the series to return
  562. * @return float
  563. */
  564. public static function PERCENTILE() {
  565. $aArgs = self::flattenArray(func_get_args());
  566. // Calculate
  567. $entry = array_pop($aArgs);
  568. if ((is_numeric($entry)) && (!is_string($entry))) {
  569. if (($entry < 0) || ($entry > 1)) {
  570. return self::$_errorCodes['num'];
  571. }
  572. $mArgs = array();
  573. foreach ($aArgs as $arg) {
  574. // Is it a numeric value?
  575. if ((is_numeric($arg)) && (!is_string($arg))) {
  576. $mArgs[] = $arg;
  577. }
  578. }
  579. $mValueCount = count($mArgs);
  580. if ($mValueCount > 0) {
  581. sort($mArgs);
  582. $count = self::COUNT($mArgs);
  583. $index = $entry * ($count-1);
  584. $iBase = floor($index);
  585. if ($index == $iBase) {
  586. return $mArgs[$index];
  587. } else {
  588. $iNext = $iBase + 1;
  589. $iProportion = $index - $iBase;
  590. return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion) ;
  591. }
  592. }
  593. }
  594. return self::$_errorCodes['value'];
  595. }
  596. /**
  597. * QUARTILE
  598. *
  599. * Returns the quartile of a data set.
  600. *
  601. * @param array of mixed Data Series
  602. * @param float $entry Entry in the series to return
  603. * @return float
  604. */
  605. public static function QUARTILE() {
  606. $aArgs = self::flattenArray(func_get_args());
  607. // Calculate
  608. $entry = floor(array_pop($aArgs));
  609. if ((is_numeric($entry)) && (!is_string($entry))) {
  610. $entry /= 4;
  611. if (($entry < 0) || ($entry > 1)) {
  612. return self::$_errorCodes['num'];
  613. }
  614. return self::PERCENTILE($aArgs,$entry);
  615. }
  616. return self::$_errorCodes['value'];
  617. }
  618. /**
  619. * COUNT
  620. *
  621. * Counts the number of cells that contain numbers within the list of arguments
  622. *
  623. * @param array of mixed Data Series
  624. * @return int
  625. */
  626. public static function COUNT() {
  627. // Return value
  628. $returnValue = 0;
  629. // Loop trough arguments
  630. $aArgs = self::flattenArray(func_get_args());
  631. foreach ($aArgs as $arg) {
  632. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  633. $arg = (int) $arg;
  634. }
  635. // Is it a numeric value?
  636. if ((is_numeric($arg)) && (!is_string($arg))) {
  637. ++$returnValue;
  638. }
  639. }
  640. // Return
  641. return $returnValue;
  642. }
  643. /**
  644. * COUNTBLANK
  645. *
  646. * Counts the number of empty cells within the list of arguments
  647. *
  648. * @param array of mixed Data Series
  649. * @return int
  650. */
  651. public static function COUNTBLANK() {
  652. // Return value
  653. $returnValue = 0;
  654. // Loop trough arguments
  655. $aArgs = self::flattenArray(func_get_args());
  656. foreach ($aArgs as $arg) {
  657. // Is it a blank cell?
  658. if ((is_null($arg)) || ((is_string($arg)) && ($arg == ''))) {
  659. ++$returnValue;
  660. }
  661. }
  662. // Return
  663. return $returnValue;
  664. }
  665. /**
  666. * COUNTA
  667. *
  668. * Counts the number of cells that are not empty within the list of arguments
  669. *
  670. * @param array of mixed Data Series
  671. * @return int
  672. */
  673. public static function COUNTA() {
  674. // Return value
  675. $returnValue = 0;
  676. // Loop through arguments
  677. $aArgs = self::flattenArray(func_get_args());
  678. foreach ($aArgs as $arg) {
  679. // Is it a numeric, boolean or string value?
  680. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  681. ++$returnValue;
  682. }
  683. }
  684. // Return
  685. return $returnValue;
  686. }
  687. /**
  688. * AVERAGE
  689. *
  690. * Returns the average (arithmetic mean) of the arguments
  691. *
  692. * @param array of mixed Data Series
  693. * @return float
  694. */
  695. public static function AVERAGE() {
  696. // Return value
  697. $returnValue = 0;
  698. // Loop through arguments
  699. $aArgs = self::flattenArray(func_get_args());
  700. $aCount = 0;
  701. foreach ($aArgs as $arg) {
  702. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  703. $arg = (integer) $arg;
  704. }
  705. // Is it a numeric value?
  706. if ((is_numeric($arg)) && (!is_string($arg))) {
  707. if (is_null($returnValue)) {
  708. $returnValue = $arg;
  709. } else {
  710. $returnValue += $arg;
  711. }
  712. ++$aCount;
  713. }
  714. }
  715. // Return
  716. if ($aCount > 0) {
  717. return $returnValue / $aCount;
  718. } else {
  719. return self::$_errorCodes['divisionbyzero'];
  720. }
  721. }
  722. /**
  723. * AVERAGEA
  724. *
  725. * Returns the average of its arguments, including numbers, text, and logical values
  726. *
  727. * @param array of mixed Data Series
  728. * @return float
  729. */
  730. public static function AVERAGEA() {
  731. // Return value
  732. $returnValue = null;
  733. // Loop through arguments
  734. $aArgs = self::flattenArray(func_get_args());
  735. $aCount = 0;
  736. foreach ($aArgs as $arg) {
  737. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  738. if (is_bool($arg)) {
  739. $arg = (integer) $arg;
  740. } elseif (is_string($arg)) {
  741. $arg = 0;
  742. }
  743. if (is_null($returnValue)) {
  744. $returnValue = $arg;
  745. } else {
  746. $returnValue += $arg;
  747. }
  748. ++$aCount;
  749. }
  750. }
  751. // Return
  752. if ($aCount > 0) {
  753. return $returnValue / $aCount;
  754. } else {
  755. return self::$_errorCodes['divisionbyzero'];
  756. }
  757. }
  758. /**
  759. * MEDIAN
  760. *
  761. * Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
  762. *
  763. * @param array of mixed Data Series
  764. * @return float
  765. */
  766. public static function MEDIAN() {
  767. // Return value
  768. $returnValue = self::$_errorCodes['num'];
  769. $mArgs = array();
  770. // Loop through arguments
  771. $aArgs = self::flattenArray(func_get_args());
  772. foreach ($aArgs as $arg) {
  773. // Is it a numeric value?
  774. if ((is_numeric($arg)) && (!is_string($arg))) {
  775. $mArgs[] = $arg;
  776. }
  777. }
  778. $mValueCount = count($mArgs);
  779. if ($mValueCount > 0) {
  780. sort($mArgs,SORT_NUMERIC);
  781. $mValueCount = $mValueCount / 2;
  782. if ($mValueCount == floor($mValueCount)) {
  783. $returnValue = ($mArgs[$mValueCount--] + $mArgs[$mValueCount]) / 2;
  784. } else {
  785. $mValueCount == floor($mValueCount);
  786. $returnValue = $mArgs[$mValueCount];
  787. }
  788. }
  789. // Return
  790. return $returnValue;
  791. }
  792. //
  793. // Special variant of array_count_values that isn't limited to strings and integers,
  794. // but can work with floating point numbers as values
  795. //
  796. private static function modeCalc($data) {
  797. $frequencyArray = array();
  798. foreach($data as $datum) {
  799. $found = False;
  800. foreach($frequencyArray as $key => $value) {
  801. if ((string)$value['value'] == (string)$datum) {
  802. ++$frequencyArray[$key]['frequency'];
  803. $found = True;
  804. break;
  805. }
  806. }
  807. if (!$found) {
  808. $frequencyArray[] = array('value' => $datum,
  809. 'frequency' => 1 );
  810. }
  811. }
  812. foreach($frequencyArray as $key => $value) {
  813. $frequencyList[$key] = $value['frequency'];
  814. $valueList[$key] = $value['value'];
  815. }
  816. array_multisort($frequencyList, SORT_DESC, $valueList, SORT_ASC, SORT_NUMERIC, $frequencyArray);
  817. if ($frequencyArray[0]['frequency'] == 1) {
  818. return self::NA();
  819. }
  820. return $frequencyArray[0]['value'];
  821. }
  822. /**
  823. * MODE
  824. *
  825. * Returns the most frequently occurring, or repetitive, value in an array or range of data
  826. *
  827. * @param array of mixed Data Series
  828. * @return float
  829. */
  830. public static function MODE() {
  831. // Return value
  832. $returnValue = self::NA();
  833. // Loop through arguments
  834. $aArgs = self::flattenArray(func_get_args());
  835. $mArgs = array();
  836. foreach ($aArgs as $arg) {
  837. // Is it a numeric value?
  838. if ((is_numeric($arg)) && (!is_string($arg))) {
  839. $mArgs[] = $arg;
  840. }
  841. }
  842. if (count($mArgs) > 0) {
  843. return self::modeCalc($mArgs);
  844. }
  845. // Return
  846. return $returnValue;
  847. }
  848. /**
  849. * DEVSQ
  850. *
  851. * Returns the sum of squares of deviations of data points from their sample mean.
  852. *
  853. * @param array of mixed Data Series
  854. * @return float
  855. */
  856. public static function DEVSQ() {
  857. // Return value
  858. $returnValue = null;
  859. $aMean = self::AVERAGE(func_get_args());
  860. if (!is_null($aMean)) {
  861. $aArgs = self::flattenArray(func_get_args());
  862. $aCount = -1;
  863. foreach ($aArgs as $arg) {
  864. // Is it a numeric value?
  865. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  866. $arg = (int) $arg;
  867. }
  868. if ((is_numeric($arg)) && (!is_string($arg))) {
  869. if (is_null($returnValue)) {
  870. $returnValue = pow(($arg - $aMean),2);
  871. } else {
  872. $returnValue += pow(($arg - $aMean),2);
  873. }
  874. ++$aCount;
  875. }
  876. }
  877. // Return
  878. if (is_null($returnValue)) {
  879. return self::$_errorCodes['num'];
  880. } else {
  881. return $returnValue;
  882. }
  883. }
  884. return self::NA();
  885. }
  886. /**
  887. * AVEDEV
  888. *
  889. * Returns the average of the absolute deviations of data points from their mean.
  890. * AVEDEV is a measure of the variability in a data set.
  891. *
  892. * @param array of mixed Data Series
  893. * @return float
  894. */
  895. public static function AVEDEV() {
  896. $aArgs = self::flattenArray(func_get_args());
  897. // Return value
  898. $returnValue = null;
  899. $aMean = self::AVERAGE($aArgs);
  900. if ($aMean != self::$_errorCodes['divisionbyzero']) {
  901. $aCount = 0;
  902. foreach ($aArgs as $arg) {
  903. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  904. $arg = (integer) $arg;
  905. }
  906. // Is it a numeric value?
  907. if ((is_numeric($arg)) && (!is_string($arg))) {
  908. if (is_null($returnValue)) {
  909. $returnValue = abs($arg - $aMean);
  910. } else {
  911. $returnValue += abs($arg - $aMean);
  912. }
  913. ++$aCount;
  914. }
  915. }
  916. // Return
  917. return $returnValue / $aCount ;
  918. }
  919. return self::$_errorCodes['num'];
  920. }
  921. /**
  922. * GEOMEAN
  923. *
  924. * Returns the geometric mean of an array or range of positive data. For example, you
  925. * can use GEOMEAN to calculate average growth rate given compound interest with
  926. * variable rates.
  927. *
  928. * @param array of mixed Data Series
  929. * @return float
  930. */
  931. public static function GEOMEAN() {
  932. $aMean = self::PRODUCT(func_get_args());
  933. if (is_numeric($aMean) && ($aMean > 0)) {
  934. $aArgs = self::flattenArray(func_get_args());
  935. $aCount = self::COUNT($aArgs) ;
  936. if (self::MIN($aArgs) > 0) {
  937. return pow($aMean, (1 / $aCount));
  938. }
  939. }
  940. return self::$_errorCodes['num'];
  941. }
  942. /**
  943. * HARMEAN
  944. *
  945. * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the
  946. * arithmetic mean of reciprocals.
  947. *
  948. * @param array of mixed Data Series
  949. * @return float
  950. */
  951. public static function HARMEAN() {
  952. // Return value
  953. $returnValue = self::NA();
  954. // Loop through arguments
  955. $aArgs = self::flattenArray(func_get_args());
  956. if (self::MIN($aArgs) < 0) {
  957. return self::$_errorCodes['num'];
  958. }
  959. $aCount = 0;
  960. foreach ($aArgs as $arg) {
  961. // Is it a numeric value?
  962. if ((is_numeric($arg)) && (!is_string($arg))) {
  963. if ($arg <= 0) {
  964. return self::$_errorCodes['num'];
  965. }
  966. if (is_null($returnValue)) {
  967. $returnValue = (1 / $arg);
  968. } else {
  969. $returnValue += (1 / $arg);
  970. }
  971. ++$aCount;
  972. }
  973. }
  974. // Return
  975. if ($aCount > 0) {
  976. return 1 / ($returnValue / $aCount);
  977. } else {
  978. return $returnValue;
  979. }
  980. }
  981. /**
  982. * TRIMMEAN
  983. *
  984. * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean
  985. * taken by excluding a percentage of data points from the top and bottom tails
  986. * of a data set.
  987. *
  988. * @param array of mixed Data Series
  989. * @param float Percentage to discard
  990. * @return float
  991. */
  992. public static function TRIMMEAN() {
  993. $aArgs = self::flattenArray(func_get_args());
  994. // Calculate
  995. $percent = array_pop($aArgs);
  996. if ((is_numeric($percent)) && (!is_string($percent))) {
  997. if (($percent < 0) || ($percent > 1)) {
  998. return self::$_errorCodes['num'];
  999. }
  1000. $mArgs = array();
  1001. foreach ($aArgs as $arg) {
  1002. // Is it a numeric value?
  1003. if ((is_numeric($arg)) && (!is_string($arg))) {
  1004. $mArgs[] = $arg;
  1005. }
  1006. }
  1007. $discard = floor(self::COUNT($mArgs) * $percent / 2);
  1008. sort($mArgs);
  1009. for ($i=0; $i < $discard; ++$i) {
  1010. array_pop($mArgs);
  1011. array_shift($mArgs);
  1012. }
  1013. return self::AVERAGE($mArgs);
  1014. }
  1015. return self::$_errorCodes['value'];
  1016. }
  1017. /**
  1018. * STDEV
  1019. *
  1020. * Estimates standard deviation based on a sample. The standard deviation is a measure of how
  1021. * widely values are dispersed from the average value (the mean).
  1022. *
  1023. * @param array of mixed Data Series
  1024. * @return float
  1025. */
  1026. public static function STDEV() {
  1027. // Return value
  1028. $returnValue = null;
  1029. $aMean = self::AVERAGE(func_get_args());
  1030. if (!is_null($aMean)) {
  1031. $aArgs = self::flattenArray(func_get_args());
  1032. $aCount = -1;
  1033. foreach ($aArgs as $arg) {
  1034. // Is it a numeric value?
  1035. if ((is_numeric($arg)) && (!is_string($arg))) {
  1036. if (is_null($returnValue)) {
  1037. $returnValue = pow(($arg - $aMean),2);
  1038. } else {
  1039. $returnValue += pow(($arg - $aMean),2);
  1040. }
  1041. ++$aCount;
  1042. }
  1043. }
  1044. // Return
  1045. if (($aCount > 0) && ($returnValue > 0)) {
  1046. return sqrt($returnValue / $aCount);
  1047. }
  1048. }
  1049. return self::$_errorCodes['divisionbyzero'];
  1050. }
  1051. /**
  1052. * STDEVA
  1053. *
  1054. * Estimates standard deviation based on a sample, including numbers, text, and logical values
  1055. *
  1056. * @param array of mixed Data Series
  1057. * @return float
  1058. */
  1059. public static function STDEVA() {
  1060. // Return value
  1061. $returnValue = null;
  1062. $aMean = self::AVERAGEA(func_get_args());
  1063. if (!is_null($aMean)) {
  1064. $aArgs = self::flattenArray(func_get_args());
  1065. $aCount = -1;
  1066. foreach ($aArgs as $arg) {
  1067. // Is it a numeric value?
  1068. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1069. if (is_bool($arg)) {
  1070. $arg = (integer) $arg;
  1071. } elseif (is_string($arg)) {
  1072. $arg = 0;
  1073. }
  1074. if (is_null($returnValue)) {
  1075. $returnValue = pow(($arg - $aMean),2);
  1076. } else {
  1077. $returnValue += pow(($arg - $aMean),2);
  1078. }
  1079. ++$aCount;
  1080. }
  1081. }
  1082. // Return
  1083. if (($aCount > 0) && ($returnValue > 0)) {
  1084. return sqrt($returnValue / $aCount);
  1085. }
  1086. }
  1087. return self::$_errorCodes['divisionbyzero'];
  1088. }
  1089. /**
  1090. * STDEVP
  1091. *
  1092. * Calculates standard deviation based on the entire population
  1093. *
  1094. * @param array of mixed Data Series
  1095. * @return float
  1096. */
  1097. public static function STDEVP() {
  1098. // Return value
  1099. $returnValue = null;
  1100. $aMean = self::AVERAGE(func_get_args());
  1101. if (!is_null($aMean)) {
  1102. $aArgs = self::flattenArray(func_get_args());
  1103. $aCount = 0;
  1104. foreach ($aArgs as $arg) {
  1105. // Is it a numeric value?
  1106. if ((is_numeric($arg)) && (!is_string($arg))) {
  1107. if (is_null($returnValue)) {
  1108. $returnValue = pow(($arg - $aMean),2);
  1109. } else {
  1110. $returnValue += pow(($arg - $aMean),2);
  1111. }
  1112. ++$aCount;
  1113. }
  1114. }
  1115. // Return
  1116. if (($aCount > 0) && ($returnValue > 0)) {
  1117. return sqrt($returnValue / $aCount);
  1118. }
  1119. }
  1120. return self::$_errorCodes['divisionbyzero'];
  1121. }
  1122. /**
  1123. * STDEVPA
  1124. *
  1125. * Calculates standard deviation based on the entire population, including numbers, text, and logical values
  1126. *
  1127. * @param array of mixed Data Series
  1128. * @return float
  1129. */
  1130. public static function STDEVPA() {
  1131. // Return value
  1132. $returnValue = null;
  1133. $aMean = self::AVERAGEA(func_get_args());
  1134. if (!is_null($aMean)) {
  1135. $aArgs = self::flattenArray(func_get_args());
  1136. $aCount = 0;
  1137. foreach ($aArgs as $arg) {
  1138. // Is it a numeric value?
  1139. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1140. if (is_bool($arg)) {
  1141. $arg = (integer) $arg;
  1142. } elseif (is_string($arg)) {
  1143. $arg = 0;
  1144. }
  1145. if (is_null($returnValue)) {
  1146. $returnValue = pow(($arg - $aMean),2);
  1147. } else {
  1148. $returnValue += pow(($arg - $aMean),2);
  1149. }
  1150. ++$aCount;
  1151. }
  1152. }
  1153. // Return
  1154. if (($aCount > 0) && ($returnValue > 0)) {
  1155. return sqrt($returnValue / $aCount);
  1156. }
  1157. }
  1158. return self::$_errorCodes['divisionbyzero'];
  1159. }
  1160. /**
  1161. * VARFunc
  1162. *
  1163. * Estimates variance based on a sample.
  1164. *
  1165. * @param array of mixed Data Series
  1166. * @return float
  1167. */
  1168. public static function VARFunc() {
  1169. // Return value
  1170. $returnValue = self::$_errorCodes['divisionbyzero'];
  1171. $summerA = $summerB = 0;
  1172. // Loop through arguments
  1173. $aArgs = self::flattenArray(func_get_args());
  1174. $aCount = 0;
  1175. foreach ($aArgs as $arg) {
  1176. // Is it a numeric value?
  1177. if ((is_numeric($arg)) && (!is_string($arg))) {
  1178. $summerA += ($arg * $arg);
  1179. $summerB += $arg;
  1180. ++$aCount;
  1181. }
  1182. }
  1183. // Return
  1184. if ($aCount > 1) {
  1185. $summerA = $summerA * $aCount;
  1186. $summerB = ($summerB * $summerB);
  1187. $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
  1188. }
  1189. return $returnValue;
  1190. }
  1191. /**
  1192. * VARA
  1193. *
  1194. * Estimates variance based on a sample, including numbers, text, and logical values
  1195. *
  1196. * @param array of mixed Data Series
  1197. * @return float
  1198. */
  1199. public static function VARA() {
  1200. // Return value
  1201. $returnValue = self::$_errorCodes['divisionbyzero'];
  1202. $summerA = $summerB = 0;
  1203. // Loop through arguments
  1204. $aArgs = self::flattenArray(func_get_args());
  1205. $aCount = 0;
  1206. foreach ($aArgs as $arg) {
  1207. // Is it a numeric value?
  1208. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1209. if (is_bool($arg)) {
  1210. $arg = (integer) $arg;
  1211. } elseif (is_string($arg)) {
  1212. $arg = 0;
  1213. }
  1214. $summerA += ($arg * $arg);
  1215. $summerB += $arg;
  1216. ++$aCount;
  1217. }
  1218. }
  1219. // Return
  1220. if ($aCount > 1) {
  1221. $summerA = $summerA * $aCount;
  1222. $summerB = ($summerB * $summerB);
  1223. $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
  1224. }
  1225. return $returnValue;
  1226. }
  1227. /**
  1228. * VARP
  1229. *
  1230. * Calculates variance based on the entire population
  1231. *
  1232. * @param array of mixed Data Series
  1233. * @return float
  1234. */
  1235. public static function VARP() {
  1236. // Return value
  1237. $returnValue = self::$_errorCodes['divisionbyzero'];
  1238. $summerA = $summerB = 0;
  1239. // Loop through arguments
  1240. $aArgs = self::flattenArray(func_get_args());
  1241. $aCount = 0;
  1242. foreach ($aArgs as $arg) {
  1243. // Is it a numeric value?
  1244. if ((is_numeric($arg)) && (!is_string($arg))) {
  1245. $summerA += ($arg * $arg);
  1246. $summerB += $arg;
  1247. ++$aCount;
  1248. }
  1249. }
  1250. // Return
  1251. if ($aCount > 0) {
  1252. $summerA = $summerA * $aCount;
  1253. $summerB = ($summerB * $summerB);
  1254. $returnValue = ($summerA - $summerB) / ($aCount * $aCount);
  1255. }
  1256. return $returnValue;
  1257. }
  1258. /**
  1259. * VARPA
  1260. *
  1261. * Calculates variance based on the entire population, including numbers, text, and logical values
  1262. *
  1263. * @param array of mixed Data Series
  1264. * @return float
  1265. */
  1266. public static function VARPA() {
  1267. // Return value
  1268. $returnValue = self::$_errorCodes['divisionbyzero'];
  1269. $summerA = $summerB = 0;
  1270. // Loop through arguments
  1271. $aArgs = self::flattenArray(func_get_args());
  1272. $aCount = 0;
  1273. foreach ($aArgs as $arg) {
  1274. // Is it a numeric value?
  1275. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1276. if (is_bool($arg)) {
  1277. $arg = (integer) $arg;
  1278. } elseif (is_string($arg)) {
  1279. $arg = 0;
  1280. }
  1281. $summerA += ($arg * $arg);
  1282. $summerB += $arg;
  1283. ++$aCount;
  1284. }
  1285. }
  1286. // Return
  1287. if ($aCount > 0) {
  1288. $summerA = $summerA * $aCount;
  1289. $summerB = ($summerB * $summerB);
  1290. $returnValue = ($summerA - $summerB) / ($aCount * $aCount);
  1291. }
  1292. return $returnValue;
  1293. }
  1294. /**
  1295. * SUBTOTAL
  1296. *
  1297. * Returns a subtotal in a list or database.
  1298. *
  1299. * @param int the number 1 to 11 that specifies which function to
  1300. * use in calculating subtotals within a list.
  1301. * @param array of mixed Data Series
  1302. * @return float
  1303. */
  1304. public static function SUBTOTAL() {
  1305. $aArgs = self::flattenArray(func_get_args());
  1306. // Calculate
  1307. $subtotal = array_shift($aArgs);
  1308. if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
  1309. switch($subtotal) {
  1310. case 1 :
  1311. return self::AVERAGE($aArgs);
  1312. break;
  1313. case 2 :
  1314. return self::COUNT($aArgs);
  1315. break;
  1316. case 3 :
  1317. return self::COUNTA($aArgs);
  1318. break;
  1319. case 4 :
  1320. return self::MAX($aArgs);
  1321. break;
  1322. case 5 :
  1323. return self::MIN($aArgs);
  1324. break;
  1325. case 6 :
  1326. return self::PRODUCT($aArgs);
  1327. break;
  1328. case 7 :
  1329. return self::STDEV($aArgs);
  1330. break;
  1331. case 8 :
  1332. return self::STDEVP($aArgs);
  1333. break;
  1334. case 9 :
  1335. return self::SUM($aArgs);
  1336. break;
  1337. case 10 :
  1338. return self::VARFunc($aArgs);
  1339. break;
  1340. case 11 :
  1341. return self::VARP($aArgs);
  1342. break;
  1343. }
  1344. }
  1345. return self::$_errorCodes['value'];
  1346. }
  1347. /**
  1348. * SQRTPI
  1349. *
  1350. * Returns the square root of (number * pi).
  1351. *
  1352. * @param float $number Number
  1353. * @return float Square Root of Number * Pi
  1354. */
  1355. public static function SQRTPI($number) {
  1356. $number = self::flattenSingleValue($number);
  1357. if (is_numeric($number)) {
  1358. if ($number < 0) {
  1359. return self::$_errorCodes['num'];
  1360. }
  1361. return sqrt($number * pi()) ;
  1362. }
  1363. return self::$_errorCodes['value'];
  1364. }
  1365. /**
  1366. * FACT
  1367. *
  1368. * Returns the factorial of a number.
  1369. *
  1370. * @param float $factVal Factorial Value
  1371. * @return int Factorial
  1372. */
  1373. public static function FACT($factVal) {
  1374. $factVal = self::flattenSingleValue($factVal);
  1375. if (is_numeric($factVal)) {
  1376. if ($factVal < 0) {
  1377. return self::$_errorCodes['num'];
  1378. }
  1379. $factLoop = floor($factVal);
  1380. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  1381. if ($factVal > $factLoop) {
  1382. return self::$_errorCodes['num'];
  1383. }
  1384. }
  1385. $factorial = 1;
  1386. while ($factLoop > 1) {
  1387. $factorial *= $factLoop--;
  1388. }
  1389. return $factorial ;
  1390. }
  1391. return self::$_errorCodes['value'];
  1392. }
  1393. /**
  1394. * FACTDOUBLE
  1395. *
  1396. * Returns the double factorial of a number.
  1397. *
  1398. * @param float $factVal Factorial Value
  1399. * @return int Double Factorial
  1400. */
  1401. public static function FACTDOUBLE($factVal) {
  1402. $factLoop = floor(self::flattenSingleValue($factVal));
  1403. if (is_numeric($factLoop)) {
  1404. if ($factVal < 0) {
  1405. return self::$_errorCodes['num'];
  1406. }
  1407. $factorial = 1;
  1408. while ($factLoop > 1) {
  1409. $factorial *= $factLoop--;
  1410. --$factLoop;
  1411. }
  1412. return $factorial ;
  1413. }
  1414. return self::$_errorCodes['value'];
  1415. }
  1416. /**
  1417. * MULTINOMIAL
  1418. *
  1419. * Returns the ratio of the factorial of a sum of values to the product of factorials.
  1420. *
  1421. * @param array of mixed Data Series
  1422. * @return float
  1423. */
  1424. public static function MULTINOMIAL() {
  1425. // Loop through arguments
  1426. $aArgs = self::flattenArray(func_get_args());
  1427. $summer = 0;
  1428. $divisor = 1;
  1429. foreach ($aArgs as $arg) {
  1430. // Is it a numeric value?
  1431. if (is_numeric($arg)) {
  1432. if ($arg < 1) {
  1433. return self::$_errorCodes['num'];
  1434. }
  1435. $summer += floor($arg);
  1436. $divisor *= self::FACT($arg);
  1437. } else {
  1438. return self::$_errorCodes['value'];
  1439. }
  1440. }
  1441. // Return
  1442. if ($summer > 0) {
  1443. $summer = self::FACT($summer);
  1444. return $summer / $divisor;
  1445. }
  1446. return 0;
  1447. }
  1448. /**
  1449. * CEILING
  1450. *
  1451. * Returns number rounded up, away from zero, to the nearest multiple of significance.
  1452. *
  1453. * @param float $number Number to round
  1454. * @param float $significance Significance
  1455. * @return float Rounded Number
  1456. */
  1457. public static function CEILING($number,$significance=null) {
  1458. $number = self::flattenSingleValue($number);
  1459. $significance = self::flattenSingleValue($significance);
  1460. if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  1461. $significance = $number/abs($number);
  1462. }
  1463. if ((is_numeric($number)) && (is_numeric($significance))) {
  1464. if (self::SIGN($number) == self::SIGN($significance)) {
  1465. if ($significance == 0.0) {
  1466. return 0;
  1467. }
  1468. return ceil($number / $significance) * $significance;
  1469. } else {
  1470. return self::$_errorCodes['num'];
  1471. }
  1472. }
  1473. return self::$_errorCodes['value'];
  1474. }
  1475. /**
  1476. * EVEN
  1477. *
  1478. * Returns number rounded up to the nearest even integer.
  1479. *
  1480. * @param float $number Number to round
  1481. * @return int Rounded Number
  1482. */
  1483. public static function EVEN($number) {
  1484. $number = self::flattenSingleValue($number);
  1485. if (is_numeric($number)) {
  1486. $significance = 2 * self::SIGN($number);
  1487. return self::CEILING($number,$significance);
  1488. }
  1489. return self::$_errorCodes['value'];
  1490. }
  1491. /**
  1492. * ODD
  1493. *
  1494. * Returns number rounded up to the nearest odd integer.
  1495. *
  1496. * @param float $number Number to round
  1497. * @return int Rounded Number
  1498. */
  1499. public static function ODD($number) {
  1500. $number = self::flattenSingleValue($number);
  1501. if (is_numeric($number)) {
  1502. $significance = self::SIGN($number);
  1503. if ($significance == 0) {
  1504. return 1;
  1505. }
  1506. $result = self::CEILING($number,$significance);
  1507. if (self::IS_EVEN($result)) {
  1508. $result += $significance;
  1509. }
  1510. return $result;
  1511. }
  1512. return self::$_errorCodes['value'];
  1513. }
  1514. /**
  1515. * ROUNDUP
  1516. *
  1517. * Rounds a number up to a specified number of decimal places
  1518. *
  1519. * @param float $number Number to round
  1520. * @param int $digits Number of digits to which you want to round $number
  1521. * @return float Rounded Number
  1522. */
  1523. public static function ROUNDUP($number,$digits) {
  1524. $number = self::flattenSingleValue($number);
  1525. $digits = self::flattenSingleValue($digits);
  1526. if (is_numeric($number)) {
  1527. if ((is_numeric($digits)) && ($digits >= 0)) {
  1528. $significance = pow(10,$digits);
  1529. return ceil($number * $significance) / $significance;
  1530. }
  1531. }
  1532. return self::$_errorCodes['value'];
  1533. }
  1534. /**
  1535. * ROUNDDOWN
  1536. *
  1537. * Rounds a number down to a specified number of decimal places
  1538. *
  1539. * @param float $number Number to round
  1540. * @param int $digits Number of digits to which you want to round $number
  1541. * @return float Rounded Number
  1542. */
  1543. public static function ROUNDDOWN($number,$digits) {
  1544. $number = self::flattenSingleValue($number);
  1545. $digits = self::flattenSingleValue($digits);
  1546. if (is_numeric($number)) {
  1547. if ((is_numeric($digits)) && ($digits >= 0)) {
  1548. $significance = pow(10,$digits);
  1549. return floor($number * $significance) / $significance;
  1550. }
  1551. }
  1552. return self::$_errorCodes['value'];
  1553. }
  1554. /**
  1555. * MROUND
  1556. *
  1557. * Rounds a number to the nearest multiple of a specified value
  1558. *
  1559. * @param float $number Number to round
  1560. * @param int $multiple Multiple to which you want to round $number
  1561. * @return float Rounded Number
  1562. */
  1563. public static function MROUND($number,$multiple) {
  1564. $number = self::flattenSingleValue($number);
  1565. $multiple = self::flattenSingleValue($multiple);
  1566. if ((is_numeric($number)) && (is_numeric($multiple))) {
  1567. if ((self::SIGN($number)) == (self::SIGN($multiple))) {
  1568. $lowerVal = floor($number / $multiple) * $multiple;
  1569. $upperVal = ceil($number / $multiple) * $multiple;
  1570. $adjustUp = abs($number - $upperVal);
  1571. $adjustDown = abs($number - $lowerVal) + PRECISION;
  1572. if ($adjustDown < $adjustUp) {
  1573. return $lowerVal;
  1574. }
  1575. return $upperVal;
  1576. }
  1577. return self::$_errorCodes['num'];
  1578. }
  1579. return self::$_errorCodes['value'];
  1580. }
  1581. /**
  1582. * SIGN
  1583. *
  1584. * Determines the sign of a number. Returns 1 if the number is positive, zero (0)
  1585. * if the number is 0, and -1 if the number is negative.
  1586. *
  1587. * @param float $number Number to round
  1588. * @return int sign value
  1589. */
  1590. public static function SIGN($number) {
  1591. $number = self::flattenSingleValue($number);
  1592. if (is_numeric($number)) {
  1593. if ($number == 0.0) {
  1594. return 0;
  1595. }
  1596. return $number / abs($number);
  1597. }
  1598. return self::$_errorCodes['value'];
  1599. }
  1600. /**
  1601. * FLOOR
  1602. *
  1603. * Rounds number down, toward zero, to the nearest multiple of significance.
  1604. *
  1605. * @param float $number Number to round
  1606. * @param float $significance Significance
  1607. * @return float Rounded Number
  1608. */
  1609. public static function FLOOR($number,$significance=null) {
  1610. $number = self::flattenSingleValue($number);
  1611. $significance = self::flattenSingleValue($significance);
  1612. if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  1613. $significance = $number/abs($number);
  1614. }
  1615. if ((is_numeric($number)) && (is_numeric($significance))) {
  1616. if ((float) $significance == 0.0) {
  1617. return self::$_errorCodes['divisionbyzero'];
  1618. }
  1619. if (self::SIGN($number) == self::SIGN($significance)) {
  1620. return floor($number / $significance) * $significance;
  1621. } else {
  1622. return self::$_errorCodes['num'];
  1623. }
  1624. }
  1625. return self::$_errorCodes['value'];
  1626. }
  1627. /**
  1628. * PERMUT
  1629. *
  1630. * Returns the number of permutations for a given number of objects that can be
  1631. * selected from number objects. A permutation is any set or subset of objects or
  1632. * events where internal order is significant. Permutations are different from
  1633. * combinations, for which the internal order is not significant. Use this function
  1634. * for lottery-style probability calculations.
  1635. *
  1636. * @param int $numObjs Number of different objects
  1637. * @param int $numInSet Number of objects in each permutation
  1638. * @return int Number of permutations
  1639. */
  1640. public static function PERMUT($numObjs,$numInSet) {
  1641. $numObjs = self::flattenSingleValue($numObjs);
  1642. $numInSet = self::flattenSingleValue($numInSet);
  1643. if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
  1644. if ($numObjs < $numInSet) {
  1645. return self::$_errorCodes['num'];
  1646. }
  1647. return self::FACT($numObjs) / self::FACT($numObjs - $numInSet);
  1648. }
  1649. return self::$_errorCodes['value'];
  1650. }
  1651. /**
  1652. * COMBIN
  1653. *
  1654. * Returns the number of combinations for a given number of items. Use COMBIN to
  1655. * determine the total possible number of groups for a given number of items.
  1656. *
  1657. * @param int $numObjs Number of different objects
  1658. * @param int $numInSet Number of objects in each combination
  1659. * @return int Number of combinations
  1660. */
  1661. public static function COMBIN($numObjs,$numInSet) {
  1662. $numObjs = self::flattenSingleValue($numObjs);
  1663. $numInSet = self::flattenSingleValue($numInSet);
  1664. if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
  1665. if ($numObjs < $numInSet) {
  1666. return self::$_errorCodes['num'];
  1667. } elseif ($numInSet < 0) {
  1668. return self::$_errorCodes['num'];
  1669. }
  1670. return (self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
  1671. }
  1672. return self::$_errorCodes['value'];
  1673. }
  1674. /**
  1675. * SERIESSUM
  1676. *
  1677. * Returns the sum of a power series
  1678. *
  1679. * @param float $x Input value to the power series
  1680. * @param float $n Initial power to which you want to raise $x
  1681. * @param float $m Step by which to increase $n for each term in the series
  1682. * @param array of mixed Data Series
  1683. * @return float
  1684. */
  1685. public static function SERIESSUM() {
  1686. // Return value
  1687. $returnValue = 0;
  1688. // Loop trough arguments
  1689. $aArgs = self::flattenArray(func_get_args());
  1690. $x = array_shift($aArgs);
  1691. $n = array_shift($aArgs);
  1692. $m = array_shift($aArgs);
  1693. if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) {
  1694. // Calculate
  1695. $i = 0;
  1696. foreach($aArgs as $arg) {
  1697. // Is it a numeric value?
  1698. if ((is_numeric($arg)) && (!is_string($arg))) {
  1699. $returnValue += $arg * pow($x,$n + ($m * $i++));
  1700. } else {
  1701. return self::$_errorCodes['value'];
  1702. }
  1703. }
  1704. // Return
  1705. return $returnValue;
  1706. }
  1707. return self::$_errorCodes['value'];
  1708. }
  1709. /**
  1710. * STANDARDIZE
  1711. *
  1712. * Returns a normalized value from a distribution characterized by mean and standard_dev.
  1713. *
  1714. * @param float $value Value to normalize
  1715. * @param float $mean Mean Value
  1716. * @param float $stdDev Standard Deviation
  1717. * @return float Standardized value
  1718. */
  1719. public static function STANDARDIZE($value,$mean,$stdDev) {
  1720. $value = self::flattenSingleValue($value);
  1721. $mean = self::flattenSingleValue($mean);
  1722. $stdDev = self::flattenSingleValue($stdDev);
  1723. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  1724. if ($stdDev <= 0) {
  1725. return self::$_errorCodes['num'];
  1726. }
  1727. return ($value - $mean) / $stdDev ;
  1728. }
  1729. return self::$_errorCodes['value'];
  1730. }
  1731. //
  1732. // Private method to return an array of the factors of the input value
  1733. //
  1734. private static function factors($value) {
  1735. $startVal = floor(sqrt($value));
  1736. $factorArray = array();
  1737. for ($i = $startVal; $i > 1; --$i) {
  1738. if (($value % $i) == 0) {
  1739. $factorArray = array_merge($factorArray,self::factors($value / $i));
  1740. $factorArray = array_merge($factorArray,self::factors($i));
  1741. if ($i <= sqrt($value)) {
  1742. break;
  1743. }
  1744. }
  1745. }
  1746. if (count($factorArray) > 0) {
  1747. rsort($factorArray);
  1748. return $factorArray;
  1749. } else {
  1750. return array((integer) $value);
  1751. }
  1752. }
  1753. /**
  1754. * LCM
  1755. *
  1756. * Returns the lowest common multiplier of a series of numbers
  1757. *
  1758. * @param $array Values to calculate the Lowest Common Multiplier
  1759. * @return int Lowest Common Multiplier
  1760. */
  1761. public static function LCM() {
  1762. $aArgs = self::flattenArray(func_get_args());
  1763. $returnValue = 1;
  1764. $allPoweredFactors = array();
  1765. foreach($aArgs as $value) {
  1766. if (!is_numeric($value)) {
  1767. return self::$_errorCodes['value'];
  1768. }
  1769. if ($value < 1) {
  1770. return self::$_errorCodes['num'];
  1771. }
  1772. $myFactors = self::factors(floor($value));
  1773. $myCountedFactors = array_count_values($myFactors);
  1774. $myPoweredFactors = array();
  1775. foreach($myCountedFactors as $myCountedFactor => $myCountedPower) {
  1776. $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor,$myCountedPower);
  1777. }
  1778. foreach($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
  1779. if (array_key_exists($myPoweredValue,$allPoweredFactors)) {
  1780. if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
  1781. $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
  1782. }
  1783. } else {
  1784. $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
  1785. }
  1786. }
  1787. }
  1788. foreach($allPoweredFactors as $allPoweredFactor) {
  1789. $returnValue *= (integer) $allPoweredFactor;
  1790. }
  1791. return $returnValue;
  1792. }
  1793. /**
  1794. * GCD
  1795. *
  1796. * Returns the greatest common divisor of a series of numbers
  1797. *
  1798. * @param $array Values to calculate the Greatest Common Divisor
  1799. * @return int Greatest Common Divisor
  1800. */
  1801. public static function GCD() {
  1802. $aArgs = self::flattenArray(func_get_args());
  1803. $returnValue = 1;
  1804. $allPoweredFactors = array();
  1805. foreach($aArgs as $value) {
  1806. if ($value == 0) {
  1807. return 0;
  1808. }
  1809. $myFactors = self::factors($value);
  1810. $myCountedFactors = array_count_values($myFactors);
  1811. $allValuesFactors[] = $myCountedFactors;
  1812. }
  1813. $allValuesCount = count($allValuesFactors);
  1814. $mergedArray = $allValuesFactors[0];
  1815. for ($i=1;$i < $allValuesCount; ++$i) {
  1816. $mergedArray = array_intersect_key($mergedArray,$allValuesFactors[$i]);
  1817. }
  1818. $mergedArrayValues = count($mergedArray);
  1819. if ($mergedArrayValues == 0) {
  1820. return $returnValue;
  1821. } elseif ($mergedArrayValues > 1) {
  1822. foreach($mergedArray as $mergedKey => $mergedValue) {
  1823. foreach($allValuesFactors as $highestPowerTest) {
  1824. foreach($highestPowerTest as $testKey => $testValue) {
  1825. if (($testKey == $mergedKey) && ($testValue < $mergedValue)) {
  1826. $mergedArray[$mergedKey] = $testValue;
  1827. $mergedValue = $testValue;
  1828. }
  1829. }
  1830. }
  1831. }
  1832. $returnValue = 1;
  1833. foreach($mergedArray as $key => $value) {
  1834. $returnValue *= pow($key,$value);
  1835. }
  1836. return $returnValue;
  1837. } else {
  1838. $keys = array_keys($mergedArray);
  1839. $key = $keys[0];
  1840. $value = $mergedArray[$key];
  1841. foreach($allValuesFactors as $testValue) {
  1842. foreach($testValue as $mergedKey => $mergedValue) {
  1843. if (($mergedKey == $key) && ($mergedValue < $value)) {
  1844. $value = $mergedValue;
  1845. }
  1846. }
  1847. }
  1848. return pow($key,$value);
  1849. }
  1850. }
  1851. /**
  1852. * BINOMDIST
  1853. *
  1854. * Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
  1855. * a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
  1856. * when trials are independent, and when the probability of success is constant throughout the
  1857. * experiment. For example, BINOMDIST can calculate the probability that two of the next three
  1858. * babies born are male.
  1859. *
  1860. * @param float $value Number of successes in trials
  1861. * @param float $trials Number of trials
  1862. * @param float $probability Probability of success on each trial
  1863. * @param boolean $cumulative
  1864. * @return float
  1865. *
  1866. * @todo Cumulative distribution function
  1867. *
  1868. */
  1869. public static function BINOMDIST($value, $trials, $probability, $cumulative) {
  1870. $value = floor(self::flattenSingleValue($value));
  1871. $trials = floor(self::flattenSingleValue($trials));
  1872. $probability = self::flattenSingleValue($probability);
  1873. if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) {
  1874. if (($value < 0) || ($value > $trials)) {
  1875. return self::$_errorCodes['num'];
  1876. }
  1877. if (($probability < 0) || ($probability > 1)) {
  1878. return self::$_errorCodes['num'];
  1879. }
  1880. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  1881. if ($cumulative) {
  1882. $summer = 0;
  1883. for ($i = 0; $i <= $value; ++$i) {
  1884. $summer += self::COMBIN($trials,$i) * pow($probability,$i) * pow(1 - $probability,$trials - $i);
  1885. }
  1886. return $summer;
  1887. } else {
  1888. return self::COMBIN($trials,$value) * pow($probability,$value) * pow(1 - $probability,$trials - $value) ;
  1889. }
  1890. }
  1891. }
  1892. return self::$_errorCodes['value'];
  1893. }
  1894. /**
  1895. * NEGBINOMDIST
  1896. *
  1897. * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
  1898. * there will be number_f failures before the number_s-th success, when the constant
  1899. * probability of a success is probability_s. This function is similar to the binomial
  1900. * distribution, except that the number of successes is fixed, and the number of trials is
  1901. * variable. Like the binomial, trials are assumed to be independent.
  1902. *
  1903. * @param float $failures Number of Failures
  1904. * @param float $successes Threshold number of Successes
  1905. * @param float $probability Probability of success on each trial
  1906. * @return float
  1907. *
  1908. */
  1909. public static function NEGBINOMDIST($failures, $successes, $probability) {
  1910. $failures = floor(self::flattenSingleValue($failures));
  1911. $successes = floor(self::flattenSingleValue($successes));
  1912. $probability = self::flattenSingleValue($probability);
  1913. if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) {
  1914. if (($failures < 0) || ($successes < 1)) {
  1915. return self::$_errorCodes['num'];
  1916. }
  1917. if (($probability < 0) || ($probability > 1)) {
  1918. return self::$_errorCodes['num'];
  1919. }
  1920. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  1921. if (($failures + $successes - 1) <= 0) {
  1922. return self::$_errorCodes['num'];
  1923. }
  1924. }
  1925. return (self::COMBIN($failures + $successes - 1,$successes - 1)) * (pow($probability,$successes)) * (pow(1 - $probability,$failures)) ;
  1926. }
  1927. return self::$_errorCodes['value'];
  1928. }
  1929. /**
  1930. * CRITBINOM
  1931. *
  1932. * Returns the smallest value for which the cumulative binomial distribution is greater
  1933. * than or equal to a criterion value
  1934. *
  1935. * See http://support.microsoft.com/kb/828117/ for details of the algorithm used
  1936. *
  1937. * @param float $trials number of Bernoulli trials
  1938. * @param float $probability probability of a success on each trial
  1939. * @param float $alpha criterion value
  1940. * @return int
  1941. *
  1942. * @todo Warning. This implementation differs from the algorithm detailed on the MS
  1943. * web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess
  1944. * This eliminates a potential endless loop error, but may have an adverse affect on the
  1945. * accuracy of the function (although all my tests have so far returned correct results).
  1946. *
  1947. */
  1948. public static function CRITBINOM($trials, $probability, $alpha) {
  1949. $trials = floor(self::flattenSingleValue($trials));
  1950. $probability = self::flattenSingleValue($probability);
  1951. $alpha = self::flattenSingleValue($alpha);
  1952. if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) {
  1953. if ($trials < 0) {
  1954. return self::$_errorCodes['num'];
  1955. }
  1956. if (($probability < 0) || ($probability > 1)) {
  1957. return self::$_errorCodes['num'];
  1958. }
  1959. if (($alpha < 0) || ($alpha > 1)) {
  1960. return self::$_errorCodes['num'];
  1961. }
  1962. if ($alpha <= 0.5) {
  1963. $t = sqrt(log(1 / pow($alpha,2)));
  1964. $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));
  1965. } else {
  1966. $t = sqrt(log(1 / pow(1 - $alpha,2)));
  1967. $trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t);
  1968. }
  1969. $Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability)));
  1970. if ($Guess < 0) {
  1971. $Guess = 0;
  1972. } elseif ($Guess > $trials) {
  1973. $Guess = $trials;
  1974. }
  1975. $TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0;
  1976. $EssentiallyZero = 10e-12;
  1977. $m = floor($trials * $probability);
  1978. ++$TotalUnscaledProbability;
  1979. if ($m == $Guess) { ++$UnscaledPGuess; }
  1980. if ($m <= $Guess) { ++$UnscaledCumPGuess; }
  1981. $PreviousValue = 1;
  1982. $Done = False;
  1983. $k = $m + 1;
  1984. while ((!$Done) && ($k <= $trials)) {
  1985. $CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability));
  1986. $TotalUnscaledProbability += $CurrentValue;
  1987. if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
  1988. if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
  1989. if ($CurrentValue <= $EssentiallyZero) { $Done = True; }
  1990. $PreviousValue = $CurrentValue;
  1991. ++$k;
  1992. }
  1993. $PreviousValue = 1;
  1994. $Done = False;
  1995. $k = $m - 1;
  1996. while ((!$Done) && ($k >= 0)) {
  1997. $CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability);
  1998. $TotalUnscaledProbability += $CurrentValue;
  1999. if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
  2000. if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
  2001. if (CurrentValue <= EssentiallyZero) { $Done = True; }
  2002. $PreviousValue = $CurrentValue;
  2003. --$k;
  2004. }
  2005. $PGuess = $UnscaledPGuess / $TotalUnscaledProbability;
  2006. $CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability;
  2007. // $CumPGuessMinus1 = $CumPGuess - $PGuess;
  2008. $CumPGuessMinus1 = $CumPGuess - 1;
  2009. while (True) {
  2010. if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) {
  2011. return $Guess;
  2012. } elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) {
  2013. $PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability);
  2014. $CumPGuessMinus1 = $CumPGuess;
  2015. $CumPGuess = $CumPGuess + $PGuessPlus1;
  2016. $PGuess = $PGuessPlus1;
  2017. ++$Guess;
  2018. } elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) {
  2019. $PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability;
  2020. $CumPGuess = $CumPGuessMinus1;
  2021. $CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess;
  2022. $PGuess = $PGuessMinus1;
  2023. --$Guess;
  2024. }
  2025. }
  2026. }
  2027. return self::$_errorCodes['value'];
  2028. }
  2029. /**
  2030. * CHIDIST
  2031. *
  2032. * Returns the one-tailed probability of the chi-squared distribution.
  2033. *
  2034. * @param float $value Value for the function
  2035. * @param float $degrees degrees of freedom
  2036. * @return float
  2037. */
  2038. public static function CHIDIST($value, $degrees) {
  2039. $value = self::flattenSingleValue($value);
  2040. $degrees = floor(self::flattenSingleValue($degrees));
  2041. if ((is_numeric($value)) && (is_numeric($degrees))) {
  2042. if ($degrees < 1) {
  2043. return self::$_errorCodes['num'];
  2044. }
  2045. if ($value < 0) {
  2046. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  2047. return 1;
  2048. }
  2049. return self::$_errorCodes['num'];
  2050. }
  2051. return 1 - (self::incompleteGamma($degrees/2,$value/2) / self::gamma($degrees/2));
  2052. }
  2053. return self::$_errorCodes['value'];
  2054. }
  2055. /**
  2056. * CHIINV
  2057. *
  2058. * Returns the one-tailed probability of the chi-squared distribution.
  2059. *
  2060. * @param float $probability Probability for the function
  2061. * @param float $degrees degrees of freedom
  2062. * @return float
  2063. */
  2064. public static function CHIINV($probability, $degrees) {
  2065. $probability = self::flattenSingleValue($probability);
  2066. $degrees = floor(self::flattenSingleValue($degrees));
  2067. if ((is_numeric($probability)) && (is_numeric($degrees))) {
  2068. $xLo = 100;
  2069. $xHi = 0;
  2070. $maxIteration = 100;
  2071. $x = $xNew = 1;
  2072. $dx = 1;
  2073. $i = 0;
  2074. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  2075. // Apply Newton-Raphson step
  2076. $result = self::CHIDIST($x, $degrees);
  2077. $error = $result - $probability;
  2078. if ($error == 0.0) {
  2079. $dx = 0;
  2080. } elseif ($error < 0.0) {
  2081. $xLo = $x;
  2082. } else {
  2083. $xHi = $x;
  2084. }
  2085. // Avoid division by zero
  2086. if ($result != 0.0) {
  2087. $dx = $error / $result;
  2088. $xNew = $x - $dx;
  2089. }
  2090. // If the NR fails to converge (which for example may be the
  2091. // case if the initial guess is too rough) we apply a bisection
  2092. // step to determine a more narrow interval around the root.
  2093. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  2094. $xNew = ($xLo + $xHi) / 2;
  2095. $dx = $xNew - $x;
  2096. }
  2097. $x = $xNew;
  2098. }
  2099. if ($i == MAX_ITERATIONS) {
  2100. return self::$_errorCodes['na'];
  2101. }
  2102. return round($x,12);
  2103. }
  2104. return self::$_errorCodes['value'];
  2105. }
  2106. /**
  2107. * EXPONDIST
  2108. *
  2109. * Returns the exponential distribution. Use EXPONDIST to model the time between events,
  2110. * such as how long an automated bank teller takes to deliver cash. For example, you can
  2111. * use EXPONDIST to determine the probability that the process takes at most 1 minute.
  2112. *
  2113. * @param float $value Value of the function
  2114. * @param float $lambda The parameter value
  2115. * @param boolean $cumulative
  2116. * @return float
  2117. */
  2118. public static function EXPONDIST($value, $lambda, $cumulative) {
  2119. $value = self::flattenSingleValue($value);
  2120. $lambda = self::flattenSingleValue($lambda);
  2121. $cumulative = self::flattenSingleValue($cumulative);
  2122. if ((is_numeric($value)) && (is_numeric($lambda))) {
  2123. if (($value < 0) || ($lambda < 0)) {
  2124. return self::$_errorCodes['num'];
  2125. }
  2126. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  2127. if ($cumulative) {
  2128. return 1 - exp(0-$value*$lambda);
  2129. } else {
  2130. return $lambda * exp(0-$value*$lambda);
  2131. }
  2132. }
  2133. }
  2134. return self::$_errorCodes['value'];
  2135. }
  2136. /**
  2137. * FISHER
  2138. *
  2139. * Returns the Fisher transformation at x. This transformation produces a function that
  2140. * is normally distributed rather than skewed. Use this function to perform hypothesis
  2141. * testing on the correlation coefficient.
  2142. *
  2143. * @param float $value
  2144. * @return float
  2145. */
  2146. public static function FISHER($value) {
  2147. $value = self::flattenSingleValue($value);
  2148. if (is_numeric($value)) {
  2149. if (($value <= -1) || ($lambda >= 1)) {
  2150. return self::$_errorCodes['num'];
  2151. }
  2152. return 0.5 * log((1+$value)/(1-$value));
  2153. }
  2154. return self::$_errorCodes['value'];
  2155. }
  2156. /**
  2157. * FISHERINV
  2158. *
  2159. * Returns the inverse of the Fisher transformation. Use this transformation when
  2160. * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
  2161. * FISHERINV(y) = x.
  2162. *
  2163. * @param float $value
  2164. * @return float
  2165. */
  2166. public static function FISHERINV($value) {
  2167. $value = self::flattenSingleValue($value);
  2168. if (is_numeric($value)) {
  2169. return (exp(2 * $value) - 1) / (exp(2 * $value) + 1);
  2170. }
  2171. return self::$_errorCodes['value'];
  2172. }
  2173. // Function cache for logBeta
  2174. private static $logBetaCache_p = 0.0;
  2175. private static $logBetaCache_q = 0.0;
  2176. private static $logBetaCache_result = 0.0;
  2177. /**
  2178. * The natural logarithm of the beta function.
  2179. * @param p require p>0
  2180. * @param q require q>0
  2181. * @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
  2182. * @author Jaco van Kooten
  2183. */
  2184. private static function logBeta($p, $q) {
  2185. if ($p != self::$logBetaCache_p || $q != self::$logBetaCache_q) {
  2186. self::$logBetaCache_p = $p;
  2187. self::$logBetaCache_q = $q;
  2188. if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
  2189. self::$logBetaCache_result = 0.0;
  2190. } else {
  2191. self::$logBetaCache_result = self::logGamma($p) + self::logGamma($q) - self::logGamma($p + $q);
  2192. }
  2193. }
  2194. return self::$logBetaCache_result;
  2195. }
  2196. /**
  2197. * Evaluates of continued fraction part of incomplete beta function.
  2198. * Based on an idea from Numerical Recipes (W.H. Press et al, 1992).
  2199. * @author Jaco van Kooten
  2200. */
  2201. private static function betaFraction($x, $p, $q) {
  2202. $c = 1.0;
  2203. $sum_pq = $p + $q;
  2204. $p_plus = $p + 1.0;
  2205. $p_minus = $p - 1.0;
  2206. $h = 1.0 - $sum_pq * $x / $p_plus;
  2207. if (abs($h) < XMININ) {
  2208. $h = XMININ;
  2209. }
  2210. $h = 1.0 / $h;
  2211. $frac = $h;
  2212. $m = 1;
  2213. $delta = 0.0;
  2214. while ($m <= MAX_ITERATIONS && abs($delta-1.0) > PRECISION ) {
  2215. $m2 = 2 * $m;
  2216. // even index for d
  2217. $d = $m * ($q - $m) * $x / ( ($p_minus + $m2) * ($p + $m2));
  2218. $h = 1.0 + $d * $h;
  2219. if (abs($h) < XMININ) {
  2220. $h = XMININ;
  2221. }
  2222. $h = 1.0 / $h;
  2223. $c = 1.0 + $d / $c;
  2224. if (abs($c) < XMININ) {
  2225. $c = XMININ;
  2226. }
  2227. $frac *= $h * $c;
  2228. // odd index for d
  2229. $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2));
  2230. $h = 1.0 + $d * $h;
  2231. if (abs($h) < XMININ) {
  2232. $h = XMININ;
  2233. }
  2234. $h = 1.0 / $h;
  2235. $c = 1.0 + $d / $c;
  2236. if (abs($c) < XMININ) {
  2237. $c = XMININ;
  2238. }
  2239. $delta = $h * $c;
  2240. $frac *= $delta;
  2241. ++$m;
  2242. }
  2243. return $frac;
  2244. }
  2245. /**
  2246. * logGamma function
  2247. *
  2248. * @version 1.1
  2249. * @author Jaco van Kooten
  2250. *
  2251. * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
  2252. *
  2253. * The natural logarithm of the gamma function. <br />
  2254. * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
  2255. * Applied Mathematics Division <br />
  2256. * Argonne National Laboratory <br />
  2257. * Argonne, IL 60439 <br />
  2258. * <p>
  2259. * References:
  2260. * <ol>
  2261. * <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
  2262. * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
  2263. * <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
  2264. * <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
  2265. * </ol>
  2266. * </p>
  2267. * <p>
  2268. * From the original documentation:
  2269. * </p>
  2270. * <p>
  2271. * This routine calculates the LOG(GAMMA) function for a positive real argument X.
  2272. * Computation is based on an algorithm outlined in references 1 and 2.
  2273. * The program uses rational functions that theoretically approximate LOG(GAMMA)
  2274. * to at least 18 significant decimal digits. The approximation for X > 12 is from
  2275. * reference 3, while approximations for X < 12.0 are similar to those in reference
  2276. * 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
  2277. * the compiler, the intrinsic functions, and proper selection of the
  2278. * machine-dependent constants.
  2279. * </p>
  2280. * <p>
  2281. * Error returns: <br />
  2282. * The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
  2283. * The computation is believed to be free of underflow and overflow.
  2284. * </p>
  2285. * @return MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
  2286. */
  2287. // Function cache for logGamma
  2288. private static $logGammaCache_result = 0.0;
  2289. private static $logGammaCache_x = 0.0;
  2290. private static function logGamma($x) {
  2291. // Log Gamma related constants
  2292. static $lg_d1 = -0.5772156649015328605195174;
  2293. static $lg_d2 = 0.4227843350984671393993777;
  2294. static $lg_d4 = 1.791759469228055000094023;
  2295. static $lg_p1 = array( 4.945235359296727046734888,
  2296. 201.8112620856775083915565,
  2297. 2290.838373831346393026739,
  2298. 11319.67205903380828685045,
  2299. 28557.24635671635335736389,
  2300. 38484.96228443793359990269,
  2301. 26377.48787624195437963534,
  2302. 7225.813979700288197698961 );
  2303. static $lg_p2 = array( 4.974607845568932035012064,
  2304. 542.4138599891070494101986,
  2305. 15506.93864978364947665077,
  2306. 184793.2904445632425417223,
  2307. 1088204.76946882876749847,
  2308. 3338152.967987029735917223,
  2309. 5106661.678927352456275255,
  2310. 3074109.054850539556250927 );
  2311. static $lg_p4 = array( 14745.02166059939948905062,
  2312. 2426813.369486704502836312,
  2313. 121475557.4045093227939592,
  2314. 2663432449.630976949898078,
  2315. 29403789566.34553899906876,
  2316. 170266573776.5398868392998,
  2317. 492612579337.743088758812,
  2318. 560625185622.3951465078242 );
  2319. static $lg_q1 = array( 67.48212550303777196073036,
  2320. 1113.332393857199323513008,
  2321. 7738.757056935398733233834,
  2322. 27639.87074403340708898585,
  2323. 54993.10206226157329794414,
  2324. 61611.22180066002127833352,
  2325. 36351.27591501940507276287,
  2326. 8785.536302431013170870835 );
  2327. static $lg_q2 = array( 183.0328399370592604055942,
  2328. 7765.049321445005871323047,
  2329. 133190.3827966074194402448,
  2330. 1136705.821321969608938755,
  2331. 5267964.117437946917577538,
  2332. 13467014.54311101692290052,
  2333. 17827365.30353274213975932,
  2334. 9533095.591844353613395747 );
  2335. static $lg_q4 = array( 2690.530175870899333379843,
  2336. 639388.5654300092398984238,
  2337. 41355999.30241388052042842,
  2338. 1120872109.61614794137657,
  2339. 14886137286.78813811542398,
  2340. 101680358627.2438228077304,
  2341. 341747634550.7377132798597,
  2342. 446315818741.9713286462081 );
  2343. static $lg_c = array( -0.001910444077728,
  2344. 8.4171387781295e-4,
  2345. -5.952379913043012e-4,
  2346. 7.93650793500350248e-4,
  2347. -0.002777777777777681622553,
  2348. 0.08333333333333333331554247,
  2349. 0.0057083835261 );
  2350. // Rough estimate of the fourth root of logGamma_xBig
  2351. static $lg_frtbig = 2.25e76;
  2352. static $pnt68 = 0.6796875;
  2353. if ($x == self::$logGammaCache_x) {
  2354. return self::$logGammaCache_result;
  2355. }
  2356. $y = $x;
  2357. if ($y > 0.0 && $y <= LOG_GAMMA_X_MAX_VALUE) {
  2358. if ($y <= EPS) {
  2359. $res = -log(y);
  2360. } elseif ($y <= 1.5) {
  2361. // ---------------------
  2362. // EPS .LT. X .LE. 1.5
  2363. // ---------------------
  2364. if ($y < $pnt68) {
  2365. $corr = -log($y);
  2366. $xm1 = $y;
  2367. } else {
  2368. $corr = 0.0;
  2369. $xm1 = $y - 1.0;
  2370. }
  2371. if ($y <= 0.5 || $y >= $pnt68) {
  2372. $xden = 1.0;
  2373. $xnum = 0.0;
  2374. for ($i = 0; $i < 8; ++$i) {
  2375. $xnum = $xnum * $xm1 + $lg_p1[$i];
  2376. $xden = $xden * $xm1 + $lg_q1[$i];
  2377. }
  2378. $res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden));
  2379. } else {
  2380. $xm2 = $y - 1.0;
  2381. $xden = 1.0;
  2382. $xnum = 0.0;
  2383. for ($i = 0; $i < 8; ++$i) {
  2384. $xnum = $xnum * $xm2 + $lg_p2[$i];
  2385. $xden = $xden * $xm2 + $lg_q2[$i];
  2386. }
  2387. $res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
  2388. }
  2389. } elseif ($y <= 4.0) {
  2390. // ---------------------
  2391. // 1.5 .LT. X .LE. 4.0
  2392. // ---------------------
  2393. $xm2 = $y - 2.0;
  2394. $xden = 1.0;
  2395. $xnum = 0.0;
  2396. for ($i = 0; $i < 8; ++$i) {
  2397. $xnum = $xnum * $xm2 + $lg_p2[$i];
  2398. $xden = $xden * $xm2 + $lg_q2[$i];
  2399. }
  2400. $res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
  2401. } elseif ($y <= 12.0) {
  2402. // ----------------------
  2403. // 4.0 .LT. X .LE. 12.0
  2404. // ----------------------
  2405. $xm4 = $y - 4.0;
  2406. $xden = -1.0;
  2407. $xnum = 0.0;
  2408. for ($i = 0; $i < 8; ++$i) {
  2409. $xnum = $xnum * $xm4 + $lg_p4[$i];
  2410. $xden = $xden * $xm4 + $lg_q4[$i];
  2411. }
  2412. $res = $lg_d4 + $xm4 * ($xnum / $xden);
  2413. } else {
  2414. // ---------------------------------
  2415. // Evaluate for argument .GE. 12.0
  2416. // ---------------------------------
  2417. $res = 0.0;
  2418. if ($y <= $lg_frtbig) {
  2419. $res = $lg_c[6];
  2420. $ysq = $y * $y;
  2421. for ($i = 0; $i < 6; ++$i)
  2422. $res = $res / $ysq + $lg_c[$i];
  2423. }
  2424. $res /= $y;
  2425. $corr = log($y);
  2426. $res = $res + log(SQRT2PI) - 0.5 * $corr;
  2427. $res += $y * ($corr - 1.0);
  2428. }
  2429. } else {
  2430. // --------------------------
  2431. // Return for bad arguments
  2432. // --------------------------
  2433. $res = MAX_VALUE;
  2434. }
  2435. // ------------------------------
  2436. // Final adjustments and return
  2437. // ------------------------------
  2438. self::$logGammaCache_x = $x;
  2439. self::$logGammaCache_result = $res;
  2440. return $res;
  2441. }
  2442. /**
  2443. * Beta function.
  2444. *
  2445. * @author Jaco van Kooten
  2446. *
  2447. * @param p require p>0
  2448. * @param q require q>0
  2449. * @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
  2450. */
  2451. private static function beta($p, $q) {
  2452. if ($p <= 0.0 || $q <= 0.0 || ($p + $q) > LOG_GAMMA_X_MAX_VALUE) {
  2453. return 0.0;
  2454. } else {
  2455. return exp(self::logBeta($p, $q));
  2456. }
  2457. }
  2458. /**
  2459. * Incomplete beta function
  2460. *
  2461. * @author Jaco van Kooten
  2462. * @author Paul Meagher
  2463. *
  2464. * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992).
  2465. * @param x require 0<=x<=1
  2466. * @param p require p>0
  2467. * @param q require q>0
  2468. * @return 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow
  2469. */
  2470. private static function incompleteBeta($x, $p, $q) {
  2471. if ($x <= 0.0) {
  2472. return 0.0;
  2473. } elseif ($x >= 1.0) {
  2474. return 1.0;
  2475. } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
  2476. return 0.0;
  2477. }
  2478. $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x));
  2479. if ($x < ($p + 1.0) / ($p + $q + 2.0)) {
  2480. return $beta_gam * self::betaFraction($x, $p, $q) / $p;
  2481. } else {
  2482. return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q);
  2483. }
  2484. }
  2485. /**
  2486. * BETADIST
  2487. *
  2488. * Returns the beta distribution.
  2489. *
  2490. * @param float $value Value at which you want to evaluate the distribution
  2491. * @param float $alpha Parameter to the distribution
  2492. * @param float $beta Parameter to the distribution
  2493. * @param boolean $cumulative
  2494. * @return float
  2495. *
  2496. */
  2497. public static function BETADIST($value,$alpha,$beta,$rMin=0,$rMax=1) {
  2498. $value = self::flattenSingleValue($value);
  2499. $alpha = self::flattenSingleValue($alpha);
  2500. $beta = self::flattenSingleValue($beta);
  2501. $rMin = self::flattenSingleValue($rMin);
  2502. $rMax = self::flattenSingleValue($rMax);
  2503. if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) {
  2504. if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) {
  2505. return self::$_errorCodes['num'];
  2506. }
  2507. if ($rMin > $rMax) {
  2508. $tmp = $rMin;
  2509. $rMin = $rMax;
  2510. $rMax = $tmp;
  2511. }
  2512. $value -= $rMin;
  2513. $value /= ($rMax - $rMin);
  2514. return self::incompleteBeta($value,$alpha,$beta);
  2515. }
  2516. return self::$_errorCodes['value'];
  2517. }
  2518. /**
  2519. * BETAINV
  2520. *
  2521. * Returns the inverse of the beta distribution.
  2522. *
  2523. * @param float $probability Probability at which you want to evaluate the distribution
  2524. * @param float $alpha Parameter to the distribution
  2525. * @param float $beta Parameter to the distribution
  2526. * @param boolean $cumulative
  2527. * @return float
  2528. *
  2529. */
  2530. public static function BETAINV($probability,$alpha,$beta,$rMin=0,$rMax=1) {
  2531. $probability = self::flattenSingleValue($probability);
  2532. $alpha = self::flattenSingleValue($alpha);
  2533. $beta = self::flattenSingleValue($beta);
  2534. $rMin = self::flattenSingleValue($rMin);
  2535. $rMax = self::flattenSingleValue($rMax);
  2536. if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) {
  2537. if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) {
  2538. return self::$_errorCodes['num'];
  2539. }
  2540. if ($rMin > $rMax) {
  2541. $tmp = $rMin;
  2542. $rMin = $rMax;
  2543. $rMax = $tmp;
  2544. }
  2545. $a = 0;
  2546. $b = 2;
  2547. $maxIteration = 100;
  2548. $i = 0;
  2549. while ((($b - $a) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  2550. $guess = ($a + $b) / 2;
  2551. $result = self::BETADIST($guess, $alpha, $beta);
  2552. if (($result == $probability) || ($result == 0)) {
  2553. $b = $a;
  2554. } elseif ($result > $probability) {
  2555. $b = $guess;
  2556. } else {
  2557. $a = $guess;
  2558. }
  2559. }
  2560. if ($i == MAX_ITERATIONS) {
  2561. return self::$_errorCodes['na'];
  2562. }
  2563. return round($rMin + $guess * ($rMax - $rMin),12);
  2564. }
  2565. return self::$_errorCodes['value'];
  2566. }
  2567. //
  2568. // Private implementation of the incomplete Gamma function
  2569. //
  2570. private static function incompleteGamma($a,$x) {
  2571. static $max = 32;
  2572. $summer = 0;
  2573. for ($n=0; $n<=$max; ++$n) {
  2574. $divisor = $a;
  2575. for ($i=1; $i<=$n; ++$i) {
  2576. $divisor *= ($a + $i);
  2577. }
  2578. $summer += (pow($x,$n) / $divisor);
  2579. }
  2580. return pow($x,$a) * exp(0-$x) * $summer;
  2581. }
  2582. //
  2583. // Private implementation of the Gamma function
  2584. //
  2585. private static function gamma($data) {
  2586. if ($data == 0.0) return 0;
  2587. static $p0 = 1.000000000190015;
  2588. static $p = array ( 1 => 76.18009172947146,
  2589. 2 => -86.50532032941677,
  2590. 3 => 24.01409824083091,
  2591. 4 => -1.231739572450155,
  2592. 5 => 1.208650973866179e-3,
  2593. 6 => -5.395239384953e-6
  2594. );
  2595. $y = $x = $data;
  2596. $tmp = $x + 5.5;
  2597. $tmp -= ($x + 0.5) * log($tmp);
  2598. $summer = $p0;
  2599. for ($j=1;$j<=6;++$j) {
  2600. $summer += ($p[$j] / ++$y);
  2601. }
  2602. return exp(0 - $tmp + log(2.5066282746310005 * $summer / $x));
  2603. }
  2604. /**
  2605. * GAMMADIST
  2606. *
  2607. * Returns the gamma distribution.
  2608. *
  2609. * @param float $value Value at which you want to evaluate the distribution
  2610. * @param float $a Parameter to the distribution
  2611. * @param float $b Parameter to the distribution
  2612. * @param boolean $cumulative
  2613. * @return float
  2614. *
  2615. */
  2616. public static function GAMMADIST($value,$a,$b,$cumulative) {
  2617. $value = self::flattenSingleValue($value);
  2618. $a = self::flattenSingleValue($a);
  2619. $b = self::flattenSingleValue($b);
  2620. if ((is_numeric($value)) && (is_numeric($a)) && (is_numeric($b))) {
  2621. if (($value < 0) || ($a <= 0) || ($b <= 0)) {
  2622. return self::$_errorCodes['num'];
  2623. }
  2624. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  2625. if ($cumulative) {
  2626. return self::incompleteGamma($a,$value / $b) / self::gamma($a);
  2627. } else {
  2628. return (1 / (pow($b,$a) * self::gamma($a))) * pow($value,$a-1) * exp(0-($value / $b));
  2629. }
  2630. }
  2631. }
  2632. return self::$_errorCodes['value'];
  2633. }
  2634. /**
  2635. * GAMMAINV
  2636. *
  2637. * Returns the inverse of the beta distribution.
  2638. *
  2639. * @param float $probability Probability at which you want to evaluate the distribution
  2640. * @param float $alpha Parameter to the distribution
  2641. * @param float $beta Parameter to the distribution
  2642. * @param boolean $cumulative
  2643. * @return float
  2644. *
  2645. */
  2646. public static function GAMMAINV($probability,$alpha,$beta) {
  2647. $probability = self::flattenSingleValue($probability);
  2648. $alpha = self::flattenSingleValue($alpha);
  2649. $beta = self::flattenSingleValue($beta);
  2650. $rMin = self::flattenSingleValue($rMin);
  2651. $rMax = self::flattenSingleValue($rMax);
  2652. if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta))) {
  2653. if (($alpha <= 0) || ($beta <= 0) || ($probability <= 0) || ($probability > 1)) {
  2654. return self::$_errorCodes['num'];
  2655. }
  2656. $xLo = 0;
  2657. $xHi = 100;
  2658. $maxIteration = 100;
  2659. $x = $xNew = 1;
  2660. $dx = 1;
  2661. $i = 0;
  2662. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  2663. // Apply Newton-Raphson step
  2664. $result = self::GAMMADIST($x, $alpha, $beta, True);
  2665. $error = $result - $probability;
  2666. if ($error == 0.0) {
  2667. $dx = 0;
  2668. } elseif ($error < 0.0) {
  2669. $xLo = $x;
  2670. } else {
  2671. $xHi = $x;
  2672. }
  2673. // Avoid division by zero
  2674. if ($result != 0.0) {
  2675. $dx = $error / $result;
  2676. $xNew = $x - $dx;
  2677. }
  2678. // If the NR fails to converge (which for example may be the
  2679. // case if the initial guess is too rough) we apply a bisection
  2680. // step to determine a more narrow interval around the root.
  2681. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  2682. $xNew = ($xLo + $xHi) / 2;
  2683. $dx = $xNew - $x;
  2684. }
  2685. $x = $xNew;
  2686. }
  2687. if ($i == MAX_ITERATIONS) {
  2688. return self::$_errorCodes['na'];
  2689. }
  2690. return round($x,12);
  2691. }
  2692. return self::$_errorCodes['value'];
  2693. }
  2694. /**
  2695. * GAMMALN
  2696. *
  2697. * Returns the natural logarithm of the gamma function.
  2698. *
  2699. * @param float $value
  2700. * @return float
  2701. */
  2702. public static function GAMMALN($value) {
  2703. $value = self::flattenSingleValue($value);
  2704. if (is_numeric($value)) {
  2705. if ($value <= 0) {
  2706. return self::$_errorCodes['num'];
  2707. }
  2708. return log(self::gamma($value));
  2709. }
  2710. return self::$_errorCodes['value'];
  2711. }
  2712. /**
  2713. * NORMDIST
  2714. *
  2715. * Returns the normal distribution for the specified mean and standard deviation. This
  2716. * function has a very wide range of applications in statistics, including hypothesis
  2717. * testing.
  2718. *
  2719. * @param float $value
  2720. * @param float $mean Mean Value
  2721. * @param float $stdDev Standard Deviation
  2722. * @param boolean $cumulative
  2723. * @return float
  2724. *
  2725. */
  2726. public static function NORMDIST($value, $mean, $stdDev, $cumulative) {
  2727. $value = self::flattenSingleValue($value);
  2728. $mean = self::flattenSingleValue($mean);
  2729. $stdDev = self::flattenSingleValue($stdDev);
  2730. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  2731. if ($stdDev < 0) {
  2732. return self::$_errorCodes['num'];
  2733. }
  2734. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  2735. if ($cumulative) {
  2736. return 0.5 * (1 + self::erfVal(($value - $mean) / ($stdDev * sqrt(2))));
  2737. } else {
  2738. return (1 / (SQRT2PI * $stdDev)) * exp(0 - (pow($value - $mean,2) / (2 * pow($stdDev,2))));
  2739. }
  2740. }
  2741. }
  2742. return self::$_errorCodes['value'];
  2743. }
  2744. /**
  2745. * NORMSDIST
  2746. *
  2747. * Returns the standard normal cumulative distribution function. The distribution has
  2748. * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
  2749. * table of standard normal curve areas.
  2750. *
  2751. * @param float $value
  2752. * @return float
  2753. */
  2754. public static function NORMSDIST($value) {
  2755. $value = self::flattenSingleValue($value);
  2756. return self::NORMDIST($value, 0, 1, True);
  2757. }
  2758. /**
  2759. * LOGNORMDIST
  2760. *
  2761. * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
  2762. * with parameters mean and standard_dev.
  2763. *
  2764. * @param float $value
  2765. * @return float
  2766. */
  2767. public static function LOGNORMDIST($value, $mean, $stdDev) {
  2768. $value = self::flattenSingleValue($value);
  2769. $mean = self::flattenSingleValue($mean);
  2770. $stdDev = self::flattenSingleValue($stdDev);
  2771. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  2772. if (($value <= 0) || ($stdDev <= 0)) {
  2773. return self::$_errorCodes['num'];
  2774. }
  2775. return self::NORMSDIST((log($value) - $mean) / $stdDev);
  2776. }
  2777. return self::$_errorCodes['value'];
  2778. }
  2779. /***************************************************************************
  2780. * inverse_ncdf.php
  2781. * -------------------
  2782. * begin : Friday, January 16, 2004
  2783. * copyright : (C) 2004 Michael Nickerson
  2784. * email : nickersonm@yahoo.com
  2785. *
  2786. ***************************************************************************/
  2787. private static function inverse_ncdf($p) {
  2788. // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
  2789. // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
  2790. // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
  2791. // I have not checked the accuracy of this implementation. Be aware that PHP
  2792. // will truncate the coeficcients to 14 digits.
  2793. // You have permission to use and distribute this function freely for
  2794. // whatever purpose you want, but please show common courtesy and give credit
  2795. // where credit is due.
  2796. // Input paramater is $p - probability - where 0 < p < 1.
  2797. // Coefficients in rational approximations
  2798. static $a = array( 1 => -3.969683028665376e+01,
  2799. 2 => 2.209460984245205e+02,
  2800. 3 => -2.759285104469687e+02,
  2801. 4 => 1.383577518672690e+02,
  2802. 5 => -3.066479806614716e+01,
  2803. 6 => 2.506628277459239e+00
  2804. );
  2805. static $b = array( 1 => -5.447609879822406e+01,
  2806. 2 => 1.615858368580409e+02,
  2807. 3 => -1.556989798598866e+02,
  2808. 4 => 6.680131188771972e+01,
  2809. 5 => -1.328068155288572e+01
  2810. );
  2811. static $c = array( 1 => -7.784894002430293e-03,
  2812. 2 => -3.223964580411365e-01,
  2813. 3 => -2.400758277161838e+00,
  2814. 4 => -2.549732539343734e+00,
  2815. 5 => 4.374664141464968e+00,
  2816. 6 => 2.938163982698783e+00
  2817. );
  2818. static $d = array( 1 => 7.784695709041462e-03,
  2819. 2 => 3.224671290700398e-01,
  2820. 3 => 2.445134137142996e+00,
  2821. 4 => 3.754408661907416e+00
  2822. );
  2823. // Define lower and upper region break-points.
  2824. $p_low = 0.02425; //Use lower region approx. below this
  2825. $p_high = 1 - $p_low; //Use upper region approx. above this
  2826. if (0 < $p && $p < $p_low) {
  2827. // Rational approximation for lower region.
  2828. $q = sqrt(-2 * log($p));
  2829. return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
  2830. (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
  2831. } elseif ($p_low <= $p && $p <= $p_high) {
  2832. // Rational approximation for central region.
  2833. $q = $p - 0.5;
  2834. $r = $q * $q;
  2835. return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
  2836. ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
  2837. } elseif ($p_high < $p && $p < 1) {
  2838. // Rational approximation for upper region.
  2839. $q = sqrt(-2 * log(1 - $p));
  2840. return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
  2841. (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
  2842. }
  2843. // If 0 < p < 1, return a null value
  2844. return self::$_errorCodes['null'];
  2845. }
  2846. private static function inverse_ncdf2($prob) {
  2847. // Approximation of inverse standard normal CDF developed by
  2848. // B. Moro, "The Full Monte," Risk 8(2), Feb 1995, 57-58.
  2849. $a1 = 2.50662823884;
  2850. $a2 = -18.61500062529;
  2851. $a3 = 41.39119773534;
  2852. $a4 = -25.44106049637;
  2853. $b1 = -8.4735109309;
  2854. $b2 = 23.08336743743;
  2855. $b3 = -21.06224101826;
  2856. $b4 = 3.13082909833;
  2857. $c1 = 0.337475482272615;
  2858. $c2 = 0.976169019091719;
  2859. $c3 = 0.160797971491821;
  2860. $c4 = 2.76438810333863E-02;
  2861. $c5 = 3.8405729373609E-03;
  2862. $c6 = 3.951896511919E-04;
  2863. $c7 = 3.21767881768E-05;
  2864. $c8 = 2.888167364E-07;
  2865. $c9 = 3.960315187E-07;
  2866. $y = $prob - 0.5;
  2867. if (abs($y) < 0.42) {
  2868. $z = pow($y,2);
  2869. $z = $y * ((($a4 * $z + $a3) * $z + $a2) * $z + $a1) / (((($b4 * $z + $b3) * $z + $b2) * $z + $b1) * $z + 1);
  2870. } else {
  2871. if ($y > 0) {
  2872. $z = log(-log(1 - $prob));
  2873. } else {
  2874. $z = log(-log($prob));
  2875. }
  2876. $z = $c1 + $z * ($c2 + $z * ($c3 + $z * ($c4 + $z * ($c5 + $z * ($c6 + $z * ($c7 + $z * ($c8 + $z * $c9)))))));
  2877. if ($y < 0) {
  2878. $z = -$z;
  2879. }
  2880. }
  2881. return $z;
  2882. }
  2883. private static function inverse_ncdf3($p) {
  2884. // ALGORITHM AS241 APPL. STATIST. (1988) VOL. 37, NO. 3.
  2885. // Produces the normal deviate Z corresponding to a given lower
  2886. // tail area of P; Z is accurate to about 1 part in 10**16.
  2887. //
  2888. // This is a PHP version of the original FORTRAN code that can
  2889. // be found at http://lib.stat.cmu.edu/apstat/
  2890. $split1 = 0.425;
  2891. $split2 = 5;
  2892. $const1 = 0.180625;
  2893. $const2 = 1.6;
  2894. // coefficients for p close to 0.5
  2895. $a0 = 3.3871328727963666080;
  2896. $a1 = 1.3314166789178437745E+2;
  2897. $a2 = 1.9715909503065514427E+3;
  2898. $a3 = 1.3731693765509461125E+4;
  2899. $a4 = 4.5921953931549871457E+4;
  2900. $a5 = 6.7265770927008700853E+4;
  2901. $a6 = 3.3430575583588128105E+4;
  2902. $a7 = 2.5090809287301226727E+3;
  2903. $b1 = 4.2313330701600911252E+1;
  2904. $b2 = 6.8718700749205790830E+2;
  2905. $b3 = 5.3941960214247511077E+3;
  2906. $b4 = 2.1213794301586595867E+4;
  2907. $b5 = 3.9307895800092710610E+4;
  2908. $b6 = 2.8729085735721942674E+4;
  2909. $b7 = 5.2264952788528545610E+3;
  2910. // coefficients for p not close to 0, 0.5 or 1.
  2911. $c0 = 1.42343711074968357734;
  2912. $c1 = 4.63033784615654529590;
  2913. $c2 = 5.76949722146069140550;
  2914. $c3 = 3.64784832476320460504;
  2915. $c4 = 1.27045825245236838258;
  2916. $c5 = 2.41780725177450611770E-1;
  2917. $c6 = 2.27238449892691845833E-2;
  2918. $c7 = 7.74545014278341407640E-4;
  2919. $d1 = 2.05319162663775882187;
  2920. $d2 = 1.67638483018380384940;
  2921. $d3 = 6.89767334985100004550E-1;
  2922. $d4 = 1.48103976427480074590E-1;
  2923. $d5 = 1.51986665636164571966E-2;
  2924. $d6 = 5.47593808499534494600E-4;
  2925. $d7 = 1.05075007164441684324E-9;
  2926. // coefficients for p near 0 or 1.
  2927. $e0 = 6.65790464350110377720;
  2928. $e1 = 5.46378491116411436990;
  2929. $e2 = 1.78482653991729133580;
  2930. $e3 = 2.96560571828504891230E-1;
  2931. $e4 = 2.65321895265761230930E-2;
  2932. $e5 = 1.24266094738807843860E-3;
  2933. $e6 = 2.71155556874348757815E-5;
  2934. $e7 = 2.01033439929228813265E-7;
  2935. $f1 = 5.99832206555887937690E-1;
  2936. $f2 = 1.36929880922735805310E-1;
  2937. $f3 = 1.48753612908506148525E-2;
  2938. $f4 = 7.86869131145613259100E-4;
  2939. $f5 = 1.84631831751005468180E-5;
  2940. $f6 = 1.42151175831644588870E-7;
  2941. $f7 = 2.04426310338993978564E-15;
  2942. $q = $p - 0.5;
  2943. // computation for p close to 0.5
  2944. if (abs($q) <= split1) {
  2945. $R = $const1 - $q * $q;
  2946. $z = $q * ((((((($a7 * $R + $a6) * $R + $a5) * $R + $a4) * $R + $a3) * $R + $a2) * $R + $a1) * $R + $a0) /
  2947. ((((((($b7 * $R + $b6) * $R + $b5) * $R + $b4) * $R + $b3) * $R + $b2) * $R + $b1) * $R + 1);
  2948. } else {
  2949. if ($q < 0) {
  2950. $R = $p;
  2951. } else {
  2952. $R = 1 - $p;
  2953. }
  2954. $R = pow(-log($R),2);
  2955. // computation for p not close to 0, 0.5 or 1.
  2956. If ($R <= $split2) {
  2957. $R = $R - $const2;
  2958. $z = ((((((($c7 * $R + $c6) * $R + $c5) * $R + $c4) * $R + $c3) * $R + $c2) * $R + $c1) * $R + $c0) /
  2959. ((((((($d7 * $R + $d6) * $R + $d5) * $R + $d4) * $R + $d3) * $R + $d2) * $R + $d1) * $R + 1);
  2960. } else {
  2961. // computation for p near 0 or 1.
  2962. $R = $R - $split2;
  2963. $z = ((((((($e7 * $R + $e6) * $R + $e5) * $R + $e4) * $R + $e3) * $R + $e2) * $R + $e1) * $R + $e0) /
  2964. ((((((($f7 * $R + $f6) * $R + $f5) * $R + $f4) * $R + $f3) * $R + $f2) * $R + $f1) * $R + 1);
  2965. }
  2966. if ($q < 0) {
  2967. $z = -$z;
  2968. }
  2969. }
  2970. return $z;
  2971. }
  2972. /**
  2973. * NORMINV
  2974. *
  2975. * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
  2976. *
  2977. * @param float $value
  2978. * @param float $mean Mean Value
  2979. * @param float $stdDev Standard Deviation
  2980. * @return float
  2981. *
  2982. */
  2983. public static function NORMINV($probability,$mean,$stdDev) {
  2984. $probability = self::flattenSingleValue($probability);
  2985. $mean = self::flattenSingleValue($mean);
  2986. $stdDev = self::flattenSingleValue($stdDev);
  2987. if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  2988. if (($probability < 0) || ($probability > 1)) {
  2989. return self::$_errorCodes['num'];
  2990. }
  2991. if ($stdDev < 0) {
  2992. return self::$_errorCodes['num'];
  2993. }
  2994. return (self::inverse_ncdf($probability) * $stdDev) + $mean;
  2995. }
  2996. return self::$_errorCodes['value'];
  2997. }
  2998. /**
  2999. * NORMSINV
  3000. *
  3001. * Returns the inverse of the standard normal cumulative distribution
  3002. *
  3003. * @param float $value
  3004. * @return float
  3005. */
  3006. public static function NORMSINV($value) {
  3007. return self::NORMINV($value, 0, 1);
  3008. }
  3009. /**
  3010. * LOGINV
  3011. *
  3012. * Returns the inverse of the normal cumulative distribution
  3013. *
  3014. * @param float $value
  3015. * @return float
  3016. *
  3017. * @todo Try implementing P J Acklam's refinement algorithm for greater
  3018. * accuracy if I can get my head round the mathematics
  3019. * (as described at) http://home.online.no/~pjacklam/notes/invnorm/
  3020. */
  3021. public static function LOGINV($probability, $mean, $stdDev) {
  3022. $probability = self::flattenSingleValue($probability);
  3023. $mean = self::flattenSingleValue($mean);
  3024. $stdDev = self::flattenSingleValue($stdDev);
  3025. if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3026. if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) {
  3027. return self::$_errorCodes['num'];
  3028. }
  3029. return exp($mean + $stdDev * self::NORMSINV($probability));
  3030. }
  3031. return self::$_errorCodes['value'];
  3032. }
  3033. /**
  3034. * HYPGEOMDIST
  3035. *
  3036. * Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of
  3037. * sample successes, given the sample size, population successes, and population size.
  3038. *
  3039. * @param float $sampleSuccesses Number of successes in the sample
  3040. * @param float $sampleNumber Size of the sample
  3041. * @param float $populationSuccesses Number of successes in the population
  3042. * @param float $populationNumber Population size
  3043. * @return float
  3044. *
  3045. */
  3046. public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
  3047. $sampleSuccesses = floor(self::flattenSingleValue($sampleSuccesses));
  3048. $sampleNumber = floor(self::flattenSingleValue($sampleNumber));
  3049. $populationSuccesses = floor(self::flattenSingleValue($populationSuccesses));
  3050. $populationNumber = floor(self::flattenSingleValue($populationNumber));
  3051. if ((is_numeric($sampleSuccesses)) && (is_numeric($sampleNumber)) && (is_numeric($populationSuccesses)) && (is_numeric($populationNumber))) {
  3052. if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
  3053. return self::$_errorCodes['num'];
  3054. }
  3055. if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
  3056. return self::$_errorCodes['num'];
  3057. }
  3058. if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
  3059. return self::$_errorCodes['num'];
  3060. }
  3061. return self::COMBIN($populationSuccesses,$sampleSuccesses) *
  3062. self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
  3063. self::COMBIN($populationNumber,$sampleNumber);
  3064. }
  3065. return self::$_errorCodes['value'];
  3066. }
  3067. public static function hypGeom($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
  3068. return self::COMBIN($populationSuccesses,$sampleSuccesses) *
  3069. self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
  3070. self::COMBIN($populationNumber,$sampleNumber);
  3071. }
  3072. /**
  3073. * TDIST
  3074. *
  3075. * Returns the probability of Student's T distribution.
  3076. *
  3077. * @param float $value Value for the function
  3078. * @param float $degrees degrees of freedom
  3079. * @param float $tails number of tails (1 or 2)
  3080. * @return float
  3081. */
  3082. public static function TDIST($value, $degrees, $tails) {
  3083. $value = self::flattenSingleValue($value);
  3084. $degrees = floor(self::flattenSingleValue($degrees));
  3085. $tails = floor(self::flattenSingleValue($tails));
  3086. if ((is_numeric($value)) && (is_numeric($degrees)) && (is_numeric($tails))) {
  3087. if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
  3088. return self::$_errorCodes['num'];
  3089. }
  3090. // tdist, which finds the probability that corresponds to a given value
  3091. // of t with k degrees of freedom. This algorithm is translated from a
  3092. // pascal function on p81 of "Statistical Computing in Pascal" by D
  3093. // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
  3094. // London). The above Pascal algorithm is itself a translation of the
  3095. // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
  3096. // Laboratory as reported in (among other places) "Applied Statistics
  3097. // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
  3098. // Horwood Ltd.; W. Sussex, England).
  3099. // $ta = 2 / pi();
  3100. $ta = 0.636619772367581;
  3101. $tterm = $degrees;
  3102. $ttheta = atan2($value,sqrt($tterm));
  3103. $tc = cos($ttheta);
  3104. $ts = sin($ttheta);
  3105. $tsum = 0;
  3106. if (($degrees % 2) == 1) {
  3107. $ti = 3;
  3108. $tterm = $tc;
  3109. } else {
  3110. $ti = 2;
  3111. $tterm = 1;
  3112. }
  3113. $tsum = $tterm;
  3114. while ($ti < $degrees) {
  3115. $tterm *= $tc * $tc * ($ti - 1) / $ti;
  3116. $tsum += $tterm;
  3117. $ti += 2;
  3118. }
  3119. $tsum *= $ts;
  3120. if (($degrees % 2) == 1) { $tsum = $ta * ($tsum + $ttheta); }
  3121. $tValue = 0.5 * (1 + $tsum);
  3122. if ($tails == 1) {
  3123. return 1 - abs($tValue);
  3124. } else {
  3125. return 1 - abs((1 - $tValue) - $tValue);
  3126. }
  3127. }
  3128. return self::$_errorCodes['value'];
  3129. }
  3130. /**
  3131. * TINV
  3132. *
  3133. * Returns the one-tailed probability of the chi-squared distribution.
  3134. *
  3135. * @param float $probability Probability for the function
  3136. * @param float $degrees degrees of freedom
  3137. * @return float
  3138. */
  3139. public static function TINV($probability, $degrees) {
  3140. $probability = self::flattenSingleValue($probability);
  3141. $degrees = floor(self::flattenSingleValue($degrees));
  3142. if ((is_numeric($probability)) && (is_numeric($degrees))) {
  3143. $xLo = 100;
  3144. $xHi = 0;
  3145. $maxIteration = 100;
  3146. $x = $xNew = 1;
  3147. $dx = 1;
  3148. $i = 0;
  3149. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  3150. // Apply Newton-Raphson step
  3151. $result = self::TDIST($x, $degrees, 2);
  3152. $error = $result - $probability;
  3153. if ($error == 0.0) {
  3154. $dx = 0;
  3155. } elseif ($error < 0.0) {
  3156. $xLo = $x;
  3157. } else {
  3158. $xHi = $x;
  3159. }
  3160. // Avoid division by zero
  3161. if ($result != 0.0) {
  3162. $dx = $error / $result;
  3163. $xNew = $x - $dx;
  3164. }
  3165. // If the NR fails to converge (which for example may be the
  3166. // case if the initial guess is too rough) we apply a bisection
  3167. // step to determine a more narrow interval around the root.
  3168. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  3169. $xNew = ($xLo + $xHi) / 2;
  3170. $dx = $xNew - $x;
  3171. }
  3172. $x = $xNew;
  3173. }
  3174. if ($i == MAX_ITERATIONS) {
  3175. return self::$_errorCodes['na'];
  3176. }
  3177. return round($x,12);
  3178. }
  3179. return self::$_errorCodes['value'];
  3180. }
  3181. /**
  3182. * CONFIDENCE
  3183. *
  3184. * Returns the confidence interval for a population mean
  3185. *
  3186. * @param float $alpha
  3187. * @param float $stdDev Standard Deviation
  3188. * @param float $size
  3189. * @return float
  3190. *
  3191. */
  3192. public static function CONFIDENCE($alpha,$stdDev,$size) {
  3193. $alpha = self::flattenSingleValue($alpha);
  3194. $stdDev = self::flattenSingleValue($stdDev);
  3195. $size = floor(self::flattenSingleValue($size));
  3196. if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) {
  3197. if (($alpha <= 0) || ($alpha >= 1)) {
  3198. return self::$_errorCodes['num'];
  3199. }
  3200. if (($stdDev <= 0) || ($size < 1)) {
  3201. return self::$_errorCodes['num'];
  3202. }
  3203. return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
  3204. }
  3205. return self::$_errorCodes['value'];
  3206. }
  3207. /**
  3208. * POISSON
  3209. *
  3210. * Returns the Poisson distribution. A common application of the Poisson distribution
  3211. * is predicting the number of events over a specific time, such as the number of
  3212. * cars arriving at a toll plaza in 1 minute.
  3213. *
  3214. * @param float $value
  3215. * @param float $mean Mean Value
  3216. * @param boolean $cumulative
  3217. * @return float
  3218. *
  3219. */
  3220. public static function POISSON($value, $mean, $cumulative) {
  3221. $value = self::flattenSingleValue($value);
  3222. $mean = self::flattenSingleValue($mean);
  3223. if ((is_numeric($value)) && (is_numeric($mean))) {
  3224. if (($value <= 0) || ($mean <= 0)) {
  3225. return self::$_errorCodes['num'];
  3226. }
  3227. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3228. if ($cumulative) {
  3229. $summer = 0;
  3230. for ($i = 0; $i <= floor($value); ++$i) {
  3231. $summer += pow($mean,$i) / self::FACT($i);
  3232. }
  3233. return exp(0-$mean) * $summer;
  3234. } else {
  3235. return (exp(0-$mean) * pow($mean,$value)) / self::FACT($value);
  3236. }
  3237. }
  3238. }
  3239. return self::$_errorCodes['value'];
  3240. }
  3241. /**
  3242. * WEIBULL
  3243. *
  3244. * Returns the Weibull distribution. Use this distribution in reliability
  3245. * analysis, such as calculating a device's mean time to failure.
  3246. *
  3247. * @param float $value
  3248. * @param float $alpha Alpha Parameter
  3249. * @param float $beta Beta Parameter
  3250. * @param boolean $cumulative
  3251. * @return float
  3252. *
  3253. */
  3254. public static function WEIBULL($value, $alpha, $beta, $cumulative) {
  3255. $value = self::flattenSingleValue($value);
  3256. $alpha = self::flattenSingleValue($alpha);
  3257. $beta = self::flattenSingleValue($beta);
  3258. if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta))) {
  3259. if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) {
  3260. return self::$_errorCodes['num'];
  3261. }
  3262. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3263. if ($cumulative) {
  3264. return 1 - exp(0 - pow($value / $beta,$alpha));
  3265. } else {
  3266. return ($alpha / pow($beta,$alpha)) * pow($value,$alpha - 1) * exp(0 - pow($value / $beta,$alpha));
  3267. }
  3268. }
  3269. }
  3270. return self::$_errorCodes['value'];
  3271. }
  3272. /**
  3273. * SKEW
  3274. *
  3275. * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry
  3276. * of a distribution around its mean. Positive skewness indicates a distribution with an
  3277. * asymmetric tail extending toward more positive values. Negative skewness indicates a
  3278. * distribution with an asymmetric tail extending toward more negative values.
  3279. *
  3280. * @param array Data Series
  3281. * @return float
  3282. */
  3283. public static function SKEW() {
  3284. $aArgs = self::flattenArray(func_get_args());
  3285. $mean = self::AVERAGE($aArgs);
  3286. $stdDev = self::STDEV($aArgs);
  3287. $count = $summer = 0;
  3288. // Loop through arguments
  3289. foreach ($aArgs as $arg) {
  3290. // Is it a numeric value?
  3291. if ((is_numeric($arg)) && (!is_string($arg))) {
  3292. $summer += pow((($arg - $mean) / $stdDev),3) ;
  3293. ++$count;
  3294. }
  3295. }
  3296. // Return
  3297. if ($count > 2) {
  3298. return $summer * ($count / (($count-1) * ($count-2)));
  3299. }
  3300. return self::$_errorCodes['divisionbyzero'];
  3301. }
  3302. /**
  3303. * KURT
  3304. *
  3305. * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness
  3306. * or flatness of a distribution compared with the normal distribution. Positive
  3307. * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a
  3308. * relatively flat distribution.
  3309. *
  3310. * @param array Data Series
  3311. * @return float
  3312. */
  3313. public static function KURT() {
  3314. $aArgs = self::flattenArray(func_get_args());
  3315. $mean = self::AVERAGE($aArgs);
  3316. $stdDev = self::STDEV($aArgs);
  3317. if ($stdDev > 0) {
  3318. $count = $summer = 0;
  3319. // Loop through arguments
  3320. foreach ($aArgs as $arg) {
  3321. // Is it a numeric value?
  3322. if ((is_numeric($arg)) && (!is_string($arg))) {
  3323. $summer += pow((($arg - $mean) / $stdDev),4) ;
  3324. ++$count;
  3325. }
  3326. }
  3327. // Return
  3328. if ($count > 3) {
  3329. return $summer * ($count * ($count+1) / (($count-1) * ($count-2) * ($count-3))) - (3 * pow($count-1,2) / (($count-2) * ($count-3)));
  3330. }
  3331. }
  3332. return self::$_errorCodes['divisionbyzero'];
  3333. }
  3334. /**
  3335. * RAND
  3336. *
  3337. * @param int $min Minimal value
  3338. * @param int $max Maximal value
  3339. * @return int Random number
  3340. */
  3341. public static function RAND($min = 0, $max = 0) {
  3342. $min = self::flattenSingleValue($min);
  3343. $max = self::flattenSingleValue($max);
  3344. if ($min == 0 && $max == 0) {
  3345. return (rand(0,10000000)) / 10000000;
  3346. } else {
  3347. return rand($min, $max);
  3348. }
  3349. }
  3350. /**
  3351. * MOD
  3352. *
  3353. * @param int $a Dividend
  3354. * @param int $b Divisor
  3355. * @return int Remainder
  3356. */
  3357. public static function MOD($a = 1, $b = 1) {
  3358. $a = self::flattenSingleValue($a);
  3359. $b = self::flattenSingleValue($b);
  3360. return $a % $b;
  3361. }
  3362. /**
  3363. * ASCIICODE
  3364. *
  3365. * @param string $character Value
  3366. * @return int
  3367. */
  3368. public static function ASCIICODE($characters) {
  3369. $characters = self::flattenSingleValue($characters);
  3370. if (is_bool($characters)) {
  3371. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  3372. $characters = (int) $characters;
  3373. } else {
  3374. if ($characters) {
  3375. $characters = 'True';
  3376. } else {
  3377. $characters = 'False';
  3378. }
  3379. }
  3380. }
  3381. if (strlen($characters) > 0) {
  3382. return ord(substr($characters, 0, 1));
  3383. }
  3384. return self::$_errorCodes['value'];
  3385. }
  3386. /**
  3387. * CONCATENATE
  3388. *
  3389. * @return string
  3390. */
  3391. public static function CONCATENATE() {
  3392. // Return value
  3393. $returnValue = '';
  3394. // Loop trough arguments
  3395. $aArgs = self::flattenArray(func_get_args());
  3396. foreach ($aArgs as $arg) {
  3397. if (is_bool($arg)) {
  3398. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  3399. $arg = (int) $arg;
  3400. } else {
  3401. if ($arg) {
  3402. $arg = 'TRUE';
  3403. } else {
  3404. $arg = 'FALSE';
  3405. }
  3406. }
  3407. }
  3408. $returnValue .= $arg;
  3409. }
  3410. // Return
  3411. return $returnValue;
  3412. }
  3413. /**
  3414. * SEARCHSENSITIVE
  3415. *
  3416. * @param string $needle The string to look for
  3417. * @param string $haystack The string in which to look
  3418. * @param int $offset Offset within $haystack
  3419. * @return string
  3420. */
  3421. public static function SEARCHSENSITIVE($needle,$haystack,$offset=1) {
  3422. $needle = (string) self::flattenSingleValue($needle);
  3423. $haystack = (string) self::flattenSingleValue($haystack);
  3424. $offset = self::flattenSingleValue($offset);
  3425. if (($offset > 0) && (strlen($haystack) > $offset)) {
  3426. $pos = strpos($haystack, $needle, --$offset);
  3427. if ($pos !== false) {
  3428. return ++$pos;
  3429. }
  3430. }
  3431. return self::$_errorCodes['value'];
  3432. }
  3433. /**
  3434. * SEARCHINSENSITIVE
  3435. *
  3436. * @param string $needle The string to look for
  3437. * @param string $haystack The string in which to look
  3438. * @param int $offset Offset within $haystack
  3439. * @return string
  3440. */
  3441. public static function SEARCHINSENSITIVE($needle,$haystack,$offset=1) {
  3442. $needle = (string) self::flattenSingleValue($needle);
  3443. $haystack = (string) self::flattenSingleValue($haystack);
  3444. $offset = self::flattenSingleValue($offset);
  3445. if (($offset > 0) && (strlen($haystack) > $offset)) {
  3446. $pos = stripos($haystack, $needle, --$offset);
  3447. if ($pos !== false) {
  3448. return ++$pos;
  3449. }
  3450. }
  3451. return self::$_errorCodes['value'];
  3452. }
  3453. /**
  3454. * LEFT
  3455. *
  3456. * @param string $value Value
  3457. * @param int $chars Number of characters
  3458. * @return string
  3459. */
  3460. public static function LEFT($value = '', $chars = null) {
  3461. $value = self::flattenSingleValue($value);
  3462. $chars = self::flattenSingleValue($chars);
  3463. return substr($value, 0, $chars);
  3464. }
  3465. /**
  3466. * RIGHT
  3467. *
  3468. * @param string $value Value
  3469. * @param int $chars Number of characters
  3470. * @return string
  3471. */
  3472. public static function RIGHT($value = '', $chars = null) {
  3473. $value = self::flattenSingleValue($value);
  3474. $chars = self::flattenSingleValue($chars);
  3475. return substr($value, strlen($value) - $chars);
  3476. }
  3477. /**
  3478. * MID
  3479. *
  3480. * @param string $value Value
  3481. * @param int $start Start character
  3482. * @param int $chars Number of characters
  3483. * @return string
  3484. */
  3485. public static function MID($value = '', $start = 1, $chars = null) {
  3486. $value = self::flattenSingleValue($value);
  3487. $start = self::flattenSingleValue($start);
  3488. $chars = self::flattenSingleValue($chars);
  3489. return substr($value, --$start, $chars);
  3490. }
  3491. /**
  3492. * RETURNSTRING
  3493. *
  3494. * @param mixed $value Value to check
  3495. * @return boolean
  3496. */
  3497. public static function RETURNSTRING($testValue = '') {
  3498. $testValue = self::flattenSingleValue($testValue);
  3499. if (is_string($testValue)) {
  3500. return $testValue;
  3501. }
  3502. return Null;
  3503. }
  3504. /**
  3505. * TRIMSPACES
  3506. *
  3507. * @param mixed $value Value to check
  3508. * @return string
  3509. */
  3510. public static function TRIMSPACES($stringValue = '') {
  3511. $stringValue = self::flattenSingleValue($stringValue);
  3512. if (is_string($stringValue)) {
  3513. return str_replace(' ',' ',trim($stringValue));
  3514. }
  3515. return Null;
  3516. }
  3517. private static $_invalidChars = Null;
  3518. /**
  3519. * TRIMNONPRINTABLE
  3520. *
  3521. * @param mixed $value Value to check
  3522. * @return string
  3523. */
  3524. public static function TRIMNONPRINTABLE($stringValue = '') {
  3525. $stringValue = self::flattenSingleValue($stringValue);
  3526. if (self::$_invalidChars == Null) {
  3527. self::$_invalidChars = range(chr(0),chr(31));
  3528. }
  3529. if (is_string($stringValue)) {
  3530. return str_replace(self::$_invalidChars,'',trim($stringValue,"\x00..\x1F"));
  3531. }
  3532. return Null;
  3533. }
  3534. /**
  3535. * ERROR_TYPE
  3536. *
  3537. * @param mixed $value Value to check
  3538. * @return boolean
  3539. */
  3540. public static function ERROR_TYPE($value = '') {
  3541. $value = self::flattenSingleValue($value);
  3542. $i == i;
  3543. foreach(self::$_errorCodes as $errorCode) {
  3544. if ($value == $errorCode) {
  3545. return $i;
  3546. }
  3547. ++$i;
  3548. }
  3549. return self::$_errorCodes['na'];
  3550. }
  3551. /**
  3552. * IS_BLANK
  3553. *
  3554. * @param mixed $value Value to check
  3555. * @return boolean
  3556. */
  3557. public static function IS_BLANK($value = '') {
  3558. $value = self::flattenSingleValue($value);
  3559. return (is_null($value) || (is_string($value) && ($value == '')));
  3560. }
  3561. /**
  3562. * IS_ERR
  3563. *
  3564. * @param mixed $value Value to check
  3565. * @return boolean
  3566. */
  3567. public static function IS_ERR($value = '') {
  3568. $value = self::flattenSingleValue($value);
  3569. return self::IS_ERROR($value) && (!self::IS_NA($value));
  3570. }
  3571. /**
  3572. * IS_ERROR
  3573. *
  3574. * @param mixed $value Value to check
  3575. * @return boolean
  3576. */
  3577. public static function IS_ERROR($value = '') {
  3578. $value = self::flattenSingleValue($value);
  3579. return in_array($value, array_values(self::$_errorCodes));
  3580. }
  3581. /**
  3582. * IS_NA
  3583. *
  3584. * @param mixed $value Value to check
  3585. * @return boolean
  3586. */
  3587. public static function IS_NA($value = '') {
  3588. $value = self::flattenSingleValue($value);
  3589. return ($value == self::$_errorCodes['na']);
  3590. }
  3591. /**
  3592. * IS_EVEN
  3593. *
  3594. * @param mixed $value Value to check
  3595. * @return boolean
  3596. */
  3597. public static function IS_EVEN($value = 0) {
  3598. $value = self::flattenSingleValue($value);
  3599. while (intval($value) != $value) {
  3600. $value *= 10;
  3601. }
  3602. return ($value % 2 == 0);
  3603. }
  3604. /**
  3605. * IS_NUMBER
  3606. *
  3607. * @param mixed $value Value to check
  3608. * @return boolean
  3609. */
  3610. public static function IS_NUMBER($value = 0) {
  3611. $value = self::flattenSingleValue($value);
  3612. return is_numeric($value);
  3613. }
  3614. /**
  3615. * IS_LOGICAL
  3616. *
  3617. * @param mixed $value Value to check
  3618. * @return boolean
  3619. */
  3620. public static function IS_LOGICAL($value = true) {
  3621. $value = self::flattenSingleValue($value);
  3622. return is_bool($value);
  3623. }
  3624. /**
  3625. * IS_TEXT
  3626. *
  3627. * @param mixed $value Value to check
  3628. * @return boolean
  3629. */
  3630. public static function IS_TEXT($value = '') {
  3631. $value = self::flattenSingleValue($value);
  3632. return is_string($value);
  3633. }
  3634. /**
  3635. * STATEMENT_IF
  3636. *
  3637. * @param mixed $value Value to check
  3638. * @param mixed $truepart Value when true
  3639. * @param mixed $falsepart Value when false
  3640. * @return mixed
  3641. */
  3642. public static function STATEMENT_IF($value = true, $truepart = '', $falsepart = '') {
  3643. $value = self::flattenSingleValue($value);
  3644. $truepart = self::flattenSingleValue($truepart);
  3645. $falsepart = self::flattenSingleValue($falsepart);
  3646. return ($value ? $truepart : $falsepart);
  3647. }
  3648. /**
  3649. * STATEMENT_IFERROR
  3650. *
  3651. * @param mixed $value Value to check , is also value when no error
  3652. * @param mixed $errorpart Value when error
  3653. * @return mixed
  3654. */
  3655. public static function STATEMENT_IFERROR($value = '', $errorpart = '') {
  3656. return self::STATEMENT_IF(self::IS_ERROR($value), $errorpart, $value);
  3657. }
  3658. /**
  3659. * VERSION
  3660. *
  3661. * @return string Version information
  3662. */
  3663. public static function VERSION() {
  3664. return 'PHPExcel ##VERSION##, ##DATE##';
  3665. }
  3666. /**
  3667. * DATE
  3668. *
  3669. * @param long $year
  3670. * @param long $month
  3671. * @param long $day
  3672. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  3673. * depending on the value of the ReturnDateType flag
  3674. */
  3675. public static function DATE($year = 0, $month = 1, $day = 1) {
  3676. $year = (integer) self::flattenSingleValue($year);
  3677. $month = (integer) self::flattenSingleValue($month);
  3678. $day = (integer) self::flattenSingleValue($day);
  3679. $baseYear = PHPExcel_Shared_Date::getExcelCalendar();
  3680. // Validate parameters
  3681. if ($year < ($baseYear-1900)) {
  3682. return self::$_errorCodes['num'];
  3683. }
  3684. if ((($baseYear-1900) != 0) && ($year < $baseYear) && ($year >= 1900)) {
  3685. return self::$_errorCodes['num'];
  3686. }
  3687. if (($year < $baseYear) && ($year > ($baseYear-1900))) {
  3688. $year += 1900;
  3689. }
  3690. if ($month < 1) {
  3691. // Handle year/month adjustment if month < 1
  3692. --$month;
  3693. $year += ceil($month / 12) - 1;
  3694. $month = 13 - abs($month % 12);
  3695. } elseif ($month > 12) {
  3696. // Handle year/month adjustment if month > 12
  3697. $year += floor($month / 12);
  3698. $month = ($month % 12);
  3699. }
  3700. // Re-validate the year parameter after adjustments
  3701. if (($year < $baseYear) || ($year >= 10000)) {
  3702. return self::$_errorCodes['num'];
  3703. }
  3704. // Execute function
  3705. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($year, $month, $day);
  3706. switch (self::getReturnDateType()) {
  3707. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  3708. break;
  3709. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateValue);
  3710. break;
  3711. case self::RETURNDATE_PHP_OBJECT : return PHPExcel_Shared_Date::ExcelToPHPObject($excelDateValue);
  3712. break;
  3713. }
  3714. }
  3715. /**
  3716. * TIME
  3717. *
  3718. * @param long $hour
  3719. * @param long $minute
  3720. * @param long $second
  3721. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  3722. * depending on the value of the ReturnDateType flag
  3723. */
  3724. public static function TIME($hour = 0, $minute = 0, $second = 0) {
  3725. $hour = self::flattenSingleValue($hour);
  3726. $minute = self::flattenSingleValue($minute);
  3727. $second = self::flattenSingleValue($second);
  3728. if ($hour == '') { $hour = 0; }
  3729. if ($minute == '') { $minute = 0; }
  3730. if ($second == '') { $second = 0; }
  3731. if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) {
  3732. return self::$_errorCodes['value'];
  3733. }
  3734. $hour = (integer) $hour;
  3735. $minute = (integer) $minute;
  3736. $second = (integer) $second;
  3737. if ($second < 0) {
  3738. $minute += floor($second / 60);
  3739. $second = 60 - abs($second % 60);
  3740. if ($second == 60) { $second = 0; }
  3741. } elseif ($second >= 60) {
  3742. $minute += floor($second / 60);
  3743. $second = $second % 60;
  3744. }
  3745. if ($minute < 0) {
  3746. $hour += floor($minute / 60);
  3747. $minute = 60 - abs($minute % 60);
  3748. if ($minute == 60) { $minute = 0; }
  3749. } elseif ($minute >= 60) {
  3750. $hour += floor($minute / 60);
  3751. $minute = $minute % 60;
  3752. }
  3753. if ($hour > 23) {
  3754. $hour = $hour % 24;
  3755. } elseif ($hour < 0) {
  3756. return self::$_errorCodes['num'];
  3757. }
  3758. // Execute function
  3759. switch (self::getReturnDateType()) {
  3760. case self::RETURNDATE_EXCEL : $date = 0;
  3761. $calendar = PHPExcel_Shared_Date::getExcelCalendar();
  3762. if ($calendar != PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900) {
  3763. $date = 1;
  3764. }
  3765. return (float) PHPExcel_Shared_Date::FormattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
  3766. break;
  3767. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::FormattedPHPToExcel(1970, 1, 1, $hour-1, $minute, $second)); // -2147468400; // -2147472000 + 3600
  3768. break;
  3769. case self::RETURNDATE_PHP_OBJECT : $dayAdjust = 0;
  3770. if ($hour < 0) {
  3771. $dayAdjust = floor($hour / 24);
  3772. $hour = 24 - abs($hour % 24);
  3773. if ($hour == 24) { $hour = 0; }
  3774. } elseif ($hour >= 24) {
  3775. $dayAdjust = floor($hour / 24);
  3776. $hour = $hour % 24;
  3777. }
  3778. $phpDateObject = new DateTime('1900-01-01 '.$hour.':'.$minute.':'.$second);
  3779. if ($dayAdjust != 0) {
  3780. $phpDateObject->modify($dayAdjust.' days');
  3781. }
  3782. return $phpDateObject;
  3783. break;
  3784. }
  3785. }
  3786. /**
  3787. * DATEVALUE
  3788. *
  3789. * @param string $dateValue
  3790. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  3791. * depending on the value of the ReturnDateType flag
  3792. */
  3793. public static function DATEVALUE($dateValue = 1) {
  3794. $dateValue = self::flattenSingleValue($dateValue);
  3795. $PHPDateArray = date_parse($dateValue);
  3796. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  3797. $testVal1 = strtok($dateValue,'/- ');
  3798. if ($testVal1 !== False) {
  3799. $testVal2 = strtok('/- ');
  3800. if ($testVal2 !== False) {
  3801. $testVal3 = strtok('/- ');
  3802. if ($testVal3 === False) {
  3803. $testVal3 = strftime('%Y');
  3804. }
  3805. } else {
  3806. return self::$_errorCodes['value'];
  3807. }
  3808. } else {
  3809. return self::$_errorCodes['value'];
  3810. }
  3811. $PHPDateArray = date_parse($testVal1.'-'.$testVal2.'-'.$testVal3);
  3812. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  3813. $PHPDateArray = date_parse($testVal2.'-'.$testVal1.'-'.$testVal3);
  3814. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  3815. return self::$_errorCodes['value'];
  3816. }
  3817. }
  3818. }
  3819. if (($PHPDateArray !== False) && ($PHPDateArray['error_count'] == 0)) {
  3820. // Execute function
  3821. if ($PHPDateArray['year'] == '') { $PHPDateArray['year'] = strftime('%Y'); }
  3822. if ($PHPDateArray['month'] == '') { $PHPDateArray['month'] = strftime('%m'); }
  3823. if ($PHPDateArray['day'] == '') { $PHPDateArray['day'] = strftime('%d'); }
  3824. $excelDateValue = floor(PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']));
  3825. switch (self::getReturnDateType()) {
  3826. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  3827. break;
  3828. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateValue);
  3829. break;
  3830. case self::RETURNDATE_PHP_OBJECT : return new DateTime($PHPDateArray['year'].'-'.$PHPDateArray['month'].'-'.$PHPDateArray['day'].' 00:00:00');
  3831. break;
  3832. }
  3833. }
  3834. return self::$_errorCodes['value'];
  3835. }
  3836. /**
  3837. * _getDateValue
  3838. *
  3839. * @param string $dateValue
  3840. * @return mixed Excel date/time serial value, or string if error
  3841. */
  3842. private static function _getDateValue($dateValue) {
  3843. if (!is_numeric($dateValue)) {
  3844. if ((is_string($dateValue)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  3845. return self::$_errorCodes['value'];
  3846. }
  3847. if ((is_object($dateValue)) && ($dateValue instanceof PHPExcel_Shared_Date::$dateTimeObjectType)) {
  3848. $dateValue = PHPExcel_Shared_Date::PHPToExcel($dateValue);
  3849. } else {
  3850. $saveReturnDateType = self::getReturnDateType();
  3851. self::setReturnDateType(self::RETURNDATE_EXCEL);
  3852. $dateValue = self::DATEVALUE($dateValue);
  3853. self::setReturnDateType($saveReturnDateType);
  3854. }
  3855. } elseif (!is_float($dateValue)) {
  3856. $dateValue = PHPExcel_Shared_Date::PHPToExcel($dateValue);
  3857. }
  3858. return $dateValue;
  3859. }
  3860. /**
  3861. * TIMEVALUE
  3862. *
  3863. * @param string $timeValue
  3864. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  3865. * depending on the value of the ReturnDateType flag
  3866. */
  3867. public static function TIMEVALUE($timeValue) {
  3868. $timeValue = self::flattenSingleValue($timeValue);
  3869. if ((($PHPDateArray = date_parse($timeValue)) !== False) && ($PHPDateArray['error_count'] == 0)) {
  3870. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  3871. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']);
  3872. } else {
  3873. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel(1900,1,1,$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']) - 1;
  3874. }
  3875. switch (self::getReturnDateType()) {
  3876. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  3877. break;
  3878. case self::RETURNDATE_PHP_NUMERIC : return (integer) $phpDateValue = PHPExcel_Shared_Date::ExcelToPHP($excelDateValue+25569) - 3600;;
  3879. break;
  3880. case self::RETURNDATE_PHP_OBJECT : return new DateTime('1900-01-01 '.$PHPDateArray['hour'].':'.$PHPDateArray['minute'].':'.$PHPDateArray['second']);
  3881. break;
  3882. }
  3883. }
  3884. return self::$_errorCodes['value'];
  3885. }
  3886. /**
  3887. * _getTimeValue
  3888. *
  3889. * @param string $timeValue
  3890. * @return mixed Excel date/time serial value, or string if error
  3891. */
  3892. private static function _getTimeValue($timeValue) {
  3893. $saveReturnDateType = self::getReturnDateType();
  3894. self::setReturnDateType(self::RETURNDATE_EXCEL);
  3895. $timeValue = self::TIMEVALUE($timeValue);
  3896. self::setReturnDateType($saveReturnDateType);
  3897. return $timeValue;
  3898. }
  3899. /**
  3900. * DATETIMENOW
  3901. *
  3902. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  3903. * depending on the value of the ReturnDateType flag
  3904. */
  3905. public static function DATETIMENOW() {
  3906. $saveTimeZone = date_default_timezone_get();
  3907. date_default_timezone_set('UTC');
  3908. $retValue = False;
  3909. switch (self::getReturnDateType()) {
  3910. case self::RETURNDATE_EXCEL : $retValue = (float) PHPExcel_Shared_Date::PHPToExcel(time());
  3911. break;
  3912. case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) time();
  3913. break;
  3914. case self::RETURNDATE_PHP_OBJECT : $retValue = new DateTime();
  3915. break;
  3916. }
  3917. date_default_timezone_set($saveTimeZone);
  3918. return $retValue;
  3919. }
  3920. /**
  3921. * DATENOW
  3922. *
  3923. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  3924. * depending on the value of the ReturnDateType flag
  3925. */
  3926. public static function DATENOW() {
  3927. $saveTimeZone = date_default_timezone_get();
  3928. date_default_timezone_set('UTC');
  3929. $retValue = False;
  3930. $excelDateTime = floor(PHPExcel_Shared_Date::PHPToExcel(time()));
  3931. switch (self::getReturnDateType()) {
  3932. case self::RETURNDATE_EXCEL : $retValue = (float) $excelDateTime;
  3933. break;
  3934. case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateTime) - 3600;
  3935. break;
  3936. case self::RETURNDATE_PHP_OBJECT : $retValue = PHPExcel_Shared_Date::ExcelToPHPObject($excelDateTime);
  3937. break;
  3938. }
  3939. date_default_timezone_set($saveTimeZone);
  3940. return $retValue;
  3941. }
  3942. private static function isLeapYear($year) {
  3943. return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0));
  3944. }
  3945. private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) {
  3946. if ($startDay == 31) {
  3947. --$startDay;
  3948. } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) {
  3949. $startDay = 30;
  3950. }
  3951. if ($endDay == 31) {
  3952. if ($methodUS && $startDay != 30) {
  3953. $endDay = 1;
  3954. if ($endMonth == 12) {
  3955. ++$endYear;
  3956. $endMonth = 1;
  3957. } else {
  3958. ++$endMonth;
  3959. }
  3960. } else {
  3961. $endDay = 30;
  3962. }
  3963. }
  3964. return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
  3965. }
  3966. /**
  3967. * DAYS360
  3968. *
  3969. * @param long $startDate Excel date serial value or a standard date string
  3970. * @param long $endDate Excel date serial value or a standard date string
  3971. * @param boolean $method US or European Method
  3972. * @return long PHP date/time serial
  3973. */
  3974. public static function DAYS360($startDate = 0, $endDate = 0, $method = false) {
  3975. $startDate = self::flattenSingleValue($startDate);
  3976. $endDate = self::flattenSingleValue($endDate);
  3977. if (is_string($startDate = self::_getDateValue($startDate))) {
  3978. return self::$_errorCodes['value'];
  3979. }
  3980. if (is_string($endDate = self::_getDateValue($endDate))) {
  3981. return self::$_errorCodes['value'];
  3982. }
  3983. // Execute function
  3984. $PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
  3985. $startDay = $PHPStartDateObject->format('j');
  3986. $startMonth = $PHPStartDateObject->format('n');
  3987. $startYear = $PHPStartDateObject->format('Y');
  3988. $PHPEndDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  3989. $endDay = $PHPEndDateObject->format('j');
  3990. $endMonth = $PHPEndDateObject->format('n');
  3991. $endYear = $PHPEndDateObject->format('Y');
  3992. return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method);
  3993. }
  3994. /**
  3995. * DATEDIF
  3996. *
  3997. * @param long $startDate Excel date serial value or a standard date string
  3998. * @param long $endDate Excel date serial value or a standard date string
  3999. * @param string $unit
  4000. * @return long Interval between the dates
  4001. */
  4002. public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') {
  4003. $startDate = self::flattenSingleValue($startDate);
  4004. $endDate = self::flattenSingleValue($endDate);
  4005. $unit = strtoupper(self::flattenSingleValue($unit));
  4006. if (is_string($startDate = self::_getDateValue($startDate))) {
  4007. return self::$_errorCodes['value'];
  4008. }
  4009. if (is_string($endDate = self::_getDateValue($endDate))) {
  4010. return self::$_errorCodes['value'];
  4011. }
  4012. // Validate parameters
  4013. if ($startDate >= $endDate) {
  4014. return self::$_errorCodes['num'];
  4015. }
  4016. // Execute function
  4017. $difference = $endDate - $startDate;
  4018. $PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
  4019. $startDays = $PHPStartDateObject->format('j');
  4020. $startMonths = $PHPStartDateObject->format('n');
  4021. $startYears = $PHPStartDateObject->format('Y');
  4022. $PHPEndDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  4023. $endDays = $PHPEndDateObject->format('j');
  4024. $endMonths = $PHPEndDateObject->format('n');
  4025. $endYears = $PHPEndDateObject->format('Y');
  4026. $retVal = self::$_errorCodes['num'];
  4027. switch ($unit) {
  4028. case 'D':
  4029. $retVal = intval($difference);
  4030. break;
  4031. case 'M':
  4032. $retVal = intval($endMonths - $startMonths) + (intval($endYears - $startYears) * 12);
  4033. // We're only interested in full months
  4034. if ($endDays < $startDays) {
  4035. --$retVal;
  4036. }
  4037. break;
  4038. case 'Y':
  4039. $retVal = intval($endYears - $startYears);
  4040. // We're only interested in full months
  4041. if ($endMonths < $startMonths) {
  4042. --$retVal;
  4043. } elseif (($endMonths == $startMonths) && ($endDays < $startDays)) {
  4044. --$retVal;
  4045. }
  4046. break;
  4047. case 'MD':
  4048. if ($endDays < $startDays) {
  4049. $retVal = $endDays;
  4050. $PHPEndDateObject->modify('-'.$endDays.' days');
  4051. $adjustDays = $PHPEndDateObject->format('j');
  4052. if ($adjustDays > $startDays) {
  4053. $retVal += ($adjustDays - $startDays);
  4054. }
  4055. } else {
  4056. $retVal = $endDays - $startDays;
  4057. }
  4058. break;
  4059. case 'YM':
  4060. $retVal = abs(intval($endMonths - $startMonths));
  4061. // We're only interested in full months
  4062. if ($endDays < $startDays) {
  4063. --$retVal;
  4064. }
  4065. break;
  4066. case 'YD':
  4067. $retVal = intval($difference);
  4068. if ($endYears > $startYears) {
  4069. while ($endYears > $startYears) {
  4070. $PHPEndDateObject->modify('-1 year');
  4071. $endYears = $PHPEndDateObject->format('Y');
  4072. }
  4073. $retVal = abs($PHPEndDateObject->format('z') - $PHPStartDateObject->format('z'));
  4074. }
  4075. break;
  4076. }
  4077. return $retVal;
  4078. }
  4079. /**
  4080. * YEARFRAC
  4081. *
  4082. * @param long $startDate Excel date serial value or a standard date string
  4083. * @param long $endDate Excel date serial value or a standard date string
  4084. * @param integer $method Method used for the calculation
  4085. * @return long PHP date/time serial
  4086. */
  4087. public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) {
  4088. $startDate = self::flattenSingleValue($startDate);
  4089. $endDate = self::flattenSingleValue($endDate);
  4090. $method = self::flattenSingleValue($method);
  4091. if (is_string($startDate = self::_getDateValue($startDate))) {
  4092. return self::$_errorCodes['value'];
  4093. }
  4094. if (is_string($endDate = self::_getDateValue($endDate))) {
  4095. return self::$_errorCodes['value'];
  4096. }
  4097. if ((is_numeric($method)) && (!is_string($method))) {
  4098. switch($method) {
  4099. case 0 :
  4100. return self::DAYS360($startDate,$endDate) / 360;
  4101. break;
  4102. case 1 :
  4103. $startYear = self::YEAR($startDate);
  4104. $endYear = self::YEAR($endDate);
  4105. $leapDay = 0;
  4106. if (self::isLeapYear($startYear) || self::isLeapYear($endYear)) {
  4107. $leapDay = 1;
  4108. }
  4109. return self::DATEDIF($startDate,$endDate) / (365 + $leapDay);
  4110. break;
  4111. case 2 :
  4112. return self::DATEDIF($startDate,$endDate) / 360;
  4113. break;
  4114. case 3 :
  4115. return self::DATEDIF($startDate,$endDate) / 365;
  4116. break;
  4117. case 4 :
  4118. return self::DAYS360($startDate,$endDate,True) / 360;
  4119. break;
  4120. }
  4121. }
  4122. return self::$_errorCodes['value'];
  4123. }
  4124. /**
  4125. * NETWORKDAYS
  4126. *
  4127. * @param mixed Start date
  4128. * @param mixed End date
  4129. * @param array of mixed Optional Date Series
  4130. * @return long Interval between the dates
  4131. */
  4132. public static function NETWORKDAYS($startDate,$endDate) {
  4133. // Flush the mandatory start and end date that are referenced in the function definition
  4134. $dateArgs = self::flattenArray(func_get_args());
  4135. array_shift($dateArgs);
  4136. array_shift($dateArgs);
  4137. // Validate the start and end dates
  4138. if (is_string($startDate = $sDate = self::_getDateValue($startDate))) {
  4139. return self::$_errorCodes['value'];
  4140. }
  4141. if (is_string($endDate = $eDate = self::_getDateValue($endDate))) {
  4142. return self::$_errorCodes['value'];
  4143. }
  4144. if ($sDate > $eDate) {
  4145. $startDate = $eDate;
  4146. $endDate = $sDate;
  4147. }
  4148. // Execute function
  4149. $startDoW = 6 - self::DAYOFWEEK($startDate,2);
  4150. if ($startDoW < 0) { $startDoW = 0; }
  4151. $endDoW = self::DAYOFWEEK($endDate,2);
  4152. if ($endDoW >= 6) { $endDoW = 0; }
  4153. $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5;
  4154. $partWeekDays = $endDoW + $startDoW;
  4155. if ($partWeekDays > 5) {
  4156. $partWeekDays -= 5;
  4157. }
  4158. // Test any extra holiday parameters
  4159. $holidayCountedArray = array();
  4160. foreach ($dateArgs as $holidayDate) {
  4161. if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
  4162. return self::$_errorCodes['value'];
  4163. }
  4164. if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
  4165. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  4166. --$partWeekDays;
  4167. $holidayCountedArray[] = $holidayDate;
  4168. }
  4169. }
  4170. }
  4171. if ($sDate > $eDate) {
  4172. return 0 - ($wholeWeekDays + $partWeekDays);
  4173. }
  4174. return $wholeWeekDays + $partWeekDays;
  4175. }
  4176. /**
  4177. * WORKDAY
  4178. *
  4179. * @param mixed Start date
  4180. * @param mixed number of days for adjustment
  4181. * @param array of mixed Optional Date Series
  4182. * @return long Interval between the dates
  4183. */
  4184. public static function WORKDAY($startDate,$endDays) {
  4185. $dateArgs = self::flattenArray(func_get_args());
  4186. array_shift($dateArgs);
  4187. array_shift($dateArgs);
  4188. if (is_string($startDate = self::_getDateValue($startDate))) {
  4189. return self::$_errorCodes['value'];
  4190. }
  4191. if (!is_numeric($endDays)) {
  4192. return self::$_errorCodes['value'];
  4193. }
  4194. $endDate = (float) $startDate + (floor($endDays / 5) * 7) + ($endDays % 5);
  4195. if ($endDays < 0) {
  4196. $endDate += 7;
  4197. }
  4198. $endDoW = self::DAYOFWEEK($endDate,3);
  4199. if ($endDoW >= 5) {
  4200. if ($endDays >= 0) {
  4201. $endDate += (7 - $endDoW);
  4202. } else {
  4203. $endDate -= ($endDoW - 5);
  4204. }
  4205. }
  4206. // Test any extra holiday parameters
  4207. if (count($dateArgs) > 0) {
  4208. $holidayCountedArray = $holidayDates = array();
  4209. foreach ($dateArgs as $holidayDate) {
  4210. if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
  4211. return self::$_errorCodes['value'];
  4212. }
  4213. $holidayDates[] = $holidayDate;
  4214. }
  4215. if ($endDays >= 0) {
  4216. sort($holidayDates, SORT_NUMERIC);
  4217. } else {
  4218. rsort($holidayDates, SORT_NUMERIC);
  4219. }
  4220. foreach ($holidayDates as $holidayDate) {
  4221. if ($endDays >= 0) {
  4222. if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
  4223. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  4224. ++$endDate;
  4225. $holidayCountedArray[] = $holidayDate;
  4226. }
  4227. }
  4228. } else {
  4229. if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
  4230. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  4231. --$endDate;
  4232. $holidayCountedArray[] = $holidayDate;
  4233. }
  4234. }
  4235. }
  4236. $endDoW = self::DAYOFWEEK($endDate,3);
  4237. if ($endDoW >= 5) {
  4238. if ($endDays >= 0) {
  4239. $endDate += (7 - $endDoW);
  4240. } else {
  4241. $endDate -= ($endDoW - 5);
  4242. }
  4243. }
  4244. }
  4245. }
  4246. switch (self::getReturnDateType()) {
  4247. case self::RETURNDATE_EXCEL : return (float) $endDate;
  4248. break;
  4249. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($endDate);
  4250. break;
  4251. case self::RETURNDATE_PHP_OBJECT : return PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  4252. break;
  4253. }
  4254. }
  4255. /**
  4256. * DAYOFMONTH
  4257. *
  4258. * @param long $dateValue Excel date serial value or a standard date string
  4259. * @return int Day
  4260. */
  4261. public static function DAYOFMONTH($dateValue = 1) {
  4262. $dateValue = self::flattenSingleValue($dateValue);
  4263. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4264. return self::$_errorCodes['value'];
  4265. }
  4266. // Execute function
  4267. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4268. return $PHPDateObject->format('j');
  4269. }
  4270. /**
  4271. * DAYOFWEEK
  4272. *
  4273. * @param long $dateValue Excel date serial value or a standard date string
  4274. * @return int Day
  4275. */
  4276. public static function DAYOFWEEK($dateValue = 1, $style = 1) {
  4277. $dateValue = self::flattenSingleValue($dateValue);
  4278. $style = floor(self::flattenSingleValue($style));
  4279. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4280. return self::$_errorCodes['value'];
  4281. }
  4282. // Execute function
  4283. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4284. $DoW = $PHPDateObject->format('w');
  4285. $firstDay = 1;
  4286. switch ($style) {
  4287. case 1: ++$DoW;
  4288. break;
  4289. case 2: if ($DoW == 0) { $DoW = 7; }
  4290. break;
  4291. case 3: if ($DoW == 0) { $DoW = 7; }
  4292. $firstDay = 0;
  4293. --$DoW;
  4294. break;
  4295. default:
  4296. }
  4297. if (self::$compatibilityMode == self::COMPATIBILITY_EXCEL) {
  4298. // Test for Excel's 1900 leap year, and introduce the error as required
  4299. if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) {
  4300. --$DoW;
  4301. if ($DoW < $firstDay) {
  4302. $DoW += 7;
  4303. }
  4304. }
  4305. }
  4306. return $DoW;
  4307. }
  4308. /**
  4309. * WEEKOFYEAR
  4310. *
  4311. * @param long $dateValue Excel date serial value or a standard date string
  4312. * @param boolean $method Week begins on Sunday or Monday
  4313. * @return int Week Number
  4314. */
  4315. public static function WEEKOFYEAR($dateValue = 1, $method = 1) {
  4316. $dateValue = self::flattenSingleValue($dateValue);
  4317. $method = floor(self::flattenSingleValue($method));
  4318. if (!is_numeric($method)) {
  4319. return self::$_errorCodes['value'];
  4320. } elseif (($method < 1) || ($method > 2)) {
  4321. return self::$_errorCodes['num'];
  4322. }
  4323. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4324. return self::$_errorCodes['value'];
  4325. }
  4326. // Execute function
  4327. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4328. $dayOfYear = $PHPDateObject->format('z');
  4329. $dow = $PHPDateObject->format('w');
  4330. $PHPDateObject->modify('-'.$dayOfYear.' days');
  4331. $dow = $PHPDateObject->format('w');
  4332. $daysInFirstWeek = 7 - (($dow + (2 - $method)) % 7);
  4333. $dayOfYear -= $daysInFirstWeek;
  4334. $weekOfYear = ceil($dayOfYear / 7) + 1;
  4335. return $weekOfYear;
  4336. }
  4337. /**
  4338. * MONTHOFYEAR
  4339. *
  4340. * @param long $dateValue Excel date serial value or a standard date string
  4341. * @return int Month
  4342. */
  4343. public static function MONTHOFYEAR($dateValue = 1) {
  4344. $dateValue = self::flattenSingleValue($dateValue);
  4345. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4346. return self::$_errorCodes['value'];
  4347. }
  4348. // Execute function
  4349. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4350. return $PHPDateObject->format('n');
  4351. }
  4352. /**
  4353. * YEAR
  4354. *
  4355. * @param long $dateValue Excel date serial value or a standard date string
  4356. * @return int Year
  4357. */
  4358. public static function YEAR($dateValue = 1) {
  4359. $dateValue = self::flattenSingleValue($dateValue);
  4360. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4361. return self::$_errorCodes['value'];
  4362. }
  4363. // Execute function
  4364. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4365. return $PHPDateObject->format('Y');
  4366. }
  4367. /**
  4368. * HOUROFDAY
  4369. *
  4370. * @param mixed $timeValue Excel time serial value or a standard time string
  4371. * @return int Hour
  4372. */
  4373. public static function HOUROFDAY($timeValue = 0) {
  4374. $timeValue = self::flattenSingleValue($timeValue);
  4375. if (!is_numeric($timeValue)) {
  4376. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4377. $testVal = strtok($timeValue,'/-: ');
  4378. if (strlen($testVal) < strlen($timeValue)) {
  4379. return self::$_errorCodes['value'];
  4380. }
  4381. }
  4382. $timeValue = self::_getTimeValue($timeValue);
  4383. if (is_string($timeValue)) {
  4384. return self::$_errorCodes['value'];
  4385. }
  4386. }
  4387. // Execute function
  4388. if (is_real($timeValue)) {
  4389. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  4390. }
  4391. return date('G',$timeValue);
  4392. }
  4393. /**
  4394. * MINUTEOFHOUR
  4395. *
  4396. * @param long $timeValue Excel time serial value or a standard time string
  4397. * @return int Minute
  4398. */
  4399. public static function MINUTEOFHOUR($timeValue = 0) {
  4400. $timeValue = $timeTester = self::flattenSingleValue($timeValue);
  4401. if (!is_numeric($timeValue)) {
  4402. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4403. $testVal = strtok($timeValue,'/-: ');
  4404. if (strlen($testVal) < strlen($timeValue)) {
  4405. return self::$_errorCodes['value'];
  4406. }
  4407. }
  4408. $timeValue = self::_getTimeValue($timeValue);
  4409. if (is_string($timeValue)) {
  4410. return self::$_errorCodes['value'];
  4411. }
  4412. }
  4413. // Execute function
  4414. if (is_real($timeValue)) {
  4415. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  4416. }
  4417. return (int) date('i',$timeValue);
  4418. }
  4419. /**
  4420. * SECONDOFMINUTE
  4421. *
  4422. * @param long $timeValue Excel time serial value or a standard time string
  4423. * @return int Second
  4424. */
  4425. public static function SECONDOFMINUTE($timeValue = 0) {
  4426. $timeValue = self::flattenSingleValue($timeValue);
  4427. if (!is_numeric($timeValue)) {
  4428. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4429. $testVal = strtok($timeValue,'/-: ');
  4430. if (strlen($testVal) < strlen($timeValue)) {
  4431. return self::$_errorCodes['value'];
  4432. }
  4433. }
  4434. $timeValue = self::_getTimeValue($timeValue);
  4435. if (is_string($timeValue)) {
  4436. return self::$_errorCodes['value'];
  4437. }
  4438. }
  4439. // Execute function
  4440. if (is_real($timeValue)) {
  4441. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  4442. }
  4443. return (int) date('s',$timeValue);
  4444. }
  4445. private static function adjustDateByMonths ($dateValue = 0, $adjustmentMonths = 0) {
  4446. // Execute function
  4447. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4448. $oMonth = (int) $PHPDateObject->format('m');
  4449. $oYear = (int) $PHPDateObject->format('Y');
  4450. $adjustmentMonthsString = (string) $adjustmentMonths;
  4451. if ($adjustmentMonths > 0) {
  4452. $adjustmentMonthsString = '+'.$adjustmentMonths;
  4453. }
  4454. if ($adjustmentMonths != 0) {
  4455. $PHPDateObject->modify($adjustmentMonthsString.' months');
  4456. }
  4457. $nMonth = (int) $PHPDateObject->format('m');
  4458. $nYear = (int) $PHPDateObject->format('Y');
  4459. $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
  4460. if ($monthDiff != $adjustmentMonths) {
  4461. $adjustDays = (int) $PHPDateObject->format('d');
  4462. $adjustDaysString = '-'.$adjustDays.' days';
  4463. $PHPDateObject->modify($adjustDaysString);
  4464. }
  4465. return $PHPDateObject;
  4466. }
  4467. /**
  4468. * EDATE
  4469. *
  4470. * Returns the serial number that represents the date that is the indicated number of months before or after a specified date
  4471. * (the start_date). Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue.
  4472. *
  4473. * @param long $dateValue Excel date serial value or a standard date string
  4474. * @param int $adjustmentMonths Number of months to adjust by
  4475. * @return long Excel date serial value
  4476. */
  4477. public static function EDATE($dateValue = 1, $adjustmentMonths = 0) {
  4478. $dateValue = self::flattenSingleValue($dateValue);
  4479. $adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
  4480. if (!is_numeric($adjustmentMonths)) {
  4481. return self::$_errorCodes['value'];
  4482. }
  4483. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4484. return self::$_errorCodes['value'];
  4485. }
  4486. // Execute function
  4487. $PHPDateObject = self::adjustDateByMonths($dateValue,$adjustmentMonths);
  4488. switch (self::getReturnDateType()) {
  4489. case self::RETURNDATE_EXCEL : return (float) PHPExcel_Shared_Date::PHPToExcel($PHPDateObject);
  4490. break;
  4491. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::PHPToExcel($PHPDateObject));
  4492. break;
  4493. case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
  4494. break;
  4495. }
  4496. }
  4497. /**
  4498. * EOMONTH
  4499. *
  4500. * Returns the serial number for the last day of the month that is the indicated number of months before or after start_date.
  4501. * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
  4502. *
  4503. * @param long $dateValue Excel date serial value or a standard date string
  4504. * @param int $adjustmentMonths Number of months to adjust by
  4505. * @return long Excel date serial value
  4506. */
  4507. public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) {
  4508. $dateValue = self::flattenSingleValue($dateValue);
  4509. $adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
  4510. if (!is_numeric($adjustmentMonths)) {
  4511. return self::$_errorCodes['value'];
  4512. }
  4513. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4514. return self::$_errorCodes['value'];
  4515. }
  4516. // Execute function
  4517. $PHPDateObject = self::adjustDateByMonths($dateValue,$adjustmentMonths+1);
  4518. $adjustDays = (int) $PHPDateObject->format('d');
  4519. $adjustDaysString = '-'.$adjustDays.' days';
  4520. $PHPDateObject->modify($adjustDaysString);
  4521. switch (self::getReturnDateType()) {
  4522. case self::RETURNDATE_EXCEL : return (float) PHPExcel_Shared_Date::PHPToExcel($PHPDateObject);
  4523. break;
  4524. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::PHPToExcel($PHPDateObject));
  4525. break;
  4526. case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
  4527. break;
  4528. }
  4529. }
  4530. /**
  4531. * TRUNC
  4532. *
  4533. * Truncates value to the number of fractional digits by number_digits.
  4534. *
  4535. * @param float $value
  4536. * @param int $number_digits
  4537. * @return float Truncated value
  4538. */
  4539. public static function TRUNC($value = 0, $number_digits = 0) {
  4540. $value = self::flattenSingleValue($value);
  4541. $number_digits = self::flattenSingleValue($number_digits);
  4542. // Validate parameters
  4543. if ($number_digits < 0) {
  4544. return self::$_errorCodes['value'];
  4545. }
  4546. // Truncate
  4547. if ($number_digits > 0) {
  4548. $value = $value * pow(10, $number_digits);
  4549. }
  4550. $value = intval($value);
  4551. if ($number_digits > 0) {
  4552. $value = $value / pow(10, $number_digits);
  4553. }
  4554. // Return
  4555. return $value;
  4556. }
  4557. /**
  4558. * POWER
  4559. *
  4560. * Computes x raised to the power y.
  4561. *
  4562. * @param float $x
  4563. * @param float $y
  4564. * @return float
  4565. */
  4566. public static function POWER($x = 0, $y = 2) {
  4567. $x = self::flattenSingleValue($x);
  4568. $y = self::flattenSingleValue($y);
  4569. // Validate parameters
  4570. if ($x < 0) {
  4571. return self::$_errorCodes['num'];
  4572. }
  4573. if ($x == 0 && $y <= 0) {
  4574. return self::$_errorCodes['divisionbyzero'];
  4575. }
  4576. // Return
  4577. return pow($x, $y);
  4578. }
  4579. /**
  4580. * BINTODEC
  4581. *
  4582. * Return a binary value as Decimal.
  4583. *
  4584. * @param string $x
  4585. * @return string
  4586. */
  4587. public static function BINTODEC($x) {
  4588. $x = self::flattenSingleValue($x);
  4589. if (is_bool($x)) {
  4590. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4591. $x = (int) $x;
  4592. } else {
  4593. return self::$_errorCodes['value'];
  4594. }
  4595. }
  4596. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4597. $x = floor($x);
  4598. }
  4599. $x = (string) $x;
  4600. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  4601. return self::$_errorCodes['num'];
  4602. }
  4603. if (strlen($x) > 10) {
  4604. return self::$_errorCodes['num'];
  4605. } elseif (strlen($x) == 10) {
  4606. // Two's Complement
  4607. $x = substr($x,-9);
  4608. return '-'.(512-bindec($x));
  4609. }
  4610. return bindec($x);
  4611. }
  4612. /**
  4613. * BINTOHEX
  4614. *
  4615. * Return a binary value as Hex.
  4616. *
  4617. * @param string $x
  4618. * @return string
  4619. */
  4620. public static function BINTOHEX($x) {
  4621. $x = floor(self::flattenSingleValue($x));
  4622. if (is_bool($x)) {
  4623. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4624. $x = (int) $x;
  4625. } else {
  4626. return self::$_errorCodes['value'];
  4627. }
  4628. }
  4629. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4630. $x = floor($x);
  4631. }
  4632. $x = (string) $x;
  4633. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  4634. return self::$_errorCodes['num'];
  4635. }
  4636. if (strlen($x) > 10) {
  4637. return self::$_errorCodes['num'];
  4638. } elseif (strlen($x) == 10) {
  4639. // Two's Complement
  4640. return str_repeat('F',8).substr(strtoupper(dechex(bindec(substr($x,-9)))),-2);
  4641. }
  4642. return strtoupper(dechex(bindec($x)));
  4643. }
  4644. /**
  4645. * BINTOOCT
  4646. *
  4647. * Return a binary value as Octal.
  4648. *
  4649. * @param string $x
  4650. * @return string
  4651. */
  4652. public static function BINTOOCT($x) {
  4653. $x = floor(self::flattenSingleValue($x));
  4654. if (is_bool($x)) {
  4655. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4656. $x = (int) $x;
  4657. } else {
  4658. return self::$_errorCodes['value'];
  4659. }
  4660. }
  4661. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4662. $x = floor($x);
  4663. }
  4664. $x = (string) $x;
  4665. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  4666. return self::$_errorCodes['num'];
  4667. }
  4668. if (strlen($x) > 10) {
  4669. return self::$_errorCodes['num'];
  4670. } elseif (strlen($x) == 10) {
  4671. // Two's Complement
  4672. return str_repeat('7',7).substr(strtoupper(dechex(bindec(substr($x,-9)))),-3);
  4673. }
  4674. return decoct(bindec($x));
  4675. }
  4676. /**
  4677. * DECTOBIN
  4678. *
  4679. * Return an octal value as binary.
  4680. *
  4681. * @param string $x
  4682. * @return string
  4683. */
  4684. public static function DECTOBIN($x) {
  4685. $x = self::flattenSingleValue($x);
  4686. if (is_bool($x)) {
  4687. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4688. $x = (int) $x;
  4689. } else {
  4690. return self::$_errorCodes['value'];
  4691. }
  4692. }
  4693. $x = (string) $x;
  4694. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  4695. return self::$_errorCodes['value'];
  4696. }
  4697. $x = (string) floor($x);
  4698. $r = decbin($x);
  4699. if (strlen($r) == 32) {
  4700. // Two's Complement
  4701. $r = substr($r,-10);
  4702. } elseif (strlen($r) > 11) {
  4703. return self::$_errorCodes['num'];
  4704. }
  4705. return $r;
  4706. }
  4707. /**
  4708. * DECTOOCT
  4709. *
  4710. * Return an octal value as binary.
  4711. *
  4712. * @param string $x
  4713. * @return string
  4714. */
  4715. public static function DECTOOCT($x) {
  4716. $x = self::flattenSingleValue($x);
  4717. if (is_bool($x)) {
  4718. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4719. $x = (int) $x;
  4720. } else {
  4721. return self::$_errorCodes['value'];
  4722. }
  4723. }
  4724. $x = (string) $x;
  4725. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  4726. return self::$_errorCodes['value'];
  4727. }
  4728. $x = (string) floor($x);
  4729. $r = decoct($x);
  4730. if (strlen($r) == 11) {
  4731. // Two's Complement
  4732. $r = substr($r,-10);
  4733. }
  4734. return ($r);
  4735. }
  4736. /**
  4737. * DECTOHEX
  4738. *
  4739. * Return an octal value as binary.
  4740. *
  4741. * @param string $x
  4742. * @return string
  4743. */
  4744. public static function DECTOHEX($x) {
  4745. $x = self::flattenSingleValue($x);
  4746. if (is_bool($x)) {
  4747. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4748. $x = (int) $x;
  4749. } else {
  4750. return self::$_errorCodes['value'];
  4751. }
  4752. }
  4753. $x = (string) $x;
  4754. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  4755. return self::$_errorCodes['value'];
  4756. }
  4757. $x = (string) floor($x);
  4758. $r = strtoupper(dechex($x));
  4759. if (strlen($r) == 8) {
  4760. // Two's Complement
  4761. $r = 'FF'.$r;
  4762. }
  4763. return ($r);
  4764. }
  4765. /**
  4766. * HEXTOBIN
  4767. *
  4768. * Return a hex value as binary.
  4769. *
  4770. * @param string $x
  4771. * @return string
  4772. */
  4773. public static function HEXTOBIN($x) {
  4774. $x = self::flattenSingleValue($x);
  4775. if (is_bool($x)) {
  4776. return self::$_errorCodes['value'];
  4777. }
  4778. $x = (string) $x;
  4779. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  4780. return self::$_errorCodes['num'];
  4781. }
  4782. return decbin(hexdec($x));
  4783. }
  4784. /**
  4785. * HEXTOOCT
  4786. *
  4787. * Return a hex value as octal.
  4788. *
  4789. * @param string $x
  4790. * @return string
  4791. */
  4792. public static function HEXTOOCT($x) {
  4793. $x = self::flattenSingleValue($x);
  4794. if (is_bool($x)) {
  4795. return self::$_errorCodes['value'];
  4796. }
  4797. $x = (string) $x;
  4798. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  4799. return self::$_errorCodes['num'];
  4800. }
  4801. return decoct(hexdec($x));
  4802. }
  4803. /**
  4804. * HEXTODEC
  4805. *
  4806. * Return a hex value as octal.
  4807. *
  4808. * @param string $x
  4809. * @return string
  4810. */
  4811. public static function HEXTODEC($x) {
  4812. $x = self::flattenSingleValue($x);
  4813. if (is_bool($x)) {
  4814. return self::$_errorCodes['value'];
  4815. }
  4816. $x = (string) $x;
  4817. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  4818. return self::$_errorCodes['num'];
  4819. }
  4820. return hexdec($x);
  4821. }
  4822. /**
  4823. * OCTTOBIN
  4824. *
  4825. * Return an octal value as binary.
  4826. *
  4827. * @param string $x
  4828. * @return string
  4829. */
  4830. public static function OCTTOBIN($x) {
  4831. $x = self::flattenSingleValue($x);
  4832. if (is_bool($x)) {
  4833. return self::$_errorCodes['value'];
  4834. }
  4835. $x = (string) $x;
  4836. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  4837. return self::$_errorCodes['num'];
  4838. }
  4839. return decbin(octdec($x));
  4840. }
  4841. /**
  4842. * OCTTODEC
  4843. *
  4844. * Return an octal value as binary.
  4845. *
  4846. * @param string $x
  4847. * @return string
  4848. */
  4849. public static function OCTTODEC($x) {
  4850. $x = self::flattenSingleValue($x);
  4851. if (is_bool($x)) {
  4852. return self::$_errorCodes['value'];
  4853. }
  4854. $x = (string) $x;
  4855. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  4856. return self::$_errorCodes['num'];
  4857. }
  4858. return octdec($x);
  4859. }
  4860. /**
  4861. * OCTTOHEX
  4862. *
  4863. * Return an octal value as hex.
  4864. *
  4865. * @param string $x
  4866. * @return string
  4867. */
  4868. public static function OCTTOHEX($x) {
  4869. $x = self::flattenSingleValue($x);
  4870. if (is_bool($x)) {
  4871. return self::$_errorCodes['value'];
  4872. }
  4873. $x = (string) $x;
  4874. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  4875. return self::$_errorCodes['num'];
  4876. }
  4877. return strtoupper(dechex(octdec($x)));
  4878. }
  4879. public function parseComplex($complexNumber) {
  4880. $workString = $complexNumber;
  4881. $realNumber = $imaginary = 0;
  4882. // Extract the suffix, if there is one
  4883. $suffix = substr($workString,-1);
  4884. if (!is_numeric($suffix)) {
  4885. $workString = substr($workString,0,-1);
  4886. } else {
  4887. $suffix = '';
  4888. }
  4889. // Split the input into its Real and Imaginary components
  4890. $leadingSign = (($workString{0} == '+') || ($workString{0} == '-')) ? 1 : 0;
  4891. $power = '';
  4892. $realNumber = strtok($workString, '+-');
  4893. if (strtoupper(substr($realNumber,-1)) == 'E') {
  4894. $power = strtok('+-');
  4895. ++$leadingSign;
  4896. }
  4897. $realNumber = substr($workString,0,strlen($realNumber)+strlen($power)+$leadingSign);
  4898. if ($suffix != '') {
  4899. $imaginary = substr($workString,strlen($realNumber));
  4900. if (($imaginary == '') && (($realNumber == '') || ($realNumber == '+') || ($realNumber == '-'))) {
  4901. $imaginary = $realNumber.'1';
  4902. $realNumber = '0';
  4903. } else if ($imaginary == '') {
  4904. $imaginary = $realNumber;
  4905. $realNumber = '0';
  4906. } elseif (($imaginary == '+') || ($imaginary == '-')) {
  4907. $imaginary .= '1';
  4908. }
  4909. }
  4910. $complexArray = array( 'real' => $realNumber,
  4911. 'imaginary' => $imaginary,
  4912. 'suffix' => $suffix
  4913. );
  4914. return $complexArray;
  4915. }
  4916. /**
  4917. * COMPLEX
  4918. *
  4919. * returns a complex number of the form x + yi or x + yj.
  4920. *
  4921. * @param float $realNumber
  4922. * @param float $imaginary
  4923. * @param string $suffix
  4924. * @return string
  4925. */
  4926. public static function COMPLEX($realNumber=0.0, $imaginary=0.0, $suffix='i') {
  4927. $realNumber = self::flattenSingleValue($realNumber);
  4928. $imaginary = self::flattenSingleValue($imaginary);
  4929. $suffix = self::flattenSingleValue($suffix);
  4930. if (((is_numeric($realNumber)) && (is_numeric($imaginary))) &&
  4931. (($suffix == 'i') || ($suffix == 'j'))) {
  4932. if ($realNumber == 0.0) {
  4933. if ($imaginary == 0.0) {
  4934. return (string) '0';
  4935. } elseif ($imaginary == 1.0) {
  4936. return (string) $suffix;
  4937. } elseif ($imaginary == -1.0) {
  4938. return (string) '-'.$suffix;
  4939. }
  4940. return (string) $imaginary.$suffix;
  4941. } elseif ($imaginary == 0.0) {
  4942. return (string) $realNumber;
  4943. } elseif ($imaginary == 1.0) {
  4944. return (string) $realNumber.'+'.$suffix;
  4945. } elseif ($imaginary == -1.0) {
  4946. return (string) $realNumber.'-'.$suffix;
  4947. }
  4948. if ($imaginary > 0) { $imaginary = (string) '+'.$imaginary; }
  4949. return (string) $realNumber.$imaginary.$suffix;
  4950. }
  4951. return self::$_errorCodes['value'];
  4952. }
  4953. /**
  4954. * IMAGINARY
  4955. *
  4956. * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
  4957. *
  4958. * @param string $complexNumber
  4959. * @return real
  4960. */
  4961. public static function IMAGINARY($complexNumber) {
  4962. $complexNumber = self::flattenSingleValue($complexNumber);
  4963. $parsedComplex = self::parseComplex($complexNumber);
  4964. if (!is_array($parsedComplex)) {
  4965. return $parsedComplex;
  4966. }
  4967. return $parsedComplex['imaginary'];
  4968. }
  4969. /**
  4970. * IMREAL
  4971. *
  4972. * Returns the real coefficient of a complex number in x + yi or x + yj text format.
  4973. *
  4974. * @param string $complexNumber
  4975. * @return real
  4976. */
  4977. public static function IMREAL($complexNumber) {
  4978. $complexNumber = self::flattenSingleValue($complexNumber);
  4979. $parsedComplex = self::parseComplex($complexNumber);
  4980. if (!is_array($parsedComplex)) {
  4981. return $parsedComplex;
  4982. }
  4983. return $parsedComplex['real'];
  4984. }
  4985. /**
  4986. * IMABS
  4987. *
  4988. * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
  4989. *
  4990. * @param string $complexNumber
  4991. * @return real
  4992. */
  4993. public static function IMABS($complexNumber) {
  4994. $complexNumber = self::flattenSingleValue($complexNumber);
  4995. $parsedComplex = self::parseComplex($complexNumber);
  4996. if (!is_array($parsedComplex)) {
  4997. return $parsedComplex;
  4998. }
  4999. return sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
  5000. }
  5001. /**
  5002. * IMARGUMENT
  5003. *
  5004. * Returns the argument theta of a complex number, i.e. the angle in radians from the real axis to the representation of the number in polar coordinates.
  5005. *
  5006. * @param string $complexNumber
  5007. * @return string
  5008. */
  5009. public static function IMARGUMENT($complexNumber) {
  5010. $complexNumber = self::flattenSingleValue($complexNumber);
  5011. $parsedComplex = self::parseComplex($complexNumber);
  5012. if (!is_array($parsedComplex)) {
  5013. return $parsedComplex;
  5014. }
  5015. if ($parsedComplex['real'] == 0.0) {
  5016. if ($parsedComplex['imaginary'] == 0.0) {
  5017. return 0.0;
  5018. } elseif($parsedComplex['imaginary'] < 0.0) {
  5019. return pi() / -2;
  5020. } else {
  5021. return pi() / 2;
  5022. }
  5023. } elseif ($parsedComplex['real'] > 0.0) {
  5024. return atan($parsedComplex['imaginary'] / $parsedComplex['real']);
  5025. } elseif ($parsedComplex['imaginary'] < 0.0) {
  5026. return 0 - (pi() - atan(abs($parsedComplex['imaginary']) / abs($parsedComplex['real'])));
  5027. } else {
  5028. return pi() - atan($parsedComplex['imaginary'] / abs($parsedComplex['real']));
  5029. }
  5030. }
  5031. /**
  5032. * IMCONJUGATE
  5033. *
  5034. * Returns the complex conjugate of a complex number in x + yi or x + yj text format.
  5035. *
  5036. * @param string $complexNumber
  5037. * @return string
  5038. */
  5039. public static function IMCONJUGATE($complexNumber) {
  5040. $complexNumber = self::flattenSingleValue($complexNumber);
  5041. $parsedComplex = self::parseComplex($complexNumber);
  5042. if (!is_array($parsedComplex)) {
  5043. return $parsedComplex;
  5044. }
  5045. if ($parsedComplex['imaginary'] == 0.0) {
  5046. return $parsedComplex['real'];
  5047. } else {
  5048. return self::COMPLEX($parsedComplex['real'], 0 - $parsedComplex['imaginary'], $parsedComplex['suffix']);
  5049. }
  5050. }
  5051. /**
  5052. * IMCOS
  5053. *
  5054. * Returns the cosine of a complex number in x + yi or x + yj text format.
  5055. *
  5056. * @param string $complexNumber
  5057. * @return string
  5058. */
  5059. public static function IMCOS($complexNumber) {
  5060. $complexNumber = self::flattenSingleValue($complexNumber);
  5061. $parsedComplex = self::parseComplex($complexNumber);
  5062. if (!is_array($parsedComplex)) {
  5063. return $parsedComplex;
  5064. }
  5065. if ($parsedComplex['imaginary'] == 0.0) {
  5066. return cos($parsedComplex['real']);
  5067. } else {
  5068. return self::IMCONJUGATE(self::COMPLEX(cos($parsedComplex['real']) * cosh($parsedComplex['imaginary']),sin($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']));
  5069. }
  5070. }
  5071. /**
  5072. * IMSIN
  5073. *
  5074. * Returns the sine of a complex number in x + yi or x + yj text format.
  5075. *
  5076. * @param string $complexNumber
  5077. * @return string
  5078. */
  5079. public static function IMSIN($complexNumber) {
  5080. $complexNumber = self::flattenSingleValue($complexNumber);
  5081. $parsedComplex = self::parseComplex($complexNumber);
  5082. if (!is_array($parsedComplex)) {
  5083. return $parsedComplex;
  5084. }
  5085. if ($parsedComplex['imaginary'] == 0.0) {
  5086. return sin($parsedComplex['real']);
  5087. } else {
  5088. return self::COMPLEX(sin($parsedComplex['real']) * cosh($parsedComplex['imaginary']),cos($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']);
  5089. }
  5090. }
  5091. /**
  5092. * IMSQRT
  5093. *
  5094. * Returns the square root of a complex number in x + yi or x + yj text format.
  5095. *
  5096. * @param string $complexNumber
  5097. * @return string
  5098. */
  5099. public static function IMSQRT($complexNumber) {
  5100. $complexNumber = self::flattenSingleValue($complexNumber);
  5101. $parsedComplex = self::parseComplex($complexNumber);
  5102. if (!is_array($parsedComplex)) {
  5103. return $parsedComplex;
  5104. }
  5105. $theta = self::IMARGUMENT($complexNumber);
  5106. $d1 = cos($theta / 2);
  5107. $d2 = sin($theta / 2);
  5108. $r = sqrt(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
  5109. if ($parsedComplex['suffix'] == '') {
  5110. return self::COMPLEX($d1 * $r,$d2 * $r);
  5111. } else {
  5112. return self::COMPLEX($d1 * $r,$d2 * $r,$parsedComplex['suffix']);
  5113. }
  5114. }
  5115. /**
  5116. * IMLN
  5117. *
  5118. * Returns the natural logarithm of a complex number in x + yi or x + yj text format.
  5119. *
  5120. * @param string $complexNumber
  5121. * @return string
  5122. */
  5123. public static function IMLN($complexNumber) {
  5124. $complexNumber = self::flattenSingleValue($complexNumber);
  5125. $parsedComplex = self::parseComplex($complexNumber);
  5126. if (!is_array($parsedComplex)) {
  5127. return $parsedComplex;
  5128. }
  5129. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5130. return self::$_errorCodes['num'];
  5131. }
  5132. $logR = log(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
  5133. $t = self::IMARGUMENT($complexNumber);
  5134. if ($parsedComplex['suffix'] == '') {
  5135. return self::COMPLEX($logR,$t);
  5136. } else {
  5137. return self::COMPLEX($logR,$t,$parsedComplex['suffix']);
  5138. }
  5139. }
  5140. /**
  5141. * IMLOG10
  5142. *
  5143. * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
  5144. *
  5145. * @param string $complexNumber
  5146. * @return string
  5147. */
  5148. public static function IMLOG10($complexNumber) {
  5149. $complexNumber = self::flattenSingleValue($complexNumber);
  5150. $parsedComplex = self::parseComplex($complexNumber);
  5151. if (!is_array($parsedComplex)) {
  5152. return $parsedComplex;
  5153. }
  5154. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5155. return self::$_errorCodes['num'];
  5156. } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5157. return log10($parsedComplex['real']);
  5158. }
  5159. return self::IMPRODUCT(log10(EULER),self::IMLN($complexNumber));
  5160. }
  5161. /**
  5162. * IMLOG2
  5163. *
  5164. * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
  5165. *
  5166. * @param string $complexNumber
  5167. * @return string
  5168. */
  5169. public static function IMLOG2($complexNumber) {
  5170. $complexNumber = self::flattenSingleValue($complexNumber);
  5171. $parsedComplex = self::parseComplex($complexNumber);
  5172. if (!is_array($parsedComplex)) {
  5173. return $parsedComplex;
  5174. }
  5175. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5176. return self::$_errorCodes['num'];
  5177. } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5178. return log($parsedComplex['real'],2);
  5179. }
  5180. return self::IMPRODUCT(log(EULER,2),self::IMLN($complexNumber));
  5181. }
  5182. /**
  5183. * IMEXP
  5184. *
  5185. * Returns the exponential of a complex number in x + yi or x + yj text format.
  5186. *
  5187. * @param string $complexNumber
  5188. * @return string
  5189. */
  5190. public static function IMEXP($complexNumber) {
  5191. $complexNumber = self::flattenSingleValue($complexNumber);
  5192. $parsedComplex = self::parseComplex($complexNumber);
  5193. if (!is_array($parsedComplex)) {
  5194. return $parsedComplex;
  5195. }
  5196. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5197. return '1';
  5198. }
  5199. $e = exp($parsedComplex['real']);
  5200. $eX = $e * cos($parsedComplex['imaginary']);
  5201. $eY = $e * sin($parsedComplex['imaginary']);
  5202. if ($parsedComplex['suffix'] == '') {
  5203. return self::COMPLEX($eX,$eY);
  5204. } else {
  5205. return self::COMPLEX($eX,$eY,$parsedComplex['suffix']);
  5206. }
  5207. }
  5208. /**
  5209. * IMPOWER
  5210. *
  5211. * Returns a complex number in x + yi or x + yj text format raised to a power.
  5212. *
  5213. * @param string $complexNumber
  5214. * @return string
  5215. */
  5216. public static function IMPOWER($complexNumber,$realNumber) {
  5217. $complexNumber = self::flattenSingleValue($complexNumber);
  5218. $realNumber = self::flattenSingleValue($realNumber);
  5219. if (!is_numeric($realNumber)) {
  5220. return self::$_errorCodes['value'];
  5221. }
  5222. $parsedComplex = self::parseComplex($complexNumber);
  5223. if (!is_array($parsedComplex)) {
  5224. return $parsedComplex;
  5225. }
  5226. $r = sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
  5227. $rPower = pow($r,$realNumber);
  5228. $theta = self::IMARGUMENT($complexNumber) * $realNumber;
  5229. if ($parsedComplex['imaginary'] == 0.0) {
  5230. return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
  5231. } else {
  5232. return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
  5233. }
  5234. }
  5235. /**
  5236. * IMDIV
  5237. *
  5238. * Returns the quotient of two complex numbers in x + yi or x + yj text format.
  5239. *
  5240. * @param string $complexDividend
  5241. * @param string $complexDivisor
  5242. * @return real
  5243. */
  5244. public static function IMDIV($complexDividend,$complexDivisor) {
  5245. $complexDividend = self::flattenSingleValue($complexDividend);
  5246. $complexDivisor = self::flattenSingleValue($complexDivisor);
  5247. $parsedComplexDividend = self::parseComplex($complexDividend);
  5248. if (!is_array($parsedComplexDividend)) {
  5249. return $parsedComplexDividend;
  5250. }
  5251. $parsedComplexDivisor = self::parseComplex($complexDivisor);
  5252. if (!is_array($parsedComplexDivisor)) {
  5253. return $parsedComplexDividend;
  5254. }
  5255. if ($parsedComplexDividend['suffix'] != $parsedComplexDivisor['suffix']) {
  5256. return self::$_errorCodes['num'];
  5257. }
  5258. $d1 = ($parsedComplexDividend['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['imaginary']);
  5259. $d2 = ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['real']) - ($parsedComplexDividend['real'] * $parsedComplexDivisor['imaginary']);
  5260. $d3 = ($parsedComplexDivisor['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDivisor['imaginary'] * $parsedComplexDivisor['imaginary']);
  5261. return $d1/$d3.$d2/$d3.$parsedComplexDivisor['suffix'];
  5262. }
  5263. /**
  5264. * IMSUB
  5265. *
  5266. * Returns the difference of two complex numbers in x + yi or x + yj text format.
  5267. *
  5268. * @param string $complexNumber1
  5269. * @param string $complexNumber2
  5270. * @return real
  5271. */
  5272. public static function IMSUB($complexNumber1,$complexNumber2) {
  5273. $complexNumber1 = self::flattenSingleValue($complexNumber1);
  5274. $complexNumber2 = self::flattenSingleValue($complexNumber2);
  5275. $parsedComplex1 = self::parseComplex($complexNumber1);
  5276. if (!is_array($parsedComplex1)) {
  5277. return $parsedComplex1;
  5278. }
  5279. $parsedComplex2 = self::parseComplex($complexNumber2);
  5280. if (!is_array($parsedComplex2)) {
  5281. return $parsedComplex2;
  5282. }
  5283. if ($parsedComplex1['suffix'] != $parsedComplex2['suffix']) {
  5284. return self::$_errorCodes['num'];
  5285. }
  5286. $d1 = $parsedComplex1['real'] - $parsedComplex2['real'];
  5287. $d2 = $parsedComplex1['imaginary'] - $parsedComplex2['imaginary'];
  5288. return self::COMPLEX($d1,$d2,$parsedComplex1['suffix']);
  5289. }
  5290. /**
  5291. * IMSUM
  5292. *
  5293. * Returns the sum of two or more complex numbers in x + yi or x + yj text format.
  5294. *
  5295. * @param array of mixed Data Series
  5296. * @return real
  5297. */
  5298. public static function IMSUM() {
  5299. // Return value
  5300. $returnValue = self::parseComplex('0');
  5301. $activeSuffix = '';
  5302. // Loop through the arguments
  5303. $aArgs = self::flattenArray(func_get_args());
  5304. foreach ($aArgs as $arg) {
  5305. $parsedComplex = self::parseComplex($arg);
  5306. if (!is_array($parsedComplex)) {
  5307. return $parsedComplex;
  5308. }
  5309. if ($activeSuffix == '') {
  5310. $activeSuffix = $parsedComplex['suffix'];
  5311. } elseif ($activeSuffix != $parsedComplex['suffix']) {
  5312. return self::$_errorCodes['num'];
  5313. }
  5314. $returnValue['real'] += $parsedComplex['real'];
  5315. $returnValue['imaginary'] += $parsedComplex['imaginary'];
  5316. }
  5317. if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
  5318. return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
  5319. }
  5320. /**
  5321. * IMPRODUCT
  5322. *
  5323. * Returns the product of two or more complex numbers in x + yi or x + yj text format.
  5324. *
  5325. * @param array of mixed Data Series
  5326. * @return real
  5327. */
  5328. public static function IMPRODUCT() {
  5329. // Return value
  5330. $returnValue = self::parseComplex('1');
  5331. $activeSuffix = '';
  5332. // Loop through the arguments
  5333. $aArgs = self::flattenArray(func_get_args());
  5334. foreach ($aArgs as $arg) {
  5335. $parsedComplex = self::parseComplex($arg);
  5336. if (!is_array($parsedComplex)) {
  5337. return $parsedComplex;
  5338. }
  5339. $workValue = $returnValue;
  5340. if (($parsedComplex['suffix'] != '') && ($activeSuffix == '')) {
  5341. $activeSuffix = $parsedComplex['suffix'];
  5342. } elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
  5343. return self::$_errorCodes['num'];
  5344. }
  5345. $returnValue['real'] = ($workValue['real'] * $parsedComplex['real']) - ($workValue['imaginary'] * $parsedComplex['imaginary']);
  5346. $returnValue['imaginary'] = ($workValue['real'] * $parsedComplex['imaginary']) + ($workValue['imaginary'] * $parsedComplex['real']);
  5347. }
  5348. if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
  5349. return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
  5350. }
  5351. /**
  5352. * BESSELI
  5353. *
  5354. * Returns the modified Bessel function, which is equivalent to the Bessel function evaluated for purely imaginary arguments
  5355. *
  5356. * @param float $x
  5357. * @param float $n
  5358. * @return int
  5359. */
  5360. public static function BESSELI($x, $n) {
  5361. $x = self::flattenSingleValue($x);
  5362. $n = floor(self::flattenSingleValue($n));
  5363. if ((is_numeric($x)) && (is_numeric($n))) {
  5364. if ($n < 0) {
  5365. return self::$_errorCodes['num'];
  5366. }
  5367. $f_2_PI = 2 * pi();
  5368. if (abs($x) <= 30) {
  5369. $fTerm = pow($x / 2, $n) / self::FACT($n);
  5370. $nK = 1;
  5371. $fResult = $fTerm;
  5372. $fSqrX = pow($x,2) / 4;
  5373. do {
  5374. $fTerm *= $fSqrX;
  5375. $fTerm /= ($nK * ($nK + $n));
  5376. $fResult += $fTerm;
  5377. } while ((abs($fTerm) > 1e-10) && (++$nK < 100));
  5378. } else {
  5379. $fXAbs = abs($x);
  5380. $fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs);
  5381. if (($n && 1) && ($x < 0)) {
  5382. $fResult = -$fResult;
  5383. }
  5384. }
  5385. return $fResult;
  5386. }
  5387. return self::$_errorCodes['value'];
  5388. }
  5389. /**
  5390. * BESSELJ
  5391. *
  5392. * Returns the Bessel function
  5393. *
  5394. * @param float $x
  5395. * @param float $n
  5396. * @return int
  5397. */
  5398. public static function BESSELJ($x, $n) {
  5399. $x = self::flattenSingleValue($x);
  5400. $n = floor(self::flattenSingleValue($n));
  5401. if ((is_numeric($x)) && (is_numeric($n))) {
  5402. if ($n < 0) {
  5403. return self::$_errorCodes['num'];
  5404. }
  5405. $f_2_DIV_PI = 2 / pi();
  5406. $f_PI_DIV_2 = pi() / 2;
  5407. $f_PI_DIV_4 = pi() / 4;
  5408. $fResult = 0;
  5409. if (abs($x) <= 30) {
  5410. $fTerm = pow($x / 2, $n) / self::FACT($n);
  5411. $nK = 1;
  5412. $fResult = $fTerm;
  5413. $fSqrX = pow($x,2) / -4;
  5414. do {
  5415. $fTerm *= $fSqrX;
  5416. $fTerm /= ($nK * ($nK + $n));
  5417. $fResult += $fTerm;
  5418. } while ((abs($fTerm) > 1e-10) && (++$nK < 100));
  5419. } else {
  5420. $fXAbs = abs($x);
  5421. $fResult = sqrt($f_2_DIV_PI / $fXAbs) * cos($fXAbs - $n * $f_PI_DIV_2 - $f_PI_DIV_4);
  5422. if (($n && 1) && ($x < 0)) {
  5423. $fResult = -$fResult;
  5424. }
  5425. }
  5426. return $fResult;
  5427. }
  5428. return self::$_errorCodes['value'];
  5429. }
  5430. private static function Besselk0($fNum) {
  5431. if ($fNum <= 2) {
  5432. $fNum2 = $fNum * 0.5;
  5433. $y = pow($fNum2,2);
  5434. $fRet = -log($fNum2) * self::BESSELI($fNum, 0) +
  5435. (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y *
  5436. (0.10750e-3 + $y * 0.74e-5))))));
  5437. } else {
  5438. $y = 2 / $fNum;
  5439. $fRet = exp(-$fNum) / sqrt($fNum) *
  5440. (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y *
  5441. (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
  5442. }
  5443. return $fRet;
  5444. }
  5445. private static function Besselk1($fNum) {
  5446. if ($fNum <= 2) {
  5447. $fNum2 = $fNum * 0.5;
  5448. $y = pow($fNum2,2);
  5449. $fRet = log($fNum2) * self::BESSELI($fNum, 1) +
  5450. (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y *
  5451. (-0.110404e-2 + $y * (-0.4686e-4))))))) / $fNum;
  5452. } else {
  5453. $y = 2 / $fNum;
  5454. $fRet = exp(-$fNum) / sqrt($fNum) *
  5455. (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y *
  5456. (0.325614e-2 + $y * (-0.68245e-3)))))));
  5457. }
  5458. return $fRet;
  5459. }
  5460. /**
  5461. * BESSELK
  5462. *
  5463. * Returns the modified Bessel function, which is equivalent to the Bessel functions evaluated for purely imaginary arguments.
  5464. *
  5465. * @param float $x
  5466. * @param float $n
  5467. * @return int
  5468. */
  5469. public static function BESSELK($x, $ord) {
  5470. $x = self::flattenSingleValue($x);
  5471. $n = floor(self::flattenSingleValue($ord));
  5472. if ((is_numeric($x)) && (is_numeric($ord))) {
  5473. if ($ord < 0) {
  5474. return self::$_errorCodes['num'];
  5475. }
  5476. switch($ord) {
  5477. case 0 : return self::Besselk0($x);
  5478. break;
  5479. case 1 : return self::Besselk1($x);
  5480. break;
  5481. default : $fTox = 2 / $x;
  5482. $fBkm = self::Besselk0($x);
  5483. $fBk = self::Besselk1($x);
  5484. for ($n = 1; $n < $ord; ++$n) {
  5485. $fBkp = $fBkm + $n * $fTox * $fBk;
  5486. $fBkm = $fBk;
  5487. $fBk = $fBkp;
  5488. }
  5489. }
  5490. return $fBk;
  5491. }
  5492. return self::$_errorCodes['value'];
  5493. }
  5494. private static function Bessely0($fNum) {
  5495. if ($fNum < 8) {
  5496. $y = pow($fNum,2);
  5497. $f1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * (-86327.92757 + $y * 228.4622733))));
  5498. $f2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * (47447.26470 + $y * (226.1030244 + $y))));
  5499. $fRet = $f1 / $f2 + 0.636619772 * self::BESSELJ($fNum, 0) * log($fNum);
  5500. } else {
  5501. $z = 8 / $fNum;
  5502. $y = pow($z,2);
  5503. $xx = $fNum - 0.785398164;
  5504. $f1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
  5505. $f2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * (-0.934945152e-7))));
  5506. $fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
  5507. }
  5508. return $fRet;
  5509. }
  5510. private static function Bessely1($fNum) {
  5511. if ($fNum < 8) {
  5512. $y = pow($fNum,2);
  5513. $f1 = $fNum * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * (0.7349264551e9 + $y *
  5514. (-0.4237922726e7 + $y * 0.8511937935e4)))));
  5515. $f2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
  5516. (0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
  5517. $fRet = $f1 / $f2 + 0.636619772 * ( self::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum);
  5518. } else {
  5519. $z = 8 / $fNum;
  5520. $y = $z * $z;
  5521. $xx = $fNum - 2.356194491;
  5522. $f1 = 1 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e6))));
  5523. $f2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * (-0.88228987e-6 + $y * 0.105787412e-6)));
  5524. $fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
  5525. #i12430# ...but this seems to work much better.
  5526. // $fRet = sqrt(0.636619772 / $fNum) * sin($fNum - 2.356194491);
  5527. }
  5528. return $fRet;
  5529. }
  5530. /**
  5531. * BESSELY
  5532. *
  5533. * Returns the Bessel function, which is also called the Weber function or the Neumann function.
  5534. *
  5535. * @param float $x
  5536. * @param float $n
  5537. * @return int
  5538. */
  5539. public static function BESSELY($x, $ord) {
  5540. $x = self::flattenSingleValue($x);
  5541. $n = floor(self::flattenSingleValue($ord));
  5542. if ((is_numeric($x)) && (is_numeric($ord))) {
  5543. if ($ord < 0) {
  5544. return self::$_errorCodes['num'];
  5545. }
  5546. switch($ord) {
  5547. case 0 : return self::Bessely0($x);
  5548. break;
  5549. case 1 : return self::Bessely1($x);
  5550. break;
  5551. default: $fTox = 2 / $x;
  5552. $fBym = self::Bessely0($x);
  5553. $fBy = self::Bessely1($x);
  5554. for ($n = 1; $n < $ord; ++$n) {
  5555. $fByp = $n * $fTox * $fBy - $fBym;
  5556. $fBym = $fBy;
  5557. $fBy = $fByp;
  5558. }
  5559. }
  5560. return $fBy;
  5561. }
  5562. return self::$_errorCodes['value'];
  5563. }
  5564. /**
  5565. * DELTA
  5566. *
  5567. * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
  5568. *
  5569. * @param float $a
  5570. * @param float $b
  5571. * @return int
  5572. */
  5573. public static function DELTA($a, $b=0) {
  5574. $a = self::flattenSingleValue($a);
  5575. $b = self::flattenSingleValue($b);
  5576. return (int) ($a == $b);
  5577. }
  5578. /**
  5579. * GESTEP
  5580. *
  5581. * Returns 1 if number = step; returns 0 (zero) otherwise
  5582. *
  5583. * @param float $number
  5584. * @param float $step
  5585. * @return int
  5586. */
  5587. public static function GESTEP($number, $step=0) {
  5588. $number = self::flattenSingleValue($number);
  5589. $step = self::flattenSingleValue($step);
  5590. return (int) ($number >= $step);
  5591. }
  5592. //
  5593. // Private method to calculate the erf value
  5594. //
  5595. private static $two_sqrtpi = 1.128379167095512574;
  5596. private static $rel_error = 1E-15;
  5597. private static function erfVal($x) {
  5598. if (abs($x) > 2.2) {
  5599. return 1 - self::erfcVal($x);
  5600. }
  5601. $sum = $term = $x;
  5602. $xsqr = pow($x,2);
  5603. $j = 1;
  5604. do {
  5605. $term *= $xsqr / $j;
  5606. $sum -= $term / (2 * $j + 1);
  5607. ++$j;
  5608. $term *= $xsqr / $j;
  5609. $sum += $term / (2 * $j + 1);
  5610. ++$j;
  5611. if ($sum == 0) {
  5612. break;
  5613. }
  5614. } while (abs($term / $sum) > self::$rel_error);
  5615. return self::$two_sqrtpi * $sum;
  5616. }
  5617. /**
  5618. * ERF
  5619. *
  5620. * Returns the error function integrated between lower_limit and upper_limit
  5621. *
  5622. * @param float $lower lower bound for integrating ERF
  5623. * @param float $upper upper bound for integrating ERF.
  5624. * If omitted, ERF integrates between zero and lower_limit
  5625. * @return int
  5626. */
  5627. public static function ERF($lower, $upper = 0) {
  5628. $lower = self::flattenSingleValue($lower);
  5629. $upper = self::flattenSingleValue($upper);
  5630. if ((is_numeric($lower)) && (is_numeric($upper))) {
  5631. if (($lower < 0) || ($upper < 0)) {
  5632. return self::$_errorCodes['num'];
  5633. }
  5634. if ($upper > $lower) {
  5635. return self::erfVal($upper) - self::erfVal($lower);
  5636. } else {
  5637. return self::erfVal($lower) - self::erfVal($upper);
  5638. }
  5639. }
  5640. return self::$_errorCodes['value'];
  5641. }
  5642. //
  5643. // Private method to calculate the erfc value
  5644. //
  5645. private static $one_sqrtpi = 0.564189583547756287;
  5646. private static function erfcVal($x) {
  5647. if (abs($x) < 2.2) {
  5648. return 1 - self::erfVal($x);
  5649. }
  5650. if ($x < 0) {
  5651. return 2 - self::erfc(-$x);
  5652. }
  5653. $a = $n = 1;
  5654. $b = $c = $x;
  5655. $d = pow($x,2) + 0.5;
  5656. $q1 = $q2 = $b / $d;
  5657. $t = 0;
  5658. do {
  5659. $t = $a * $n + $b * $x;
  5660. $a = $b;
  5661. $b = $t;
  5662. $t = $c * $n + $d * $x;
  5663. $c = $d;
  5664. $d = $t;
  5665. $n += 0.5;
  5666. $q1 = $q2;
  5667. $q2 = $b / $d;
  5668. } while ((abs($q1 - $q2) / $q2) > self::$rel_error);
  5669. return self::$one_sqrtpi * exp(-$x * $x) * $q2;
  5670. }
  5671. /**
  5672. * ERFC
  5673. *
  5674. * Returns the complementary ERF function integrated between x and infinity
  5675. *
  5676. * @param float $x The lower bound for integrating ERF
  5677. * @return int
  5678. */
  5679. public static function ERFC($x) {
  5680. $x = self::flattenSingleValue($x);
  5681. if (is_numeric($x)) {
  5682. if ($x < 0) {
  5683. return self::$_errorCodes['num'];
  5684. }
  5685. return self::erfcVal($x);
  5686. }
  5687. return self::$_errorCodes['value'];
  5688. }
  5689. /**
  5690. * EFFECT
  5691. *
  5692. * Returns the effective interest rate given the nominal rate and the number of compounding payments per year.
  5693. *
  5694. * @param float $nominal_rate Nominal interest rate
  5695. * @param int $npery Number of compounding payments per year
  5696. * @return float
  5697. */
  5698. public static function EFFECT($nominal_rate = 0, $npery = 0) {
  5699. $nominal_rate = self::flattenSingleValue($$nominal_rate);
  5700. $npery = (int)self::flattenSingleValue($npery);
  5701. // Validate parameters
  5702. if ($$nominal_rate <= 0 || $npery < 1) {
  5703. return self::$_errorCodes['num'];
  5704. }
  5705. return pow((1 + $nominal_rate / $npery), $npery) - 1;
  5706. }
  5707. /**
  5708. * NOMINAL
  5709. *
  5710. * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
  5711. *
  5712. * @param float $effect_rate Effective interest rate
  5713. * @param int $npery Number of compounding payments per year
  5714. * @return float
  5715. */
  5716. public static function NOMINAL($effect_rate = 0, $npery = 0) {
  5717. $effect_rate = self::flattenSingleValue($effect_rate);
  5718. $npery = (int)self::flattenSingleValue($npery);
  5719. // Validate parameters
  5720. if ($effect_rate <= 0 || $npery < 1) {
  5721. return self::$_errorCodes['num'];
  5722. }
  5723. // Calculate
  5724. return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
  5725. }
  5726. /**
  5727. * PV
  5728. *
  5729. * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
  5730. *
  5731. * @param float $rate Interest rate per period
  5732. * @param int $nper Number of periods
  5733. * @param float $pmt Periodic payment (annuity)
  5734. * @param float $fv Future Value
  5735. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  5736. * @return float
  5737. */
  5738. public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0) {
  5739. $rate = self::flattenSingleValue($rate);
  5740. $nper = self::flattenSingleValue($nper);
  5741. $pmt = self::flattenSingleValue($pmt);
  5742. $fv = self::flattenSingleValue($fv);
  5743. $type = self::flattenSingleValue($type);
  5744. // Validate parameters
  5745. if ($type != 0 && $type != 1) {
  5746. return self::$_errorCodes['num'];
  5747. }
  5748. // Calculate
  5749. if (!is_null($rate) && $rate != 0) {
  5750. return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
  5751. } else {
  5752. return -$fv - $pmt * $nper;
  5753. }
  5754. }
  5755. /**
  5756. * FV
  5757. *
  5758. * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
  5759. *
  5760. * @param float $rate Interest rate per period
  5761. * @param int $nper Number of periods
  5762. * @param float $pmt Periodic payment (annuity)
  5763. * @param float $pv Present Value
  5764. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  5765. * @return float
  5766. */
  5767. public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0) {
  5768. $rate = self::flattenSingleValue($rate);
  5769. $nper = self::flattenSingleValue($nper);
  5770. $pmt = self::flattenSingleValue($pmt);
  5771. $pv = self::flattenSingleValue($pv);
  5772. $type = self::flattenSingleValue($type);
  5773. // Validate parameters
  5774. if ($type != 0 && $type != 1) {
  5775. return self::$_errorCodes['num'];
  5776. }
  5777. // Calculate
  5778. if (!is_null($rate) && $rate != 0) {
  5779. return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
  5780. } else {
  5781. return -$pv - $pmt * $nper;
  5782. }
  5783. }
  5784. /**
  5785. * PMT
  5786. *
  5787. * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
  5788. *
  5789. * @param float $rate Interest rate per period
  5790. * @param int $nper Number of periods
  5791. * @param float $pv Present Value
  5792. * @param float $fv Future Value
  5793. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  5794. * @return float
  5795. */
  5796. public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) {
  5797. $rate = self::flattenSingleValue($rate);
  5798. $nper = self::flattenSingleValue($nper);
  5799. $pv = self::flattenSingleValue($pv);
  5800. $fv = self::flattenSingleValue($fv);
  5801. $type = self::flattenSingleValue($type);
  5802. // Validate parameters
  5803. if ($type != 0 && $type != 1) {
  5804. return self::$_errorCodes['num'];
  5805. }
  5806. // Calculate
  5807. if (!is_null($rate) && $rate != 0) {
  5808. return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
  5809. } else {
  5810. return (-$pv - $fv) / $nper;
  5811. }
  5812. }
  5813. /**
  5814. * NPER
  5815. *
  5816. * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
  5817. *
  5818. * @param float $rate Interest rate per period
  5819. * @param int $pmt Periodic payment (annuity)
  5820. * @param float $pv Present Value
  5821. * @param float $fv Future Value
  5822. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  5823. * @return float
  5824. */
  5825. public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0) {
  5826. $rate = self::flattenSingleValue($rate);
  5827. $pmt = self::flattenSingleValue($pmt);
  5828. $pv = self::flattenSingleValue($pv);
  5829. $fv = self::flattenSingleValue($fv);
  5830. $type = self::flattenSingleValue($type);
  5831. // Validate parameters
  5832. if ($type != 0 && $type != 1) {
  5833. return self::$_errorCodes['num'];
  5834. }
  5835. // Calculate
  5836. if (!is_null($rate) && $rate != 0) {
  5837. if ($pmt == 0 && $pv == 0) {
  5838. return self::$_errorCodes['num'];
  5839. }
  5840. return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
  5841. } else {
  5842. if ($pmt == 0) {
  5843. return self::$_errorCodes['num'];
  5844. }
  5845. return (-$pv -$fv) / $pmt;
  5846. }
  5847. }
  5848. /**
  5849. * NPV
  5850. *
  5851. * Returns the Net Present Value of a cash flow series given a discount rate.
  5852. *
  5853. * @param float Discount interest rate
  5854. * @param array Cash flow series
  5855. * @return float
  5856. */
  5857. public static function NPV() {
  5858. // Return value
  5859. $returnValue = 0;
  5860. // Loop trough arguments
  5861. $aArgs = self::flattenArray(func_get_args());
  5862. // Calculate
  5863. $rate = array_shift($aArgs);
  5864. for ($i = 1; $i <= count($aArgs); ++$i) {
  5865. // Is it a numeric value?
  5866. if (is_numeric($aArgs[$i - 1])) {
  5867. $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
  5868. }
  5869. }
  5870. // Return
  5871. return $returnValue;
  5872. }
  5873. /**
  5874. * ACCRINT
  5875. *
  5876. * Computes the accrued interest for a security that pays periodic interest.
  5877. *
  5878. * @param int $issue
  5879. * @param int $firstInterest
  5880. * @param int $settlement
  5881. * @param int $rate
  5882. * @param int $par
  5883. * @param int $frequency
  5884. * @param int $basis
  5885. * @return int The accrued interest for a security that pays periodic interest.
  5886. */
  5887. /*
  5888. public static function ACCRINT($issue = 0, $firstInterest = 0, $settlement = 0, $rate = 0, $par = 1000, $frequency = 1, $basis = 0) {
  5889. $issue = self::flattenSingleValue($issue);
  5890. $firstInterest = self::flattenSingleValue($firstInterest);
  5891. $settlement = self::flattenSingleValue($settlement);
  5892. $rate = self::flattenSingleValue($rate);
  5893. $par = self::flattenSingleValue($par);
  5894. $frequency = self::flattenSingleValue($frequency);
  5895. $basis = self::flattenSingleValue($basis);
  5896. // Perform checks
  5897. if ($issue >= $settlement || $rate <= 0 || $par <= 0 || !($frequency == 1 || $frequency == 2 || $frequency == 4) || $basis < 0 || $basis > 4) return self::$_errorCodes['num'];
  5898. // Calculate value
  5899. return $par * ($rate / $frequency) *
  5900. }
  5901. */
  5902. /**
  5903. * SLN
  5904. *
  5905. * Returns the straight-line depreciation of an asset for one period
  5906. *
  5907. * @param cost Initial cost of the asset
  5908. * @param salvage Value at the end of the depreciation
  5909. * @param life Number of periods over which the asset is depreciated
  5910. * @return float
  5911. */
  5912. public static function SLN($cost, $salvage, $life) {
  5913. $cost = self::flattenSingleValue($cost);
  5914. $salvage = self::flattenSingleValue($salvage);
  5915. $life = self::flattenSingleValue($life);
  5916. // Calculate
  5917. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
  5918. if ($life < 0) {
  5919. return self::$_errorCodes['num'];
  5920. }
  5921. return ($cost - $salvage) / $life;
  5922. }
  5923. return self::$_errorCodes['value'];
  5924. }
  5925. /**
  5926. * CELL_ADDRESS
  5927. *
  5928. * Returns the straight-line depreciation of an asset for one period
  5929. *
  5930. * @param row Row number to use in the cell reference
  5931. * @param column Column number to use in the cell reference
  5932. * @param relativity Flag indicating the type of reference to return
  5933. * @param sheetText Name of worksheet to use
  5934. * @return string
  5935. */
  5936. public static function CELL_ADDRESS($row, $column, $relativity=1, $referenceStyle=True, $sheetText='') {
  5937. $row = self::flattenSingleValue($row);
  5938. $column = self::flattenSingleValue($column);
  5939. $relativity = self::flattenSingleValue($relativity);
  5940. $sheetText = self::flattenSingleValue($sheetText);
  5941. if ($sheetText > '') {
  5942. if (strpos($sheetText,' ') !== False) { $sheetText = "'".$sheetText."'"; }
  5943. $sheetText .='!';
  5944. }
  5945. if (!$referenceStyle) {
  5946. if (($relativity == 2) || ($relativity == 4)) { $column = '['.$column.']'; }
  5947. if (($relativity == 3) || ($relativity == 4)) { $row = '['.$row.']'; }
  5948. return $sheetText.'R'.$row.'C'.$column;
  5949. } else {
  5950. $rowRelative = $columnRelative = '$';
  5951. $column = PHPExcel_Cell::stringFromColumnIndex($column-1);
  5952. if (($relativity == 2) || ($relativity == 4)) { $columnRelative = ''; }
  5953. if (($relativity == 3) || ($relativity == 4)) { $rowRelative = ''; }
  5954. return $sheetText.$columnRelative.$column.$rowRelative.$row;
  5955. }
  5956. }
  5957. public static function COLUMN($cellAddress=Null) {
  5958. if (is_null($cellAddress) || $cellAddress === '') {
  5959. return 0;
  5960. }
  5961. foreach($cellAddress as $columnKey => $value) {
  5962. return PHPExcel_Cell::columnIndexFromString($columnKey);
  5963. }
  5964. } // function COLUMN()
  5965. public static function ROW($cellAddress=Null) {
  5966. if ($cellAddress === Null) {
  5967. return 0;
  5968. }
  5969. foreach($cellAddress as $columnKey => $rowValue) {
  5970. foreach($rowValue as $rowKey => $cellValue) {
  5971. return $rowKey;
  5972. }
  5973. }
  5974. } // function ROW()
  5975. public static function OFFSET($cellAddress=Null,$rows=0,$columns=0,$height=null,$width=null) {
  5976. if ($cellAddress == Null) {
  5977. return 0;
  5978. }
  5979. foreach($cellAddress as $startColumnKey => $rowValue) {
  5980. $startColumnIndex = PHPExcel_Cell::columnIndexFromString($startColumnKey);
  5981. foreach($rowValue as $startRowKey => $cellValue) {
  5982. break 2;
  5983. }
  5984. }
  5985. foreach($cellAddress as $endColumnKey => $rowValue) {
  5986. foreach($rowValue as $endRowKey => $cellValue) {
  5987. }
  5988. }
  5989. $endColumnIndex = PHPExcel_Cell::columnIndexFromString($endColumnKey);
  5990. $startColumnIndex += --$columns;
  5991. $startRowKey += $rows;
  5992. if ($width == null) {
  5993. $endColumnIndex += $columns -1;
  5994. } else {
  5995. $endColumnIndex = $startColumnIndex + $width;
  5996. }
  5997. if ($height == null) {
  5998. $endRowKey += $rows;
  5999. } else {
  6000. $endRowKey = $startRowKey + $height -1;
  6001. }
  6002. if (($startColumnIndex < 0) || ($startRowKey <= 0)) {
  6003. return self::$_errorCodes['reference'];
  6004. }
  6005. $startColumnKey = PHPExcel_Cell::stringFromColumnIndex($startColumnIndex);
  6006. $endColumnKey = PHPExcel_Cell::stringFromColumnIndex($endColumnIndex);
  6007. $startCell = $startColumnKey.$startRowKey;
  6008. $endCell = $endColumnKey.$endRowKey;
  6009. if ($startCell == $endCell) {
  6010. return $startColumnKey.$startRowKey;
  6011. } else {
  6012. return $startColumnKey.$startRowKey.':'.$endColumnKey.$endRowKey;
  6013. }
  6014. } // function COLUMN()
  6015. public static function CHOOSE() {
  6016. $chooseArgs = func_get_args();
  6017. $chosenEntry = self::flattenSingleValue(array_shift($chooseArgs));
  6018. $entryCount = count($chooseArgs) - 1;
  6019. if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
  6020. --$chosenEntry;
  6021. } else {
  6022. return self::$_errorCodes['value'];
  6023. }
  6024. $chosenEntry = floor($chosenEntry);
  6025. if (($chosenEntry <= 0) || ($chosenEntry > $entryCount)) {
  6026. return self::$_errorCodes['value'];
  6027. }
  6028. if (is_array($chooseArgs[$chosenEntry])) {
  6029. return self::flattenArray($chooseArgs[$chosenEntry]);
  6030. } else {
  6031. return $chooseArgs[$chosenEntry];
  6032. }
  6033. }
  6034. /**
  6035. * MATCH
  6036. * The MATCH function searches for a specified item in a range of cells
  6037. * @param lookup_value The value that you want to match in lookup_array
  6038. * @param lookup_array The range of cells being searched
  6039. * @param match_type The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. If match_type is 1 or -1, the list has to be ordered.
  6040. * @return integer the relative position of the found item
  6041. */
  6042. public static function MATCH($lookup_value, $lookup_array, $match_type=1) {
  6043. // flatten the lookup_array
  6044. $lookup_array = self::flattenArray($lookup_array);
  6045. // flatten lookup_value since it may be a cell reference to a value or the value itself
  6046. $lookup_value = self::flattenSingleValue($lookup_value);
  6047. // MATCH is not case sensitive
  6048. $lookup_value = strtolower($lookup_value);
  6049. /*
  6050. echo "--------------------<br>looking for $lookup_value in <br>";
  6051. print_r($lookup_array);
  6052. echo "<br>";
  6053. //return 1;
  6054. /**/
  6055. // **
  6056. // check inputs
  6057. // **
  6058. // lookup_value type has to be number, text, or logical values
  6059. if (!is_numeric($lookup_value) && !is_string($lookup_value) && !is_bool($lookup_value)){
  6060. // error: lookup_array should contain only number, text, or logical values
  6061. //echo "error: lookup_array should contain only number, text, or logical values<br>";
  6062. return self::$_errorCodes['na'];
  6063. }
  6064. // match_type is 0, 1 or -1
  6065. if ($match_type!==0 && $match_type!==-1 && $match_type!==1){
  6066. // error: wrong value for match_type
  6067. //echo "error: wrong value for match_type<br>";
  6068. return self::$_errorCodes['na'];
  6069. }
  6070. // lookup_array should not be empty
  6071. if (sizeof($lookup_array)<=0){
  6072. // error: empty range
  6073. //echo "error: empty range ".sizeof($lookup_array)."<br>";
  6074. return self::$_errorCodes['na'];
  6075. }
  6076. // lookup_array should contain only number, text, or logical values
  6077. for ($i=0;$i<sizeof($lookup_array);++$i){
  6078. // check the type of the value
  6079. if (!is_numeric($lookup_array[$i]) && !is_string($lookup_array[$i]) && !is_bool($lookup_array[$i])){
  6080. // error: lookup_array should contain only number, text, or logical values
  6081. //echo "error: lookup_array should contain only number, text, or logical values<br>";
  6082. return self::$_errorCodes['na'];
  6083. }
  6084. // convert tpo lowercase
  6085. if (is_string($lookup_array[$i]))
  6086. $lookup_array[$i] = strtolower($lookup_array[$i]);
  6087. }
  6088. // if match_type is 1 or -1, the list has to be ordered
  6089. if($match_type==1 || $match_type==-1){
  6090. // **
  6091. // iniitialization
  6092. // store the last value
  6093. $iLastValue=$lookup_array[0];
  6094. // **
  6095. // loop on the cells
  6096. for ($i=0;$i<sizeof($lookup_array);++$i){
  6097. // check ascending order
  6098. if(($match_type==1 && $lookup_array[$i]<$iLastValue)
  6099. // OR check descending order
  6100. || ($match_type==-1 && $lookup_array[$i]>$iLastValue)){
  6101. // error: list is not ordered correctly
  6102. //echo "error: list is not ordered correctly<br>";
  6103. return self::$_errorCodes['na'];
  6104. }
  6105. }
  6106. }
  6107. // **
  6108. // find the match
  6109. // **
  6110. // loop on the cells
  6111. for ($i=0; $i < sizeof($lookup_array); ++$i){
  6112. // if match_type is 0 <=> find the first value that is exactly equal to lookup_value
  6113. if ($match_type==0 && $lookup_array[$i]==$lookup_value){
  6114. // this is the exact match
  6115. return $i+1;
  6116. }
  6117. // if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
  6118. if ($match_type==-1 && $lookup_array[$i] < $lookup_value){
  6119. if ($i<1){
  6120. // 1st cell was allready smaller than the lookup_value
  6121. break;
  6122. }
  6123. else
  6124. // the previous cell was the match
  6125. return $i;
  6126. }
  6127. // if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
  6128. if ($match_type==1 && $lookup_array[$i] > $lookup_value){
  6129. if ($i<1){
  6130. // 1st cell was allready bigger than the lookup_value
  6131. break;
  6132. }
  6133. else
  6134. // the previous cell was the match
  6135. return $i;
  6136. }
  6137. }
  6138. // unsuccessful in finding a match, return #N/A error value
  6139. //echo "unsuccessful in finding a match<br>";
  6140. return self::$_errorCodes['na'];
  6141. }
  6142. /**
  6143. * Uses an index to choose a value from a reference or array
  6144. * implemented: Return the value of a specified cell or array of cells Array form
  6145. * not implemented: Return a reference to specified cells Reference form
  6146. *
  6147. * @param range_array a range of cells or an array constant
  6148. * @param row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required.
  6149. * @param column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required.
  6150. */
  6151. public static function INDEX($range_array,$row_num=null,$column_num=null) {
  6152. // **
  6153. // check inputs
  6154. // **
  6155. // at least one of row_num and column_num is required
  6156. if ($row_num==null && $column_num==null){
  6157. // error: row_num and column_num are both undefined
  6158. //echo "error: row_num and column_num are both undefined<br>";
  6159. return self::$_errorCodes['value'];
  6160. }
  6161. // default values for row_num and column_num
  6162. if ($row_num==null){
  6163. $row_num = 1;
  6164. }
  6165. if ($column_num==null){
  6166. $column_num = 1;
  6167. }
  6168. /* debug
  6169. print_r($range_array);
  6170. echo "<br>$row_num , $column_num<br>";
  6171. /**/
  6172. // row_num and column_num may not have negative values
  6173. if (($row_num!=null && $row_num < 0) || ($column_num!=null && $column_num < 0)) {
  6174. // error: row_num or column_num has negative value
  6175. //echo "error: row_num or column_num has negative value<br>";
  6176. return self::$_errorCodes['value'];
  6177. }
  6178. // **
  6179. // convert column and row numbers into array indeces
  6180. // **
  6181. // array is zero based
  6182. --$column_num;
  6183. --$row_num;
  6184. // retrieve the columns
  6185. $columnKeys = array_keys($range_array);
  6186. // retrieve the rows
  6187. $rowKeys = array_keys($range_array[$columnKeys[0]]);
  6188. // test ranges
  6189. if ($column_num >= sizeof($columnKeys)){
  6190. // error: column_num is out of range
  6191. //echo "error: column_num is out of range - $column_num > ".sizeof($columnKeys)."<br>";
  6192. return self::$_errorCodes['reference'];
  6193. }
  6194. if ($row_num >= sizeof($rowKeys)){
  6195. // error: row_num is out of range
  6196. //echo "error: row_num is out of range - $row_num > ".sizeof($rowKeys)."<br>";
  6197. return self::$_errorCodes['reference'];
  6198. }
  6199. // compute and return result
  6200. return $range_array[$columnKeys[$column_num]][$rowKeys[$row_num]];
  6201. }
  6202. /* public static function INDEX($arrayValues,$rowNum = 0,$columnNum = 0) {
  6203. if (($rowNum < 0) || ($columnNum < 0)) {
  6204. return self::$_errorCodes['value'];
  6205. }
  6206. $columnKeys = array_keys($arrayValues);
  6207. $rowKeys = array_keys($arrayValues[$columnKeys[0]]);
  6208. if ($columnNum > count($columnKeys)) {
  6209. return self::$_errorCodes['value'];
  6210. } elseif ($columnNum == 0) {
  6211. if ($rowNum == 0) {
  6212. return $arrayValues;
  6213. }
  6214. $rowNum = $rowKeys[--$rowNum];
  6215. $returnArray = array();
  6216. foreach($arrayValues as $arrayColumn) {
  6217. $returnArray[] = $arrayColumn[$rowNum];
  6218. }
  6219. return $returnArray;
  6220. }
  6221. $columnNum = $columnKeys[--$columnNum];
  6222. if ($rowNum > count($rowKeys)) {
  6223. return self::$_errorCodes['value'];
  6224. } elseif ($rowNum == 0) {
  6225. return $arrayValues[$columnNum];
  6226. }
  6227. $rowNum = $rowKeys[--$rowNum];
  6228. return $arrayValues[$columnNum][$rowNum];
  6229. }
  6230. */
  6231. /**
  6232. * SYD
  6233. *
  6234. * Returns the sum-of-years' digits depreciation of an asset for a specified period.
  6235. *
  6236. * @param cost Initial cost of the asset
  6237. * @param salvage Value at the end of the depreciation
  6238. * @param life Number of periods over which the asset is depreciated
  6239. * @param period Period
  6240. * @return float
  6241. */
  6242. public static function SYD($cost, $salvage, $life, $period) {
  6243. $cost = self::flattenSingleValue($cost);
  6244. $salvage = self::flattenSingleValue($salvage);
  6245. $life = self::flattenSingleValue($life);
  6246. $period = self::flattenSingleValue($period);
  6247. // Calculate
  6248. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
  6249. if (($life < 1) || ($salvage < $life)) {
  6250. return self::$_errorCodes['num'];
  6251. }
  6252. return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
  6253. }
  6254. return self::$_errorCodes['value'];
  6255. }
  6256. /**
  6257. * TRANSPOSE
  6258. *
  6259. * @param mixed $value Value to check
  6260. * @return boolean
  6261. *
  6262. * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix.
  6263. */
  6264. public static function TRANSPOSE($matrixData) {
  6265. $returnMatrix = array();
  6266. $column = 0;
  6267. foreach($matrixData as $matrixRow) {
  6268. $row = 0;
  6269. foreach($matrixRow as $matrixCell) {
  6270. $returnMatrix[$column][$row] = $matrixCell;
  6271. ++$row;
  6272. }
  6273. ++$column;
  6274. }
  6275. return $returnMatrix;
  6276. }
  6277. /**
  6278. * MMULT
  6279. *
  6280. * @param mixed $value Value to check
  6281. * @return boolean
  6282. */
  6283. public static function MMULT($matrixData1,$matrixData2) {
  6284. $matrixAData = $matrixBData = array();
  6285. $rowA = 0;
  6286. foreach($matrixData1 as $matrixRow) {
  6287. $columnA = 0;
  6288. foreach($matrixRow as $matrixCell) {
  6289. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  6290. return self::$_errorCodes['value'];
  6291. }
  6292. $matrixAData[$columnA][$rowA] = $matrixCell;
  6293. ++$columnA;
  6294. }
  6295. ++$rowA;
  6296. }
  6297. $matrixA = new Matrix($matrixAData);
  6298. $rowB = 0;
  6299. foreach($matrixData2 as $matrixRow) {
  6300. $columnB = 0;
  6301. foreach($matrixRow as $matrixCell) {
  6302. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  6303. return self::$_errorCodes['value'];
  6304. }
  6305. $matrixBData[$columnB][$rowB] = $matrixCell;
  6306. ++$columnB;
  6307. }
  6308. ++$rowB;
  6309. }
  6310. $matrixB = new Matrix($matrixBData);
  6311. if (($rowA != $columnB) || ($rowB != $columnA)) {
  6312. return self::$_errorCodes['value'];
  6313. }
  6314. return $matrixA->times($matrixB)->getArray();
  6315. }
  6316. /**
  6317. * VLOOKUP
  6318. * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number.
  6319. * @param lookup_value The value that you want to match in lookup_array
  6320. * @param lookup_array The range of cells being searched
  6321. * @param index_number The column number in table_array from which the matching value must be returned. The first column is 1.
  6322. * @param not_exact_match Determines if you are looking for an exact match based on lookup_value.
  6323. * @return mixed The value of the found cell
  6324. */
  6325. public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match=true) {
  6326. // index_number must be greater than or equal to 1
  6327. if ($index_number < 1) {
  6328. return self::$_errorCodes['value'];
  6329. }
  6330. // index_number must be less than or equal to the number of columns in lookup_array
  6331. if ($index_number > count($lookup_array)) {
  6332. return self::$_errorCodes['reference'];
  6333. }
  6334. // re-index lookup_array with numeric keys starting at 1
  6335. array_unshift($lookup_array, array());
  6336. $lookup_array = array_slice(array_values($lookup_array), 1, count($lookup_array), true);
  6337. // look for an exact match
  6338. $row_number = array_search($lookup_value, $lookup_array[1]);
  6339. // if an exact match is required, we have what we need to return an appropriate response
  6340. if ($not_exact_match == false) {
  6341. if ($row_number === false) {
  6342. return self::$_errorCodes['na'];
  6343. } else {
  6344. return $lookup_array[$index_number][$row_number];
  6345. }
  6346. }
  6347. // TODO: The VLOOKUP spec in Excel states that, at this point, we should search for
  6348. // the highest value that is less than lookup_value. However, documentation on how string
  6349. // values should be treated here is sparse.
  6350. return self::$_errorCodes['na'];
  6351. }
  6352. /**
  6353. * LOOKUP
  6354. * The LOOKUP function searches for value either from a one-row or one-column range or from an array.
  6355. * @param lookup_value The value that you want to match in lookup_array
  6356. * @param lookup_vector The range of cells being searched
  6357. * @param result_vector The column from which the matching value must be returned
  6358. * @return mixed The value of the found cell
  6359. */
  6360. public static function LOOKUP($lookup_value, $lookup_vector, $result_vector=null) {
  6361. // check for LOOKUP Syntax (view Excel documentation)
  6362. if( is_null($result_vector) )
  6363. {
  6364. // TODO: Syntax 2 (array)
  6365. } else {
  6366. // Syntax 1 (vector)
  6367. // get key (column or row) of lookup_vector
  6368. $kl = key($lookup_vector);
  6369. // check if lookup_value exists in lookup_vector
  6370. if( in_array($lookup_value, $lookup_vector[$kl]) )
  6371. {
  6372. // FOUND IT! Get key of lookup_vector
  6373. $k_res = array_search($lookup_value, $lookup_vector[$kl]);
  6374. } else {
  6375. // value NOT FOUND
  6376. // Get the smallest value in lookup_vector
  6377. // The LOOKUP spec in Excel states --> IMPORTANT - The values in lookup_vector must be placed in ascending order!
  6378. $ksv = key($lookup_vector[$kl]);
  6379. $smallest_value = $lookup_vector[$kl][$ksv];
  6380. // If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP gives the #N/A error value.
  6381. if( $lookup_value < $smallest_value )
  6382. {
  6383. return self::$_errorCodes['na'];
  6384. } else {
  6385. // If LOOKUP can't find the lookup_value, it matches the largest value in lookup_vector that is less than or equal to lookup_value.
  6386. // IMPORTANT : In Excel Documentation is not clear what happen if lookup_value is text!
  6387. foreach( $lookup_vector[$kl] AS $kk => $value )
  6388. {
  6389. if( $lookup_value >= $value )
  6390. {
  6391. $k_res = $kk;
  6392. }
  6393. }
  6394. }
  6395. }
  6396. // Returns a value from the same position in result_vector
  6397. // get key (column or row) of result_vector
  6398. $kr = key($result_vector);
  6399. if( isset($result_vector[$kr][$k_res]) )
  6400. {
  6401. return $result_vector[$kr][$k_res];
  6402. } else {
  6403. // TODO: In Excel Documentation is not clear what happen here...
  6404. }
  6405. }
  6406. }
  6407. /**
  6408. * Flatten multidemensional array
  6409. *
  6410. * @param array $array Array to be flattened
  6411. * @return array Flattened array
  6412. */
  6413. public static function flattenArray($array) {
  6414. if(!is_array ( $array ) ){
  6415. $array = array ( $array );
  6416. }
  6417. $arrayValues = array();
  6418. foreach ($array as $value) {
  6419. if (is_scalar($value)) {
  6420. $arrayValues[] = self::flattenSingleValue($value);
  6421. } elseif (is_array($value)) {
  6422. $arrayValues = array_merge($arrayValues, self::flattenArray($value));
  6423. } else {
  6424. $arrayValues[] = $value;
  6425. }
  6426. }
  6427. return $arrayValues;
  6428. }
  6429. /**
  6430. * Convert an array with one element to a flat value
  6431. *
  6432. * @param mixed $value Array or flat value
  6433. * @return mixed
  6434. */
  6435. public static function flattenSingleValue($value = '') {
  6436. if (is_array($value)) {
  6437. $value = self::flattenSingleValue(array_pop($value));
  6438. }
  6439. return $value;
  6440. }
  6441. }
  6442. //
  6443. // There are a few mathematical functions that aren't available on all versions of PHP for all platforms
  6444. // These functions aren't available in Windows implementations of PHP prior to version 5.3.0
  6445. // So we test if they do exist for this version of PHP/operating platform; and if not we create them
  6446. //
  6447. if (!function_exists('acosh')) {
  6448. function acosh($x) {
  6449. return 2 * log(sqrt(($x + 1) / 2) + sqrt(($x - 1) / 2));
  6450. }
  6451. }
  6452. if (!function_exists('asinh')) {
  6453. function asinh($x) {
  6454. return log($x + sqrt(1 + $x * $x));
  6455. }
  6456. }
  6457. if (!function_exists('atanh')) {
  6458. function atanh($x) {
  6459. return (log(1 + $x) - log(1 - $x)) / 2;
  6460. }
  6461. }
  6462. ?>