PageRenderTime 71ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/branches/wi16-graphs/Classes/PHPExcel/Calculation/Functions.php

#
PHP | 9539 lines | 5840 code | 883 blank | 2816 comment | 1625 complexity | 3add6adc98cb272c1888cc27bbd91a7f 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 < 16) {
  37. ini_set('precision',16);
  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. include_once('PHPExcel/Shared/trend/trendClass.php');
  48. /**
  49. * PHPExcel_Calculation_Functions
  50. *
  51. * @category PHPExcel
  52. * @package PHPExcel_Calculation
  53. * @copyright Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
  54. */
  55. class PHPExcel_Calculation_Functions {
  56. /** constants */
  57. const COMPATIBILITY_EXCEL = 'Excel';
  58. const COMPATIBILITY_GNUMERIC = 'Gnumeric';
  59. const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
  60. const RETURNDATE_PHP_NUMERIC = 'P';
  61. const RETURNDATE_PHP_OBJECT = 'O';
  62. const RETURNDATE_EXCEL = 'E';
  63. /**
  64. * Compatibility mode to use for error checking and responses
  65. *
  66. * @var string
  67. */
  68. private static $compatibilityMode = self::COMPATIBILITY_EXCEL;
  69. /**
  70. * Data Type to use when returning date values
  71. *
  72. * @var integer
  73. */
  74. private static $ReturnDateType = self::RETURNDATE_PHP_NUMERIC;
  75. /**
  76. * List of error codes
  77. *
  78. * @var array
  79. */
  80. private static $_errorCodes = array( 'null' => '#NULL!',
  81. 'divisionbyzero' => '#DIV/0!',
  82. 'value' => '#VALUE!',
  83. 'reference' => '#REF!',
  84. 'name' => '#NAME?',
  85. 'num' => '#NUM!',
  86. 'na' => '#N/A',
  87. 'gettingdata' => '#GETTING_DATA'
  88. );
  89. /**
  90. * Set the Compatibility Mode
  91. *
  92. * @param string $compatibilityMode Compatibility Mode
  93. * Permitted values are:
  94. * PHPExcel_Calculation_Functions::COMPATIBILITY_EXCEL 'Excel'
  95. * PHPExcel_Calculation_Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
  96. * PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
  97. * @return boolean (Success or Failure)
  98. */
  99. public static function setCompatibilityMode($compatibilityMode) {
  100. if (($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
  101. ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
  102. ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  103. self::$compatibilityMode = $compatibilityMode;
  104. return True;
  105. }
  106. return False;
  107. } // function setCompatibilityMode()
  108. /**
  109. * Return the current Compatibility Mode
  110. *
  111. * @return string $compatibilityMode Compatibility Mode
  112. * Possible Return values are:
  113. * PHPExcel_Calculation_Functions::COMPATIBILITY_EXCEL 'Excel'
  114. * PHPExcel_Calculation_Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
  115. * PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
  116. */
  117. public static function getCompatibilityMode() {
  118. return self::$compatibilityMode;
  119. } // function getCompatibilityMode()
  120. /**
  121. * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized or PHP Object)
  122. *
  123. * @param integer $returnDateType Return Date Format
  124. * Permitted values are:
  125. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_NUMERIC 'P'
  126. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_OBJECT 'O'
  127. * PHPExcel_Calculation_Functions::RETURNDATE_EXCEL 'E'
  128. * @return boolean Success or failure
  129. */
  130. public static function setReturnDateType($returnDateType) {
  131. if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) ||
  132. ($returnDateType == self::RETURNDATE_PHP_OBJECT) ||
  133. ($returnDateType == self::RETURNDATE_EXCEL)) {
  134. self::$ReturnDateType = $returnDateType;
  135. return True;
  136. }
  137. return False;
  138. } // function setReturnDateType()
  139. /**
  140. * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized or PHP Object)
  141. *
  142. * @return integer $returnDateType Return Date Format
  143. * Possible Return values are:
  144. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_NUMERIC 'P'
  145. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_OBJECT 'O'
  146. * PHPExcel_Calculation_Functions::RETURNDATE_EXCEL 'E'
  147. */
  148. public static function getReturnDateType() {
  149. return self::$ReturnDateType;
  150. } // function getReturnDateType()
  151. /**
  152. * DUMMY
  153. *
  154. * @return string #NAME?
  155. */
  156. public static function DUMMY() {
  157. return '#Not Yet Implemented';
  158. } // function DUMMY()
  159. /**
  160. * NA
  161. *
  162. * @return string #N/A!
  163. */
  164. public static function NA() {
  165. return self::$_errorCodes['na'];
  166. } // function NA()
  167. /**
  168. * LOGICAL_AND
  169. *
  170. * Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
  171. *
  172. * Booleans arguments are treated as True or False as appropriate
  173. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  174. * If any argument value is a string, or a Null, it is ignored
  175. *
  176. * Quirk of Excel:
  177. * String values passed directly to the function rather than through a cell reference
  178. * e.g.=AND(1,"A",1)
  179. * will return a #VALUE! error, _not_ ignoring the string.
  180. * This behaviour is not replicated
  181. *
  182. * @param array of mixed Data Series
  183. * @return boolean
  184. */
  185. public static function LOGICAL_AND() {
  186. // Return value
  187. $returnValue = True;
  188. // Loop through the arguments
  189. $aArgs = self::flattenArray(func_get_args());
  190. $argCount = 0;
  191. foreach ($aArgs as $arg) {
  192. // Is it a boolean value?
  193. if (is_bool($arg)) {
  194. $returnValue = $returnValue && $arg;
  195. ++$argCount;
  196. } elseif ((is_numeric($arg)) && (!is_string($arg))) {
  197. $returnValue = $returnValue && ($arg != 0);
  198. ++$argCount;
  199. }
  200. }
  201. // Return
  202. if ($argCount == 0) {
  203. return self::$_errorCodes['value'];
  204. }
  205. return $returnValue;
  206. } // function LOGICAL_AND()
  207. /**
  208. * LOGICAL_OR
  209. *
  210. * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
  211. *
  212. * Booleans arguments are treated as True or False as appropriate
  213. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  214. * If any argument value is a string, or a Null, it is ignored
  215. *
  216. * @param array of mixed Data Series
  217. * @return boolean
  218. */
  219. public static function LOGICAL_OR() {
  220. // Return value
  221. $returnValue = False;
  222. // Loop through the arguments
  223. $aArgs = self::flattenArray(func_get_args());
  224. $argCount = 0;
  225. foreach ($aArgs as $arg) {
  226. // Is it a boolean value?
  227. if (is_bool($arg)) {
  228. $returnValue = $returnValue || $arg;
  229. ++$argCount;
  230. } elseif ((is_numeric($arg)) && (!is_string($arg))) {
  231. $returnValue = $returnValue || ($arg != 0);
  232. ++$argCount;
  233. }
  234. }
  235. // Return
  236. if ($argCount == 0) {
  237. return self::$_errorCodes['value'];
  238. }
  239. return $returnValue;
  240. } // function LOGICAL_OR()
  241. /**
  242. * LOGICAL_FALSE
  243. *
  244. * Returns FALSE.
  245. *
  246. * @return boolean
  247. */
  248. public static function LOGICAL_FALSE() {
  249. return False;
  250. } // function LOGICAL_FALSE()
  251. /**
  252. * LOGICAL_TRUE
  253. *
  254. * Returns TRUE.
  255. *
  256. * @return boolean
  257. */
  258. public static function LOGICAL_TRUE() {
  259. return True;
  260. } // function LOGICAL_TRUE()
  261. /**
  262. * ATAN2
  263. *
  264. * This function calculates the arc tangent of the two variables x and y. It is similar to
  265. * calculating the arc tangent of y / x, except that the signs of both arguments are used
  266. * to determine the quadrant of the result.
  267. * Note that Excel reverses the arguments, so we need to reverse them here before calling the
  268. * standard PHP atan() function
  269. *
  270. * @param float $x Number
  271. * @param float $y Number
  272. * @return float Square Root of Number * Pi
  273. */
  274. public static function REVERSE_ATAN2($x, $y) {
  275. $x = self::flattenSingleValue($x);
  276. $y = self::flattenSingleValue($y);
  277. return atan2($y, $x);
  278. } // function REVERSE_ATAN2()
  279. /**
  280. * SUM
  281. *
  282. * SUM computes the sum of all the values and cells referenced in the argument list.
  283. *
  284. * @param array of mixed Data Series
  285. * @return float
  286. */
  287. public static function SUM() {
  288. // Return value
  289. $returnValue = 0;
  290. // Loop through the arguments
  291. $aArgs = self::flattenArray(func_get_args());
  292. foreach ($aArgs as $arg) {
  293. // Is it a numeric value?
  294. if ((is_numeric($arg)) && (!is_string($arg))) {
  295. $returnValue += $arg;
  296. }
  297. }
  298. // Return
  299. return $returnValue;
  300. } // function SUM()
  301. /**
  302. * SUMSQ
  303. *
  304. * Returns the sum of the squares of the arguments
  305. *
  306. * @param array of mixed Data Series
  307. * @return float
  308. */
  309. public static function SUMSQ() {
  310. // Return value
  311. $returnValue = 0;
  312. // Loop trough arguments
  313. $aArgs = self::flattenArray(func_get_args());
  314. foreach ($aArgs as $arg) {
  315. // Is it a numeric value?
  316. if ((is_numeric($arg)) && (!is_string($arg))) {
  317. $returnValue += pow($arg,2);
  318. }
  319. }
  320. // Return
  321. return $returnValue;
  322. } // function SUMSQ()
  323. /**
  324. * PRODUCT
  325. *
  326. * PRODUCT returns the product of all the values and cells referenced in the argument list.
  327. *
  328. * @param array of mixed Data Series
  329. * @return float
  330. */
  331. public static function PRODUCT() {
  332. // Return value
  333. $returnValue = null;
  334. // Loop trough arguments
  335. $aArgs = self::flattenArray(func_get_args());
  336. foreach ($aArgs as $arg) {
  337. // Is it a numeric value?
  338. if ((is_numeric($arg)) && (!is_string($arg))) {
  339. if (is_null($returnValue)) {
  340. $returnValue = $arg;
  341. } else {
  342. $returnValue *= $arg;
  343. }
  344. }
  345. }
  346. // Return
  347. if (is_null($returnValue)) {
  348. return 0;
  349. }
  350. return $returnValue;
  351. } // function PRODUCT()
  352. /**
  353. * QUOTIENT
  354. *
  355. * QUOTIENT function returns the integer portion of a division.numerator is the divided number
  356. * and denominator is the divisor.
  357. *
  358. * @param array of mixed Data Series
  359. * @return float
  360. */
  361. public static function QUOTIENT() {
  362. // Return value
  363. $returnValue = null;
  364. // Loop trough arguments
  365. $aArgs = self::flattenArray(func_get_args());
  366. foreach ($aArgs as $arg) {
  367. // Is it a numeric value?
  368. if ((is_numeric($arg)) && (!is_string($arg))) {
  369. if (is_null($returnValue)) {
  370. if (($returnValue == 0) || ($arg == 0)) {
  371. $returnValue = 0;
  372. } else {
  373. $returnValue = $arg;
  374. }
  375. } else {
  376. if (($returnValue == 0) || ($arg == 0)) {
  377. $returnValue = 0;
  378. } else {
  379. $returnValue /= $arg;
  380. }
  381. }
  382. }
  383. }
  384. // Return
  385. return intval($returnValue);
  386. } // function QUOTIENT()
  387. /**
  388. * MIN
  389. *
  390. * MIN returns the value of the element of the values passed that has the smallest value,
  391. * with negative numbers considered smaller than positive numbers.
  392. *
  393. * @param array of mixed Data Series
  394. * @return float
  395. */
  396. public static function MIN() {
  397. // Return value
  398. $returnValue = null;
  399. // Loop trough arguments
  400. $aArgs = self::flattenArray(func_get_args());
  401. foreach ($aArgs as $arg) {
  402. // Is it a numeric value?
  403. if ((is_numeric($arg)) && (!is_string($arg))) {
  404. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  405. $returnValue = $arg;
  406. }
  407. }
  408. }
  409. // Return
  410. if(is_null($returnValue)) {
  411. return 0;
  412. }
  413. return $returnValue;
  414. } // function MIN()
  415. /**
  416. * MINA
  417. *
  418. * Returns the smallest value in a list of arguments, including numbers, text, and logical values
  419. *
  420. * @param array of mixed Data Series
  421. * @return float
  422. */
  423. public static function MINA() {
  424. // Return value
  425. $returnValue = null;
  426. // Loop through arguments
  427. $aArgs = self::flattenArray(func_get_args());
  428. foreach ($aArgs as $arg) {
  429. // Is it a numeric value?
  430. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  431. if (is_bool($arg)) {
  432. $arg = (integer) $arg;
  433. } elseif (is_string($arg)) {
  434. $arg = 0;
  435. }
  436. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  437. $returnValue = $arg;
  438. }
  439. }
  440. }
  441. // Return
  442. if(is_null($returnValue)) {
  443. return 0;
  444. }
  445. return $returnValue;
  446. } // function MINA()
  447. /**
  448. * SMALL
  449. *
  450. * Returns the nth smallest value in a data set. You can use this function to
  451. * select a value based on its relative standing.
  452. *
  453. * @param array of mixed Data Series
  454. * @param float Entry in the series to return
  455. * @return float
  456. */
  457. public static function SMALL() {
  458. $aArgs = self::flattenArray(func_get_args());
  459. // Calculate
  460. $n = array_pop($aArgs);
  461. if ((is_numeric($n)) && (!is_string($n))) {
  462. $mArgs = array();
  463. foreach ($aArgs as $arg) {
  464. // Is it a numeric value?
  465. if ((is_numeric($arg)) && (!is_string($arg))) {
  466. $mArgs[] = $arg;
  467. }
  468. }
  469. $count = self::COUNT($mArgs);
  470. $n = floor(--$n);
  471. if (($n < 0) || ($n >= $count) || ($count == 0)) {
  472. return self::$_errorCodes['num'];
  473. }
  474. sort($mArgs);
  475. return $mArgs[$n];
  476. }
  477. return self::$_errorCodes['value'];
  478. }
  479. /**
  480. * MAX
  481. *
  482. * MAX returns the value of the element of the values passed that has the highest value,
  483. * with negative numbers considered smaller than positive numbers.
  484. *
  485. * @param array of mixed Data Series
  486. * @return float
  487. */
  488. public static function MAX() {
  489. // Return value
  490. $returnValue = null;
  491. // Loop trough arguments
  492. $aArgs = self::flattenArray(func_get_args());
  493. foreach ($aArgs as $arg) {
  494. // Is it a numeric value?
  495. if ((is_numeric($arg)) && (!is_string($arg))) {
  496. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  497. $returnValue = $arg;
  498. }
  499. }
  500. }
  501. // Return
  502. if(is_null($returnValue)) {
  503. return 0;
  504. }
  505. return $returnValue;
  506. }
  507. /**
  508. * MAXA
  509. *
  510. * Returns the greatest value in a list of arguments, including numbers, text, and logical values
  511. *
  512. * @param array of mixed Data Series
  513. * @return float
  514. */
  515. public static function MAXA() {
  516. // Return value
  517. $returnValue = null;
  518. // Loop through arguments
  519. $aArgs = self::flattenArray(func_get_args());
  520. foreach ($aArgs as $arg) {
  521. // Is it a numeric value?
  522. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  523. if (is_bool($arg)) {
  524. $arg = (integer) $arg;
  525. } elseif (is_string($arg)) {
  526. $arg = 0;
  527. }
  528. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  529. $returnValue = $arg;
  530. }
  531. }
  532. }
  533. // Return
  534. if(is_null($returnValue)) {
  535. return 0;
  536. }
  537. return $returnValue;
  538. }
  539. /**
  540. * LARGE
  541. *
  542. * Returns the nth largest value in a data set. You can use this function to
  543. * select a value based on its relative standing.
  544. *
  545. * @param array of mixed Data Series
  546. * @param float Entry in the series to return
  547. * @return float
  548. *
  549. */
  550. public static function LARGE() {
  551. $aArgs = self::flattenArray(func_get_args());
  552. // Calculate
  553. $n = floor(array_pop($aArgs));
  554. if ((is_numeric($n)) && (!is_string($n))) {
  555. $mArgs = array();
  556. foreach ($aArgs as $arg) {
  557. // Is it a numeric value?
  558. if ((is_numeric($arg)) && (!is_string($arg))) {
  559. $mArgs[] = $arg;
  560. }
  561. }
  562. $count = self::COUNT($mArgs);
  563. $n = floor(--$n);
  564. if (($n < 0) || ($n >= $count) || ($count == 0)) {
  565. return self::$_errorCodes['num'];
  566. }
  567. rsort($mArgs);
  568. return $mArgs[$n];
  569. }
  570. return self::$_errorCodes['value'];
  571. }
  572. /**
  573. * PERCENTILE
  574. *
  575. * Returns the nth percentile of values in a range..
  576. *
  577. * @param array of mixed Data Series
  578. * @param float $entry Entry in the series to return
  579. * @return float
  580. */
  581. public static function PERCENTILE() {
  582. $aArgs = self::flattenArray(func_get_args());
  583. // Calculate
  584. $entry = array_pop($aArgs);
  585. if ((is_numeric($entry)) && (!is_string($entry))) {
  586. if (($entry < 0) || ($entry > 1)) {
  587. return self::$_errorCodes['num'];
  588. }
  589. $mArgs = array();
  590. foreach ($aArgs as $arg) {
  591. // Is it a numeric value?
  592. if ((is_numeric($arg)) && (!is_string($arg))) {
  593. $mArgs[] = $arg;
  594. }
  595. }
  596. $mValueCount = count($mArgs);
  597. if ($mValueCount > 0) {
  598. sort($mArgs);
  599. $count = self::COUNT($mArgs);
  600. $index = $entry * ($count-1);
  601. $iBase = floor($index);
  602. if ($index == $iBase) {
  603. return $mArgs[$index];
  604. } else {
  605. $iNext = $iBase + 1;
  606. $iProportion = $index - $iBase;
  607. return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion) ;
  608. }
  609. }
  610. }
  611. return self::$_errorCodes['value'];
  612. }
  613. /**
  614. * QUARTILE
  615. *
  616. * Returns the quartile of a data set.
  617. *
  618. * @param array of mixed Data Series
  619. * @param float $entry Entry in the series to return
  620. * @return float
  621. */
  622. public static function QUARTILE() {
  623. $aArgs = self::flattenArray(func_get_args());
  624. // Calculate
  625. $entry = floor(array_pop($aArgs));
  626. if ((is_numeric($entry)) && (!is_string($entry))) {
  627. $entry /= 4;
  628. if (($entry < 0) || ($entry > 1)) {
  629. return self::$_errorCodes['num'];
  630. }
  631. return self::PERCENTILE($aArgs,$entry);
  632. }
  633. return self::$_errorCodes['value'];
  634. }
  635. /**
  636. * COUNT
  637. *
  638. * Counts the number of cells that contain numbers within the list of arguments
  639. *
  640. * @param array of mixed Data Series
  641. * @return int
  642. */
  643. public static function COUNT() {
  644. // Return value
  645. $returnValue = 0;
  646. // Loop trough arguments
  647. $aArgs = self::flattenArray(func_get_args());
  648. foreach ($aArgs as $arg) {
  649. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  650. $arg = (int) $arg;
  651. }
  652. // Is it a numeric value?
  653. if ((is_numeric($arg)) && (!is_string($arg))) {
  654. ++$returnValue;
  655. }
  656. }
  657. // Return
  658. return $returnValue;
  659. }
  660. /**
  661. * COUNTBLANK
  662. *
  663. * Counts the number of empty cells within the list of arguments
  664. *
  665. * @param array of mixed Data Series
  666. * @return int
  667. */
  668. public static function COUNTBLANK() {
  669. // Return value
  670. $returnValue = 0;
  671. // Loop trough arguments
  672. $aArgs = self::flattenArray(func_get_args());
  673. foreach ($aArgs as $arg) {
  674. // Is it a blank cell?
  675. if ((is_null($arg)) || ((is_string($arg)) && ($arg == ''))) {
  676. ++$returnValue;
  677. }
  678. }
  679. // Return
  680. return $returnValue;
  681. }
  682. /**
  683. * COUNTA
  684. *
  685. * Counts the number of cells that are not empty within the list of arguments
  686. *
  687. * @param array of mixed Data Series
  688. * @return int
  689. */
  690. public static function COUNTA() {
  691. // Return value
  692. $returnValue = 0;
  693. // Loop through arguments
  694. $aArgs = self::flattenArray(func_get_args());
  695. foreach ($aArgs as $arg) {
  696. // Is it a numeric, boolean or string value?
  697. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  698. ++$returnValue;
  699. }
  700. }
  701. // Return
  702. return $returnValue;
  703. }
  704. /**
  705. * AVERAGE
  706. *
  707. * Returns the average (arithmetic mean) of the arguments
  708. *
  709. * @param array of mixed Data Series
  710. * @return float
  711. */
  712. public static function AVERAGE() {
  713. // Return value
  714. $returnValue = 0;
  715. // Loop through arguments
  716. $aArgs = self::flattenArray(func_get_args());
  717. $aCount = 0;
  718. foreach ($aArgs as $arg) {
  719. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  720. $arg = (integer) $arg;
  721. }
  722. // Is it a numeric value?
  723. if ((is_numeric($arg)) && (!is_string($arg))) {
  724. if (is_null($returnValue)) {
  725. $returnValue = $arg;
  726. } else {
  727. $returnValue += $arg;
  728. }
  729. ++$aCount;
  730. }
  731. }
  732. // Return
  733. if ($aCount > 0) {
  734. return $returnValue / $aCount;
  735. } else {
  736. return self::$_errorCodes['divisionbyzero'];
  737. }
  738. }
  739. /**
  740. * AVERAGEA
  741. *
  742. * Returns the average of its arguments, including numbers, text, and logical values
  743. *
  744. * @param array of mixed Data Series
  745. * @return float
  746. */
  747. public static function AVERAGEA() {
  748. // Return value
  749. $returnValue = null;
  750. // Loop through arguments
  751. $aArgs = self::flattenArray(func_get_args());
  752. $aCount = 0;
  753. foreach ($aArgs as $arg) {
  754. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  755. if (is_bool($arg)) {
  756. $arg = (integer) $arg;
  757. } elseif (is_string($arg)) {
  758. $arg = 0;
  759. }
  760. if (is_null($returnValue)) {
  761. $returnValue = $arg;
  762. } else {
  763. $returnValue += $arg;
  764. }
  765. ++$aCount;
  766. }
  767. }
  768. // Return
  769. if ($aCount > 0) {
  770. return $returnValue / $aCount;
  771. } else {
  772. return self::$_errorCodes['divisionbyzero'];
  773. }
  774. }
  775. /**
  776. * MEDIAN
  777. *
  778. * Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
  779. *
  780. * @param array of mixed Data Series
  781. * @return float
  782. */
  783. public static function MEDIAN() {
  784. // Return value
  785. $returnValue = self::$_errorCodes['num'];
  786. $mArgs = array();
  787. // Loop through arguments
  788. $aArgs = self::flattenArray(func_get_args());
  789. foreach ($aArgs as $arg) {
  790. // Is it a numeric value?
  791. if ((is_numeric($arg)) && (!is_string($arg))) {
  792. $mArgs[] = $arg;
  793. }
  794. }
  795. $mValueCount = count($mArgs);
  796. if ($mValueCount > 0) {
  797. sort($mArgs,SORT_NUMERIC);
  798. $mValueCount = $mValueCount / 2;
  799. if ($mValueCount == floor($mValueCount)) {
  800. $returnValue = ($mArgs[$mValueCount--] + $mArgs[$mValueCount]) / 2;
  801. } else {
  802. $mValueCount == floor($mValueCount);
  803. $returnValue = $mArgs[$mValueCount];
  804. }
  805. }
  806. // Return
  807. return $returnValue;
  808. }
  809. //
  810. // Special variant of array_count_values that isn't limited to strings and integers,
  811. // but can work with floating point numbers as values
  812. //
  813. private static function modeCalc($data) {
  814. $frequencyArray = array();
  815. foreach($data as $datum) {
  816. $found = False;
  817. foreach($frequencyArray as $key => $value) {
  818. if ((string)$value['value'] == (string)$datum) {
  819. ++$frequencyArray[$key]['frequency'];
  820. $found = True;
  821. break;
  822. }
  823. }
  824. if (!$found) {
  825. $frequencyArray[] = array('value' => $datum,
  826. 'frequency' => 1 );
  827. }
  828. }
  829. foreach($frequencyArray as $key => $value) {
  830. $frequencyList[$key] = $value['frequency'];
  831. $valueList[$key] = $value['value'];
  832. }
  833. array_multisort($frequencyList, SORT_DESC, $valueList, SORT_ASC, SORT_NUMERIC, $frequencyArray);
  834. if ($frequencyArray[0]['frequency'] == 1) {
  835. return self::NA();
  836. }
  837. return $frequencyArray[0]['value'];
  838. }
  839. /**
  840. * MODE
  841. *
  842. * Returns the most frequently occurring, or repetitive, value in an array or range of data
  843. *
  844. * @param array of mixed Data Series
  845. * @return float
  846. */
  847. public static function MODE() {
  848. // Return value
  849. $returnValue = self::NA();
  850. // Loop through arguments
  851. $aArgs = self::flattenArray(func_get_args());
  852. $mArgs = array();
  853. foreach ($aArgs as $arg) {
  854. // Is it a numeric value?
  855. if ((is_numeric($arg)) && (!is_string($arg))) {
  856. $mArgs[] = $arg;
  857. }
  858. }
  859. if (count($mArgs) > 0) {
  860. return self::modeCalc($mArgs);
  861. }
  862. // Return
  863. return $returnValue;
  864. }
  865. /**
  866. * DEVSQ
  867. *
  868. * Returns the sum of squares of deviations of data points from their sample mean.
  869. *
  870. * @param array of mixed Data Series
  871. * @return float
  872. */
  873. public static function DEVSQ() {
  874. // Return value
  875. $returnValue = null;
  876. $aMean = self::AVERAGE(func_get_args());
  877. if (!is_null($aMean)) {
  878. $aArgs = self::flattenArray(func_get_args());
  879. $aCount = -1;
  880. foreach ($aArgs as $arg) {
  881. // Is it a numeric value?
  882. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  883. $arg = (int) $arg;
  884. }
  885. if ((is_numeric($arg)) && (!is_string($arg))) {
  886. if (is_null($returnValue)) {
  887. $returnValue = pow(($arg - $aMean),2);
  888. } else {
  889. $returnValue += pow(($arg - $aMean),2);
  890. }
  891. ++$aCount;
  892. }
  893. }
  894. // Return
  895. if (is_null($returnValue)) {
  896. return self::$_errorCodes['num'];
  897. } else {
  898. return $returnValue;
  899. }
  900. }
  901. return self::NA();
  902. }
  903. /**
  904. * AVEDEV
  905. *
  906. * Returns the average of the absolute deviations of data points from their mean.
  907. * AVEDEV is a measure of the variability in a data set.
  908. *
  909. * @param array of mixed Data Series
  910. * @return float
  911. */
  912. public static function AVEDEV() {
  913. $aArgs = self::flattenArray(func_get_args());
  914. // Return value
  915. $returnValue = null;
  916. $aMean = self::AVERAGE($aArgs);
  917. if ($aMean != self::$_errorCodes['divisionbyzero']) {
  918. $aCount = 0;
  919. foreach ($aArgs as $arg) {
  920. if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  921. $arg = (integer) $arg;
  922. }
  923. // Is it a numeric value?
  924. if ((is_numeric($arg)) && (!is_string($arg))) {
  925. if (is_null($returnValue)) {
  926. $returnValue = abs($arg - $aMean);
  927. } else {
  928. $returnValue += abs($arg - $aMean);
  929. }
  930. ++$aCount;
  931. }
  932. }
  933. // Return
  934. return $returnValue / $aCount ;
  935. }
  936. return self::$_errorCodes['num'];
  937. }
  938. /**
  939. * GEOMEAN
  940. *
  941. * Returns the geometric mean of an array or range of positive data. For example, you
  942. * can use GEOMEAN to calculate average growth rate given compound interest with
  943. * variable rates.
  944. *
  945. * @param array of mixed Data Series
  946. * @return float
  947. */
  948. public static function GEOMEAN() {
  949. $aMean = self::PRODUCT(func_get_args());
  950. if (is_numeric($aMean) && ($aMean > 0)) {
  951. $aArgs = self::flattenArray(func_get_args());
  952. $aCount = self::COUNT($aArgs) ;
  953. if (self::MIN($aArgs) > 0) {
  954. return pow($aMean, (1 / $aCount));
  955. }
  956. }
  957. return self::$_errorCodes['num'];
  958. }
  959. /**
  960. * HARMEAN
  961. *
  962. * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the
  963. * arithmetic mean of reciprocals.
  964. *
  965. * @param array of mixed Data Series
  966. * @return float
  967. */
  968. public static function HARMEAN() {
  969. // Return value
  970. $returnValue = self::NA();
  971. // Loop through arguments
  972. $aArgs = self::flattenArray(func_get_args());
  973. if (self::MIN($aArgs) < 0) {
  974. return self::$_errorCodes['num'];
  975. }
  976. $aCount = 0;
  977. foreach ($aArgs as $arg) {
  978. // Is it a numeric value?
  979. if ((is_numeric($arg)) && (!is_string($arg))) {
  980. if ($arg <= 0) {
  981. return self::$_errorCodes['num'];
  982. }
  983. if (is_null($returnValue)) {
  984. $returnValue = (1 / $arg);
  985. } else {
  986. $returnValue += (1 / $arg);
  987. }
  988. ++$aCount;
  989. }
  990. }
  991. // Return
  992. if ($aCount > 0) {
  993. return 1 / ($returnValue / $aCount);
  994. } else {
  995. return $returnValue;
  996. }
  997. }
  998. /**
  999. * TRIMMEAN
  1000. *
  1001. * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean
  1002. * taken by excluding a percentage of data points from the top and bottom tails
  1003. * of a data set.
  1004. *
  1005. * @param array of mixed Data Series
  1006. * @param float Percentage to discard
  1007. * @return float
  1008. */
  1009. public static function TRIMMEAN() {
  1010. $aArgs = self::flattenArray(func_get_args());
  1011. // Calculate
  1012. $percent = array_pop($aArgs);
  1013. if ((is_numeric($percent)) && (!is_string($percent))) {
  1014. if (($percent < 0) || ($percent > 1)) {
  1015. return self::$_errorCodes['num'];
  1016. }
  1017. $mArgs = array();
  1018. foreach ($aArgs as $arg) {
  1019. // Is it a numeric value?
  1020. if ((is_numeric($arg)) && (!is_string($arg))) {
  1021. $mArgs[] = $arg;
  1022. }
  1023. }
  1024. $discard = floor(self::COUNT($mArgs) * $percent / 2);
  1025. sort($mArgs);
  1026. for ($i=0; $i < $discard; ++$i) {
  1027. array_pop($mArgs);
  1028. array_shift($mArgs);
  1029. }
  1030. return self::AVERAGE($mArgs);
  1031. }
  1032. return self::$_errorCodes['value'];
  1033. }
  1034. /**
  1035. * STDEV
  1036. *
  1037. * Estimates standard deviation based on a sample. The standard deviation is a measure of how
  1038. * widely values are dispersed from the average value (the mean).
  1039. *
  1040. * @param array of mixed Data Series
  1041. * @return float
  1042. */
  1043. public static function STDEV() {
  1044. // Return value
  1045. $returnValue = null;
  1046. $aMean = self::AVERAGE(func_get_args());
  1047. if (!is_null($aMean)) {
  1048. $aArgs = self::flattenArray(func_get_args());
  1049. $aCount = -1;
  1050. foreach ($aArgs as $arg) {
  1051. // Is it a numeric value?
  1052. if ((is_numeric($arg)) && (!is_string($arg))) {
  1053. if (is_null($returnValue)) {
  1054. $returnValue = pow(($arg - $aMean),2);
  1055. } else {
  1056. $returnValue += pow(($arg - $aMean),2);
  1057. }
  1058. ++$aCount;
  1059. }
  1060. }
  1061. // Return
  1062. if (($aCount > 0) && ($returnValue > 0)) {
  1063. return sqrt($returnValue / $aCount);
  1064. }
  1065. }
  1066. return self::$_errorCodes['divisionbyzero'];
  1067. }
  1068. /**
  1069. * STDEVA
  1070. *
  1071. * Estimates standard deviation based on a sample, including numbers, text, and logical values
  1072. *
  1073. * @param array of mixed Data Series
  1074. * @return float
  1075. */
  1076. public static function STDEVA() {
  1077. // Return value
  1078. $returnValue = null;
  1079. $aMean = self::AVERAGEA(func_get_args());
  1080. if (!is_null($aMean)) {
  1081. $aArgs = self::flattenArray(func_get_args());
  1082. $aCount = -1;
  1083. foreach ($aArgs as $arg) {
  1084. // Is it a numeric value?
  1085. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1086. if (is_bool($arg)) {
  1087. $arg = (integer) $arg;
  1088. } elseif (is_string($arg)) {
  1089. $arg = 0;
  1090. }
  1091. if (is_null($returnValue)) {
  1092. $returnValue = pow(($arg - $aMean),2);
  1093. } else {
  1094. $returnValue += pow(($arg - $aMean),2);
  1095. }
  1096. ++$aCount;
  1097. }
  1098. }
  1099. // Return
  1100. if (($aCount > 0) && ($returnValue > 0)) {
  1101. return sqrt($returnValue / $aCount);
  1102. }
  1103. }
  1104. return self::$_errorCodes['divisionbyzero'];
  1105. }
  1106. /**
  1107. * STDEVP
  1108. *
  1109. * Calculates standard deviation based on the entire population
  1110. *
  1111. * @param array of mixed Data Series
  1112. * @return float
  1113. */
  1114. public static function STDEVP() {
  1115. // Return value
  1116. $returnValue = null;
  1117. $aMean = self::AVERAGE(func_get_args());
  1118. if (!is_null($aMean)) {
  1119. $aArgs = self::flattenArray(func_get_args());
  1120. $aCount = 0;
  1121. foreach ($aArgs as $arg) {
  1122. // Is it a numeric value?
  1123. if ((is_numeric($arg)) && (!is_string($arg))) {
  1124. if (is_null($returnValue)) {
  1125. $returnValue = pow(($arg - $aMean),2);
  1126. } else {
  1127. $returnValue += pow(($arg - $aMean),2);
  1128. }
  1129. ++$aCount;
  1130. }
  1131. }
  1132. // Return
  1133. if (($aCount > 0) && ($returnValue > 0)) {
  1134. return sqrt($returnValue / $aCount);
  1135. }
  1136. }
  1137. return self::$_errorCodes['divisionbyzero'];
  1138. }
  1139. /**
  1140. * STDEVPA
  1141. *
  1142. * Calculates standard deviation based on the entire population, including numbers, text, and logical values
  1143. *
  1144. * @param array of mixed Data Series
  1145. * @return float
  1146. */
  1147. public static function STDEVPA() {
  1148. // Return value
  1149. $returnValue = null;
  1150. $aMean = self::AVERAGEA(func_get_args());
  1151. if (!is_null($aMean)) {
  1152. $aArgs = self::flattenArray(func_get_args());
  1153. $aCount = 0;
  1154. foreach ($aArgs as $arg) {
  1155. // Is it a numeric value?
  1156. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1157. if (is_bool($arg)) {
  1158. $arg = (integer) $arg;
  1159. } elseif (is_string($arg)) {
  1160. $arg = 0;
  1161. }
  1162. if (is_null($returnValue)) {
  1163. $returnValue = pow(($arg - $aMean),2);
  1164. } else {
  1165. $returnValue += pow(($arg - $aMean),2);
  1166. }
  1167. ++$aCount;
  1168. }
  1169. }
  1170. // Return
  1171. if (($aCount > 0) && ($returnValue > 0)) {
  1172. return sqrt($returnValue / $aCount);
  1173. }
  1174. }
  1175. return self::$_errorCodes['divisionbyzero'];
  1176. }
  1177. /**
  1178. * VARFunc
  1179. *
  1180. * Estimates variance based on a sample.
  1181. *
  1182. * @param array of mixed Data Series
  1183. * @return float
  1184. */
  1185. public static function VARFunc() {
  1186. // Return value
  1187. $returnValue = self::$_errorCodes['divisionbyzero'];
  1188. $summerA = $summerB = 0;
  1189. // Loop through arguments
  1190. $aArgs = self::flattenArray(func_get_args());
  1191. $aCount = 0;
  1192. foreach ($aArgs as $arg) {
  1193. // Is it a numeric value?
  1194. if ((is_numeric($arg)) && (!is_string($arg))) {
  1195. $summerA += ($arg * $arg);
  1196. $summerB += $arg;
  1197. ++$aCount;
  1198. }
  1199. }
  1200. // Return
  1201. if ($aCount > 1) {
  1202. $summerA = $summerA * $aCount;
  1203. $summerB = ($summerB * $summerB);
  1204. $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
  1205. }
  1206. return $returnValue;
  1207. }
  1208. /**
  1209. * VARA
  1210. *
  1211. * Estimates variance based on a sample, including numbers, text, and logical values
  1212. *
  1213. * @param array of mixed Data Series
  1214. * @return float
  1215. */
  1216. public static function VARA() {
  1217. // Return value
  1218. $returnValue = self::$_errorCodes['divisionbyzero'];
  1219. $summerA = $summerB = 0;
  1220. // Loop through arguments
  1221. $aArgs = self::flattenArray(func_get_args());
  1222. $aCount = 0;
  1223. foreach ($aArgs as $arg) {
  1224. // Is it a numeric value?
  1225. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1226. if (is_bool($arg)) {
  1227. $arg = (integer) $arg;
  1228. } elseif (is_string($arg)) {
  1229. $arg = 0;
  1230. }
  1231. $summerA += ($arg * $arg);
  1232. $summerB += $arg;
  1233. ++$aCount;
  1234. }
  1235. }
  1236. // Return
  1237. if ($aCount > 1) {
  1238. $summerA = $summerA * $aCount;
  1239. $summerB = ($summerB * $summerB);
  1240. $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
  1241. }
  1242. return $returnValue;
  1243. }
  1244. /**
  1245. * VARP
  1246. *
  1247. * Calculates variance based on the entire population
  1248. *
  1249. * @param array of mixed Data Series
  1250. * @return float
  1251. */
  1252. public static function VARP() {
  1253. // Return value
  1254. $returnValue = self::$_errorCodes['divisionbyzero'];
  1255. $summerA = $summerB = 0;
  1256. // Loop through arguments
  1257. $aArgs = self::flattenArray(func_get_args());
  1258. $aCount = 0;
  1259. foreach ($aArgs as $arg) {
  1260. // Is it a numeric value?
  1261. if ((is_numeric($arg)) && (!is_string($arg))) {
  1262. $summerA += ($arg * $arg);
  1263. $summerB += $arg;
  1264. ++$aCount;
  1265. }
  1266. }
  1267. // Return
  1268. if ($aCount > 0) {
  1269. $summerA = $summerA * $aCount;
  1270. $summerB = ($summerB * $summerB);
  1271. $returnValue = ($summerA - $summerB) / ($aCount * $aCount);
  1272. }
  1273. return $returnValue;
  1274. }
  1275. /**
  1276. * VARPA
  1277. *
  1278. * Calculates variance based on the entire population, including numbers, text, and logical values
  1279. *
  1280. * @param array of mixed Data Series
  1281. * @return float
  1282. */
  1283. public static function VARPA() {
  1284. // Return value
  1285. $returnValue = self::$_errorCodes['divisionbyzero'];
  1286. $summerA = $summerB = 0;
  1287. // Loop through arguments
  1288. $aArgs = self::flattenArray(func_get_args());
  1289. $aCount = 0;
  1290. foreach ($aArgs as $arg) {
  1291. // Is it a numeric value?
  1292. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1293. if (is_bool($arg)) {
  1294. $arg = (integer) $arg;
  1295. } elseif (is_string($arg)) {
  1296. $arg = 0;
  1297. }
  1298. $summerA += ($arg * $arg);
  1299. $summerB += $arg;
  1300. ++$aCount;
  1301. }
  1302. }
  1303. // Return
  1304. if ($aCount > 0) {
  1305. $summerA = $summerA * $aCount;
  1306. $summerB = ($summerB * $summerB);
  1307. $returnValue = ($summerA - $summerB) / ($aCount * $aCount);
  1308. }
  1309. return $returnValue;
  1310. }
  1311. /**
  1312. * RANK
  1313. *
  1314. * Returns the rank of a number in a list of numbers.
  1315. *
  1316. * @param number The number whose rank you want to find.
  1317. * @param array of number An array of, or a reference to, a list of numbers.
  1318. * @param mixed Order to sort the values in the value set
  1319. * @return float
  1320. */
  1321. public static function RANK($value,$valueSet,$order=0) {
  1322. $value = self::flattenSingleValue($value);
  1323. $valueSet = self::flattenArray($valueSet);
  1324. $order = self::flattenSingleValue($order);
  1325. foreach($valueSet as $key => $valueEntry) {
  1326. if (!is_numeric($valueEntry)) {
  1327. unset($valueSet[$key]);
  1328. }
  1329. }
  1330. if ($order == 0) {
  1331. rsort($valueSet,SORT_NUMERIC);
  1332. } else {
  1333. sort($valueSet,SORT_NUMERIC);
  1334. }
  1335. $pos = array_search($value,$valueSet);
  1336. if ($pos === False) {
  1337. return self::$_errorCodes['na'];
  1338. }
  1339. return ++$pos;
  1340. } // function RANK()
  1341. /**
  1342. * PERCENTRANK
  1343. *
  1344. * Returns the rank of a value in a data set as a percentage of the data set.
  1345. *
  1346. * @param array of number An array of, or a reference to, a list of numbers.
  1347. * @param number The number whose rank you want to find.
  1348. * @param number The number of significant digits for the returned percentage value.
  1349. * @return float
  1350. */
  1351. public static function PERCENTRANK($valueSet,$value,$significance=3) {
  1352. $valueSet = self::flattenArray($valueSet);
  1353. $value = self::flattenSingleValue($value);
  1354. $significance = self::flattenSingleValue($significance);
  1355. foreach($valueSet as $key => $valueEntry) {
  1356. if (!is_numeric($valueEntry)) {
  1357. unset($valueSet[$key]);
  1358. }
  1359. }
  1360. sort($valueSet,SORT_NUMERIC);
  1361. $valueCount = count($valueSet);
  1362. if ($valueCount == 0) {
  1363. return self::$_errorCodes['num'];
  1364. }
  1365. $valueAdjustor = $valueCount - 1;
  1366. if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
  1367. return self::$_errorCodes['na'];
  1368. }
  1369. $pos = array_search($value,$valueSet);
  1370. if ($pos === False) {
  1371. $pos = 0;
  1372. $testValue = $valueSet[0];
  1373. while ($testValue < $value) {
  1374. $testValue = $valueSet[++$pos];
  1375. }
  1376. --$pos;
  1377. $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos]));
  1378. }
  1379. return round($pos / $valueAdjustor,$significance);
  1380. } // function PERCENTRANK()
  1381. private static function _checkTrendArray($values) {
  1382. foreach($values as $key => $value) {
  1383. if ((is_bool($value)) || ($value == '')) {
  1384. unset($values[$key]);
  1385. } elseif (is_string($value)) {
  1386. if (is_numeric($value)) {
  1387. $values[$key] = (float) $value;
  1388. } else {
  1389. unset($values[$key]);
  1390. }
  1391. }
  1392. }
  1393. return $values;
  1394. } // function _checkTrendArray()
  1395. /**
  1396. * INTERCEPT
  1397. *
  1398. * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values.
  1399. *
  1400. * @param array of mixed Data Series Y
  1401. * @param array of mixed Data Series X
  1402. * @return float
  1403. */
  1404. public static function INTERCEPT($yValues,$xValues) {
  1405. $yValues = self::flattenArray($yValues);
  1406. $xValues = self::flattenArray($xValues);
  1407. $yValues = self::_checkTrendArray($yValues);
  1408. $yValueCount = count($yValues);
  1409. $xValues = self::_checkTrendArray($xValues);
  1410. $xValueCount = count($xValues);
  1411. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1412. return self::$_errorCodes['na'];
  1413. } elseif ($yValueCount == 1) {
  1414. return self::$_errorCodes['divisionbyzero'];
  1415. }
  1416. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1417. return $bestFitLinear->getIntersect();
  1418. } // function INTERCEPT()
  1419. /**
  1420. * RSQ
  1421. *
  1422. * Returns the square of the Pearson product moment correlation coefficient through data points in known_y's and known_x's.
  1423. *
  1424. * @param array of mixed Data Series Y
  1425. * @param array of mixed Data Series X
  1426. * @return float
  1427. */
  1428. public static function RSQ($yValues,$xValues) {
  1429. $yValues = self::flattenArray($yValues);
  1430. $xValues = self::flattenArray($xValues);
  1431. $yValues = self::_checkTrendArray($yValues);
  1432. $yValueCount = count($yValues);
  1433. $xValues = self::_checkTrendArray($xValues);
  1434. $xValueCount = count($xValues);
  1435. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1436. return self::$_errorCodes['na'];
  1437. } elseif ($yValueCount == 1) {
  1438. return self::$_errorCodes['divisionbyzero'];
  1439. }
  1440. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1441. return $bestFitLinear->getGoodnessOfFit();
  1442. } // function RSQ()
  1443. /**
  1444. * SLOPE
  1445. *
  1446. * Returns the slope of the linear regression line through data points in known_y's and known_x's.
  1447. *
  1448. * @param array of mixed Data Series Y
  1449. * @param array of mixed Data Series X
  1450. * @return float
  1451. */
  1452. public static function SLOPE($yValues,$xValues) {
  1453. $yValues = self::flattenArray($yValues);
  1454. $xValues = self::flattenArray($xValues);
  1455. $yValues = self::_checkTrendArray($yValues);
  1456. $yValueCount = count($yValues);
  1457. $xValues = self::_checkTrendArray($xValues);
  1458. $xValueCount = count($xValues);
  1459. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1460. return self::$_errorCodes['na'];
  1461. } elseif ($yValueCount == 1) {
  1462. return self::$_errorCodes['divisionbyzero'];
  1463. }
  1464. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1465. return $bestFitLinear->getSlope();
  1466. } // function SLOPE()
  1467. /**
  1468. * STEYX
  1469. *
  1470. * Returns the standard error of the predicted y-value for each x in the regression.
  1471. *
  1472. * @param array of mixed Data Series Y
  1473. * @param array of mixed Data Series X
  1474. * @return float
  1475. */
  1476. public static function STEYX($yValues,$xValues) {
  1477. $yValues = self::flattenArray($yValues);
  1478. $xValues = self::flattenArray($xValues);
  1479. $yValues = self::_checkTrendArray($yValues);
  1480. $yValueCount = count($yValues);
  1481. $xValues = self::_checkTrendArray($xValues);
  1482. $xValueCount = count($xValues);
  1483. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1484. return self::$_errorCodes['na'];
  1485. } elseif ($yValueCount == 1) {
  1486. return self::$_errorCodes['divisionbyzero'];
  1487. }
  1488. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1489. return $bestFitLinear->getStdevOfResiduals();
  1490. } // function STEYX()
  1491. /**
  1492. * COVAR
  1493. *
  1494. * Returns covariance, the average of the products of deviations for each data point pair.
  1495. *
  1496. * @param array of mixed Data Series Y
  1497. * @param array of mixed Data Series X
  1498. * @return float
  1499. */
  1500. public static function COVAR($yValues,$xValues) {
  1501. $yValues = self::flattenArray($yValues);
  1502. $xValues = self::flattenArray($xValues);
  1503. $yValues = self::_checkTrendArray($yValues);
  1504. $yValueCount = count($yValues);
  1505. $xValues = self::_checkTrendArray($xValues);
  1506. $xValueCount = count($xValues);
  1507. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1508. return self::$_errorCodes['na'];
  1509. } elseif ($yValueCount == 1) {
  1510. return self::$_errorCodes['divisionbyzero'];
  1511. }
  1512. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1513. return $bestFitLinear->getCovariance();
  1514. } // function COVAR()
  1515. /**
  1516. * CORREL
  1517. *
  1518. * Returns covariance, the average of the products of deviations for each data point pair.
  1519. *
  1520. * @param array of mixed Data Series Y
  1521. * @param array of mixed Data Series X
  1522. * @return float
  1523. */
  1524. public static function CORREL($yValues,$xValues) {
  1525. $yValues = self::flattenArray($yValues);
  1526. $xValues = self::flattenArray($xValues);
  1527. $yValues = self::_checkTrendArray($yValues);
  1528. $yValueCount = count($yValues);
  1529. $xValues = self::_checkTrendArray($xValues);
  1530. $xValueCount = count($xValues);
  1531. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1532. return self::$_errorCodes['na'];
  1533. } elseif ($yValueCount == 1) {
  1534. return self::$_errorCodes['divisionbyzero'];
  1535. }
  1536. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1537. return $bestFitLinear->getCorrelation();
  1538. } // function CORREL()
  1539. /**
  1540. * LINEST
  1541. *
  1542. * Calculates the statistics for a line by using the "least squares" method to calculate a straight line that best fits your data,
  1543. * and then returns an array that describes the line.
  1544. *
  1545. * @param array of mixed Data Series Y
  1546. * @param array of mixed Data Series X
  1547. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  1548. * @param boolean A logical value specifying whether to return additional regression statistics.
  1549. * @return array
  1550. */
  1551. public static function LINEST($yValues,$xValues,$const=True,$stats=False) {
  1552. $yValues = self::flattenArray($yValues);
  1553. $xValues = self::flattenArray($xValues);
  1554. $const = (boolean) self::flattenSingleValue($const);
  1555. $stats = (boolean) self::flattenSingleValue($stats);
  1556. $yValues = self::_checkTrendArray($yValues);
  1557. $yValueCount = count($yValues);
  1558. $xValues = self::_checkTrendArray($xValues);
  1559. $xValueCount = count($xValues);
  1560. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1561. return self::$_errorCodes['na'];
  1562. } elseif ($yValueCount == 1) {
  1563. return self::$_errorCodes['divisionbyzero'];
  1564. }
  1565. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues,$const);
  1566. if ($stats) {
  1567. return array( array( $bestFitLinear->getSlope(),
  1568. $bestFitLinear->getSlopeSE(),
  1569. $bestFitLinear->getGoodnessOfFit(),
  1570. $bestFitLinear->getF(),
  1571. $bestFitLinear->getSSRegression(),
  1572. ),
  1573. array( $bestFitLinear->getIntersect(),
  1574. $bestFitLinear->getIntersectSE(),
  1575. $bestFitLinear->getStdevOfResiduals(),
  1576. $bestFitLinear->getDFResiduals(),
  1577. $bestFitLinear->getSSResiduals()
  1578. )
  1579. );
  1580. } else {
  1581. return array( $bestFitLinear->getSlope(),
  1582. $bestFitLinear->getIntersect()
  1583. );
  1584. }
  1585. } // function LINEST()
  1586. /**
  1587. * LOGEST
  1588. *
  1589. * Calculates an exponential curve that best fits the X and Y data series,
  1590. * and then returns an array that describes the line.
  1591. *
  1592. * @param array of mixed Data Series Y
  1593. * @param array of mixed Data Series X
  1594. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  1595. * @param boolean A logical value specifying whether to return additional regression statistics.
  1596. * @return array
  1597. */
  1598. public static function LOGEST($yValues,$xValues,$const=True,$stats=False) {
  1599. $yValues = self::flattenArray($yValues);
  1600. $xValues = self::flattenArray($xValues);
  1601. $const = (boolean) self::flattenSingleValue($const);
  1602. $stats = (boolean) self::flattenSingleValue($stats);
  1603. $yValues = self::_checkTrendArray($yValues);
  1604. $yValueCount = count($yValues);
  1605. $xValues = self::_checkTrendArray($xValues);
  1606. $xValueCount = count($xValues);
  1607. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1608. return self::$_errorCodes['na'];
  1609. } elseif ($yValueCount == 1) {
  1610. return self::$_errorCodes['divisionbyzero'];
  1611. }
  1612. $bestFitExponential = trendClass::calculate(trendClass::TREND_EXPONENTIAL,$yValues,$xValues,$const);
  1613. if ($stats) {
  1614. return array( array( $bestFitExponential->getSlope(),
  1615. $bestFitExponential->getSlopeSE(),
  1616. $bestFitExponential->getGoodnessOfFit(),
  1617. $bestFitExponential->getF(),
  1618. $bestFitExponential->getSSRegression(),
  1619. ),
  1620. array( $bestFitExponential->getIntersect(),
  1621. $bestFitExponential->getIntersectSE(),
  1622. $bestFitExponential->getStdevOfResiduals(),
  1623. $bestFitExponential->getDFResiduals(),
  1624. $bestFitExponential->getSSResiduals()
  1625. )
  1626. );
  1627. } else {
  1628. return array( $bestFitExponential->getSlope(),
  1629. $bestFitExponential->getIntersect()
  1630. );
  1631. }
  1632. } // function LOGEST()
  1633. /**
  1634. * FORECAST
  1635. *
  1636. * Calculates, or predicts, a future value by using existing values. The predicted value is a y-value for a given x-value.
  1637. *
  1638. * @param float Value of X for which we want to find Y
  1639. * @param array of mixed Data Series Y
  1640. * @param array of mixed Data Series X
  1641. * @return float
  1642. */
  1643. public static function FORECAST($xValue,$yValues,$xValues) {
  1644. $xValue = self::flattenSingleValue($xValue);
  1645. $yValues = self::flattenArray($yValues);
  1646. $xValues = self::flattenArray($xValues);
  1647. if (!is_numeric($xValue)) {
  1648. return self::$_errorCodes['value'];
  1649. }
  1650. $yValues = self::_checkTrendArray($yValues);
  1651. $yValueCount = count($yValues);
  1652. $xValues = self::_checkTrendArray($xValues);
  1653. $xValueCount = count($xValues);
  1654. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  1655. return self::$_errorCodes['na'];
  1656. } elseif ($yValueCount == 1) {
  1657. return self::$_errorCodes['divisionbyzero'];
  1658. }
  1659. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  1660. return $bestFitLinear->getValueOfYForX($xValue);
  1661. } // function FORECAST()
  1662. /**
  1663. * TREND
  1664. *
  1665. * Returns values along a linear trend
  1666. *
  1667. * @param array of mixed Data Series Y
  1668. * @param array of mixed Data Series X
  1669. * @param array of mixed Values of X for which we want to find Y
  1670. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  1671. * @return array of float
  1672. */
  1673. public static function TREND($yValues,$xValues=array(),$newValues=array(),$const=True) {
  1674. $yValues = self::flattenArray($yValues);
  1675. $xValues = self::flattenArray($xValues);
  1676. $newValues = self::flattenArray($newValues);
  1677. $const = (boolean) self::flattenSingleValue($const);
  1678. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues,$const);
  1679. if (count($newValues) == 0) {
  1680. $newValues = $bestFitLinear->getXValues();
  1681. }
  1682. $returnArray = array();
  1683. foreach($newValues as $xValue) {
  1684. $returnArray[0][] = $bestFitLinear->getValueOfYForX($xValue);
  1685. }
  1686. return $returnArray;
  1687. } // function TREND()
  1688. /**
  1689. * GROWTH
  1690. *
  1691. * Returns values along a predicted emponential trend
  1692. *
  1693. * @param array of mixed Data Series Y
  1694. * @param array of mixed Data Series X
  1695. * @param array of mixed Values of X for which we want to find Y
  1696. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  1697. * @return array of float
  1698. */
  1699. public static function GROWTH($yValues,$xValues=array(),$newValues=array(),$const=True) {
  1700. $yValues = self::flattenArray($yValues);
  1701. $xValues = self::flattenArray($xValues);
  1702. $newValues = self::flattenArray($newValues);
  1703. $const = (boolean) self::flattenSingleValue($const);
  1704. $bestFitExponential = trendClass::calculate(trendClass::TREND_EXPONENTIAL,$yValues,$xValues,$const);
  1705. if (count($newValues) == 0) {
  1706. $newValues = $bestFitExponential->getXValues();
  1707. }
  1708. $returnArray = array();
  1709. foreach($newValues as $xValue) {
  1710. $returnArray[0][] = $bestFitExponential->getValueOfYForX($xValue);
  1711. }
  1712. return $returnArray;
  1713. } // function GROWTH()
  1714. private static function _romanCut($num, $n) {
  1715. return ($num - ($num % $n ) ) / $n;
  1716. }
  1717. public static function ROMAN ($aValue, $style=0) {
  1718. $aValue = (integer) self::flattenSingleValue($aValue);
  1719. if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) {
  1720. return self::$_errorCodes['value'];
  1721. }
  1722. if ($aValue == 0) {
  1723. return '';
  1724. }
  1725. $mill = Array('', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM');
  1726. $cent = Array('', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM');
  1727. $tens = Array('', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC');
  1728. $ones = Array('', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX');
  1729. $roman = '';
  1730. while ($aValue > 5999) {
  1731. $roman .= 'M';
  1732. $aValue -= 1000;
  1733. }
  1734. $m = self::_romanCut($aValue, 1000); $aValue %= 1000;
  1735. $c = self::_romanCut($aValue, 100); $aValue %= 100;
  1736. $t = self::_romanCut($aValue, 10); $aValue %= 10;
  1737. return $roman.$mill[$m].$cent[$c].$tens[$t].$ones[$aValue];
  1738. }
  1739. /**
  1740. * SUBTOTAL
  1741. *
  1742. * Returns a subtotal in a list or database.
  1743. *
  1744. * @param int the number 1 to 11 that specifies which function to
  1745. * use in calculating subtotals within a list.
  1746. * @param array of mixed Data Series
  1747. * @return float
  1748. */
  1749. public static function SUBTOTAL() {
  1750. $aArgs = self::flattenArray(func_get_args());
  1751. // Calculate
  1752. $subtotal = array_shift($aArgs);
  1753. if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
  1754. switch($subtotal) {
  1755. case 1 :
  1756. return self::AVERAGE($aArgs);
  1757. break;
  1758. case 2 :
  1759. return self::COUNT($aArgs);
  1760. break;
  1761. case 3 :
  1762. return self::COUNTA($aArgs);
  1763. break;
  1764. case 4 :
  1765. return self::MAX($aArgs);
  1766. break;
  1767. case 5 :
  1768. return self::MIN($aArgs);
  1769. break;
  1770. case 6 :
  1771. return self::PRODUCT($aArgs);
  1772. break;
  1773. case 7 :
  1774. return self::STDEV($aArgs);
  1775. break;
  1776. case 8 :
  1777. return self::STDEVP($aArgs);
  1778. break;
  1779. case 9 :
  1780. return self::SUM($aArgs);
  1781. break;
  1782. case 10 :
  1783. return self::VARFunc($aArgs);
  1784. break;
  1785. case 11 :
  1786. return self::VARP($aArgs);
  1787. break;
  1788. }
  1789. }
  1790. return self::$_errorCodes['value'];
  1791. }
  1792. /**
  1793. * SQRTPI
  1794. *
  1795. * Returns the square root of (number * pi).
  1796. *
  1797. * @param float $number Number
  1798. * @return float Square Root of Number * Pi
  1799. */
  1800. public static function SQRTPI($number) {
  1801. $number = self::flattenSingleValue($number);
  1802. if (is_numeric($number)) {
  1803. if ($number < 0) {
  1804. return self::$_errorCodes['num'];
  1805. }
  1806. return sqrt($number * pi()) ;
  1807. }
  1808. return self::$_errorCodes['value'];
  1809. }
  1810. /**
  1811. * FACT
  1812. *
  1813. * Returns the factorial of a number.
  1814. *
  1815. * @param float $factVal Factorial Value
  1816. * @return int Factorial
  1817. */
  1818. public static function FACT($factVal) {
  1819. $factVal = self::flattenSingleValue($factVal);
  1820. if (is_numeric($factVal)) {
  1821. if ($factVal < 0) {
  1822. return self::$_errorCodes['num'];
  1823. }
  1824. $factLoop = floor($factVal);
  1825. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  1826. if ($factVal > $factLoop) {
  1827. return self::$_errorCodes['num'];
  1828. }
  1829. }
  1830. $factorial = 1;
  1831. while ($factLoop > 1) {
  1832. $factorial *= $factLoop--;
  1833. }
  1834. return $factorial ;
  1835. }
  1836. return self::$_errorCodes['value'];
  1837. }
  1838. /**
  1839. * FACTDOUBLE
  1840. *
  1841. * Returns the double factorial of a number.
  1842. *
  1843. * @param float $factVal Factorial Value
  1844. * @return int Double Factorial
  1845. */
  1846. public static function FACTDOUBLE($factVal) {
  1847. $factLoop = floor(self::flattenSingleValue($factVal));
  1848. if (is_numeric($factLoop)) {
  1849. if ($factVal < 0) {
  1850. return self::$_errorCodes['num'];
  1851. }
  1852. $factorial = 1;
  1853. while ($factLoop > 1) {
  1854. $factorial *= $factLoop--;
  1855. --$factLoop;
  1856. }
  1857. return $factorial ;
  1858. }
  1859. return self::$_errorCodes['value'];
  1860. }
  1861. /**
  1862. * MULTINOMIAL
  1863. *
  1864. * Returns the ratio of the factorial of a sum of values to the product of factorials.
  1865. *
  1866. * @param array of mixed Data Series
  1867. * @return float
  1868. */
  1869. public static function MULTINOMIAL() {
  1870. // Loop through arguments
  1871. $aArgs = self::flattenArray(func_get_args());
  1872. $summer = 0;
  1873. $divisor = 1;
  1874. foreach ($aArgs as $arg) {
  1875. // Is it a numeric value?
  1876. if (is_numeric($arg)) {
  1877. if ($arg < 1) {
  1878. return self::$_errorCodes['num'];
  1879. }
  1880. $summer += floor($arg);
  1881. $divisor *= self::FACT($arg);
  1882. } else {
  1883. return self::$_errorCodes['value'];
  1884. }
  1885. }
  1886. // Return
  1887. if ($summer > 0) {
  1888. $summer = self::FACT($summer);
  1889. return $summer / $divisor;
  1890. }
  1891. return 0;
  1892. }
  1893. /**
  1894. * CEILING
  1895. *
  1896. * Returns number rounded up, away from zero, to the nearest multiple of significance.
  1897. *
  1898. * @param float $number Number to round
  1899. * @param float $significance Significance
  1900. * @return float Rounded Number
  1901. */
  1902. public static function CEILING($number,$significance=null) {
  1903. $number = self::flattenSingleValue($number);
  1904. $significance = self::flattenSingleValue($significance);
  1905. if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  1906. $significance = $number/abs($number);
  1907. }
  1908. if ((is_numeric($number)) && (is_numeric($significance))) {
  1909. if (self::SIGN($number) == self::SIGN($significance)) {
  1910. if ($significance == 0.0) {
  1911. return 0;
  1912. }
  1913. return ceil($number / $significance) * $significance;
  1914. } else {
  1915. return self::$_errorCodes['num'];
  1916. }
  1917. }
  1918. return self::$_errorCodes['value'];
  1919. }
  1920. /**
  1921. * EVEN
  1922. *
  1923. * Returns number rounded up to the nearest even integer.
  1924. *
  1925. * @param float $number Number to round
  1926. * @return int Rounded Number
  1927. */
  1928. public static function EVEN($number) {
  1929. $number = self::flattenSingleValue($number);
  1930. if (is_numeric($number)) {
  1931. $significance = 2 * self::SIGN($number);
  1932. return self::CEILING($number,$significance);
  1933. }
  1934. return self::$_errorCodes['value'];
  1935. }
  1936. /**
  1937. * ODD
  1938. *
  1939. * Returns number rounded up to the nearest odd integer.
  1940. *
  1941. * @param float $number Number to round
  1942. * @return int Rounded Number
  1943. */
  1944. public static function ODD($number) {
  1945. $number = self::flattenSingleValue($number);
  1946. if (is_numeric($number)) {
  1947. $significance = self::SIGN($number);
  1948. if ($significance == 0) {
  1949. return 1;
  1950. }
  1951. $result = self::CEILING($number,$significance);
  1952. if (self::IS_EVEN($result)) {
  1953. $result += $significance;
  1954. }
  1955. return $result;
  1956. }
  1957. return self::$_errorCodes['value'];
  1958. }
  1959. /**
  1960. * ROUNDUP
  1961. *
  1962. * Rounds a number up to a specified number of decimal places
  1963. *
  1964. * @param float $number Number to round
  1965. * @param int $digits Number of digits to which you want to round $number
  1966. * @return float Rounded Number
  1967. */
  1968. public static function ROUNDUP($number,$digits) {
  1969. $number = self::flattenSingleValue($number);
  1970. $digits = self::flattenSingleValue($digits);
  1971. if (is_numeric($number)) {
  1972. if ((is_numeric($digits)) && ($digits >= 0)) {
  1973. $significance = pow(10,$digits);
  1974. return ceil($number * $significance) / $significance;
  1975. }
  1976. }
  1977. return self::$_errorCodes['value'];
  1978. }
  1979. /**
  1980. * ROUNDDOWN
  1981. *
  1982. * Rounds a number down to a specified number of decimal places
  1983. *
  1984. * @param float $number Number to round
  1985. * @param int $digits Number of digits to which you want to round $number
  1986. * @return float Rounded Number
  1987. */
  1988. public static function ROUNDDOWN($number,$digits) {
  1989. $number = self::flattenSingleValue($number);
  1990. $digits = self::flattenSingleValue($digits);
  1991. if (is_numeric($number)) {
  1992. if ((is_numeric($digits)) && ($digits >= 0)) {
  1993. $significance = pow(10,$digits);
  1994. return floor($number * $significance) / $significance;
  1995. }
  1996. }
  1997. return self::$_errorCodes['value'];
  1998. }
  1999. /**
  2000. * MROUND
  2001. *
  2002. * Rounds a number to the nearest multiple of a specified value
  2003. *
  2004. * @param float $number Number to round
  2005. * @param int $multiple Multiple to which you want to round $number
  2006. * @return float Rounded Number
  2007. */
  2008. public static function MROUND($number,$multiple) {
  2009. $number = self::flattenSingleValue($number);
  2010. $multiple = self::flattenSingleValue($multiple);
  2011. if ((is_numeric($number)) && (is_numeric($multiple))) {
  2012. if ($multiple == 0) {
  2013. return 0;
  2014. }
  2015. if ((self::SIGN($number)) == (self::SIGN($multiple))) {
  2016. $multiplier = 1 / $multiple;
  2017. return round($number * $multiplier) / $multiplier;
  2018. }
  2019. return self::$_errorCodes['num'];
  2020. }
  2021. return self::$_errorCodes['value'];
  2022. }
  2023. /**
  2024. * SIGN
  2025. *
  2026. * Determines the sign of a number. Returns 1 if the number is positive, zero (0)
  2027. * if the number is 0, and -1 if the number is negative.
  2028. *
  2029. * @param float $number Number to round
  2030. * @return int sign value
  2031. */
  2032. public static function SIGN($number) {
  2033. $number = self::flattenSingleValue($number);
  2034. if (is_numeric($number)) {
  2035. if ($number == 0.0) {
  2036. return 0;
  2037. }
  2038. return $number / abs($number);
  2039. }
  2040. return self::$_errorCodes['value'];
  2041. }
  2042. /**
  2043. * FLOOR
  2044. *
  2045. * Rounds number down, toward zero, to the nearest multiple of significance.
  2046. *
  2047. * @param float $number Number to round
  2048. * @param float $significance Significance
  2049. * @return float Rounded Number
  2050. */
  2051. public static function FLOOR($number,$significance=null) {
  2052. $number = self::flattenSingleValue($number);
  2053. $significance = self::flattenSingleValue($significance);
  2054. if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  2055. $significance = $number/abs($number);
  2056. }
  2057. if ((is_numeric($number)) && (is_numeric($significance))) {
  2058. if ((float) $significance == 0.0) {
  2059. return self::$_errorCodes['divisionbyzero'];
  2060. }
  2061. if (self::SIGN($number) == self::SIGN($significance)) {
  2062. return floor($number / $significance) * $significance;
  2063. } else {
  2064. return self::$_errorCodes['num'];
  2065. }
  2066. }
  2067. return self::$_errorCodes['value'];
  2068. }
  2069. /**
  2070. * PERMUT
  2071. *
  2072. * Returns the number of permutations for a given number of objects that can be
  2073. * selected from number objects. A permutation is any set or subset of objects or
  2074. * events where internal order is significant. Permutations are different from
  2075. * combinations, for which the internal order is not significant. Use this function
  2076. * for lottery-style probability calculations.
  2077. *
  2078. * @param int $numObjs Number of different objects
  2079. * @param int $numInSet Number of objects in each permutation
  2080. * @return int Number of permutations
  2081. */
  2082. public static function PERMUT($numObjs,$numInSet) {
  2083. $numObjs = self::flattenSingleValue($numObjs);
  2084. $numInSet = self::flattenSingleValue($numInSet);
  2085. if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
  2086. if ($numObjs < $numInSet) {
  2087. return self::$_errorCodes['num'];
  2088. }
  2089. return self::FACT($numObjs) / self::FACT($numObjs - $numInSet);
  2090. }
  2091. return self::$_errorCodes['value'];
  2092. }
  2093. /**
  2094. * COMBIN
  2095. *
  2096. * Returns the number of combinations for a given number of items. Use COMBIN to
  2097. * determine the total possible number of groups for a given number of items.
  2098. *
  2099. * @param int $numObjs Number of different objects
  2100. * @param int $numInSet Number of objects in each combination
  2101. * @return int Number of combinations
  2102. */
  2103. public static function COMBIN($numObjs,$numInSet) {
  2104. $numObjs = self::flattenSingleValue($numObjs);
  2105. $numInSet = self::flattenSingleValue($numInSet);
  2106. if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
  2107. if ($numObjs < $numInSet) {
  2108. return self::$_errorCodes['num'];
  2109. } elseif ($numInSet < 0) {
  2110. return self::$_errorCodes['num'];
  2111. }
  2112. return (self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
  2113. }
  2114. return self::$_errorCodes['value'];
  2115. }
  2116. /**
  2117. * SERIESSUM
  2118. *
  2119. * Returns the sum of a power series
  2120. *
  2121. * @param float $x Input value to the power series
  2122. * @param float $n Initial power to which you want to raise $x
  2123. * @param float $m Step by which to increase $n for each term in the series
  2124. * @param array of mixed Data Series
  2125. * @return float
  2126. */
  2127. public static function SERIESSUM() {
  2128. // Return value
  2129. $returnValue = 0;
  2130. // Loop trough arguments
  2131. $aArgs = self::flattenArray(func_get_args());
  2132. $x = array_shift($aArgs);
  2133. $n = array_shift($aArgs);
  2134. $m = array_shift($aArgs);
  2135. if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) {
  2136. // Calculate
  2137. $i = 0;
  2138. foreach($aArgs as $arg) {
  2139. // Is it a numeric value?
  2140. if ((is_numeric($arg)) && (!is_string($arg))) {
  2141. $returnValue += $arg * pow($x,$n + ($m * $i++));
  2142. } else {
  2143. return self::$_errorCodes['value'];
  2144. }
  2145. }
  2146. // Return
  2147. return $returnValue;
  2148. }
  2149. return self::$_errorCodes['value'];
  2150. }
  2151. /**
  2152. * STANDARDIZE
  2153. *
  2154. * Returns a normalized value from a distribution characterized by mean and standard_dev.
  2155. *
  2156. * @param float $value Value to normalize
  2157. * @param float $mean Mean Value
  2158. * @param float $stdDev Standard Deviation
  2159. * @return float Standardized value
  2160. */
  2161. public static function STANDARDIZE($value,$mean,$stdDev) {
  2162. $value = self::flattenSingleValue($value);
  2163. $mean = self::flattenSingleValue($mean);
  2164. $stdDev = self::flattenSingleValue($stdDev);
  2165. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  2166. if ($stdDev <= 0) {
  2167. return self::$_errorCodes['num'];
  2168. }
  2169. return ($value - $mean) / $stdDev ;
  2170. }
  2171. return self::$_errorCodes['value'];
  2172. }
  2173. //
  2174. // Private method to return an array of the factors of the input value
  2175. //
  2176. private static function factors($value) {
  2177. $startVal = floor(sqrt($value));
  2178. $factorArray = array();
  2179. for ($i = $startVal; $i > 1; --$i) {
  2180. if (($value % $i) == 0) {
  2181. $factorArray = array_merge($factorArray,self::factors($value / $i));
  2182. $factorArray = array_merge($factorArray,self::factors($i));
  2183. if ($i <= sqrt($value)) {
  2184. break;
  2185. }
  2186. }
  2187. }
  2188. if (count($factorArray) > 0) {
  2189. rsort($factorArray);
  2190. return $factorArray;
  2191. } else {
  2192. return array((integer) $value);
  2193. }
  2194. }
  2195. /**
  2196. * LCM
  2197. *
  2198. * Returns the lowest common multiplier of a series of numbers
  2199. *
  2200. * @param $array Values to calculate the Lowest Common Multiplier
  2201. * @return int Lowest Common Multiplier
  2202. */
  2203. public static function LCM() {
  2204. $aArgs = self::flattenArray(func_get_args());
  2205. $returnValue = 1;
  2206. $allPoweredFactors = array();
  2207. foreach($aArgs as $value) {
  2208. if (!is_numeric($value)) {
  2209. return self::$_errorCodes['value'];
  2210. }
  2211. if ($value < 1) {
  2212. return self::$_errorCodes['num'];
  2213. }
  2214. $myFactors = self::factors(floor($value));
  2215. $myCountedFactors = array_count_values($myFactors);
  2216. $myPoweredFactors = array();
  2217. foreach($myCountedFactors as $myCountedFactor => $myCountedPower) {
  2218. $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor,$myCountedPower);
  2219. }
  2220. foreach($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
  2221. if (array_key_exists($myPoweredValue,$allPoweredFactors)) {
  2222. if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
  2223. $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
  2224. }
  2225. } else {
  2226. $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
  2227. }
  2228. }
  2229. }
  2230. foreach($allPoweredFactors as $allPoweredFactor) {
  2231. $returnValue *= (integer) $allPoweredFactor;
  2232. }
  2233. return $returnValue;
  2234. }
  2235. /**
  2236. * GCD
  2237. *
  2238. * Returns the greatest common divisor of a series of numbers
  2239. *
  2240. * @param $array Values to calculate the Greatest Common Divisor
  2241. * @return int Greatest Common Divisor
  2242. */
  2243. public static function GCD() {
  2244. $aArgs = self::flattenArray(func_get_args());
  2245. $returnValue = 1;
  2246. $allPoweredFactors = array();
  2247. foreach($aArgs as $value) {
  2248. if ($value == 0) {
  2249. return 0;
  2250. }
  2251. $myFactors = self::factors($value);
  2252. $myCountedFactors = array_count_values($myFactors);
  2253. $allValuesFactors[] = $myCountedFactors;
  2254. }
  2255. $allValuesCount = count($allValuesFactors);
  2256. $mergedArray = $allValuesFactors[0];
  2257. for ($i=1;$i < $allValuesCount; ++$i) {
  2258. $mergedArray = array_intersect_key($mergedArray,$allValuesFactors[$i]);
  2259. }
  2260. $mergedArrayValues = count($mergedArray);
  2261. if ($mergedArrayValues == 0) {
  2262. return $returnValue;
  2263. } elseif ($mergedArrayValues > 1) {
  2264. foreach($mergedArray as $mergedKey => $mergedValue) {
  2265. foreach($allValuesFactors as $highestPowerTest) {
  2266. foreach($highestPowerTest as $testKey => $testValue) {
  2267. if (($testKey == $mergedKey) && ($testValue < $mergedValue)) {
  2268. $mergedArray[$mergedKey] = $testValue;
  2269. $mergedValue = $testValue;
  2270. }
  2271. }
  2272. }
  2273. }
  2274. $returnValue = 1;
  2275. foreach($mergedArray as $key => $value) {
  2276. $returnValue *= pow($key,$value);
  2277. }
  2278. return $returnValue;
  2279. } else {
  2280. $keys = array_keys($mergedArray);
  2281. $key = $keys[0];
  2282. $value = $mergedArray[$key];
  2283. foreach($allValuesFactors as $testValue) {
  2284. foreach($testValue as $mergedKey => $mergedValue) {
  2285. if (($mergedKey == $key) && ($mergedValue < $value)) {
  2286. $value = $mergedValue;
  2287. }
  2288. }
  2289. }
  2290. return pow($key,$value);
  2291. }
  2292. }
  2293. /**
  2294. * BINOMDIST
  2295. *
  2296. * Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
  2297. * a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
  2298. * when trials are independent, and when the probability of success is constant throughout the
  2299. * experiment. For example, BINOMDIST can calculate the probability that two of the next three
  2300. * babies born are male.
  2301. *
  2302. * @param float $value Number of successes in trials
  2303. * @param float $trials Number of trials
  2304. * @param float $probability Probability of success on each trial
  2305. * @param boolean $cumulative
  2306. * @return float
  2307. *
  2308. * @todo Cumulative distribution function
  2309. *
  2310. */
  2311. public static function BINOMDIST($value, $trials, $probability, $cumulative) {
  2312. $value = floor(self::flattenSingleValue($value));
  2313. $trials = floor(self::flattenSingleValue($trials));
  2314. $probability = self::flattenSingleValue($probability);
  2315. if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) {
  2316. if (($value < 0) || ($value > $trials)) {
  2317. return self::$_errorCodes['num'];
  2318. }
  2319. if (($probability < 0) || ($probability > 1)) {
  2320. return self::$_errorCodes['num'];
  2321. }
  2322. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  2323. if ($cumulative) {
  2324. $summer = 0;
  2325. for ($i = 0; $i <= $value; ++$i) {
  2326. $summer += self::COMBIN($trials,$i) * pow($probability,$i) * pow(1 - $probability,$trials - $i);
  2327. }
  2328. return $summer;
  2329. } else {
  2330. return self::COMBIN($trials,$value) * pow($probability,$value) * pow(1 - $probability,$trials - $value) ;
  2331. }
  2332. }
  2333. }
  2334. return self::$_errorCodes['value'];
  2335. }
  2336. /**
  2337. * NEGBINOMDIST
  2338. *
  2339. * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
  2340. * there will be number_f failures before the number_s-th success, when the constant
  2341. * probability of a success is probability_s. This function is similar to the binomial
  2342. * distribution, except that the number of successes is fixed, and the number of trials is
  2343. * variable. Like the binomial, trials are assumed to be independent.
  2344. *
  2345. * @param float $failures Number of Failures
  2346. * @param float $successes Threshold number of Successes
  2347. * @param float $probability Probability of success on each trial
  2348. * @return float
  2349. *
  2350. */
  2351. public static function NEGBINOMDIST($failures, $successes, $probability) {
  2352. $failures = floor(self::flattenSingleValue($failures));
  2353. $successes = floor(self::flattenSingleValue($successes));
  2354. $probability = self::flattenSingleValue($probability);
  2355. if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) {
  2356. if (($failures < 0) || ($successes < 1)) {
  2357. return self::$_errorCodes['num'];
  2358. }
  2359. if (($probability < 0) || ($probability > 1)) {
  2360. return self::$_errorCodes['num'];
  2361. }
  2362. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  2363. if (($failures + $successes - 1) <= 0) {
  2364. return self::$_errorCodes['num'];
  2365. }
  2366. }
  2367. return (self::COMBIN($failures + $successes - 1,$successes - 1)) * (pow($probability,$successes)) * (pow(1 - $probability,$failures)) ;
  2368. }
  2369. return self::$_errorCodes['value'];
  2370. }
  2371. /**
  2372. * CRITBINOM
  2373. *
  2374. * Returns the smallest value for which the cumulative binomial distribution is greater
  2375. * than or equal to a criterion value
  2376. *
  2377. * See http://support.microsoft.com/kb/828117/ for details of the algorithm used
  2378. *
  2379. * @param float $trials number of Bernoulli trials
  2380. * @param float $probability probability of a success on each trial
  2381. * @param float $alpha criterion value
  2382. * @return int
  2383. *
  2384. * @todo Warning. This implementation differs from the algorithm detailed on the MS
  2385. * web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess
  2386. * This eliminates a potential endless loop error, but may have an adverse affect on the
  2387. * accuracy of the function (although all my tests have so far returned correct results).
  2388. *
  2389. */
  2390. public static function CRITBINOM($trials, $probability, $alpha) {
  2391. $trials = floor(self::flattenSingleValue($trials));
  2392. $probability = self::flattenSingleValue($probability);
  2393. $alpha = self::flattenSingleValue($alpha);
  2394. if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) {
  2395. if ($trials < 0) {
  2396. return self::$_errorCodes['num'];
  2397. }
  2398. if (($probability < 0) || ($probability > 1)) {
  2399. return self::$_errorCodes['num'];
  2400. }
  2401. if (($alpha < 0) || ($alpha > 1)) {
  2402. return self::$_errorCodes['num'];
  2403. }
  2404. if ($alpha <= 0.5) {
  2405. $t = sqrt(log(1 / pow($alpha,2)));
  2406. $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));
  2407. } else {
  2408. $t = sqrt(log(1 / pow(1 - $alpha,2)));
  2409. $trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t);
  2410. }
  2411. $Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability)));
  2412. if ($Guess < 0) {
  2413. $Guess = 0;
  2414. } elseif ($Guess > $trials) {
  2415. $Guess = $trials;
  2416. }
  2417. $TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0;
  2418. $EssentiallyZero = 10e-12;
  2419. $m = floor($trials * $probability);
  2420. ++$TotalUnscaledProbability;
  2421. if ($m == $Guess) { ++$UnscaledPGuess; }
  2422. if ($m <= $Guess) { ++$UnscaledCumPGuess; }
  2423. $PreviousValue = 1;
  2424. $Done = False;
  2425. $k = $m + 1;
  2426. while ((!$Done) && ($k <= $trials)) {
  2427. $CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability));
  2428. $TotalUnscaledProbability += $CurrentValue;
  2429. if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
  2430. if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
  2431. if ($CurrentValue <= $EssentiallyZero) { $Done = True; }
  2432. $PreviousValue = $CurrentValue;
  2433. ++$k;
  2434. }
  2435. $PreviousValue = 1;
  2436. $Done = False;
  2437. $k = $m - 1;
  2438. while ((!$Done) && ($k >= 0)) {
  2439. $CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability);
  2440. $TotalUnscaledProbability += $CurrentValue;
  2441. if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
  2442. if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
  2443. if (CurrentValue <= EssentiallyZero) { $Done = True; }
  2444. $PreviousValue = $CurrentValue;
  2445. --$k;
  2446. }
  2447. $PGuess = $UnscaledPGuess / $TotalUnscaledProbability;
  2448. $CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability;
  2449. // $CumPGuessMinus1 = $CumPGuess - $PGuess;
  2450. $CumPGuessMinus1 = $CumPGuess - 1;
  2451. while (True) {
  2452. if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) {
  2453. return $Guess;
  2454. } elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) {
  2455. $PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability);
  2456. $CumPGuessMinus1 = $CumPGuess;
  2457. $CumPGuess = $CumPGuess + $PGuessPlus1;
  2458. $PGuess = $PGuessPlus1;
  2459. ++$Guess;
  2460. } elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) {
  2461. $PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability;
  2462. $CumPGuess = $CumPGuessMinus1;
  2463. $CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess;
  2464. $PGuess = $PGuessMinus1;
  2465. --$Guess;
  2466. }
  2467. }
  2468. }
  2469. return self::$_errorCodes['value'];
  2470. }
  2471. /**
  2472. * CHIDIST
  2473. *
  2474. * Returns the one-tailed probability of the chi-squared distribution.
  2475. *
  2476. * @param float $value Value for the function
  2477. * @param float $degrees degrees of freedom
  2478. * @return float
  2479. */
  2480. public static function CHIDIST($value, $degrees) {
  2481. $value = self::flattenSingleValue($value);
  2482. $degrees = floor(self::flattenSingleValue($degrees));
  2483. if ((is_numeric($value)) && (is_numeric($degrees))) {
  2484. if ($degrees < 1) {
  2485. return self::$_errorCodes['num'];
  2486. }
  2487. if ($value < 0) {
  2488. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  2489. return 1;
  2490. }
  2491. return self::$_errorCodes['num'];
  2492. }
  2493. return 1 - (self::incompleteGamma($degrees/2,$value/2) / self::gamma($degrees/2));
  2494. }
  2495. return self::$_errorCodes['value'];
  2496. }
  2497. /**
  2498. * CHIINV
  2499. *
  2500. * Returns the one-tailed probability of the chi-squared distribution.
  2501. *
  2502. * @param float $probability Probability for the function
  2503. * @param float $degrees degrees of freedom
  2504. * @return float
  2505. */
  2506. public static function CHIINV($probability, $degrees) {
  2507. $probability = self::flattenSingleValue($probability);
  2508. $degrees = floor(self::flattenSingleValue($degrees));
  2509. if ((is_numeric($probability)) && (is_numeric($degrees))) {
  2510. $xLo = 100;
  2511. $xHi = 0;
  2512. $maxIteration = 100;
  2513. $x = $xNew = 1;
  2514. $dx = 1;
  2515. $i = 0;
  2516. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  2517. // Apply Newton-Raphson step
  2518. $result = self::CHIDIST($x, $degrees);
  2519. $error = $result - $probability;
  2520. if ($error == 0.0) {
  2521. $dx = 0;
  2522. } elseif ($error < 0.0) {
  2523. $xLo = $x;
  2524. } else {
  2525. $xHi = $x;
  2526. }
  2527. // Avoid division by zero
  2528. if ($result != 0.0) {
  2529. $dx = $error / $result;
  2530. $xNew = $x - $dx;
  2531. }
  2532. // If the NR fails to converge (which for example may be the
  2533. // case if the initial guess is too rough) we apply a bisection
  2534. // step to determine a more narrow interval around the root.
  2535. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  2536. $xNew = ($xLo + $xHi) / 2;
  2537. $dx = $xNew - $x;
  2538. }
  2539. $x = $xNew;
  2540. }
  2541. if ($i == MAX_ITERATIONS) {
  2542. return self::$_errorCodes['na'];
  2543. }
  2544. return round($x,12);
  2545. }
  2546. return self::$_errorCodes['value'];
  2547. }
  2548. /**
  2549. * EXPONDIST
  2550. *
  2551. * Returns the exponential distribution. Use EXPONDIST to model the time between events,
  2552. * such as how long an automated bank teller takes to deliver cash. For example, you can
  2553. * use EXPONDIST to determine the probability that the process takes at most 1 minute.
  2554. *
  2555. * @param float $value Value of the function
  2556. * @param float $lambda The parameter value
  2557. * @param boolean $cumulative
  2558. * @return float
  2559. */
  2560. public static function EXPONDIST($value, $lambda, $cumulative) {
  2561. $value = self::flattenSingleValue($value);
  2562. $lambda = self::flattenSingleValue($lambda);
  2563. $cumulative = self::flattenSingleValue($cumulative);
  2564. if ((is_numeric($value)) && (is_numeric($lambda))) {
  2565. if (($value < 0) || ($lambda < 0)) {
  2566. return self::$_errorCodes['num'];
  2567. }
  2568. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  2569. if ($cumulative) {
  2570. return 1 - exp(0-$value*$lambda);
  2571. } else {
  2572. return $lambda * exp(0-$value*$lambda);
  2573. }
  2574. }
  2575. }
  2576. return self::$_errorCodes['value'];
  2577. }
  2578. /**
  2579. * FISHER
  2580. *
  2581. * Returns the Fisher transformation at x. This transformation produces a function that
  2582. * is normally distributed rather than skewed. Use this function to perform hypothesis
  2583. * testing on the correlation coefficient.
  2584. *
  2585. * @param float $value
  2586. * @return float
  2587. */
  2588. public static function FISHER($value) {
  2589. $value = self::flattenSingleValue($value);
  2590. if (is_numeric($value)) {
  2591. if (($value <= -1) || ($lambda >= 1)) {
  2592. return self::$_errorCodes['num'];
  2593. }
  2594. return 0.5 * log((1+$value)/(1-$value));
  2595. }
  2596. return self::$_errorCodes['value'];
  2597. }
  2598. /**
  2599. * FISHERINV
  2600. *
  2601. * Returns the inverse of the Fisher transformation. Use this transformation when
  2602. * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
  2603. * FISHERINV(y) = x.
  2604. *
  2605. * @param float $value
  2606. * @return float
  2607. */
  2608. public static function FISHERINV($value) {
  2609. $value = self::flattenSingleValue($value);
  2610. if (is_numeric($value)) {
  2611. return (exp(2 * $value) - 1) / (exp(2 * $value) + 1);
  2612. }
  2613. return self::$_errorCodes['value'];
  2614. }
  2615. // Function cache for logBeta
  2616. private static $logBetaCache_p = 0.0;
  2617. private static $logBetaCache_q = 0.0;
  2618. private static $logBetaCache_result = 0.0;
  2619. /**
  2620. * The natural logarithm of the beta function.
  2621. * @param p require p>0
  2622. * @param q require q>0
  2623. * @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
  2624. * @author Jaco van Kooten
  2625. */
  2626. private static function logBeta($p, $q) {
  2627. if ($p != self::$logBetaCache_p || $q != self::$logBetaCache_q) {
  2628. self::$logBetaCache_p = $p;
  2629. self::$logBetaCache_q = $q;
  2630. if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
  2631. self::$logBetaCache_result = 0.0;
  2632. } else {
  2633. self::$logBetaCache_result = self::logGamma($p) + self::logGamma($q) - self::logGamma($p + $q);
  2634. }
  2635. }
  2636. return self::$logBetaCache_result;
  2637. }
  2638. /**
  2639. * Evaluates of continued fraction part of incomplete beta function.
  2640. * Based on an idea from Numerical Recipes (W.H. Press et al, 1992).
  2641. * @author Jaco van Kooten
  2642. */
  2643. private static function betaFraction($x, $p, $q) {
  2644. $c = 1.0;
  2645. $sum_pq = $p + $q;
  2646. $p_plus = $p + 1.0;
  2647. $p_minus = $p - 1.0;
  2648. $h = 1.0 - $sum_pq * $x / $p_plus;
  2649. if (abs($h) < XMININ) {
  2650. $h = XMININ;
  2651. }
  2652. $h = 1.0 / $h;
  2653. $frac = $h;
  2654. $m = 1;
  2655. $delta = 0.0;
  2656. while ($m <= MAX_ITERATIONS && abs($delta-1.0) > PRECISION ) {
  2657. $m2 = 2 * $m;
  2658. // even index for d
  2659. $d = $m * ($q - $m) * $x / ( ($p_minus + $m2) * ($p + $m2));
  2660. $h = 1.0 + $d * $h;
  2661. if (abs($h) < XMININ) {
  2662. $h = XMININ;
  2663. }
  2664. $h = 1.0 / $h;
  2665. $c = 1.0 + $d / $c;
  2666. if (abs($c) < XMININ) {
  2667. $c = XMININ;
  2668. }
  2669. $frac *= $h * $c;
  2670. // odd index for d
  2671. $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2));
  2672. $h = 1.0 + $d * $h;
  2673. if (abs($h) < XMININ) {
  2674. $h = XMININ;
  2675. }
  2676. $h = 1.0 / $h;
  2677. $c = 1.0 + $d / $c;
  2678. if (abs($c) < XMININ) {
  2679. $c = XMININ;
  2680. }
  2681. $delta = $h * $c;
  2682. $frac *= $delta;
  2683. ++$m;
  2684. }
  2685. return $frac;
  2686. }
  2687. /**
  2688. * logGamma function
  2689. *
  2690. * @version 1.1
  2691. * @author Jaco van Kooten
  2692. *
  2693. * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
  2694. *
  2695. * The natural logarithm of the gamma function. <br />
  2696. * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
  2697. * Applied Mathematics Division <br />
  2698. * Argonne National Laboratory <br />
  2699. * Argonne, IL 60439 <br />
  2700. * <p>
  2701. * References:
  2702. * <ol>
  2703. * <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
  2704. * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
  2705. * <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
  2706. * <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
  2707. * </ol>
  2708. * </p>
  2709. * <p>
  2710. * From the original documentation:
  2711. * </p>
  2712. * <p>
  2713. * This routine calculates the LOG(GAMMA) function for a positive real argument X.
  2714. * Computation is based on an algorithm outlined in references 1 and 2.
  2715. * The program uses rational functions that theoretically approximate LOG(GAMMA)
  2716. * to at least 18 significant decimal digits. The approximation for X > 12 is from
  2717. * reference 3, while approximations for X < 12.0 are similar to those in reference
  2718. * 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
  2719. * the compiler, the intrinsic functions, and proper selection of the
  2720. * machine-dependent constants.
  2721. * </p>
  2722. * <p>
  2723. * Error returns: <br />
  2724. * The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
  2725. * The computation is believed to be free of underflow and overflow.
  2726. * </p>
  2727. * @return MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
  2728. */
  2729. // Function cache for logGamma
  2730. private static $logGammaCache_result = 0.0;
  2731. private static $logGammaCache_x = 0.0;
  2732. private static function logGamma($x) {
  2733. // Log Gamma related constants
  2734. static $lg_d1 = -0.5772156649015328605195174;
  2735. static $lg_d2 = 0.4227843350984671393993777;
  2736. static $lg_d4 = 1.791759469228055000094023;
  2737. static $lg_p1 = array( 4.945235359296727046734888,
  2738. 201.8112620856775083915565,
  2739. 2290.838373831346393026739,
  2740. 11319.67205903380828685045,
  2741. 28557.24635671635335736389,
  2742. 38484.96228443793359990269,
  2743. 26377.48787624195437963534,
  2744. 7225.813979700288197698961 );
  2745. static $lg_p2 = array( 4.974607845568932035012064,
  2746. 542.4138599891070494101986,
  2747. 15506.93864978364947665077,
  2748. 184793.2904445632425417223,
  2749. 1088204.76946882876749847,
  2750. 3338152.967987029735917223,
  2751. 5106661.678927352456275255,
  2752. 3074109.054850539556250927 );
  2753. static $lg_p4 = array( 14745.02166059939948905062,
  2754. 2426813.369486704502836312,
  2755. 121475557.4045093227939592,
  2756. 2663432449.630976949898078,
  2757. 29403789566.34553899906876,
  2758. 170266573776.5398868392998,
  2759. 492612579337.743088758812,
  2760. 560625185622.3951465078242 );
  2761. static $lg_q1 = array( 67.48212550303777196073036,
  2762. 1113.332393857199323513008,
  2763. 7738.757056935398733233834,
  2764. 27639.87074403340708898585,
  2765. 54993.10206226157329794414,
  2766. 61611.22180066002127833352,
  2767. 36351.27591501940507276287,
  2768. 8785.536302431013170870835 );
  2769. static $lg_q2 = array( 183.0328399370592604055942,
  2770. 7765.049321445005871323047,
  2771. 133190.3827966074194402448,
  2772. 1136705.821321969608938755,
  2773. 5267964.117437946917577538,
  2774. 13467014.54311101692290052,
  2775. 17827365.30353274213975932,
  2776. 9533095.591844353613395747 );
  2777. static $lg_q4 = array( 2690.530175870899333379843,
  2778. 639388.5654300092398984238,
  2779. 41355999.30241388052042842,
  2780. 1120872109.61614794137657,
  2781. 14886137286.78813811542398,
  2782. 101680358627.2438228077304,
  2783. 341747634550.7377132798597,
  2784. 446315818741.9713286462081 );
  2785. static $lg_c = array( -0.001910444077728,
  2786. 8.4171387781295e-4,
  2787. -5.952379913043012e-4,
  2788. 7.93650793500350248e-4,
  2789. -0.002777777777777681622553,
  2790. 0.08333333333333333331554247,
  2791. 0.0057083835261 );
  2792. // Rough estimate of the fourth root of logGamma_xBig
  2793. static $lg_frtbig = 2.25e76;
  2794. static $pnt68 = 0.6796875;
  2795. if ($x == self::$logGammaCache_x) {
  2796. return self::$logGammaCache_result;
  2797. }
  2798. $y = $x;
  2799. if ($y > 0.0 && $y <= LOG_GAMMA_X_MAX_VALUE) {
  2800. if ($y <= EPS) {
  2801. $res = -log(y);
  2802. } elseif ($y <= 1.5) {
  2803. // ---------------------
  2804. // EPS .LT. X .LE. 1.5
  2805. // ---------------------
  2806. if ($y < $pnt68) {
  2807. $corr = -log($y);
  2808. $xm1 = $y;
  2809. } else {
  2810. $corr = 0.0;
  2811. $xm1 = $y - 1.0;
  2812. }
  2813. if ($y <= 0.5 || $y >= $pnt68) {
  2814. $xden = 1.0;
  2815. $xnum = 0.0;
  2816. for ($i = 0; $i < 8; ++$i) {
  2817. $xnum = $xnum * $xm1 + $lg_p1[$i];
  2818. $xden = $xden * $xm1 + $lg_q1[$i];
  2819. }
  2820. $res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden));
  2821. } else {
  2822. $xm2 = $y - 1.0;
  2823. $xden = 1.0;
  2824. $xnum = 0.0;
  2825. for ($i = 0; $i < 8; ++$i) {
  2826. $xnum = $xnum * $xm2 + $lg_p2[$i];
  2827. $xden = $xden * $xm2 + $lg_q2[$i];
  2828. }
  2829. $res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
  2830. }
  2831. } elseif ($y <= 4.0) {
  2832. // ---------------------
  2833. // 1.5 .LT. X .LE. 4.0
  2834. // ---------------------
  2835. $xm2 = $y - 2.0;
  2836. $xden = 1.0;
  2837. $xnum = 0.0;
  2838. for ($i = 0; $i < 8; ++$i) {
  2839. $xnum = $xnum * $xm2 + $lg_p2[$i];
  2840. $xden = $xden * $xm2 + $lg_q2[$i];
  2841. }
  2842. $res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
  2843. } elseif ($y <= 12.0) {
  2844. // ----------------------
  2845. // 4.0 .LT. X .LE. 12.0
  2846. // ----------------------
  2847. $xm4 = $y - 4.0;
  2848. $xden = -1.0;
  2849. $xnum = 0.0;
  2850. for ($i = 0; $i < 8; ++$i) {
  2851. $xnum = $xnum * $xm4 + $lg_p4[$i];
  2852. $xden = $xden * $xm4 + $lg_q4[$i];
  2853. }
  2854. $res = $lg_d4 + $xm4 * ($xnum / $xden);
  2855. } else {
  2856. // ---------------------------------
  2857. // Evaluate for argument .GE. 12.0
  2858. // ---------------------------------
  2859. $res = 0.0;
  2860. if ($y <= $lg_frtbig) {
  2861. $res = $lg_c[6];
  2862. $ysq = $y * $y;
  2863. for ($i = 0; $i < 6; ++$i)
  2864. $res = $res / $ysq + $lg_c[$i];
  2865. }
  2866. $res /= $y;
  2867. $corr = log($y);
  2868. $res = $res + log(SQRT2PI) - 0.5 * $corr;
  2869. $res += $y * ($corr - 1.0);
  2870. }
  2871. } else {
  2872. // --------------------------
  2873. // Return for bad arguments
  2874. // --------------------------
  2875. $res = MAX_VALUE;
  2876. }
  2877. // ------------------------------
  2878. // Final adjustments and return
  2879. // ------------------------------
  2880. self::$logGammaCache_x = $x;
  2881. self::$logGammaCache_result = $res;
  2882. return $res;
  2883. }
  2884. /**
  2885. * Beta function.
  2886. *
  2887. * @author Jaco van Kooten
  2888. *
  2889. * @param p require p>0
  2890. * @param q require q>0
  2891. * @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
  2892. */
  2893. private static function beta($p, $q) {
  2894. if ($p <= 0.0 || $q <= 0.0 || ($p + $q) > LOG_GAMMA_X_MAX_VALUE) {
  2895. return 0.0;
  2896. } else {
  2897. return exp(self::logBeta($p, $q));
  2898. }
  2899. }
  2900. /**
  2901. * Incomplete beta function
  2902. *
  2903. * @author Jaco van Kooten
  2904. * @author Paul Meagher
  2905. *
  2906. * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992).
  2907. * @param x require 0<=x<=1
  2908. * @param p require p>0
  2909. * @param q require q>0
  2910. * @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
  2911. */
  2912. private static function incompleteBeta($x, $p, $q) {
  2913. if ($x <= 0.0) {
  2914. return 0.0;
  2915. } elseif ($x >= 1.0) {
  2916. return 1.0;
  2917. } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
  2918. return 0.0;
  2919. }
  2920. $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x));
  2921. if ($x < ($p + 1.0) / ($p + $q + 2.0)) {
  2922. return $beta_gam * self::betaFraction($x, $p, $q) / $p;
  2923. } else {
  2924. return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q);
  2925. }
  2926. }
  2927. /**
  2928. * BETADIST
  2929. *
  2930. * Returns the beta distribution.
  2931. *
  2932. * @param float $value Value at which you want to evaluate the distribution
  2933. * @param float $alpha Parameter to the distribution
  2934. * @param float $beta Parameter to the distribution
  2935. * @param boolean $cumulative
  2936. * @return float
  2937. *
  2938. */
  2939. public static function BETADIST($value,$alpha,$beta,$rMin=0,$rMax=1) {
  2940. $value = self::flattenSingleValue($value);
  2941. $alpha = self::flattenSingleValue($alpha);
  2942. $beta = self::flattenSingleValue($beta);
  2943. $rMin = self::flattenSingleValue($rMin);
  2944. $rMax = self::flattenSingleValue($rMax);
  2945. if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) {
  2946. if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) {
  2947. return self::$_errorCodes['num'];
  2948. }
  2949. if ($rMin > $rMax) {
  2950. $tmp = $rMin;
  2951. $rMin = $rMax;
  2952. $rMax = $tmp;
  2953. }
  2954. $value -= $rMin;
  2955. $value /= ($rMax - $rMin);
  2956. return self::incompleteBeta($value,$alpha,$beta);
  2957. }
  2958. return self::$_errorCodes['value'];
  2959. }
  2960. /**
  2961. * BETAINV
  2962. *
  2963. * Returns the inverse of the beta distribution.
  2964. *
  2965. * @param float $probability Probability at which you want to evaluate the distribution
  2966. * @param float $alpha Parameter to the distribution
  2967. * @param float $beta Parameter to the distribution
  2968. * @param boolean $cumulative
  2969. * @return float
  2970. *
  2971. */
  2972. public static function BETAINV($probability,$alpha,$beta,$rMin=0,$rMax=1) {
  2973. $probability = self::flattenSingleValue($probability);
  2974. $alpha = self::flattenSingleValue($alpha);
  2975. $beta = self::flattenSingleValue($beta);
  2976. $rMin = self::flattenSingleValue($rMin);
  2977. $rMax = self::flattenSingleValue($rMax);
  2978. if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) {
  2979. if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) {
  2980. return self::$_errorCodes['num'];
  2981. }
  2982. if ($rMin > $rMax) {
  2983. $tmp = $rMin;
  2984. $rMin = $rMax;
  2985. $rMax = $tmp;
  2986. }
  2987. $a = 0;
  2988. $b = 2;
  2989. $maxIteration = 100;
  2990. $i = 0;
  2991. while ((($b - $a) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  2992. $guess = ($a + $b) / 2;
  2993. $result = self::BETADIST($guess, $alpha, $beta);
  2994. if (($result == $probability) || ($result == 0)) {
  2995. $b = $a;
  2996. } elseif ($result > $probability) {
  2997. $b = $guess;
  2998. } else {
  2999. $a = $guess;
  3000. }
  3001. }
  3002. if ($i == MAX_ITERATIONS) {
  3003. return self::$_errorCodes['na'];
  3004. }
  3005. return round($rMin + $guess * ($rMax - $rMin),12);
  3006. }
  3007. return self::$_errorCodes['value'];
  3008. }
  3009. //
  3010. // Private implementation of the incomplete Gamma function
  3011. //
  3012. private static function incompleteGamma($a,$x) {
  3013. static $max = 32;
  3014. $summer = 0;
  3015. for ($n=0; $n<=$max; ++$n) {
  3016. $divisor = $a;
  3017. for ($i=1; $i<=$n; ++$i) {
  3018. $divisor *= ($a + $i);
  3019. }
  3020. $summer += (pow($x,$n) / $divisor);
  3021. }
  3022. return pow($x,$a) * exp(0-$x) * $summer;
  3023. }
  3024. //
  3025. // Private implementation of the Gamma function
  3026. //
  3027. private static function gamma($data) {
  3028. if ($data == 0.0) return 0;
  3029. static $p0 = 1.000000000190015;
  3030. static $p = array ( 1 => 76.18009172947146,
  3031. 2 => -86.50532032941677,
  3032. 3 => 24.01409824083091,
  3033. 4 => -1.231739572450155,
  3034. 5 => 1.208650973866179e-3,
  3035. 6 => -5.395239384953e-6
  3036. );
  3037. $y = $x = $data;
  3038. $tmp = $x + 5.5;
  3039. $tmp -= ($x + 0.5) * log($tmp);
  3040. $summer = $p0;
  3041. for ($j=1;$j<=6;++$j) {
  3042. $summer += ($p[$j] / ++$y);
  3043. }
  3044. return exp(0 - $tmp + log(2.5066282746310005 * $summer / $x));
  3045. }
  3046. /**
  3047. * GAMMADIST
  3048. *
  3049. * Returns the gamma distribution.
  3050. *
  3051. * @param float $value Value at which you want to evaluate the distribution
  3052. * @param float $a Parameter to the distribution
  3053. * @param float $b Parameter to the distribution
  3054. * @param boolean $cumulative
  3055. * @return float
  3056. *
  3057. */
  3058. public static function GAMMADIST($value,$a,$b,$cumulative) {
  3059. $value = self::flattenSingleValue($value);
  3060. $a = self::flattenSingleValue($a);
  3061. $b = self::flattenSingleValue($b);
  3062. if ((is_numeric($value)) && (is_numeric($a)) && (is_numeric($b))) {
  3063. if (($value < 0) || ($a <= 0) || ($b <= 0)) {
  3064. return self::$_errorCodes['num'];
  3065. }
  3066. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3067. if ($cumulative) {
  3068. return self::incompleteGamma($a,$value / $b) / self::gamma($a);
  3069. } else {
  3070. return (1 / (pow($b,$a) * self::gamma($a))) * pow($value,$a-1) * exp(0-($value / $b));
  3071. }
  3072. }
  3073. }
  3074. return self::$_errorCodes['value'];
  3075. }
  3076. /**
  3077. * GAMMAINV
  3078. *
  3079. * Returns the inverse of the beta distribution.
  3080. *
  3081. * @param float $probability Probability at which you want to evaluate the distribution
  3082. * @param float $alpha Parameter to the distribution
  3083. * @param float $beta Parameter to the distribution
  3084. * @param boolean $cumulative
  3085. * @return float
  3086. *
  3087. */
  3088. public static function GAMMAINV($probability,$alpha,$beta) {
  3089. $probability = self::flattenSingleValue($probability);
  3090. $alpha = self::flattenSingleValue($alpha);
  3091. $beta = self::flattenSingleValue($beta);
  3092. $rMin = self::flattenSingleValue($rMin);
  3093. $rMax = self::flattenSingleValue($rMax);
  3094. if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta))) {
  3095. if (($alpha <= 0) || ($beta <= 0) || ($probability <= 0) || ($probability > 1)) {
  3096. return self::$_errorCodes['num'];
  3097. }
  3098. $xLo = 0;
  3099. $xHi = 100;
  3100. $maxIteration = 100;
  3101. $x = $xNew = 1;
  3102. $dx = 1;
  3103. $i = 0;
  3104. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  3105. // Apply Newton-Raphson step
  3106. $result = self::GAMMADIST($x, $alpha, $beta, True);
  3107. $error = $result - $probability;
  3108. if ($error == 0.0) {
  3109. $dx = 0;
  3110. } elseif ($error < 0.0) {
  3111. $xLo = $x;
  3112. } else {
  3113. $xHi = $x;
  3114. }
  3115. // Avoid division by zero
  3116. if ($result != 0.0) {
  3117. $dx = $error / $result;
  3118. $xNew = $x - $dx;
  3119. }
  3120. // If the NR fails to converge (which for example may be the
  3121. // case if the initial guess is too rough) we apply a bisection
  3122. // step to determine a more narrow interval around the root.
  3123. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  3124. $xNew = ($xLo + $xHi) / 2;
  3125. $dx = $xNew - $x;
  3126. }
  3127. $x = $xNew;
  3128. }
  3129. if ($i == MAX_ITERATIONS) {
  3130. return self::$_errorCodes['na'];
  3131. }
  3132. return round($x,12);
  3133. }
  3134. return self::$_errorCodes['value'];
  3135. }
  3136. /**
  3137. * GAMMALN
  3138. *
  3139. * Returns the natural logarithm of the gamma function.
  3140. *
  3141. * @param float $value
  3142. * @return float
  3143. */
  3144. public static function GAMMALN($value) {
  3145. $value = self::flattenSingleValue($value);
  3146. if (is_numeric($value)) {
  3147. if ($value <= 0) {
  3148. return self::$_errorCodes['num'];
  3149. }
  3150. return log(self::gamma($value));
  3151. }
  3152. return self::$_errorCodes['value'];
  3153. }
  3154. /**
  3155. * NORMDIST
  3156. *
  3157. * Returns the normal distribution for the specified mean and standard deviation. This
  3158. * function has a very wide range of applications in statistics, including hypothesis
  3159. * testing.
  3160. *
  3161. * @param float $value
  3162. * @param float $mean Mean Value
  3163. * @param float $stdDev Standard Deviation
  3164. * @param boolean $cumulative
  3165. * @return float
  3166. *
  3167. */
  3168. public static function NORMDIST($value, $mean, $stdDev, $cumulative) {
  3169. $value = self::flattenSingleValue($value);
  3170. $mean = self::flattenSingleValue($mean);
  3171. $stdDev = self::flattenSingleValue($stdDev);
  3172. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3173. if ($stdDev < 0) {
  3174. return self::$_errorCodes['num'];
  3175. }
  3176. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3177. if ($cumulative) {
  3178. return 0.5 * (1 + self::erfVal(($value - $mean) / ($stdDev * sqrt(2))));
  3179. } else {
  3180. return (1 / (SQRT2PI * $stdDev)) * exp(0 - (pow($value - $mean,2) / (2 * pow($stdDev,2))));
  3181. }
  3182. }
  3183. }
  3184. return self::$_errorCodes['value'];
  3185. }
  3186. /**
  3187. * NORMSDIST
  3188. *
  3189. * Returns the standard normal cumulative distribution function. The distribution has
  3190. * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
  3191. * table of standard normal curve areas.
  3192. *
  3193. * @param float $value
  3194. * @return float
  3195. */
  3196. public static function NORMSDIST($value) {
  3197. $value = self::flattenSingleValue($value);
  3198. return self::NORMDIST($value, 0, 1, True);
  3199. }
  3200. /**
  3201. * LOGNORMDIST
  3202. *
  3203. * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
  3204. * with parameters mean and standard_dev.
  3205. *
  3206. * @param float $value
  3207. * @return float
  3208. */
  3209. public static function LOGNORMDIST($value, $mean, $stdDev) {
  3210. $value = self::flattenSingleValue($value);
  3211. $mean = self::flattenSingleValue($mean);
  3212. $stdDev = self::flattenSingleValue($stdDev);
  3213. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3214. if (($value <= 0) || ($stdDev <= 0)) {
  3215. return self::$_errorCodes['num'];
  3216. }
  3217. return self::NORMSDIST((log($value) - $mean) / $stdDev);
  3218. }
  3219. return self::$_errorCodes['value'];
  3220. }
  3221. /***************************************************************************
  3222. * inverse_ncdf.php
  3223. * -------------------
  3224. * begin : Friday, January 16, 2004
  3225. * copyright : (C) 2004 Michael Nickerson
  3226. * email : nickersonm@yahoo.com
  3227. *
  3228. ***************************************************************************/
  3229. private static function inverse_ncdf($p) {
  3230. // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
  3231. // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
  3232. // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
  3233. // I have not checked the accuracy of this implementation. Be aware that PHP
  3234. // will truncate the coeficcients to 14 digits.
  3235. // You have permission to use and distribute this function freely for
  3236. // whatever purpose you want, but please show common courtesy and give credit
  3237. // where credit is due.
  3238. // Input paramater is $p - probability - where 0 < p < 1.
  3239. // Coefficients in rational approximations
  3240. static $a = array( 1 => -3.969683028665376e+01,
  3241. 2 => 2.209460984245205e+02,
  3242. 3 => -2.759285104469687e+02,
  3243. 4 => 1.383577518672690e+02,
  3244. 5 => -3.066479806614716e+01,
  3245. 6 => 2.506628277459239e+00
  3246. );
  3247. static $b = array( 1 => -5.447609879822406e+01,
  3248. 2 => 1.615858368580409e+02,
  3249. 3 => -1.556989798598866e+02,
  3250. 4 => 6.680131188771972e+01,
  3251. 5 => -1.328068155288572e+01
  3252. );
  3253. static $c = array( 1 => -7.784894002430293e-03,
  3254. 2 => -3.223964580411365e-01,
  3255. 3 => -2.400758277161838e+00,
  3256. 4 => -2.549732539343734e+00,
  3257. 5 => 4.374664141464968e+00,
  3258. 6 => 2.938163982698783e+00
  3259. );
  3260. static $d = array( 1 => 7.784695709041462e-03,
  3261. 2 => 3.224671290700398e-01,
  3262. 3 => 2.445134137142996e+00,
  3263. 4 => 3.754408661907416e+00
  3264. );
  3265. // Define lower and upper region break-points.
  3266. $p_low = 0.02425; //Use lower region approx. below this
  3267. $p_high = 1 - $p_low; //Use upper region approx. above this
  3268. if (0 < $p && $p < $p_low) {
  3269. // Rational approximation for lower region.
  3270. $q = sqrt(-2 * log($p));
  3271. return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
  3272. (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
  3273. } elseif ($p_low <= $p && $p <= $p_high) {
  3274. // Rational approximation for central region.
  3275. $q = $p - 0.5;
  3276. $r = $q * $q;
  3277. return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
  3278. ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
  3279. } elseif ($p_high < $p && $p < 1) {
  3280. // Rational approximation for upper region.
  3281. $q = sqrt(-2 * log(1 - $p));
  3282. return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
  3283. (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
  3284. }
  3285. // If 0 < p < 1, return a null value
  3286. return self::$_errorCodes['null'];
  3287. }
  3288. private static function inverse_ncdf2($prob) {
  3289. // Approximation of inverse standard normal CDF developed by
  3290. // B. Moro, "The Full Monte," Risk 8(2), Feb 1995, 57-58.
  3291. $a1 = 2.50662823884;
  3292. $a2 = -18.61500062529;
  3293. $a3 = 41.39119773534;
  3294. $a4 = -25.44106049637;
  3295. $b1 = -8.4735109309;
  3296. $b2 = 23.08336743743;
  3297. $b3 = -21.06224101826;
  3298. $b4 = 3.13082909833;
  3299. $c1 = 0.337475482272615;
  3300. $c2 = 0.976169019091719;
  3301. $c3 = 0.160797971491821;
  3302. $c4 = 2.76438810333863E-02;
  3303. $c5 = 3.8405729373609E-03;
  3304. $c6 = 3.951896511919E-04;
  3305. $c7 = 3.21767881768E-05;
  3306. $c8 = 2.888167364E-07;
  3307. $c9 = 3.960315187E-07;
  3308. $y = $prob - 0.5;
  3309. if (abs($y) < 0.42) {
  3310. $z = pow($y,2);
  3311. $z = $y * ((($a4 * $z + $a3) * $z + $a2) * $z + $a1) / (((($b4 * $z + $b3) * $z + $b2) * $z + $b1) * $z + 1);
  3312. } else {
  3313. if ($y > 0) {
  3314. $z = log(-log(1 - $prob));
  3315. } else {
  3316. $z = log(-log($prob));
  3317. }
  3318. $z = $c1 + $z * ($c2 + $z * ($c3 + $z * ($c4 + $z * ($c5 + $z * ($c6 + $z * ($c7 + $z * ($c8 + $z * $c9)))))));
  3319. if ($y < 0) {
  3320. $z = -$z;
  3321. }
  3322. }
  3323. return $z;
  3324. }
  3325. private static function inverse_ncdf3($p) {
  3326. // ALGORITHM AS241 APPL. STATIST. (1988) VOL. 37, NO. 3.
  3327. // Produces the normal deviate Z corresponding to a given lower
  3328. // tail area of P; Z is accurate to about 1 part in 10**16.
  3329. //
  3330. // This is a PHP version of the original FORTRAN code that can
  3331. // be found at http://lib.stat.cmu.edu/apstat/
  3332. $split1 = 0.425;
  3333. $split2 = 5;
  3334. $const1 = 0.180625;
  3335. $const2 = 1.6;
  3336. // coefficients for p close to 0.5
  3337. $a0 = 3.3871328727963666080;
  3338. $a1 = 1.3314166789178437745E+2;
  3339. $a2 = 1.9715909503065514427E+3;
  3340. $a3 = 1.3731693765509461125E+4;
  3341. $a4 = 4.5921953931549871457E+4;
  3342. $a5 = 6.7265770927008700853E+4;
  3343. $a6 = 3.3430575583588128105E+4;
  3344. $a7 = 2.5090809287301226727E+3;
  3345. $b1 = 4.2313330701600911252E+1;
  3346. $b2 = 6.8718700749205790830E+2;
  3347. $b3 = 5.3941960214247511077E+3;
  3348. $b4 = 2.1213794301586595867E+4;
  3349. $b5 = 3.9307895800092710610E+4;
  3350. $b6 = 2.8729085735721942674E+4;
  3351. $b7 = 5.2264952788528545610E+3;
  3352. // coefficients for p not close to 0, 0.5 or 1.
  3353. $c0 = 1.42343711074968357734;
  3354. $c1 = 4.63033784615654529590;
  3355. $c2 = 5.76949722146069140550;
  3356. $c3 = 3.64784832476320460504;
  3357. $c4 = 1.27045825245236838258;
  3358. $c5 = 2.41780725177450611770E-1;
  3359. $c6 = 2.27238449892691845833E-2;
  3360. $c7 = 7.74545014278341407640E-4;
  3361. $d1 = 2.05319162663775882187;
  3362. $d2 = 1.67638483018380384940;
  3363. $d3 = 6.89767334985100004550E-1;
  3364. $d4 = 1.48103976427480074590E-1;
  3365. $d5 = 1.51986665636164571966E-2;
  3366. $d6 = 5.47593808499534494600E-4;
  3367. $d7 = 1.05075007164441684324E-9;
  3368. // coefficients for p near 0 or 1.
  3369. $e0 = 6.65790464350110377720;
  3370. $e1 = 5.46378491116411436990;
  3371. $e2 = 1.78482653991729133580;
  3372. $e3 = 2.96560571828504891230E-1;
  3373. $e4 = 2.65321895265761230930E-2;
  3374. $e5 = 1.24266094738807843860E-3;
  3375. $e6 = 2.71155556874348757815E-5;
  3376. $e7 = 2.01033439929228813265E-7;
  3377. $f1 = 5.99832206555887937690E-1;
  3378. $f2 = 1.36929880922735805310E-1;
  3379. $f3 = 1.48753612908506148525E-2;
  3380. $f4 = 7.86869131145613259100E-4;
  3381. $f5 = 1.84631831751005468180E-5;
  3382. $f6 = 1.42151175831644588870E-7;
  3383. $f7 = 2.04426310338993978564E-15;
  3384. $q = $p - 0.5;
  3385. // computation for p close to 0.5
  3386. if (abs($q) <= split1) {
  3387. $R = $const1 - $q * $q;
  3388. $z = $q * ((((((($a7 * $R + $a6) * $R + $a5) * $R + $a4) * $R + $a3) * $R + $a2) * $R + $a1) * $R + $a0) /
  3389. ((((((($b7 * $R + $b6) * $R + $b5) * $R + $b4) * $R + $b3) * $R + $b2) * $R + $b1) * $R + 1);
  3390. } else {
  3391. if ($q < 0) {
  3392. $R = $p;
  3393. } else {
  3394. $R = 1 - $p;
  3395. }
  3396. $R = pow(-log($R),2);
  3397. // computation for p not close to 0, 0.5 or 1.
  3398. If ($R <= $split2) {
  3399. $R = $R - $const2;
  3400. $z = ((((((($c7 * $R + $c6) * $R + $c5) * $R + $c4) * $R + $c3) * $R + $c2) * $R + $c1) * $R + $c0) /
  3401. ((((((($d7 * $R + $d6) * $R + $d5) * $R + $d4) * $R + $d3) * $R + $d2) * $R + $d1) * $R + 1);
  3402. } else {
  3403. // computation for p near 0 or 1.
  3404. $R = $R - $split2;
  3405. $z = ((((((($e7 * $R + $e6) * $R + $e5) * $R + $e4) * $R + $e3) * $R + $e2) * $R + $e1) * $R + $e0) /
  3406. ((((((($f7 * $R + $f6) * $R + $f5) * $R + $f4) * $R + $f3) * $R + $f2) * $R + $f1) * $R + 1);
  3407. }
  3408. if ($q < 0) {
  3409. $z = -$z;
  3410. }
  3411. }
  3412. return $z;
  3413. }
  3414. /**
  3415. * NORMINV
  3416. *
  3417. * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
  3418. *
  3419. * @param float $value
  3420. * @param float $mean Mean Value
  3421. * @param float $stdDev Standard Deviation
  3422. * @return float
  3423. *
  3424. */
  3425. public static function NORMINV($probability,$mean,$stdDev) {
  3426. $probability = self::flattenSingleValue($probability);
  3427. $mean = self::flattenSingleValue($mean);
  3428. $stdDev = self::flattenSingleValue($stdDev);
  3429. if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3430. if (($probability < 0) || ($probability > 1)) {
  3431. return self::$_errorCodes['num'];
  3432. }
  3433. if ($stdDev < 0) {
  3434. return self::$_errorCodes['num'];
  3435. }
  3436. return (self::inverse_ncdf($probability) * $stdDev) + $mean;
  3437. }
  3438. return self::$_errorCodes['value'];
  3439. }
  3440. /**
  3441. * NORMSINV
  3442. *
  3443. * Returns the inverse of the standard normal cumulative distribution
  3444. *
  3445. * @param float $value
  3446. * @return float
  3447. */
  3448. public static function NORMSINV($value) {
  3449. return self::NORMINV($value, 0, 1);
  3450. }
  3451. /**
  3452. * LOGINV
  3453. *
  3454. * Returns the inverse of the normal cumulative distribution
  3455. *
  3456. * @param float $value
  3457. * @return float
  3458. *
  3459. * @todo Try implementing P J Acklam's refinement algorithm for greater
  3460. * accuracy if I can get my head round the mathematics
  3461. * (as described at) http://home.online.no/~pjacklam/notes/invnorm/
  3462. */
  3463. public static function LOGINV($probability, $mean, $stdDev) {
  3464. $probability = self::flattenSingleValue($probability);
  3465. $mean = self::flattenSingleValue($mean);
  3466. $stdDev = self::flattenSingleValue($stdDev);
  3467. if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3468. if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) {
  3469. return self::$_errorCodes['num'];
  3470. }
  3471. return exp($mean + $stdDev * self::NORMSINV($probability));
  3472. }
  3473. return self::$_errorCodes['value'];
  3474. }
  3475. /**
  3476. * HYPGEOMDIST
  3477. *
  3478. * Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of
  3479. * sample successes, given the sample size, population successes, and population size.
  3480. *
  3481. * @param float $sampleSuccesses Number of successes in the sample
  3482. * @param float $sampleNumber Size of the sample
  3483. * @param float $populationSuccesses Number of successes in the population
  3484. * @param float $populationNumber Population size
  3485. * @return float
  3486. *
  3487. */
  3488. public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
  3489. $sampleSuccesses = floor(self::flattenSingleValue($sampleSuccesses));
  3490. $sampleNumber = floor(self::flattenSingleValue($sampleNumber));
  3491. $populationSuccesses = floor(self::flattenSingleValue($populationSuccesses));
  3492. $populationNumber = floor(self::flattenSingleValue($populationNumber));
  3493. if ((is_numeric($sampleSuccesses)) && (is_numeric($sampleNumber)) && (is_numeric($populationSuccesses)) && (is_numeric($populationNumber))) {
  3494. if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
  3495. return self::$_errorCodes['num'];
  3496. }
  3497. if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
  3498. return self::$_errorCodes['num'];
  3499. }
  3500. if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
  3501. return self::$_errorCodes['num'];
  3502. }
  3503. return self::COMBIN($populationSuccesses,$sampleSuccesses) *
  3504. self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
  3505. self::COMBIN($populationNumber,$sampleNumber);
  3506. }
  3507. return self::$_errorCodes['value'];
  3508. }
  3509. public static function hypGeom($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
  3510. return self::COMBIN($populationSuccesses,$sampleSuccesses) *
  3511. self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
  3512. self::COMBIN($populationNumber,$sampleNumber);
  3513. }
  3514. /**
  3515. * TDIST
  3516. *
  3517. * Returns the probability of Student's T distribution.
  3518. *
  3519. * @param float $value Value for the function
  3520. * @param float $degrees degrees of freedom
  3521. * @param float $tails number of tails (1 or 2)
  3522. * @return float
  3523. */
  3524. public static function TDIST($value, $degrees, $tails) {
  3525. $value = self::flattenSingleValue($value);
  3526. $degrees = floor(self::flattenSingleValue($degrees));
  3527. $tails = floor(self::flattenSingleValue($tails));
  3528. if ((is_numeric($value)) && (is_numeric($degrees)) && (is_numeric($tails))) {
  3529. if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
  3530. return self::$_errorCodes['num'];
  3531. }
  3532. // tdist, which finds the probability that corresponds to a given value
  3533. // of t with k degrees of freedom. This algorithm is translated from a
  3534. // pascal function on p81 of "Statistical Computing in Pascal" by D
  3535. // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
  3536. // London). The above Pascal algorithm is itself a translation of the
  3537. // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
  3538. // Laboratory as reported in (among other places) "Applied Statistics
  3539. // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
  3540. // Horwood Ltd.; W. Sussex, England).
  3541. // $ta = 2 / pi();
  3542. $ta = 0.636619772367581;
  3543. $tterm = $degrees;
  3544. $ttheta = atan2($value,sqrt($tterm));
  3545. $tc = cos($ttheta);
  3546. $ts = sin($ttheta);
  3547. $tsum = 0;
  3548. if (($degrees % 2) == 1) {
  3549. $ti = 3;
  3550. $tterm = $tc;
  3551. } else {
  3552. $ti = 2;
  3553. $tterm = 1;
  3554. }
  3555. $tsum = $tterm;
  3556. while ($ti < $degrees) {
  3557. $tterm *= $tc * $tc * ($ti - 1) / $ti;
  3558. $tsum += $tterm;
  3559. $ti += 2;
  3560. }
  3561. $tsum *= $ts;
  3562. if (($degrees % 2) == 1) { $tsum = $ta * ($tsum + $ttheta); }
  3563. $tValue = 0.5 * (1 + $tsum);
  3564. if ($tails == 1) {
  3565. return 1 - abs($tValue);
  3566. } else {
  3567. return 1 - abs((1 - $tValue) - $tValue);
  3568. }
  3569. }
  3570. return self::$_errorCodes['value'];
  3571. }
  3572. /**
  3573. * TINV
  3574. *
  3575. * Returns the one-tailed probability of the chi-squared distribution.
  3576. *
  3577. * @param float $probability Probability for the function
  3578. * @param float $degrees degrees of freedom
  3579. * @return float
  3580. */
  3581. public static function TINV($probability, $degrees) {
  3582. $probability = self::flattenSingleValue($probability);
  3583. $degrees = floor(self::flattenSingleValue($degrees));
  3584. if ((is_numeric($probability)) && (is_numeric($degrees))) {
  3585. $xLo = 100;
  3586. $xHi = 0;
  3587. $maxIteration = 100;
  3588. $x = $xNew = 1;
  3589. $dx = 1;
  3590. $i = 0;
  3591. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  3592. // Apply Newton-Raphson step
  3593. $result = self::TDIST($x, $degrees, 2);
  3594. $error = $result - $probability;
  3595. if ($error == 0.0) {
  3596. $dx = 0;
  3597. } elseif ($error < 0.0) {
  3598. $xLo = $x;
  3599. } else {
  3600. $xHi = $x;
  3601. }
  3602. // Avoid division by zero
  3603. if ($result != 0.0) {
  3604. $dx = $error / $result;
  3605. $xNew = $x - $dx;
  3606. }
  3607. // If the NR fails to converge (which for example may be the
  3608. // case if the initial guess is too rough) we apply a bisection
  3609. // step to determine a more narrow interval around the root.
  3610. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  3611. $xNew = ($xLo + $xHi) / 2;
  3612. $dx = $xNew - $x;
  3613. }
  3614. $x = $xNew;
  3615. }
  3616. if ($i == MAX_ITERATIONS) {
  3617. return self::$_errorCodes['na'];
  3618. }
  3619. return round($x,12);
  3620. }
  3621. return self::$_errorCodes['value'];
  3622. }
  3623. /**
  3624. * CONFIDENCE
  3625. *
  3626. * Returns the confidence interval for a population mean
  3627. *
  3628. * @param float $alpha
  3629. * @param float $stdDev Standard Deviation
  3630. * @param float $size
  3631. * @return float
  3632. *
  3633. */
  3634. public static function CONFIDENCE($alpha,$stdDev,$size) {
  3635. $alpha = self::flattenSingleValue($alpha);
  3636. $stdDev = self::flattenSingleValue($stdDev);
  3637. $size = floor(self::flattenSingleValue($size));
  3638. if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) {
  3639. if (($alpha <= 0) || ($alpha >= 1)) {
  3640. return self::$_errorCodes['num'];
  3641. }
  3642. if (($stdDev <= 0) || ($size < 1)) {
  3643. return self::$_errorCodes['num'];
  3644. }
  3645. return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
  3646. }
  3647. return self::$_errorCodes['value'];
  3648. }
  3649. /**
  3650. * POISSON
  3651. *
  3652. * Returns the Poisson distribution. A common application of the Poisson distribution
  3653. * is predicting the number of events over a specific time, such as the number of
  3654. * cars arriving at a toll plaza in 1 minute.
  3655. *
  3656. * @param float $value
  3657. * @param float $mean Mean Value
  3658. * @param boolean $cumulative
  3659. * @return float
  3660. *
  3661. */
  3662. public static function POISSON($value, $mean, $cumulative) {
  3663. $value = self::flattenSingleValue($value);
  3664. $mean = self::flattenSingleValue($mean);
  3665. if ((is_numeric($value)) && (is_numeric($mean))) {
  3666. if (($value <= 0) || ($mean <= 0)) {
  3667. return self::$_errorCodes['num'];
  3668. }
  3669. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3670. if ($cumulative) {
  3671. $summer = 0;
  3672. for ($i = 0; $i <= floor($value); ++$i) {
  3673. $summer += pow($mean,$i) / self::FACT($i);
  3674. }
  3675. return exp(0-$mean) * $summer;
  3676. } else {
  3677. return (exp(0-$mean) * pow($mean,$value)) / self::FACT($value);
  3678. }
  3679. }
  3680. }
  3681. return self::$_errorCodes['value'];
  3682. }
  3683. /**
  3684. * WEIBULL
  3685. *
  3686. * Returns the Weibull distribution. Use this distribution in reliability
  3687. * analysis, such as calculating a device's mean time to failure.
  3688. *
  3689. * @param float $value
  3690. * @param float $alpha Alpha Parameter
  3691. * @param float $beta Beta Parameter
  3692. * @param boolean $cumulative
  3693. * @return float
  3694. *
  3695. */
  3696. public static function WEIBULL($value, $alpha, $beta, $cumulative) {
  3697. $value = self::flattenSingleValue($value);
  3698. $alpha = self::flattenSingleValue($alpha);
  3699. $beta = self::flattenSingleValue($beta);
  3700. if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta))) {
  3701. if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) {
  3702. return self::$_errorCodes['num'];
  3703. }
  3704. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3705. if ($cumulative) {
  3706. return 1 - exp(0 - pow($value / $beta,$alpha));
  3707. } else {
  3708. return ($alpha / pow($beta,$alpha)) * pow($value,$alpha - 1) * exp(0 - pow($value / $beta,$alpha));
  3709. }
  3710. }
  3711. }
  3712. return self::$_errorCodes['value'];
  3713. }
  3714. /**
  3715. * SKEW
  3716. *
  3717. * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry
  3718. * of a distribution around its mean. Positive skewness indicates a distribution with an
  3719. * asymmetric tail extending toward more positive values. Negative skewness indicates a
  3720. * distribution with an asymmetric tail extending toward more negative values.
  3721. *
  3722. * @param array Data Series
  3723. * @return float
  3724. */
  3725. public static function SKEW() {
  3726. $aArgs = self::flattenArray(func_get_args());
  3727. $mean = self::AVERAGE($aArgs);
  3728. $stdDev = self::STDEV($aArgs);
  3729. $count = $summer = 0;
  3730. // Loop through arguments
  3731. foreach ($aArgs as $arg) {
  3732. // Is it a numeric value?
  3733. if ((is_numeric($arg)) && (!is_string($arg))) {
  3734. $summer += pow((($arg - $mean) / $stdDev),3) ;
  3735. ++$count;
  3736. }
  3737. }
  3738. // Return
  3739. if ($count > 2) {
  3740. return $summer * ($count / (($count-1) * ($count-2)));
  3741. }
  3742. return self::$_errorCodes['divisionbyzero'];
  3743. }
  3744. /**
  3745. * KURT
  3746. *
  3747. * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness
  3748. * or flatness of a distribution compared with the normal distribution. Positive
  3749. * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a
  3750. * relatively flat distribution.
  3751. *
  3752. * @param array Data Series
  3753. * @return float
  3754. */
  3755. public static function KURT() {
  3756. $aArgs = self::flattenArray(func_get_args());
  3757. $mean = self::AVERAGE($aArgs);
  3758. $stdDev = self::STDEV($aArgs);
  3759. if ($stdDev > 0) {
  3760. $count = $summer = 0;
  3761. // Loop through arguments
  3762. foreach ($aArgs as $arg) {
  3763. // Is it a numeric value?
  3764. if ((is_numeric($arg)) && (!is_string($arg))) {
  3765. $summer += pow((($arg - $mean) / $stdDev),4) ;
  3766. ++$count;
  3767. }
  3768. }
  3769. // Return
  3770. if ($count > 3) {
  3771. return $summer * ($count * ($count+1) / (($count-1) * ($count-2) * ($count-3))) - (3 * pow($count-1,2) / (($count-2) * ($count-3)));
  3772. }
  3773. }
  3774. return self::$_errorCodes['divisionbyzero'];
  3775. }
  3776. /**
  3777. * RAND
  3778. *
  3779. * @param int $min Minimal value
  3780. * @param int $max Maximal value
  3781. * @return int Random number
  3782. */
  3783. public static function RAND($min = 0, $max = 0) {
  3784. $min = self::flattenSingleValue($min);
  3785. $max = self::flattenSingleValue($max);
  3786. if ($min == 0 && $max == 0) {
  3787. return (rand(0,10000000)) / 10000000;
  3788. } else {
  3789. return rand($min, $max);
  3790. }
  3791. }
  3792. /**
  3793. * MOD
  3794. *
  3795. * @param int $a Dividend
  3796. * @param int $b Divisor
  3797. * @return int Remainder
  3798. */
  3799. public static function MOD($a = 1, $b = 1) {
  3800. $a = self::flattenSingleValue($a);
  3801. $b = self::flattenSingleValue($b);
  3802. return $a % $b;
  3803. }
  3804. /**
  3805. * ASCIICODE
  3806. *
  3807. * @param string $character Value
  3808. * @return int
  3809. */
  3810. public static function ASCIICODE($characters) {
  3811. $characters = self::flattenSingleValue($characters);
  3812. if (is_bool($characters)) {
  3813. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  3814. $characters = (int) $characters;
  3815. } else {
  3816. if ($characters) {
  3817. $characters = 'True';
  3818. } else {
  3819. $characters = 'False';
  3820. }
  3821. }
  3822. }
  3823. if (strlen($characters) > 0) {
  3824. return ord(substr($characters, 0, 1));
  3825. }
  3826. return self::$_errorCodes['value'];
  3827. }
  3828. /**
  3829. * CONCATENATE
  3830. *
  3831. * @return string
  3832. */
  3833. public static function CONCATENATE() {
  3834. // Return value
  3835. $returnValue = '';
  3836. // Loop trough arguments
  3837. $aArgs = self::flattenArray(func_get_args());
  3838. foreach ($aArgs as $arg) {
  3839. if (is_bool($arg)) {
  3840. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  3841. $arg = (int) $arg;
  3842. } else {
  3843. if ($arg) {
  3844. $arg = 'TRUE';
  3845. } else {
  3846. $arg = 'FALSE';
  3847. }
  3848. }
  3849. }
  3850. $returnValue .= $arg;
  3851. }
  3852. // Return
  3853. return $returnValue;
  3854. }
  3855. /**
  3856. * SEARCHSENSITIVE
  3857. *
  3858. * @param string $needle The string to look for
  3859. * @param string $haystack The string in which to look
  3860. * @param int $offset Offset within $haystack
  3861. * @return string
  3862. */
  3863. public static function SEARCHSENSITIVE($needle,$haystack,$offset=1) {
  3864. $needle = (string) self::flattenSingleValue($needle);
  3865. $haystack = (string) self::flattenSingleValue($haystack);
  3866. $offset = self::flattenSingleValue($offset);
  3867. if (($offset > 0) && (strlen($haystack) > $offset)) {
  3868. $pos = strpos($haystack, $needle, --$offset);
  3869. if ($pos !== false) {
  3870. return ++$pos;
  3871. }
  3872. }
  3873. return self::$_errorCodes['value'];
  3874. }
  3875. /**
  3876. * SEARCHINSENSITIVE
  3877. *
  3878. * @param string $needle The string to look for
  3879. * @param string $haystack The string in which to look
  3880. * @param int $offset Offset within $haystack
  3881. * @return string
  3882. */
  3883. public static function SEARCHINSENSITIVE($needle,$haystack,$offset=1) {
  3884. $needle = (string) self::flattenSingleValue($needle);
  3885. $haystack = (string) self::flattenSingleValue($haystack);
  3886. $offset = self::flattenSingleValue($offset);
  3887. if (($offset > 0) && (strlen($haystack) > $offset)) {
  3888. $pos = stripos($haystack, $needle, --$offset);
  3889. if ($pos !== false) {
  3890. return ++$pos;
  3891. }
  3892. }
  3893. return self::$_errorCodes['value'];
  3894. }
  3895. /**
  3896. * LEFT
  3897. *
  3898. * @param string $value Value
  3899. * @param int $chars Number of characters
  3900. * @return string
  3901. */
  3902. public static function LEFT($value = '', $chars = null) {
  3903. $value = self::flattenSingleValue($value);
  3904. $chars = self::flattenSingleValue($chars);
  3905. return substr($value, 0, $chars);
  3906. }
  3907. /**
  3908. * RIGHT
  3909. *
  3910. * @param string $value Value
  3911. * @param int $chars Number of characters
  3912. * @return string
  3913. */
  3914. public static function RIGHT($value = '', $chars = null) {
  3915. $value = self::flattenSingleValue($value);
  3916. $chars = self::flattenSingleValue($chars);
  3917. return substr($value, strlen($value) - $chars);
  3918. }
  3919. /**
  3920. * MID
  3921. *
  3922. * @param string $value Value
  3923. * @param int $start Start character
  3924. * @param int $chars Number of characters
  3925. * @return string
  3926. */
  3927. public static function MID($value = '', $start = 1, $chars = null) {
  3928. $value = self::flattenSingleValue($value);
  3929. $start = self::flattenSingleValue($start);
  3930. $chars = self::flattenSingleValue($chars);
  3931. return substr($value, --$start, $chars);
  3932. }
  3933. /**
  3934. * RETURNSTRING
  3935. *
  3936. * @param mixed $value Value to check
  3937. * @return boolean
  3938. */
  3939. public static function RETURNSTRING($testValue = '') {
  3940. $testValue = self::flattenSingleValue($testValue);
  3941. if (is_string($testValue)) {
  3942. return $testValue;
  3943. }
  3944. return Null;
  3945. }
  3946. /**
  3947. * TRIMSPACES
  3948. *
  3949. * @param mixed $value Value to check
  3950. * @return string
  3951. */
  3952. public static function TRIMSPACES($stringValue = '') {
  3953. $stringValue = self::flattenSingleValue($stringValue);
  3954. if (is_string($stringValue)) {
  3955. return str_replace(' ',' ',trim($stringValue));
  3956. }
  3957. return Null;
  3958. }
  3959. private static $_invalidChars = Null;
  3960. /**
  3961. * TRIMNONPRINTABLE
  3962. *
  3963. * @param mixed $value Value to check
  3964. * @return string
  3965. */
  3966. public static function TRIMNONPRINTABLE($stringValue = '') {
  3967. $stringValue = self::flattenSingleValue($stringValue);
  3968. if (self::$_invalidChars == Null) {
  3969. self::$_invalidChars = range(chr(0),chr(31));
  3970. }
  3971. if (is_string($stringValue)) {
  3972. return str_replace(self::$_invalidChars,'',trim($stringValue,"\x00..\x1F"));
  3973. }
  3974. return Null;
  3975. }
  3976. /**
  3977. * ERROR_TYPE
  3978. *
  3979. * @param mixed $value Value to check
  3980. * @return boolean
  3981. */
  3982. public static function ERROR_TYPE($value = '') {
  3983. $value = self::flattenSingleValue($value);
  3984. $i == i;
  3985. foreach(self::$_errorCodes as $errorCode) {
  3986. if ($value == $errorCode) {
  3987. return $i;
  3988. }
  3989. ++$i;
  3990. }
  3991. return self::$_errorCodes['na'];
  3992. }
  3993. /**
  3994. * IS_BLANK
  3995. *
  3996. * @param mixed $value Value to check
  3997. * @return boolean
  3998. */
  3999. public static function IS_BLANK($value = '') {
  4000. $value = self::flattenSingleValue($value);
  4001. return (is_null($value) || (is_string($value) && ($value == '')));
  4002. }
  4003. /**
  4004. * IS_ERR
  4005. *
  4006. * @param mixed $value Value to check
  4007. * @return boolean
  4008. */
  4009. public static function IS_ERR($value = '') {
  4010. $value = self::flattenSingleValue($value);
  4011. return self::IS_ERROR($value) && (!self::IS_NA($value));
  4012. }
  4013. /**
  4014. * IS_ERROR
  4015. *
  4016. * @param mixed $value Value to check
  4017. * @return boolean
  4018. */
  4019. public static function IS_ERROR($value = '') {
  4020. $value = self::flattenSingleValue($value);
  4021. return in_array($value, array_values(self::$_errorCodes));
  4022. }
  4023. /**
  4024. * IS_NA
  4025. *
  4026. * @param mixed $value Value to check
  4027. * @return boolean
  4028. */
  4029. public static function IS_NA($value = '') {
  4030. $value = self::flattenSingleValue($value);
  4031. return ($value == self::$_errorCodes['na']);
  4032. }
  4033. /**
  4034. * IS_EVEN
  4035. *
  4036. * @param mixed $value Value to check
  4037. * @return boolean
  4038. */
  4039. public static function IS_EVEN($value = 0) {
  4040. $value = self::flattenSingleValue($value);
  4041. while (intval($value) != $value) {
  4042. $value *= 10;
  4043. }
  4044. return ($value % 2 == 0);
  4045. }
  4046. /**
  4047. * IS_NUMBER
  4048. *
  4049. * @param mixed $value Value to check
  4050. * @return boolean
  4051. */
  4052. public static function IS_NUMBER($value = 0) {
  4053. $value = self::flattenSingleValue($value);
  4054. return is_numeric($value);
  4055. }
  4056. /**
  4057. * IS_LOGICAL
  4058. *
  4059. * @param mixed $value Value to check
  4060. * @return boolean
  4061. */
  4062. public static function IS_LOGICAL($value = true) {
  4063. $value = self::flattenSingleValue($value);
  4064. return is_bool($value);
  4065. }
  4066. /**
  4067. * IS_TEXT
  4068. *
  4069. * @param mixed $value Value to check
  4070. * @return boolean
  4071. */
  4072. public static function IS_TEXT($value = '') {
  4073. $value = self::flattenSingleValue($value);
  4074. return is_string($value);
  4075. }
  4076. /**
  4077. * STATEMENT_IF
  4078. *
  4079. * @param mixed $value Value to check
  4080. * @param mixed $truepart Value when true
  4081. * @param mixed $falsepart Value when false
  4082. * @return mixed
  4083. */
  4084. public static function STATEMENT_IF($value = true, $truepart = '', $falsepart = '') {
  4085. $value = self::flattenSingleValue($value);
  4086. $truepart = self::flattenSingleValue($truepart);
  4087. $falsepart = self::flattenSingleValue($falsepart);
  4088. return ($value ? $truepart : $falsepart);
  4089. }
  4090. /**
  4091. * STATEMENT_IFERROR
  4092. *
  4093. * @param mixed $value Value to check , is also value when no error
  4094. * @param mixed $errorpart Value when error
  4095. * @return mixed
  4096. */
  4097. public static function STATEMENT_IFERROR($value = '', $errorpart = '') {
  4098. return self::STATEMENT_IF(self::IS_ERROR($value), $errorpart, $value);
  4099. }
  4100. /**
  4101. * VERSION
  4102. *
  4103. * @return string Version information
  4104. */
  4105. public static function VERSION() {
  4106. return 'PHPExcel ##VERSION##, ##DATE##';
  4107. }
  4108. /**
  4109. * DATE
  4110. *
  4111. * @param long $year
  4112. * @param long $month
  4113. * @param long $day
  4114. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  4115. * depending on the value of the ReturnDateType flag
  4116. */
  4117. public static function DATE($year = 0, $month = 1, $day = 1) {
  4118. $year = (integer) self::flattenSingleValue($year);
  4119. $month = (integer) self::flattenSingleValue($month);
  4120. $day = (integer) self::flattenSingleValue($day);
  4121. $baseYear = PHPExcel_Shared_Date::getExcelCalendar();
  4122. // Validate parameters
  4123. if ($year < ($baseYear-1900)) {
  4124. return self::$_errorCodes['num'];
  4125. }
  4126. if ((($baseYear-1900) != 0) && ($year < $baseYear) && ($year >= 1900)) {
  4127. return self::$_errorCodes['num'];
  4128. }
  4129. if (($year < $baseYear) && ($year > ($baseYear-1900))) {
  4130. $year += 1900;
  4131. }
  4132. if ($month < 1) {
  4133. // Handle year/month adjustment if month < 1
  4134. --$month;
  4135. $year += ceil($month / 12) - 1;
  4136. $month = 13 - abs($month % 12);
  4137. } elseif ($month > 12) {
  4138. // Handle year/month adjustment if month > 12
  4139. $year += floor($month / 12);
  4140. $month = ($month % 12);
  4141. }
  4142. // Re-validate the year parameter after adjustments
  4143. if (($year < $baseYear) || ($year >= 10000)) {
  4144. return self::$_errorCodes['num'];
  4145. }
  4146. // Execute function
  4147. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($year, $month, $day);
  4148. switch (self::getReturnDateType()) {
  4149. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  4150. break;
  4151. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateValue);
  4152. break;
  4153. case self::RETURNDATE_PHP_OBJECT : return PHPExcel_Shared_Date::ExcelToPHPObject($excelDateValue);
  4154. break;
  4155. }
  4156. }
  4157. /**
  4158. * TIME
  4159. *
  4160. * @param long $hour
  4161. * @param long $minute
  4162. * @param long $second
  4163. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  4164. * depending on the value of the ReturnDateType flag
  4165. */
  4166. public static function TIME($hour = 0, $minute = 0, $second = 0) {
  4167. $hour = self::flattenSingleValue($hour);
  4168. $minute = self::flattenSingleValue($minute);
  4169. $second = self::flattenSingleValue($second);
  4170. if ($hour == '') { $hour = 0; }
  4171. if ($minute == '') { $minute = 0; }
  4172. if ($second == '') { $second = 0; }
  4173. if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) {
  4174. return self::$_errorCodes['value'];
  4175. }
  4176. $hour = (integer) $hour;
  4177. $minute = (integer) $minute;
  4178. $second = (integer) $second;
  4179. if ($second < 0) {
  4180. $minute += floor($second / 60);
  4181. $second = 60 - abs($second % 60);
  4182. if ($second == 60) { $second = 0; }
  4183. } elseif ($second >= 60) {
  4184. $minute += floor($second / 60);
  4185. $second = $second % 60;
  4186. }
  4187. if ($minute < 0) {
  4188. $hour += floor($minute / 60);
  4189. $minute = 60 - abs($minute % 60);
  4190. if ($minute == 60) { $minute = 0; }
  4191. } elseif ($minute >= 60) {
  4192. $hour += floor($minute / 60);
  4193. $minute = $minute % 60;
  4194. }
  4195. if ($hour > 23) {
  4196. $hour = $hour % 24;
  4197. } elseif ($hour < 0) {
  4198. return self::$_errorCodes['num'];
  4199. }
  4200. // Execute function
  4201. switch (self::getReturnDateType()) {
  4202. case self::RETURNDATE_EXCEL : $date = 0;
  4203. $calendar = PHPExcel_Shared_Date::getExcelCalendar();
  4204. if ($calendar != PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900) {
  4205. $date = 1;
  4206. }
  4207. return (float) PHPExcel_Shared_Date::FormattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
  4208. break;
  4209. 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
  4210. break;
  4211. case self::RETURNDATE_PHP_OBJECT : $dayAdjust = 0;
  4212. if ($hour < 0) {
  4213. $dayAdjust = floor($hour / 24);
  4214. $hour = 24 - abs($hour % 24);
  4215. if ($hour == 24) { $hour = 0; }
  4216. } elseif ($hour >= 24) {
  4217. $dayAdjust = floor($hour / 24);
  4218. $hour = $hour % 24;
  4219. }
  4220. $phpDateObject = new DateTime('1900-01-01 '.$hour.':'.$minute.':'.$second);
  4221. if ($dayAdjust != 0) {
  4222. $phpDateObject->modify($dayAdjust.' days');
  4223. }
  4224. return $phpDateObject;
  4225. break;
  4226. }
  4227. }
  4228. /**
  4229. * DATEVALUE
  4230. *
  4231. * @param string $dateValue
  4232. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  4233. * depending on the value of the ReturnDateType flag
  4234. */
  4235. public static function DATEVALUE($dateValue = 1) {
  4236. $dateValue = self::flattenSingleValue($dateValue);
  4237. $PHPDateArray = date_parse($dateValue);
  4238. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  4239. $testVal1 = strtok($dateValue,'/- ');
  4240. if ($testVal1 !== False) {
  4241. $testVal2 = strtok('/- ');
  4242. if ($testVal2 !== False) {
  4243. $testVal3 = strtok('/- ');
  4244. if ($testVal3 === False) {
  4245. $testVal3 = strftime('%Y');
  4246. }
  4247. } else {
  4248. return self::$_errorCodes['value'];
  4249. }
  4250. } else {
  4251. return self::$_errorCodes['value'];
  4252. }
  4253. $PHPDateArray = date_parse($testVal1.'-'.$testVal2.'-'.$testVal3);
  4254. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  4255. $PHPDateArray = date_parse($testVal2.'-'.$testVal1.'-'.$testVal3);
  4256. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  4257. return self::$_errorCodes['value'];
  4258. }
  4259. }
  4260. }
  4261. if (($PHPDateArray !== False) && ($PHPDateArray['error_count'] == 0)) {
  4262. // Execute function
  4263. if ($PHPDateArray['year'] == '') { $PHPDateArray['year'] = strftime('%Y'); }
  4264. if ($PHPDateArray['month'] == '') { $PHPDateArray['month'] = strftime('%m'); }
  4265. if ($PHPDateArray['day'] == '') { $PHPDateArray['day'] = strftime('%d'); }
  4266. $excelDateValue = floor(PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']));
  4267. switch (self::getReturnDateType()) {
  4268. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  4269. break;
  4270. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateValue);
  4271. break;
  4272. case self::RETURNDATE_PHP_OBJECT : return new DateTime($PHPDateArray['year'].'-'.$PHPDateArray['month'].'-'.$PHPDateArray['day'].' 00:00:00');
  4273. break;
  4274. }
  4275. }
  4276. return self::$_errorCodes['value'];
  4277. }
  4278. /**
  4279. * _getDateValue
  4280. *
  4281. * @param string $dateValue
  4282. * @return mixed Excel date/time serial value, or string if error
  4283. */
  4284. private static function _getDateValue($dateValue) {
  4285. if (!is_numeric($dateValue)) {
  4286. if ((is_string($dateValue)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  4287. return self::$_errorCodes['value'];
  4288. }
  4289. if ((is_object($dateValue)) && ($dateValue instanceof PHPExcel_Shared_Date::$dateTimeObjectType)) {
  4290. $dateValue = PHPExcel_Shared_Date::PHPToExcel($dateValue);
  4291. } else {
  4292. $saveReturnDateType = self::getReturnDateType();
  4293. self::setReturnDateType(self::RETURNDATE_EXCEL);
  4294. $dateValue = self::DATEVALUE($dateValue);
  4295. self::setReturnDateType($saveReturnDateType);
  4296. }
  4297. } elseif (!is_float($dateValue)) {
  4298. $dateValue = PHPExcel_Shared_Date::PHPToExcel($dateValue);
  4299. }
  4300. return $dateValue;
  4301. }
  4302. /**
  4303. * TIMEVALUE
  4304. *
  4305. * @param string $timeValue
  4306. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  4307. * depending on the value of the ReturnDateType flag
  4308. */
  4309. public static function TIMEVALUE($timeValue) {
  4310. $timeValue = self::flattenSingleValue($timeValue);
  4311. if ((($PHPDateArray = date_parse($timeValue)) !== False) && ($PHPDateArray['error_count'] == 0)) {
  4312. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4313. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']);
  4314. } else {
  4315. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel(1900,1,1,$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']) - 1;
  4316. }
  4317. switch (self::getReturnDateType()) {
  4318. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  4319. break;
  4320. case self::RETURNDATE_PHP_NUMERIC : return (integer) $phpDateValue = PHPExcel_Shared_Date::ExcelToPHP($excelDateValue+25569) - 3600;;
  4321. break;
  4322. case self::RETURNDATE_PHP_OBJECT : return new DateTime('1900-01-01 '.$PHPDateArray['hour'].':'.$PHPDateArray['minute'].':'.$PHPDateArray['second']);
  4323. break;
  4324. }
  4325. }
  4326. return self::$_errorCodes['value'];
  4327. }
  4328. /**
  4329. * _getTimeValue
  4330. *
  4331. * @param string $timeValue
  4332. * @return mixed Excel date/time serial value, or string if error
  4333. */
  4334. private static function _getTimeValue($timeValue) {
  4335. $saveReturnDateType = self::getReturnDateType();
  4336. self::setReturnDateType(self::RETURNDATE_EXCEL);
  4337. $timeValue = self::TIMEVALUE($timeValue);
  4338. self::setReturnDateType($saveReturnDateType);
  4339. return $timeValue;
  4340. }
  4341. /**
  4342. * DATETIMENOW
  4343. *
  4344. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  4345. * depending on the value of the ReturnDateType flag
  4346. */
  4347. public static function DATETIMENOW() {
  4348. $saveTimeZone = date_default_timezone_get();
  4349. date_default_timezone_set('UTC');
  4350. $retValue = False;
  4351. switch (self::getReturnDateType()) {
  4352. case self::RETURNDATE_EXCEL : $retValue = (float) PHPExcel_Shared_Date::PHPToExcel(time());
  4353. break;
  4354. case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) time();
  4355. break;
  4356. case self::RETURNDATE_PHP_OBJECT : $retValue = new DateTime();
  4357. break;
  4358. }
  4359. date_default_timezone_set($saveTimeZone);
  4360. return $retValue;
  4361. }
  4362. /**
  4363. * DATENOW
  4364. *
  4365. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  4366. * depending on the value of the ReturnDateType flag
  4367. */
  4368. public static function DATENOW() {
  4369. $saveTimeZone = date_default_timezone_get();
  4370. date_default_timezone_set('UTC');
  4371. $retValue = False;
  4372. $excelDateTime = floor(PHPExcel_Shared_Date::PHPToExcel(time()));
  4373. switch (self::getReturnDateType()) {
  4374. case self::RETURNDATE_EXCEL : $retValue = (float) $excelDateTime;
  4375. break;
  4376. case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateTime) - 3600;
  4377. break;
  4378. case self::RETURNDATE_PHP_OBJECT : $retValue = PHPExcel_Shared_Date::ExcelToPHPObject($excelDateTime);
  4379. break;
  4380. }
  4381. date_default_timezone_set($saveTimeZone);
  4382. return $retValue;
  4383. }
  4384. private static function isLeapYear($year) {
  4385. return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0));
  4386. }
  4387. private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) {
  4388. if ($startDay == 31) {
  4389. --$startDay;
  4390. } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) {
  4391. $startDay = 30;
  4392. }
  4393. if ($endDay == 31) {
  4394. if ($methodUS && $startDay != 30) {
  4395. $endDay = 1;
  4396. if ($endMonth == 12) {
  4397. ++$endYear;
  4398. $endMonth = 1;
  4399. } else {
  4400. ++$endMonth;
  4401. }
  4402. } else {
  4403. $endDay = 30;
  4404. }
  4405. }
  4406. return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
  4407. }
  4408. /**
  4409. * DAYS360
  4410. *
  4411. * @param long $startDate Excel date serial value or a standard date string
  4412. * @param long $endDate Excel date serial value or a standard date string
  4413. * @param boolean $method US or European Method
  4414. * @return long PHP date/time serial
  4415. */
  4416. public static function DAYS360($startDate = 0, $endDate = 0, $method = false) {
  4417. $startDate = self::flattenSingleValue($startDate);
  4418. $endDate = self::flattenSingleValue($endDate);
  4419. if (is_string($startDate = self::_getDateValue($startDate))) {
  4420. return self::$_errorCodes['value'];
  4421. }
  4422. if (is_string($endDate = self::_getDateValue($endDate))) {
  4423. return self::$_errorCodes['value'];
  4424. }
  4425. // Execute function
  4426. $PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
  4427. $startDay = $PHPStartDateObject->format('j');
  4428. $startMonth = $PHPStartDateObject->format('n');
  4429. $startYear = $PHPStartDateObject->format('Y');
  4430. $PHPEndDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  4431. $endDay = $PHPEndDateObject->format('j');
  4432. $endMonth = $PHPEndDateObject->format('n');
  4433. $endYear = $PHPEndDateObject->format('Y');
  4434. return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method);
  4435. }
  4436. /**
  4437. * DATEDIF
  4438. *
  4439. * @param long $startDate Excel date serial value or a standard date string
  4440. * @param long $endDate Excel date serial value or a standard date string
  4441. * @param string $unit
  4442. * @return long Interval between the dates
  4443. */
  4444. public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') {
  4445. $startDate = self::flattenSingleValue($startDate);
  4446. $endDate = self::flattenSingleValue($endDate);
  4447. $unit = strtoupper(self::flattenSingleValue($unit));
  4448. if (is_string($startDate = self::_getDateValue($startDate))) {
  4449. return self::$_errorCodes['value'];
  4450. }
  4451. if (is_string($endDate = self::_getDateValue($endDate))) {
  4452. return self::$_errorCodes['value'];
  4453. }
  4454. // Validate parameters
  4455. if ($startDate >= $endDate) {
  4456. return self::$_errorCodes['num'];
  4457. }
  4458. // Execute function
  4459. $difference = $endDate - $startDate;
  4460. $PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
  4461. $startDays = $PHPStartDateObject->format('j');
  4462. $startMonths = $PHPStartDateObject->format('n');
  4463. $startYears = $PHPStartDateObject->format('Y');
  4464. $PHPEndDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  4465. $endDays = $PHPEndDateObject->format('j');
  4466. $endMonths = $PHPEndDateObject->format('n');
  4467. $endYears = $PHPEndDateObject->format('Y');
  4468. $retVal = self::$_errorCodes['num'];
  4469. switch ($unit) {
  4470. case 'D':
  4471. $retVal = intval($difference);
  4472. break;
  4473. case 'M':
  4474. $retVal = intval($endMonths - $startMonths) + (intval($endYears - $startYears) * 12);
  4475. // We're only interested in full months
  4476. if ($endDays < $startDays) {
  4477. --$retVal;
  4478. }
  4479. break;
  4480. case 'Y':
  4481. $retVal = intval($endYears - $startYears);
  4482. // We're only interested in full months
  4483. if ($endMonths < $startMonths) {
  4484. --$retVal;
  4485. } elseif (($endMonths == $startMonths) && ($endDays < $startDays)) {
  4486. --$retVal;
  4487. }
  4488. break;
  4489. case 'MD':
  4490. if ($endDays < $startDays) {
  4491. $retVal = $endDays;
  4492. $PHPEndDateObject->modify('-'.$endDays.' days');
  4493. $adjustDays = $PHPEndDateObject->format('j');
  4494. if ($adjustDays > $startDays) {
  4495. $retVal += ($adjustDays - $startDays);
  4496. }
  4497. } else {
  4498. $retVal = $endDays - $startDays;
  4499. }
  4500. break;
  4501. case 'YM':
  4502. $retVal = abs(intval($endMonths - $startMonths));
  4503. // We're only interested in full months
  4504. if ($endDays < $startDays) {
  4505. --$retVal;
  4506. }
  4507. break;
  4508. case 'YD':
  4509. $retVal = intval($difference);
  4510. if ($endYears > $startYears) {
  4511. while ($endYears > $startYears) {
  4512. $PHPEndDateObject->modify('-1 year');
  4513. $endYears = $PHPEndDateObject->format('Y');
  4514. }
  4515. $retVal = abs($PHPEndDateObject->format('z') - $PHPStartDateObject->format('z'));
  4516. }
  4517. break;
  4518. }
  4519. return $retVal;
  4520. }
  4521. /**
  4522. * YEARFRAC
  4523. *
  4524. * Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the
  4525. * end_date). Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or obligations
  4526. * to assign to a specific term.
  4527. *
  4528. * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer) or date object, or a standard date string
  4529. * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer) or date object, or a standard date string
  4530. * @param integer $method Method used for the calculation
  4531. * 0 or omitted US (NASD) 30/360
  4532. * 1 Actual/actual
  4533. * 2 Actual/360
  4534. * 3 Actual/365
  4535. * 4 European 30/360
  4536. * @return float fraction of the year
  4537. */
  4538. public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) {
  4539. $startDate = self::flattenSingleValue($startDate);
  4540. $endDate = self::flattenSingleValue($endDate);
  4541. $method = self::flattenSingleValue($method);
  4542. if (is_string($startDate = self::_getDateValue($startDate))) {
  4543. return self::$_errorCodes['value'];
  4544. }
  4545. if (is_string($endDate = self::_getDateValue($endDate))) {
  4546. return self::$_errorCodes['value'];
  4547. }
  4548. if ((is_numeric($method)) && (!is_string($method))) {
  4549. switch($method) {
  4550. case 0 :
  4551. return self::DAYS360($startDate,$endDate) / 360;
  4552. break;
  4553. case 1 :
  4554. $startYear = self::YEAR($startDate);
  4555. $endYear = self::YEAR($endDate);
  4556. $leapDay = 0;
  4557. if (self::isLeapYear($startYear) || self::isLeapYear($endYear)) {
  4558. $leapDay = 1;
  4559. }
  4560. return self::DATEDIF($startDate,$endDate) / (365 + $leapDay);
  4561. break;
  4562. case 2 :
  4563. return self::DATEDIF($startDate,$endDate) / 360;
  4564. break;
  4565. case 3 :
  4566. return self::DATEDIF($startDate,$endDate) / 365;
  4567. break;
  4568. case 4 :
  4569. return self::DAYS360($startDate,$endDate,True) / 360;
  4570. break;
  4571. }
  4572. }
  4573. return self::$_errorCodes['value'];
  4574. }
  4575. /**
  4576. * NETWORKDAYS
  4577. *
  4578. * @param mixed Start date
  4579. * @param mixed End date
  4580. * @param array of mixed Optional Date Series
  4581. * @return long Interval between the dates
  4582. */
  4583. public static function NETWORKDAYS($startDate,$endDate) {
  4584. // Flush the mandatory start and end date that are referenced in the function definition
  4585. $dateArgs = self::flattenArray(func_get_args());
  4586. array_shift($dateArgs);
  4587. array_shift($dateArgs);
  4588. // Validate the start and end dates
  4589. if (is_string($startDate = $sDate = self::_getDateValue($startDate))) {
  4590. return self::$_errorCodes['value'];
  4591. }
  4592. if (is_string($endDate = $eDate = self::_getDateValue($endDate))) {
  4593. return self::$_errorCodes['value'];
  4594. }
  4595. if ($sDate > $eDate) {
  4596. $startDate = $eDate;
  4597. $endDate = $sDate;
  4598. }
  4599. // Execute function
  4600. $startDoW = 6 - self::DAYOFWEEK($startDate,2);
  4601. if ($startDoW < 0) { $startDoW = 0; }
  4602. $endDoW = self::DAYOFWEEK($endDate,2);
  4603. if ($endDoW >= 6) { $endDoW = 0; }
  4604. $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5;
  4605. $partWeekDays = $endDoW + $startDoW;
  4606. if ($partWeekDays > 5) {
  4607. $partWeekDays -= 5;
  4608. }
  4609. // Test any extra holiday parameters
  4610. $holidayCountedArray = array();
  4611. foreach ($dateArgs as $holidayDate) {
  4612. if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
  4613. return self::$_errorCodes['value'];
  4614. }
  4615. if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
  4616. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  4617. --$partWeekDays;
  4618. $holidayCountedArray[] = $holidayDate;
  4619. }
  4620. }
  4621. }
  4622. if ($sDate > $eDate) {
  4623. return 0 - ($wholeWeekDays + $partWeekDays);
  4624. }
  4625. return $wholeWeekDays + $partWeekDays;
  4626. }
  4627. /**
  4628. * WORKDAY
  4629. *
  4630. * @param mixed Start date
  4631. * @param mixed number of days for adjustment
  4632. * @param array of mixed Optional Date Series
  4633. * @return long Interval between the dates
  4634. */
  4635. public static function WORKDAY($startDate,$endDays) {
  4636. $dateArgs = self::flattenArray(func_get_args());
  4637. array_shift($dateArgs);
  4638. array_shift($dateArgs);
  4639. if (is_string($startDate = self::_getDateValue($startDate))) {
  4640. return self::$_errorCodes['value'];
  4641. }
  4642. if (!is_numeric($endDays)) {
  4643. return self::$_errorCodes['value'];
  4644. }
  4645. $endDate = (float) $startDate + (floor($endDays / 5) * 7) + ($endDays % 5);
  4646. if ($endDays < 0) {
  4647. $endDate += 7;
  4648. }
  4649. $endDoW = self::DAYOFWEEK($endDate,3);
  4650. if ($endDoW >= 5) {
  4651. if ($endDays >= 0) {
  4652. $endDate += (7 - $endDoW);
  4653. } else {
  4654. $endDate -= ($endDoW - 5);
  4655. }
  4656. }
  4657. // Test any extra holiday parameters
  4658. if (count($dateArgs) > 0) {
  4659. $holidayCountedArray = $holidayDates = array();
  4660. foreach ($dateArgs as $holidayDate) {
  4661. if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
  4662. return self::$_errorCodes['value'];
  4663. }
  4664. $holidayDates[] = $holidayDate;
  4665. }
  4666. if ($endDays >= 0) {
  4667. sort($holidayDates, SORT_NUMERIC);
  4668. } else {
  4669. rsort($holidayDates, SORT_NUMERIC);
  4670. }
  4671. foreach ($holidayDates as $holidayDate) {
  4672. if ($endDays >= 0) {
  4673. if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
  4674. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  4675. ++$endDate;
  4676. $holidayCountedArray[] = $holidayDate;
  4677. }
  4678. }
  4679. } else {
  4680. if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
  4681. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  4682. --$endDate;
  4683. $holidayCountedArray[] = $holidayDate;
  4684. }
  4685. }
  4686. }
  4687. $endDoW = self::DAYOFWEEK($endDate,3);
  4688. if ($endDoW >= 5) {
  4689. if ($endDays >= 0) {
  4690. $endDate += (7 - $endDoW);
  4691. } else {
  4692. $endDate -= ($endDoW - 5);
  4693. }
  4694. }
  4695. }
  4696. }
  4697. switch (self::getReturnDateType()) {
  4698. case self::RETURNDATE_EXCEL : return (float) $endDate;
  4699. break;
  4700. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($endDate);
  4701. break;
  4702. case self::RETURNDATE_PHP_OBJECT : return PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  4703. break;
  4704. }
  4705. }
  4706. /**
  4707. * DAYOFMONTH
  4708. *
  4709. * @param long $dateValue Excel date serial value or a standard date string
  4710. * @return int Day
  4711. */
  4712. public static function DAYOFMONTH($dateValue = 1) {
  4713. $dateValue = self::flattenSingleValue($dateValue);
  4714. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4715. return self::$_errorCodes['value'];
  4716. }
  4717. // Execute function
  4718. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4719. return (int) $PHPDateObject->format('j');
  4720. }
  4721. /**
  4722. * DAYOFWEEK
  4723. *
  4724. * @param long $dateValue Excel date serial value or a standard date string
  4725. * @return int Day
  4726. */
  4727. public static function DAYOFWEEK($dateValue = 1, $style = 1) {
  4728. $dateValue = self::flattenSingleValue($dateValue);
  4729. $style = floor(self::flattenSingleValue($style));
  4730. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4731. return self::$_errorCodes['value'];
  4732. }
  4733. // Execute function
  4734. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4735. $DoW = $PHPDateObject->format('w');
  4736. $firstDay = 1;
  4737. switch ($style) {
  4738. case 1: ++$DoW;
  4739. break;
  4740. case 2: if ($DoW == 0) { $DoW = 7; }
  4741. break;
  4742. case 3: if ($DoW == 0) { $DoW = 7; }
  4743. $firstDay = 0;
  4744. --$DoW;
  4745. break;
  4746. default:
  4747. }
  4748. if (self::$compatibilityMode == self::COMPATIBILITY_EXCEL) {
  4749. // Test for Excel's 1900 leap year, and introduce the error as required
  4750. if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) {
  4751. --$DoW;
  4752. if ($DoW < $firstDay) {
  4753. $DoW += 7;
  4754. }
  4755. }
  4756. }
  4757. return (int) $DoW;
  4758. }
  4759. /**
  4760. * WEEKOFYEAR
  4761. *
  4762. * @param long $dateValue Excel date serial value or a standard date string
  4763. * @param boolean $method Week begins on Sunday or Monday
  4764. * @return int Week Number
  4765. */
  4766. public static function WEEKOFYEAR($dateValue = 1, $method = 1) {
  4767. $dateValue = self::flattenSingleValue($dateValue);
  4768. $method = floor(self::flattenSingleValue($method));
  4769. if (!is_numeric($method)) {
  4770. return self::$_errorCodes['value'];
  4771. } elseif (($method < 1) || ($method > 2)) {
  4772. return self::$_errorCodes['num'];
  4773. }
  4774. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4775. return self::$_errorCodes['value'];
  4776. }
  4777. // Execute function
  4778. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4779. $dayOfYear = $PHPDateObject->format('z');
  4780. $dow = $PHPDateObject->format('w');
  4781. $PHPDateObject->modify('-'.$dayOfYear.' days');
  4782. $dow = $PHPDateObject->format('w');
  4783. $daysInFirstWeek = 7 - (($dow + (2 - $method)) % 7);
  4784. $dayOfYear -= $daysInFirstWeek;
  4785. $weekOfYear = ceil($dayOfYear / 7) + 1;
  4786. return $weekOfYear;
  4787. }
  4788. /**
  4789. * MONTHOFYEAR
  4790. *
  4791. * @param long $dateValue Excel date serial value or a standard date string
  4792. * @return int Month
  4793. */
  4794. public static function MONTHOFYEAR($dateValue = 1) {
  4795. $dateValue = self::flattenSingleValue($dateValue);
  4796. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4797. return self::$_errorCodes['value'];
  4798. }
  4799. // Execute function
  4800. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4801. return $PHPDateObject->format('n');
  4802. }
  4803. /**
  4804. * YEAR
  4805. *
  4806. * @param long $dateValue Excel date serial value or a standard date string
  4807. * @return int Year
  4808. */
  4809. public static function YEAR($dateValue = 1) {
  4810. $dateValue = self::flattenSingleValue($dateValue);
  4811. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4812. return self::$_errorCodes['value'];
  4813. }
  4814. // Execute function
  4815. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4816. return $PHPDateObject->format('Y');
  4817. }
  4818. /**
  4819. * HOUROFDAY
  4820. *
  4821. * @param mixed $timeValue Excel time serial value or a standard time string
  4822. * @return int Hour
  4823. */
  4824. public static function HOUROFDAY($timeValue = 0) {
  4825. $timeValue = self::flattenSingleValue($timeValue);
  4826. if (!is_numeric($timeValue)) {
  4827. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4828. $testVal = strtok($timeValue,'/-: ');
  4829. if (strlen($testVal) < strlen($timeValue)) {
  4830. return self::$_errorCodes['value'];
  4831. }
  4832. }
  4833. $timeValue = self::_getTimeValue($timeValue);
  4834. if (is_string($timeValue)) {
  4835. return self::$_errorCodes['value'];
  4836. }
  4837. }
  4838. // Execute function
  4839. if (is_real($timeValue)) {
  4840. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  4841. }
  4842. return date('G',$timeValue);
  4843. }
  4844. /**
  4845. * MINUTEOFHOUR
  4846. *
  4847. * @param long $timeValue Excel time serial value or a standard time string
  4848. * @return int Minute
  4849. */
  4850. public static function MINUTEOFHOUR($timeValue = 0) {
  4851. $timeValue = $timeTester = self::flattenSingleValue($timeValue);
  4852. if (!is_numeric($timeValue)) {
  4853. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4854. $testVal = strtok($timeValue,'/-: ');
  4855. if (strlen($testVal) < strlen($timeValue)) {
  4856. return self::$_errorCodes['value'];
  4857. }
  4858. }
  4859. $timeValue = self::_getTimeValue($timeValue);
  4860. if (is_string($timeValue)) {
  4861. return self::$_errorCodes['value'];
  4862. }
  4863. }
  4864. // Execute function
  4865. if (is_real($timeValue)) {
  4866. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  4867. }
  4868. return (int) date('i',$timeValue);
  4869. }
  4870. /**
  4871. * SECONDOFMINUTE
  4872. *
  4873. * @param long $timeValue Excel time serial value or a standard time string
  4874. * @return int Second
  4875. */
  4876. public static function SECONDOFMINUTE($timeValue = 0) {
  4877. $timeValue = self::flattenSingleValue($timeValue);
  4878. if (!is_numeric($timeValue)) {
  4879. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  4880. $testVal = strtok($timeValue,'/-: ');
  4881. if (strlen($testVal) < strlen($timeValue)) {
  4882. return self::$_errorCodes['value'];
  4883. }
  4884. }
  4885. $timeValue = self::_getTimeValue($timeValue);
  4886. if (is_string($timeValue)) {
  4887. return self::$_errorCodes['value'];
  4888. }
  4889. }
  4890. // Execute function
  4891. if (is_real($timeValue)) {
  4892. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  4893. }
  4894. return (int) date('s',$timeValue);
  4895. }
  4896. private static function adjustDateByMonths ($dateValue = 0, $adjustmentMonths = 0) {
  4897. // Execute function
  4898. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  4899. $oMonth = (int) $PHPDateObject->format('m');
  4900. $oYear = (int) $PHPDateObject->format('Y');
  4901. $adjustmentMonthsString = (string) $adjustmentMonths;
  4902. if ($adjustmentMonths > 0) {
  4903. $adjustmentMonthsString = '+'.$adjustmentMonths;
  4904. }
  4905. if ($adjustmentMonths != 0) {
  4906. $PHPDateObject->modify($adjustmentMonthsString.' months');
  4907. }
  4908. $nMonth = (int) $PHPDateObject->format('m');
  4909. $nYear = (int) $PHPDateObject->format('Y');
  4910. $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
  4911. if ($monthDiff != $adjustmentMonths) {
  4912. $adjustDays = (int) $PHPDateObject->format('d');
  4913. $adjustDaysString = '-'.$adjustDays.' days';
  4914. $PHPDateObject->modify($adjustDaysString);
  4915. }
  4916. return $PHPDateObject;
  4917. }
  4918. /**
  4919. * EDATE
  4920. *
  4921. * Returns the serial number that represents the date that is the indicated number of months before or after a specified date
  4922. * (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.
  4923. *
  4924. * @param long $dateValue Excel date serial value or a standard date string
  4925. * @param int $adjustmentMonths Number of months to adjust by
  4926. * @return long Excel date serial value
  4927. */
  4928. public static function EDATE($dateValue = 1, $adjustmentMonths = 0) {
  4929. $dateValue = self::flattenSingleValue($dateValue);
  4930. $adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
  4931. if (!is_numeric($adjustmentMonths)) {
  4932. return self::$_errorCodes['value'];
  4933. }
  4934. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4935. return self::$_errorCodes['value'];
  4936. }
  4937. // Execute function
  4938. $PHPDateObject = self::adjustDateByMonths($dateValue,$adjustmentMonths);
  4939. switch (self::getReturnDateType()) {
  4940. case self::RETURNDATE_EXCEL : return (float) PHPExcel_Shared_Date::PHPToExcel($PHPDateObject);
  4941. break;
  4942. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::PHPToExcel($PHPDateObject));
  4943. break;
  4944. case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
  4945. break;
  4946. }
  4947. }
  4948. /**
  4949. * EOMONTH
  4950. *
  4951. * Returns the serial number for the last day of the month that is the indicated number of months before or after start_date.
  4952. * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
  4953. *
  4954. * @param long $dateValue Excel date serial value or a standard date string
  4955. * @param int $adjustmentMonths Number of months to adjust by
  4956. * @return long Excel date serial value
  4957. */
  4958. public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) {
  4959. $dateValue = self::flattenSingleValue($dateValue);
  4960. $adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
  4961. if (!is_numeric($adjustmentMonths)) {
  4962. return self::$_errorCodes['value'];
  4963. }
  4964. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  4965. return self::$_errorCodes['value'];
  4966. }
  4967. // Execute function
  4968. $PHPDateObject = self::adjustDateByMonths($dateValue,$adjustmentMonths+1);
  4969. $adjustDays = (int) $PHPDateObject->format('d');
  4970. $adjustDaysString = '-'.$adjustDays.' days';
  4971. $PHPDateObject->modify($adjustDaysString);
  4972. switch (self::getReturnDateType()) {
  4973. case self::RETURNDATE_EXCEL : return (float) PHPExcel_Shared_Date::PHPToExcel($PHPDateObject);
  4974. break;
  4975. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::PHPToExcel($PHPDateObject));
  4976. break;
  4977. case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
  4978. break;
  4979. }
  4980. }
  4981. /**
  4982. * TRUNC
  4983. *
  4984. * Truncates value to the number of fractional digits by number_digits.
  4985. *
  4986. * @param float $value
  4987. * @param int $number_digits
  4988. * @return float Truncated value
  4989. */
  4990. public static function TRUNC($value = 0, $number_digits = 0) {
  4991. $value = self::flattenSingleValue($value);
  4992. $number_digits = self::flattenSingleValue($number_digits);
  4993. // Validate parameters
  4994. if ($number_digits < 0) {
  4995. return self::$_errorCodes['value'];
  4996. }
  4997. // Truncate
  4998. if ($number_digits > 0) {
  4999. $value = $value * pow(10, $number_digits);
  5000. }
  5001. $value = intval($value);
  5002. if ($number_digits > 0) {
  5003. $value = $value / pow(10, $number_digits);
  5004. }
  5005. // Return
  5006. return $value;
  5007. }
  5008. /**
  5009. * POWER
  5010. *
  5011. * Computes x raised to the power y.
  5012. *
  5013. * @param float $x
  5014. * @param float $y
  5015. * @return float
  5016. */
  5017. public static function POWER($x = 0, $y = 2) {
  5018. $x = self::flattenSingleValue($x);
  5019. $y = self::flattenSingleValue($y);
  5020. // Validate parameters
  5021. if ($x < 0) {
  5022. return self::$_errorCodes['num'];
  5023. }
  5024. if ($x == 0 && $y <= 0) {
  5025. return self::$_errorCodes['divisionbyzero'];
  5026. }
  5027. // Return
  5028. return pow($x, $y);
  5029. }
  5030. /**
  5031. * BINTODEC
  5032. *
  5033. * Return a binary value as Decimal.
  5034. *
  5035. * @param string $x
  5036. * @return string
  5037. */
  5038. public static function BINTODEC($x) {
  5039. $x = self::flattenSingleValue($x);
  5040. if (is_bool($x)) {
  5041. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5042. $x = (int) $x;
  5043. } else {
  5044. return self::$_errorCodes['value'];
  5045. }
  5046. }
  5047. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  5048. $x = floor($x);
  5049. }
  5050. $x = (string) $x;
  5051. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  5052. return self::$_errorCodes['num'];
  5053. }
  5054. if (strlen($x) > 10) {
  5055. return self::$_errorCodes['num'];
  5056. } elseif (strlen($x) == 10) {
  5057. // Two's Complement
  5058. $x = substr($x,-9);
  5059. return '-'.(512-bindec($x));
  5060. }
  5061. return bindec($x);
  5062. }
  5063. /**
  5064. * BINTOHEX
  5065. *
  5066. * Return a binary value as Hex.
  5067. *
  5068. * @param string $x
  5069. * @return string
  5070. */
  5071. public static function BINTOHEX($x) {
  5072. $x = floor(self::flattenSingleValue($x));
  5073. if (is_bool($x)) {
  5074. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5075. $x = (int) $x;
  5076. } else {
  5077. return self::$_errorCodes['value'];
  5078. }
  5079. }
  5080. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  5081. $x = floor($x);
  5082. }
  5083. $x = (string) $x;
  5084. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  5085. return self::$_errorCodes['num'];
  5086. }
  5087. if (strlen($x) > 10) {
  5088. return self::$_errorCodes['num'];
  5089. } elseif (strlen($x) == 10) {
  5090. // Two's Complement
  5091. return str_repeat('F',8).substr(strtoupper(dechex(bindec(substr($x,-9)))),-2);
  5092. }
  5093. return strtoupper(dechex(bindec($x)));
  5094. }
  5095. /**
  5096. * BINTOOCT
  5097. *
  5098. * Return a binary value as Octal.
  5099. *
  5100. * @param string $x
  5101. * @return string
  5102. */
  5103. public static function BINTOOCT($x) {
  5104. $x = floor(self::flattenSingleValue($x));
  5105. if (is_bool($x)) {
  5106. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5107. $x = (int) $x;
  5108. } else {
  5109. return self::$_errorCodes['value'];
  5110. }
  5111. }
  5112. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  5113. $x = floor($x);
  5114. }
  5115. $x = (string) $x;
  5116. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  5117. return self::$_errorCodes['num'];
  5118. }
  5119. if (strlen($x) > 10) {
  5120. return self::$_errorCodes['num'];
  5121. } elseif (strlen($x) == 10) {
  5122. // Two's Complement
  5123. return str_repeat('7',7).substr(strtoupper(dechex(bindec(substr($x,-9)))),-3);
  5124. }
  5125. return decoct(bindec($x));
  5126. }
  5127. /**
  5128. * DECTOBIN
  5129. *
  5130. * Return an octal value as binary.
  5131. *
  5132. * @param string $x
  5133. * @return string
  5134. */
  5135. public static function DECTOBIN($x) {
  5136. $x = self::flattenSingleValue($x);
  5137. if (is_bool($x)) {
  5138. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5139. $x = (int) $x;
  5140. } else {
  5141. return self::$_errorCodes['value'];
  5142. }
  5143. }
  5144. $x = (string) $x;
  5145. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  5146. return self::$_errorCodes['value'];
  5147. }
  5148. $x = (string) floor($x);
  5149. $r = decbin($x);
  5150. if (strlen($r) == 32) {
  5151. // Two's Complement
  5152. $r = substr($r,-10);
  5153. } elseif (strlen($r) > 11) {
  5154. return self::$_errorCodes['num'];
  5155. }
  5156. return $r;
  5157. }
  5158. /**
  5159. * DECTOOCT
  5160. *
  5161. * Return an octal value as binary.
  5162. *
  5163. * @param string $x
  5164. * @return string
  5165. */
  5166. public static function DECTOOCT($x) {
  5167. $x = self::flattenSingleValue($x);
  5168. if (is_bool($x)) {
  5169. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5170. $x = (int) $x;
  5171. } else {
  5172. return self::$_errorCodes['value'];
  5173. }
  5174. }
  5175. $x = (string) $x;
  5176. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  5177. return self::$_errorCodes['value'];
  5178. }
  5179. $x = (string) floor($x);
  5180. $r = decoct($x);
  5181. if (strlen($r) == 11) {
  5182. // Two's Complement
  5183. $r = substr($r,-10);
  5184. }
  5185. return ($r);
  5186. }
  5187. /**
  5188. * DECTOHEX
  5189. *
  5190. * Return an octal value as binary.
  5191. *
  5192. * @param string $x
  5193. * @return string
  5194. */
  5195. public static function DECTOHEX($x) {
  5196. $x = self::flattenSingleValue($x);
  5197. if (is_bool($x)) {
  5198. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5199. $x = (int) $x;
  5200. } else {
  5201. return self::$_errorCodes['value'];
  5202. }
  5203. }
  5204. $x = (string) $x;
  5205. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  5206. return self::$_errorCodes['value'];
  5207. }
  5208. $x = (string) floor($x);
  5209. $r = strtoupper(dechex($x));
  5210. if (strlen($r) == 8) {
  5211. // Two's Complement
  5212. $r = 'FF'.$r;
  5213. }
  5214. return ($r);
  5215. }
  5216. /**
  5217. * HEXTOBIN
  5218. *
  5219. * Return a hex value as binary.
  5220. *
  5221. * @param string $x
  5222. * @return string
  5223. */
  5224. public static function HEXTOBIN($x) {
  5225. $x = self::flattenSingleValue($x);
  5226. if (is_bool($x)) {
  5227. return self::$_errorCodes['value'];
  5228. }
  5229. $x = (string) $x;
  5230. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  5231. return self::$_errorCodes['num'];
  5232. }
  5233. return decbin(hexdec($x));
  5234. }
  5235. /**
  5236. * HEXTOOCT
  5237. *
  5238. * Return a hex value as octal.
  5239. *
  5240. * @param string $x
  5241. * @return string
  5242. */
  5243. public static function HEXTOOCT($x) {
  5244. $x = self::flattenSingleValue($x);
  5245. if (is_bool($x)) {
  5246. return self::$_errorCodes['value'];
  5247. }
  5248. $x = (string) $x;
  5249. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  5250. return self::$_errorCodes['num'];
  5251. }
  5252. return decoct(hexdec($x));
  5253. }
  5254. /**
  5255. * HEXTODEC
  5256. *
  5257. * Return a hex value as octal.
  5258. *
  5259. * @param string $x
  5260. * @return string
  5261. */
  5262. public static function HEXTODEC($x) {
  5263. $x = self::flattenSingleValue($x);
  5264. if (is_bool($x)) {
  5265. return self::$_errorCodes['value'];
  5266. }
  5267. $x = (string) $x;
  5268. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  5269. return self::$_errorCodes['num'];
  5270. }
  5271. return hexdec($x);
  5272. }
  5273. /**
  5274. * OCTTOBIN
  5275. *
  5276. * Return an octal value as binary.
  5277. *
  5278. * @param string $x
  5279. * @return string
  5280. */
  5281. public static function OCTTOBIN($x) {
  5282. $x = self::flattenSingleValue($x);
  5283. if (is_bool($x)) {
  5284. return self::$_errorCodes['value'];
  5285. }
  5286. $x = (string) $x;
  5287. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  5288. return self::$_errorCodes['num'];
  5289. }
  5290. return decbin(octdec($x));
  5291. }
  5292. /**
  5293. * OCTTODEC
  5294. *
  5295. * Return an octal value as binary.
  5296. *
  5297. * @param string $x
  5298. * @return string
  5299. */
  5300. public static function OCTTODEC($x) {
  5301. $x = self::flattenSingleValue($x);
  5302. if (is_bool($x)) {
  5303. return self::$_errorCodes['value'];
  5304. }
  5305. $x = (string) $x;
  5306. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  5307. return self::$_errorCodes['num'];
  5308. }
  5309. return octdec($x);
  5310. }
  5311. /**
  5312. * OCTTOHEX
  5313. *
  5314. * Return an octal value as hex.
  5315. *
  5316. * @param string $x
  5317. * @return string
  5318. */
  5319. public static function OCTTOHEX($x) {
  5320. $x = self::flattenSingleValue($x);
  5321. if (is_bool($x)) {
  5322. return self::$_errorCodes['value'];
  5323. }
  5324. $x = (string) $x;
  5325. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  5326. return self::$_errorCodes['num'];
  5327. }
  5328. return strtoupper(dechex(octdec($x)));
  5329. }
  5330. public function parseComplex($complexNumber) {
  5331. $workString = $complexNumber;
  5332. $realNumber = $imaginary = 0;
  5333. // Extract the suffix, if there is one
  5334. $suffix = substr($workString,-1);
  5335. if (!is_numeric($suffix)) {
  5336. $workString = substr($workString,0,-1);
  5337. } else {
  5338. $suffix = '';
  5339. }
  5340. // Split the input into its Real and Imaginary components
  5341. $leadingSign = (($workString{0} == '+') || ($workString{0} == '-')) ? 1 : 0;
  5342. $power = '';
  5343. $realNumber = strtok($workString, '+-');
  5344. if (strtoupper(substr($realNumber,-1)) == 'E') {
  5345. $power = strtok('+-');
  5346. ++$leadingSign;
  5347. }
  5348. $realNumber = substr($workString,0,strlen($realNumber)+strlen($power)+$leadingSign);
  5349. if ($suffix != '') {
  5350. $imaginary = substr($workString,strlen($realNumber));
  5351. if (($imaginary == '') && (($realNumber == '') || ($realNumber == '+') || ($realNumber == '-'))) {
  5352. $imaginary = $realNumber.'1';
  5353. $realNumber = '0';
  5354. } else if ($imaginary == '') {
  5355. $imaginary = $realNumber;
  5356. $realNumber = '0';
  5357. } elseif (($imaginary == '+') || ($imaginary == '-')) {
  5358. $imaginary .= '1';
  5359. }
  5360. }
  5361. $complexArray = array( 'real' => $realNumber,
  5362. 'imaginary' => $imaginary,
  5363. 'suffix' => $suffix
  5364. );
  5365. return $complexArray;
  5366. }
  5367. /**
  5368. * COMPLEX
  5369. *
  5370. * returns a complex number of the form x + yi or x + yj.
  5371. *
  5372. * @param float $realNumber
  5373. * @param float $imaginary
  5374. * @param string $suffix
  5375. * @return string
  5376. */
  5377. public static function COMPLEX($realNumber=0.0, $imaginary=0.0, $suffix='i') {
  5378. $realNumber = self::flattenSingleValue($realNumber);
  5379. $imaginary = self::flattenSingleValue($imaginary);
  5380. $suffix = self::flattenSingleValue($suffix);
  5381. if (((is_numeric($realNumber)) && (is_numeric($imaginary))) &&
  5382. (($suffix == 'i') || ($suffix == 'j'))) {
  5383. if ($realNumber == 0.0) {
  5384. if ($imaginary == 0.0) {
  5385. return (string) '0';
  5386. } elseif ($imaginary == 1.0) {
  5387. return (string) $suffix;
  5388. } elseif ($imaginary == -1.0) {
  5389. return (string) '-'.$suffix;
  5390. }
  5391. return (string) $imaginary.$suffix;
  5392. } elseif ($imaginary == 0.0) {
  5393. return (string) $realNumber;
  5394. } elseif ($imaginary == 1.0) {
  5395. return (string) $realNumber.'+'.$suffix;
  5396. } elseif ($imaginary == -1.0) {
  5397. return (string) $realNumber.'-'.$suffix;
  5398. }
  5399. if ($imaginary > 0) { $imaginary = (string) '+'.$imaginary; }
  5400. return (string) $realNumber.$imaginary.$suffix;
  5401. }
  5402. return self::$_errorCodes['value'];
  5403. }
  5404. /**
  5405. * IMAGINARY
  5406. *
  5407. * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
  5408. *
  5409. * @param string $complexNumber
  5410. * @return real
  5411. */
  5412. public static function IMAGINARY($complexNumber) {
  5413. $complexNumber = self::flattenSingleValue($complexNumber);
  5414. $parsedComplex = self::parseComplex($complexNumber);
  5415. if (!is_array($parsedComplex)) {
  5416. return $parsedComplex;
  5417. }
  5418. return $parsedComplex['imaginary'];
  5419. }
  5420. /**
  5421. * IMREAL
  5422. *
  5423. * Returns the real coefficient of a complex number in x + yi or x + yj text format.
  5424. *
  5425. * @param string $complexNumber
  5426. * @return real
  5427. */
  5428. public static function IMREAL($complexNumber) {
  5429. $complexNumber = self::flattenSingleValue($complexNumber);
  5430. $parsedComplex = self::parseComplex($complexNumber);
  5431. if (!is_array($parsedComplex)) {
  5432. return $parsedComplex;
  5433. }
  5434. return $parsedComplex['real'];
  5435. }
  5436. /**
  5437. * IMABS
  5438. *
  5439. * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
  5440. *
  5441. * @param string $complexNumber
  5442. * @return real
  5443. */
  5444. public static function IMABS($complexNumber) {
  5445. $complexNumber = self::flattenSingleValue($complexNumber);
  5446. $parsedComplex = self::parseComplex($complexNumber);
  5447. if (!is_array($parsedComplex)) {
  5448. return $parsedComplex;
  5449. }
  5450. return sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
  5451. }
  5452. /**
  5453. * IMARGUMENT
  5454. *
  5455. * 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.
  5456. *
  5457. * @param string $complexNumber
  5458. * @return string
  5459. */
  5460. public static function IMARGUMENT($complexNumber) {
  5461. $complexNumber = self::flattenSingleValue($complexNumber);
  5462. $parsedComplex = self::parseComplex($complexNumber);
  5463. if (!is_array($parsedComplex)) {
  5464. return $parsedComplex;
  5465. }
  5466. if ($parsedComplex['real'] == 0.0) {
  5467. if ($parsedComplex['imaginary'] == 0.0) {
  5468. return 0.0;
  5469. } elseif($parsedComplex['imaginary'] < 0.0) {
  5470. return pi() / -2;
  5471. } else {
  5472. return pi() / 2;
  5473. }
  5474. } elseif ($parsedComplex['real'] > 0.0) {
  5475. return atan($parsedComplex['imaginary'] / $parsedComplex['real']);
  5476. } elseif ($parsedComplex['imaginary'] < 0.0) {
  5477. return 0 - (pi() - atan(abs($parsedComplex['imaginary']) / abs($parsedComplex['real'])));
  5478. } else {
  5479. return pi() - atan($parsedComplex['imaginary'] / abs($parsedComplex['real']));
  5480. }
  5481. }
  5482. /**
  5483. * IMCONJUGATE
  5484. *
  5485. * Returns the complex conjugate of a complex number in x + yi or x + yj text format.
  5486. *
  5487. * @param string $complexNumber
  5488. * @return string
  5489. */
  5490. public static function IMCONJUGATE($complexNumber) {
  5491. $complexNumber = self::flattenSingleValue($complexNumber);
  5492. $parsedComplex = self::parseComplex($complexNumber);
  5493. if (!is_array($parsedComplex)) {
  5494. return $parsedComplex;
  5495. }
  5496. if ($parsedComplex['imaginary'] == 0.0) {
  5497. return $parsedComplex['real'];
  5498. } else {
  5499. return self::COMPLEX($parsedComplex['real'], 0 - $parsedComplex['imaginary'], $parsedComplex['suffix']);
  5500. }
  5501. }
  5502. /**
  5503. * IMCOS
  5504. *
  5505. * Returns the cosine of a complex number in x + yi or x + yj text format.
  5506. *
  5507. * @param string $complexNumber
  5508. * @return string
  5509. */
  5510. public static function IMCOS($complexNumber) {
  5511. $complexNumber = self::flattenSingleValue($complexNumber);
  5512. $parsedComplex = self::parseComplex($complexNumber);
  5513. if (!is_array($parsedComplex)) {
  5514. return $parsedComplex;
  5515. }
  5516. if ($parsedComplex['imaginary'] == 0.0) {
  5517. return cos($parsedComplex['real']);
  5518. } else {
  5519. return self::IMCONJUGATE(self::COMPLEX(cos($parsedComplex['real']) * cosh($parsedComplex['imaginary']),sin($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']));
  5520. }
  5521. }
  5522. /**
  5523. * IMSIN
  5524. *
  5525. * Returns the sine of a complex number in x + yi or x + yj text format.
  5526. *
  5527. * @param string $complexNumber
  5528. * @return string
  5529. */
  5530. public static function IMSIN($complexNumber) {
  5531. $complexNumber = self::flattenSingleValue($complexNumber);
  5532. $parsedComplex = self::parseComplex($complexNumber);
  5533. if (!is_array($parsedComplex)) {
  5534. return $parsedComplex;
  5535. }
  5536. if ($parsedComplex['imaginary'] == 0.0) {
  5537. return sin($parsedComplex['real']);
  5538. } else {
  5539. return self::COMPLEX(sin($parsedComplex['real']) * cosh($parsedComplex['imaginary']),cos($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']);
  5540. }
  5541. }
  5542. /**
  5543. * IMSQRT
  5544. *
  5545. * Returns the square root of a complex number in x + yi or x + yj text format.
  5546. *
  5547. * @param string $complexNumber
  5548. * @return string
  5549. */
  5550. public static function IMSQRT($complexNumber) {
  5551. $complexNumber = self::flattenSingleValue($complexNumber);
  5552. $parsedComplex = self::parseComplex($complexNumber);
  5553. if (!is_array($parsedComplex)) {
  5554. return $parsedComplex;
  5555. }
  5556. $theta = self::IMARGUMENT($complexNumber);
  5557. $d1 = cos($theta / 2);
  5558. $d2 = sin($theta / 2);
  5559. $r = sqrt(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
  5560. if ($parsedComplex['suffix'] == '') {
  5561. return self::COMPLEX($d1 * $r,$d2 * $r);
  5562. } else {
  5563. return self::COMPLEX($d1 * $r,$d2 * $r,$parsedComplex['suffix']);
  5564. }
  5565. }
  5566. /**
  5567. * IMLN
  5568. *
  5569. * Returns the natural logarithm of a complex number in x + yi or x + yj text format.
  5570. *
  5571. * @param string $complexNumber
  5572. * @return string
  5573. */
  5574. public static function IMLN($complexNumber) {
  5575. $complexNumber = self::flattenSingleValue($complexNumber);
  5576. $parsedComplex = self::parseComplex($complexNumber);
  5577. if (!is_array($parsedComplex)) {
  5578. return $parsedComplex;
  5579. }
  5580. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5581. return self::$_errorCodes['num'];
  5582. }
  5583. $logR = log(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
  5584. $t = self::IMARGUMENT($complexNumber);
  5585. if ($parsedComplex['suffix'] == '') {
  5586. return self::COMPLEX($logR,$t);
  5587. } else {
  5588. return self::COMPLEX($logR,$t,$parsedComplex['suffix']);
  5589. }
  5590. }
  5591. /**
  5592. * IMLOG10
  5593. *
  5594. * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
  5595. *
  5596. * @param string $complexNumber
  5597. * @return string
  5598. */
  5599. public static function IMLOG10($complexNumber) {
  5600. $complexNumber = self::flattenSingleValue($complexNumber);
  5601. $parsedComplex = self::parseComplex($complexNumber);
  5602. if (!is_array($parsedComplex)) {
  5603. return $parsedComplex;
  5604. }
  5605. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5606. return self::$_errorCodes['num'];
  5607. } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5608. return log10($parsedComplex['real']);
  5609. }
  5610. return self::IMPRODUCT(log10(EULER),self::IMLN($complexNumber));
  5611. }
  5612. /**
  5613. * IMLOG2
  5614. *
  5615. * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
  5616. *
  5617. * @param string $complexNumber
  5618. * @return string
  5619. */
  5620. public static function IMLOG2($complexNumber) {
  5621. $complexNumber = self::flattenSingleValue($complexNumber);
  5622. $parsedComplex = self::parseComplex($complexNumber);
  5623. if (!is_array($parsedComplex)) {
  5624. return $parsedComplex;
  5625. }
  5626. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5627. return self::$_errorCodes['num'];
  5628. } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5629. return log($parsedComplex['real'],2);
  5630. }
  5631. return self::IMPRODUCT(log(EULER,2),self::IMLN($complexNumber));
  5632. }
  5633. /**
  5634. * IMEXP
  5635. *
  5636. * Returns the exponential of a complex number in x + yi or x + yj text format.
  5637. *
  5638. * @param string $complexNumber
  5639. * @return string
  5640. */
  5641. public static function IMEXP($complexNumber) {
  5642. $complexNumber = self::flattenSingleValue($complexNumber);
  5643. $parsedComplex = self::parseComplex($complexNumber);
  5644. if (!is_array($parsedComplex)) {
  5645. return $parsedComplex;
  5646. }
  5647. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  5648. return '1';
  5649. }
  5650. $e = exp($parsedComplex['real']);
  5651. $eX = $e * cos($parsedComplex['imaginary']);
  5652. $eY = $e * sin($parsedComplex['imaginary']);
  5653. if ($parsedComplex['suffix'] == '') {
  5654. return self::COMPLEX($eX,$eY);
  5655. } else {
  5656. return self::COMPLEX($eX,$eY,$parsedComplex['suffix']);
  5657. }
  5658. }
  5659. /**
  5660. * IMPOWER
  5661. *
  5662. * Returns a complex number in x + yi or x + yj text format raised to a power.
  5663. *
  5664. * @param string $complexNumber
  5665. * @return string
  5666. */
  5667. public static function IMPOWER($complexNumber,$realNumber) {
  5668. $complexNumber = self::flattenSingleValue($complexNumber);
  5669. $realNumber = self::flattenSingleValue($realNumber);
  5670. if (!is_numeric($realNumber)) {
  5671. return self::$_errorCodes['value'];
  5672. }
  5673. $parsedComplex = self::parseComplex($complexNumber);
  5674. if (!is_array($parsedComplex)) {
  5675. return $parsedComplex;
  5676. }
  5677. $r = sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
  5678. $rPower = pow($r,$realNumber);
  5679. $theta = self::IMARGUMENT($complexNumber) * $realNumber;
  5680. if ($parsedComplex['imaginary'] == 0.0) {
  5681. return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
  5682. } else {
  5683. return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
  5684. }
  5685. }
  5686. /**
  5687. * IMDIV
  5688. *
  5689. * Returns the quotient of two complex numbers in x + yi or x + yj text format.
  5690. *
  5691. * @param string $complexDividend
  5692. * @param string $complexDivisor
  5693. * @return real
  5694. */
  5695. public static function IMDIV($complexDividend,$complexDivisor) {
  5696. $complexDividend = self::flattenSingleValue($complexDividend);
  5697. $complexDivisor = self::flattenSingleValue($complexDivisor);
  5698. $parsedComplexDividend = self::parseComplex($complexDividend);
  5699. if (!is_array($parsedComplexDividend)) {
  5700. return $parsedComplexDividend;
  5701. }
  5702. $parsedComplexDivisor = self::parseComplex($complexDivisor);
  5703. if (!is_array($parsedComplexDivisor)) {
  5704. return $parsedComplexDividend;
  5705. }
  5706. if ($parsedComplexDividend['suffix'] != $parsedComplexDivisor['suffix']) {
  5707. return self::$_errorCodes['num'];
  5708. }
  5709. $d1 = ($parsedComplexDividend['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['imaginary']);
  5710. $d2 = ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['real']) - ($parsedComplexDividend['real'] * $parsedComplexDivisor['imaginary']);
  5711. $d3 = ($parsedComplexDivisor['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDivisor['imaginary'] * $parsedComplexDivisor['imaginary']);
  5712. return $d1/$d3.$d2/$d3.$parsedComplexDivisor['suffix'];
  5713. }
  5714. /**
  5715. * IMSUB
  5716. *
  5717. * Returns the difference of two complex numbers in x + yi or x + yj text format.
  5718. *
  5719. * @param string $complexNumber1
  5720. * @param string $complexNumber2
  5721. * @return real
  5722. */
  5723. public static function IMSUB($complexNumber1,$complexNumber2) {
  5724. $complexNumber1 = self::flattenSingleValue($complexNumber1);
  5725. $complexNumber2 = self::flattenSingleValue($complexNumber2);
  5726. $parsedComplex1 = self::parseComplex($complexNumber1);
  5727. if (!is_array($parsedComplex1)) {
  5728. return $parsedComplex1;
  5729. }
  5730. $parsedComplex2 = self::parseComplex($complexNumber2);
  5731. if (!is_array($parsedComplex2)) {
  5732. return $parsedComplex2;
  5733. }
  5734. if ($parsedComplex1['suffix'] != $parsedComplex2['suffix']) {
  5735. return self::$_errorCodes['num'];
  5736. }
  5737. $d1 = $parsedComplex1['real'] - $parsedComplex2['real'];
  5738. $d2 = $parsedComplex1['imaginary'] - $parsedComplex2['imaginary'];
  5739. return self::COMPLEX($d1,$d2,$parsedComplex1['suffix']);
  5740. }
  5741. /**
  5742. * IMSUM
  5743. *
  5744. * Returns the sum of two or more complex numbers in x + yi or x + yj text format.
  5745. *
  5746. * @param array of mixed Data Series
  5747. * @return real
  5748. */
  5749. public static function IMSUM() {
  5750. // Return value
  5751. $returnValue = self::parseComplex('0');
  5752. $activeSuffix = '';
  5753. // Loop through the arguments
  5754. $aArgs = self::flattenArray(func_get_args());
  5755. foreach ($aArgs as $arg) {
  5756. $parsedComplex = self::parseComplex($arg);
  5757. if (!is_array($parsedComplex)) {
  5758. return $parsedComplex;
  5759. }
  5760. if ($activeSuffix == '') {
  5761. $activeSuffix = $parsedComplex['suffix'];
  5762. } elseif ($activeSuffix != $parsedComplex['suffix']) {
  5763. return self::$_errorCodes['num'];
  5764. }
  5765. $returnValue['real'] += $parsedComplex['real'];
  5766. $returnValue['imaginary'] += $parsedComplex['imaginary'];
  5767. }
  5768. if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
  5769. return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
  5770. }
  5771. /**
  5772. * IMPRODUCT
  5773. *
  5774. * Returns the product of two or more complex numbers in x + yi or x + yj text format.
  5775. *
  5776. * @param array of mixed Data Series
  5777. * @return real
  5778. */
  5779. public static function IMPRODUCT() {
  5780. // Return value
  5781. $returnValue = self::parseComplex('1');
  5782. $activeSuffix = '';
  5783. // Loop through the arguments
  5784. $aArgs = self::flattenArray(func_get_args());
  5785. foreach ($aArgs as $arg) {
  5786. $parsedComplex = self::parseComplex($arg);
  5787. if (!is_array($parsedComplex)) {
  5788. return $parsedComplex;
  5789. }
  5790. $workValue = $returnValue;
  5791. if (($parsedComplex['suffix'] != '') && ($activeSuffix == '')) {
  5792. $activeSuffix = $parsedComplex['suffix'];
  5793. } elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
  5794. return self::$_errorCodes['num'];
  5795. }
  5796. $returnValue['real'] = ($workValue['real'] * $parsedComplex['real']) - ($workValue['imaginary'] * $parsedComplex['imaginary']);
  5797. $returnValue['imaginary'] = ($workValue['real'] * $parsedComplex['imaginary']) + ($workValue['imaginary'] * $parsedComplex['real']);
  5798. }
  5799. if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
  5800. return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
  5801. }
  5802. private static $conversionUnits = array( 'g' => array( 'Group' => 'Mass', 'Unit Name' => 'Gram', 'AllowPrefix' => True ),
  5803. 'sg' => array( 'Group' => 'Mass', 'Unit Name' => 'Slug', 'AllowPrefix' => False ),
  5804. 'lbm' => array( 'Group' => 'Mass', 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => False ),
  5805. 'u' => array( 'Group' => 'Mass', 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => True ),
  5806. 'ozm' => array( 'Group' => 'Mass', 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => False ),
  5807. 'm' => array( 'Group' => 'Distance', 'Unit Name' => 'Meter', 'AllowPrefix' => True ),
  5808. 'mi' => array( 'Group' => 'Distance', 'Unit Name' => 'Statute mile', 'AllowPrefix' => False ),
  5809. 'Nmi' => array( 'Group' => 'Distance', 'Unit Name' => 'Nautical mile', 'AllowPrefix' => False ),
  5810. 'in' => array( 'Group' => 'Distance', 'Unit Name' => 'Inch', 'AllowPrefix' => False ),
  5811. 'ft' => array( 'Group' => 'Distance', 'Unit Name' => 'Foot', 'AllowPrefix' => False ),
  5812. 'yd' => array( 'Group' => 'Distance', 'Unit Name' => 'Yard', 'AllowPrefix' => False ),
  5813. 'ang' => array( 'Group' => 'Distance', 'Unit Name' => 'Angstrom', 'AllowPrefix' => True ),
  5814. 'Pica' => array( 'Group' => 'Distance', 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => False ),
  5815. 'yr' => array( 'Group' => 'Time', 'Unit Name' => 'Year', 'AllowPrefix' => False ),
  5816. 'day' => array( 'Group' => 'Time', 'Unit Name' => 'Day', 'AllowPrefix' => False ),
  5817. 'hr' => array( 'Group' => 'Time', 'Unit Name' => 'Hour', 'AllowPrefix' => False ),
  5818. 'mn' => array( 'Group' => 'Time', 'Unit Name' => 'Minute', 'AllowPrefix' => False ),
  5819. 'sec' => array( 'Group' => 'Time', 'Unit Name' => 'Second', 'AllowPrefix' => True ),
  5820. 'Pa' => array( 'Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => True ),
  5821. 'p' => array( 'Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => True ),
  5822. 'atm' => array( 'Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => True ),
  5823. 'at' => array( 'Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => True ),
  5824. 'mmHg' => array( 'Group' => 'Pressure', 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => True ),
  5825. 'N' => array( 'Group' => 'Force', 'Unit Name' => 'Newton', 'AllowPrefix' => True ),
  5826. 'dyn' => array( 'Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => True ),
  5827. 'dy' => array( 'Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => True ),
  5828. 'lbf' => array( 'Group' => 'Force', 'Unit Name' => 'Pound force', 'AllowPrefix' => False ),
  5829. 'J' => array( 'Group' => 'Energy', 'Unit Name' => 'Joule', 'AllowPrefix' => True ),
  5830. 'e' => array( 'Group' => 'Energy', 'Unit Name' => 'Erg', 'AllowPrefix' => True ),
  5831. 'c' => array( 'Group' => 'Energy', 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => True ),
  5832. 'cal' => array( 'Group' => 'Energy', 'Unit Name' => 'IT calorie', 'AllowPrefix' => True ),
  5833. 'eV' => array( 'Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => True ),
  5834. 'ev' => array( 'Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => True ),
  5835. 'HPh' => array( 'Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => False ),
  5836. 'hh' => array( 'Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => False ),
  5837. 'Wh' => array( 'Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => True ),
  5838. 'wh' => array( 'Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => True ),
  5839. 'flb' => array( 'Group' => 'Energy', 'Unit Name' => 'Foot-pound', 'AllowPrefix' => False ),
  5840. 'BTU' => array( 'Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => False ),
  5841. 'btu' => array( 'Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => False ),
  5842. 'HP' => array( 'Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => False ),
  5843. 'h' => array( 'Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => False ),
  5844. 'W' => array( 'Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => True ),
  5845. 'w' => array( 'Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => True ),
  5846. 'T' => array( 'Group' => 'Magnetism', 'Unit Name' => 'Tesla', 'AllowPrefix' => True ),
  5847. 'ga' => array( 'Group' => 'Magnetism', 'Unit Name' => 'Gauss', 'AllowPrefix' => True ),
  5848. 'C' => array( 'Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => False ),
  5849. 'cel' => array( 'Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => False ),
  5850. 'F' => array( 'Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => False ),
  5851. 'fah' => array( 'Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => False ),
  5852. 'K' => array( 'Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => False ),
  5853. 'kel' => array( 'Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => False ),
  5854. 'tsp' => array( 'Group' => 'Liquid', 'Unit Name' => 'Teaspoon', 'AllowPrefix' => False ),
  5855. 'tbs' => array( 'Group' => 'Liquid', 'Unit Name' => 'Tablespoon', 'AllowPrefix' => False ),
  5856. 'oz' => array( 'Group' => 'Liquid', 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => False ),
  5857. 'cup' => array( 'Group' => 'Liquid', 'Unit Name' => 'Cup', 'AllowPrefix' => False ),
  5858. 'pt' => array( 'Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => False ),
  5859. 'us_pt' => array( 'Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => False ),
  5860. 'uk_pt' => array( 'Group' => 'Liquid', 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => False ),
  5861. 'qt' => array( 'Group' => 'Liquid', 'Unit Name' => 'Quart', 'AllowPrefix' => False ),
  5862. 'gal' => array( 'Group' => 'Liquid', 'Unit Name' => 'Gallon', 'AllowPrefix' => False ),
  5863. 'l' => array( 'Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => True ),
  5864. 'lt' => array( 'Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => True )
  5865. );
  5866. private static $conversionMultipliers = array( 'E' => array( 'multiplier' => 1E18, 'name' => 'exa' ),
  5867. 'P' => array( 'multiplier' => 1E15, 'name' => 'peta' ),
  5868. 'T' => array( 'multiplier' => 1E12, 'name' => 'tera' ),
  5869. 'G' => array( 'multiplier' => 1E9, 'name' => 'giga' ),
  5870. 'M' => array( 'multiplier' => 1E6, 'name' => 'mega' ),
  5871. 'k' => array( 'multiplier' => 1E3, 'name' => 'kilo' ),
  5872. 'h' => array( 'multiplier' => 1E2, 'name' => 'hecto' ),
  5873. 'e' => array( 'multiplier' => 1E1, 'name' => 'dekao' ),
  5874. 'd' => array( 'multiplier' => 1E-1, 'name' => 'deci' ),
  5875. 'c' => array( 'multiplier' => 1E-2, 'name' => 'centi' ),
  5876. 'm' => array( 'multiplier' => 1E-3, 'name' => 'milli' ),
  5877. 'u' => array( 'multiplier' => 1E-6, 'name' => 'micro' ),
  5878. 'n' => array( 'multiplier' => 1E-9, 'name' => 'nano' ),
  5879. 'p' => array( 'multiplier' => 1E-12, 'name' => 'pico' ),
  5880. 'f' => array( 'multiplier' => 1E-15, 'name' => 'femto' ),
  5881. 'a' => array( 'multiplier' => 1E-18, 'name' => 'atto' )
  5882. );
  5883. private static $unitConversions = array( 'Mass' => array( 'g' => array( 'g' => 1.0,
  5884. 'sg' => 6.85220500053478E-05,
  5885. 'lbm' => 2.20462291469134E-03,
  5886. 'u' => 6.02217000000000E+23,
  5887. 'ozm' => 3.52739718003627E-02
  5888. ),
  5889. 'sg' => array( 'g' => 1.45938424189287E+04,
  5890. 'sg' => 1.0,
  5891. 'lbm' => 3.21739194101647E+01,
  5892. 'u' => 8.78866000000000E+27,
  5893. 'ozm' => 5.14782785944229E+02
  5894. ),
  5895. 'lbm' => array( 'g' => 4.53592309748811E+02,
  5896. 'sg' => 3.10810749306493E-02,
  5897. 'lbm' => 1.0,
  5898. 'u' => 2.73161000000000E+26,
  5899. 'ozm' => 1.60000023429410E+01
  5900. ),
  5901. 'u' => array( 'g' => 1.66053100460465E-24,
  5902. 'sg' => 1.13782988532950E-28,
  5903. 'lbm' => 3.66084470330684E-27,
  5904. 'u' => 1.0,
  5905. 'ozm' => 5.85735238300524E-26
  5906. ),
  5907. 'ozm' => array( 'g' => 2.83495152079732E+01,
  5908. 'sg' => 1.94256689870811E-03,
  5909. 'lbm' => 6.24999908478882E-02,
  5910. 'u' => 1.70725600000000E+25,
  5911. 'ozm' => 1.0
  5912. )
  5913. ),
  5914. 'Distance' => array( 'm' => array( 'm' => 1.0,
  5915. 'mi' => 6.21371192237334E-04,
  5916. 'Nmi' => 5.39956803455724E-04,
  5917. 'in' => 3.93700787401575E+01,
  5918. 'ft' => 3.28083989501312E+00,
  5919. 'yd' => 1.09361329797891E+00,
  5920. 'ang' => 1.00000000000000E+10,
  5921. 'Pica' => 2.83464566929116E+03
  5922. ),
  5923. 'mi' => array( 'm' => 1.60934400000000E+03,
  5924. 'mi' => 1.0,
  5925. 'Nmi' => 8.68976241900648E-01,
  5926. 'in' => 6.33600000000000E+04,
  5927. 'ft' => 5.28000000000000E+03,
  5928. 'yd' => 1.76000000000000E+03,
  5929. 'ang' => 1.60934400000000E+13,
  5930. 'Pica' => 4.56191999999971E+06
  5931. ),
  5932. 'Nmi' => array( 'm' => 1.85200000000000E+03,
  5933. 'mi' => 1.15077944802354E+00,
  5934. 'Nmi' => 1.0,
  5935. 'in' => 7.29133858267717E+04,
  5936. 'ft' => 6.07611548556430E+03,
  5937. 'yd' => 2.02537182785694E+03,
  5938. 'ang' => 1.85200000000000E+13,
  5939. 'Pica' => 5.24976377952723E+06
  5940. ),
  5941. 'in' => array( 'm' => 2.54000000000000E-02,
  5942. 'mi' => 1.57828282828283E-05,
  5943. 'Nmi' => 1.37149028077754E-05,
  5944. 'in' => 1.0,
  5945. 'ft' => 8.33333333333333E-02,
  5946. 'yd' => 2.77777777686643E-02,
  5947. 'ang' => 2.54000000000000E+08,
  5948. 'Pica' => 7.19999999999955E+01
  5949. ),
  5950. 'ft' => array( 'm' => 3.04800000000000E-01,
  5951. 'mi' => 1.89393939393939E-04,
  5952. 'Nmi' => 1.64578833693305E-04,
  5953. 'in' => 1.20000000000000E+01,
  5954. 'ft' => 1.0,
  5955. 'yd' => 3.33333333223972E-01,
  5956. 'ang' => 3.04800000000000E+09,
  5957. 'Pica' => 8.63999999999946E+02
  5958. ),
  5959. 'yd' => array( 'm' => 9.14400000300000E-01,
  5960. 'mi' => 5.68181818368230E-04,
  5961. 'Nmi' => 4.93736501241901E-04,
  5962. 'in' => 3.60000000118110E+01,
  5963. 'ft' => 3.00000000000000E+00,
  5964. 'yd' => 1.0,
  5965. 'ang' => 9.14400000300000E+09,
  5966. 'Pica' => 2.59200000085023E+03
  5967. ),
  5968. 'ang' => array( 'm' => 1.00000000000000E-10,
  5969. 'mi' => 6.21371192237334E-14,
  5970. 'Nmi' => 5.39956803455724E-14,
  5971. 'in' => 3.93700787401575E-09,
  5972. 'ft' => 3.28083989501312E-10,
  5973. 'yd' => 1.09361329797891E-10,
  5974. 'ang' => 1.0,
  5975. 'Pica' => 2.83464566929116E-07
  5976. ),
  5977. 'Pica' => array( 'm' => 3.52777777777800E-04,
  5978. 'mi' => 2.19205948372629E-07,
  5979. 'Nmi' => 1.90484761219114E-07,
  5980. 'in' => 1.38888888888898E-02,
  5981. 'ft' => 1.15740740740748E-03,
  5982. 'yd' => 3.85802469009251E-04,
  5983. 'ang' => 3.52777777777800E+06,
  5984. 'Pica' => 1.0
  5985. )
  5986. ),
  5987. 'Time' => array( 'yr' => array( 'yr' => 1.0,
  5988. 'day' => 365.25,
  5989. 'hr' => 8766.0,
  5990. 'mn' => 525960.0,
  5991. 'sec' => 31557600.0
  5992. ),
  5993. 'day' => array( 'yr' => 2.73785078713210E-03,
  5994. 'day' => 1.0,
  5995. 'hr' => 24.0,
  5996. 'mn' => 1440.0,
  5997. 'sec' => 86400.0
  5998. ),
  5999. 'hr' => array( 'yr' => 1.14077116130504E-04,
  6000. 'day' => 4.16666666666667E-02,
  6001. 'hr' => 1.0,
  6002. 'mn' => 60.0,
  6003. 'sec' => 3600.0
  6004. ),
  6005. 'mn' => array( 'yr' => 1.90128526884174E-06,
  6006. 'day' => 6.94444444444444E-04,
  6007. 'hr' => 1.66666666666667E-02,
  6008. 'mn' => 1.0,
  6009. 'sec' => 60.0
  6010. ),
  6011. 'sec' => array( 'yr' => 3.16880878140289E-08,
  6012. 'day' => 1.15740740740741E-05,
  6013. 'hr' => 2.77777777777778E-04,
  6014. 'mn' => 1.66666666666667E-02,
  6015. 'sec' => 1.0
  6016. )
  6017. ),
  6018. 'Pressure' => array( 'Pa' => array( 'Pa' => 1.0,
  6019. 'p' => 1.0,
  6020. 'atm' => 9.86923299998193E-06,
  6021. 'at' => 9.86923299998193E-06,
  6022. 'mmHg' => 7.50061707998627E-03
  6023. ),
  6024. 'p' => array( 'Pa' => 1.0,
  6025. 'p' => 1.0,
  6026. 'atm' => 9.86923299998193E-06,
  6027. 'at' => 9.86923299998193E-06,
  6028. 'mmHg' => 7.50061707998627E-03
  6029. ),
  6030. 'atm' => array( 'Pa' => 1.01324996583000E+05,
  6031. 'p' => 1.01324996583000E+05,
  6032. 'atm' => 1.0,
  6033. 'at' => 1.0,
  6034. 'mmHg' => 760.0
  6035. ),
  6036. 'at' => array( 'Pa' => 1.01324996583000E+05,
  6037. 'p' => 1.01324996583000E+05,
  6038. 'atm' => 1.0,
  6039. 'at' => 1.0,
  6040. 'mmHg' => 760.0
  6041. ),
  6042. 'mmHg' => array( 'Pa' => 1.33322363925000E+02,
  6043. 'p' => 1.33322363925000E+02,
  6044. 'atm' => 1.31578947368421E-03,
  6045. 'at' => 1.31578947368421E-03,
  6046. 'mmHg' => 1.0
  6047. )
  6048. ),
  6049. 'Force' => array( 'N' => array( 'N' => 1.0,
  6050. 'dyn' => 1.0E+5,
  6051. 'dy' => 1.0E+5,
  6052. 'lbf' => 2.24808923655339E-01
  6053. ),
  6054. 'dyn' => array( 'N' => 1.0E-5,
  6055. 'dyn' => 1.0,
  6056. 'dy' => 1.0,
  6057. 'lbf' => 2.24808923655339E-06
  6058. ),
  6059. 'dy' => array( 'N' => 1.0E-5,
  6060. 'dyn' => 1.0,
  6061. 'dy' => 1.0,
  6062. 'lbf' => 2.24808923655339E-06
  6063. ),
  6064. 'lbf' => array( 'N' => 4.448222,
  6065. 'dyn' => 4.448222E+5,
  6066. 'dy' => 4.448222E+5,
  6067. 'lbf' => 1.0
  6068. )
  6069. ),
  6070. 'Energy' => array( 'J' => array( 'J' => 1.0,
  6071. 'e' => 9.99999519343231E+06,
  6072. 'c' => 2.39006249473467E-01,
  6073. 'cal' => 2.38846190642017E-01,
  6074. 'eV' => 6.24145700000000E+18,
  6075. 'ev' => 6.24145700000000E+18,
  6076. 'HPh' => 3.72506430801000E-07,
  6077. 'hh' => 3.72506430801000E-07,
  6078. 'Wh' => 2.77777916238711E-04,
  6079. 'wh' => 2.77777916238711E-04,
  6080. 'flb' => 2.37304222192651E+01,
  6081. 'BTU' => 9.47815067349015E-04,
  6082. 'btu' => 9.47815067349015E-04
  6083. ),
  6084. 'e' => array( 'J' => 1.00000048065700E-07,
  6085. 'e' => 1.0,
  6086. 'c' => 2.39006364353494E-08,
  6087. 'cal' => 2.38846305445111E-08,
  6088. 'eV' => 6.24146000000000E+11,
  6089. 'ev' => 6.24146000000000E+11,
  6090. 'HPh' => 3.72506609848824E-14,
  6091. 'hh' => 3.72506609848824E-14,
  6092. 'Wh' => 2.77778049754611E-11,
  6093. 'wh' => 2.77778049754611E-11,
  6094. 'flb' => 2.37304336254586E-06,
  6095. 'BTU' => 9.47815522922962E-11,
  6096. 'btu' => 9.47815522922962E-11
  6097. ),
  6098. 'c' => array( 'J' => 4.18399101363672E+00,
  6099. 'e' => 4.18398900257312E+07,
  6100. 'c' => 1.0,
  6101. 'cal' => 9.99330315287563E-01,
  6102. 'eV' => 2.61142000000000E+19,
  6103. 'ev' => 2.61142000000000E+19,
  6104. 'HPh' => 1.55856355899327E-06,
  6105. 'hh' => 1.55856355899327E-06,
  6106. 'Wh' => 1.16222030532950E-03,
  6107. 'wh' => 1.16222030532950E-03,
  6108. 'flb' => 9.92878733152102E+01,
  6109. 'BTU' => 3.96564972437776E-03,
  6110. 'btu' => 3.96564972437776E-03
  6111. ),
  6112. 'cal' => array( 'J' => 4.18679484613929E+00,
  6113. 'e' => 4.18679283372801E+07,
  6114. 'c' => 1.00067013349059E+00,
  6115. 'cal' => 1.0,
  6116. 'eV' => 2.61317000000000E+19,
  6117. 'ev' => 2.61317000000000E+19,
  6118. 'HPh' => 1.55960800463137E-06,
  6119. 'hh' => 1.55960800463137E-06,
  6120. 'Wh' => 1.16299914807955E-03,
  6121. 'wh' => 1.16299914807955E-03,
  6122. 'flb' => 9.93544094443283E+01,
  6123. 'BTU' => 3.96830723907002E-03,
  6124. 'btu' => 3.96830723907002E-03
  6125. ),
  6126. 'eV' => array( 'J' => 1.60219000146921E-19,
  6127. 'e' => 1.60218923136574E-12,
  6128. 'c' => 3.82933423195043E-20,
  6129. 'cal' => 3.82676978535648E-20,
  6130. 'eV' => 1.0,
  6131. 'ev' => 1.0,
  6132. 'HPh' => 5.96826078912344E-26,
  6133. 'hh' => 5.96826078912344E-26,
  6134. 'Wh' => 4.45053000026614E-23,
  6135. 'wh' => 4.45053000026614E-23,
  6136. 'flb' => 3.80206452103492E-18,
  6137. 'BTU' => 1.51857982414846E-22,
  6138. 'btu' => 1.51857982414846E-22
  6139. ),
  6140. 'ev' => array( 'J' => 1.60219000146921E-19,
  6141. 'e' => 1.60218923136574E-12,
  6142. 'c' => 3.82933423195043E-20,
  6143. 'cal' => 3.82676978535648E-20,
  6144. 'eV' => 1.0,
  6145. 'ev' => 1.0,
  6146. 'HPh' => 5.96826078912344E-26,
  6147. 'hh' => 5.96826078912344E-26,
  6148. 'Wh' => 4.45053000026614E-23,
  6149. 'wh' => 4.45053000026614E-23,
  6150. 'flb' => 3.80206452103492E-18,
  6151. 'BTU' => 1.51857982414846E-22,
  6152. 'btu' => 1.51857982414846E-22
  6153. ),
  6154. 'HPh' => array( 'J' => 2.68451741316170E+06,
  6155. 'e' => 2.68451612283024E+13,
  6156. 'c' => 6.41616438565991E+05,
  6157. 'cal' => 6.41186757845835E+05,
  6158. 'eV' => 1.67553000000000E+25,
  6159. 'ev' => 1.67553000000000E+25,
  6160. 'HPh' => 1.0,
  6161. 'hh' => 1.0,
  6162. 'Wh' => 7.45699653134593E+02,
  6163. 'wh' => 7.45699653134593E+02,
  6164. 'flb' => 6.37047316692964E+07,
  6165. 'BTU' => 2.54442605275546E+03,
  6166. 'btu' => 2.54442605275546E+03
  6167. ),
  6168. 'hh' => array( 'J' => 2.68451741316170E+06,
  6169. 'e' => 2.68451612283024E+13,
  6170. 'c' => 6.41616438565991E+05,
  6171. 'cal' => 6.41186757845835E+05,
  6172. 'eV' => 1.67553000000000E+25,
  6173. 'ev' => 1.67553000000000E+25,
  6174. 'HPh' => 1.0,
  6175. 'hh' => 1.0,
  6176. 'Wh' => 7.45699653134593E+02,
  6177. 'wh' => 7.45699653134593E+02,
  6178. 'flb' => 6.37047316692964E+07,
  6179. 'BTU' => 2.54442605275546E+03,
  6180. 'btu' => 2.54442605275546E+03
  6181. ),
  6182. 'Wh' => array( 'J' => 3.59999820554720E+03,
  6183. 'e' => 3.59999647518369E+10,
  6184. 'c' => 8.60422069219046E+02,
  6185. 'cal' => 8.59845857713046E+02,
  6186. 'eV' => 2.24692340000000E+22,
  6187. 'ev' => 2.24692340000000E+22,
  6188. 'HPh' => 1.34102248243839E-03,
  6189. 'hh' => 1.34102248243839E-03,
  6190. 'Wh' => 1.0,
  6191. 'wh' => 1.0,
  6192. 'flb' => 8.54294774062316E+04,
  6193. 'BTU' => 3.41213254164705E+00,
  6194. 'btu' => 3.41213254164705E+00
  6195. ),
  6196. 'wh' => array( 'J' => 3.59999820554720E+03,
  6197. 'e' => 3.59999647518369E+10,
  6198. 'c' => 8.60422069219046E+02,
  6199. 'cal' => 8.59845857713046E+02,
  6200. 'eV' => 2.24692340000000E+22,
  6201. 'ev' => 2.24692340000000E+22,
  6202. 'HPh' => 1.34102248243839E-03,
  6203. 'hh' => 1.34102248243839E-03,
  6204. 'Wh' => 1.0,
  6205. 'wh' => 1.0,
  6206. 'flb' => 8.54294774062316E+04,
  6207. 'BTU' => 3.41213254164705E+00,
  6208. 'btu' => 3.41213254164705E+00
  6209. ),
  6210. 'flb' => array( 'J' => 4.21400003236424E-02,
  6211. 'e' => 4.21399800687660E+05,
  6212. 'c' => 1.00717234301644E-02,
  6213. 'cal' => 1.00649785509554E-02,
  6214. 'eV' => 2.63015000000000E+17,
  6215. 'ev' => 2.63015000000000E+17,
  6216. 'HPh' => 1.56974211145130E-08,
  6217. 'hh' => 1.56974211145130E-08,
  6218. 'Wh' => 1.17055614802000E-05,
  6219. 'wh' => 1.17055614802000E-05,
  6220. 'flb' => 1.0,
  6221. 'BTU' => 3.99409272448406E-05,
  6222. 'btu' => 3.99409272448406E-05
  6223. ),
  6224. 'BTU' => array( 'J' => 1.05505813786749E+03,
  6225. 'e' => 1.05505763074665E+10,
  6226. 'c' => 2.52165488508168E+02,
  6227. 'cal' => 2.51996617135510E+02,
  6228. 'eV' => 6.58510000000000E+21,
  6229. 'ev' => 6.58510000000000E+21,
  6230. 'HPh' => 3.93015941224568E-04,
  6231. 'hh' => 3.93015941224568E-04,
  6232. 'Wh' => 2.93071851047526E-01,
  6233. 'wh' => 2.93071851047526E-01,
  6234. 'flb' => 2.50369750774671E+04,
  6235. 'BTU' => 1.0,
  6236. 'btu' => 1.0,
  6237. ),
  6238. 'btu' => array( 'J' => 1.05505813786749E+03,
  6239. 'e' => 1.05505763074665E+10,
  6240. 'c' => 2.52165488508168E+02,
  6241. 'cal' => 2.51996617135510E+02,
  6242. 'eV' => 6.58510000000000E+21,
  6243. 'ev' => 6.58510000000000E+21,
  6244. 'HPh' => 3.93015941224568E-04,
  6245. 'hh' => 3.93015941224568E-04,
  6246. 'Wh' => 2.93071851047526E-01,
  6247. 'wh' => 2.93071851047526E-01,
  6248. 'flb' => 2.50369750774671E+04,
  6249. 'BTU' => 1.0,
  6250. 'btu' => 1.0,
  6251. )
  6252. ),
  6253. 'Power' => array( 'HP' => array( 'HP' => 1.0,
  6254. 'h' => 1.0,
  6255. 'W' => 7.45701000000000E+02,
  6256. 'w' => 7.45701000000000E+02
  6257. ),
  6258. 'h' => array( 'HP' => 1.0,
  6259. 'h' => 1.0,
  6260. 'W' => 7.45701000000000E+02,
  6261. 'w' => 7.45701000000000E+02
  6262. ),
  6263. 'W' => array( 'HP' => 1.34102006031908E-03,
  6264. 'h' => 1.34102006031908E-03,
  6265. 'W' => 1.0,
  6266. 'w' => 1.0
  6267. ),
  6268. 'w' => array( 'HP' => 1.34102006031908E-03,
  6269. 'h' => 1.34102006031908E-03,
  6270. 'W' => 1.0,
  6271. 'w' => 1.0
  6272. )
  6273. ),
  6274. 'Magnetism' => array( 'T' => array( 'T' => 1.0,
  6275. 'ga' => 10000.0
  6276. ),
  6277. 'ga' => array( 'T' => 0.0001,
  6278. 'ga' => 1.0
  6279. )
  6280. ),
  6281. 'Liquid' => array( 'tsp' => array( 'tsp' => 1.0,
  6282. 'tbs' => 3.33333333333333E-01,
  6283. 'oz' => 1.66666666666667E-01,
  6284. 'cup' => 2.08333333333333E-02,
  6285. 'pt' => 1.04166666666667E-02,
  6286. 'us_pt' => 1.04166666666667E-02,
  6287. 'uk_pt' => 8.67558516821960E-03,
  6288. 'qt' => 5.20833333333333E-03,
  6289. 'gal' => 1.30208333333333E-03,
  6290. 'l' => 4.92999408400710E-03,
  6291. 'lt' => 4.92999408400710E-03
  6292. ),
  6293. 'tbs' => array( 'tsp' => 3.00000000000000E+00,
  6294. 'tbs' => 1.0,
  6295. 'oz' => 5.00000000000000E-01,
  6296. 'cup' => 6.25000000000000E-02,
  6297. 'pt' => 3.12500000000000E-02,
  6298. 'us_pt' => 3.12500000000000E-02,
  6299. 'uk_pt' => 2.60267555046588E-02,
  6300. 'qt' => 1.56250000000000E-02,
  6301. 'gal' => 3.90625000000000E-03,
  6302. 'l' => 1.47899822520213E-02,
  6303. 'lt' => 1.47899822520213E-02
  6304. ),
  6305. 'oz' => array( 'tsp' => 6.00000000000000E+00,
  6306. 'tbs' => 2.00000000000000E+00,
  6307. 'oz' => 1.0,
  6308. 'cup' => 1.25000000000000E-01,
  6309. 'pt' => 6.25000000000000E-02,
  6310. 'us_pt' => 6.25000000000000E-02,
  6311. 'uk_pt' => 5.20535110093176E-02,
  6312. 'qt' => 3.12500000000000E-02,
  6313. 'gal' => 7.81250000000000E-03,
  6314. 'l' => 2.95799645040426E-02,
  6315. 'lt' => 2.95799645040426E-02
  6316. ),
  6317. 'cup' => array( 'tsp' => 4.80000000000000E+01,
  6318. 'tbs' => 1.60000000000000E+01,
  6319. 'oz' => 8.00000000000000E+00,
  6320. 'cup' => 1.0,
  6321. 'pt' => 5.00000000000000E-01,
  6322. 'us_pt' => 5.00000000000000E-01,
  6323. 'uk_pt' => 4.16428088074541E-01,
  6324. 'qt' => 2.50000000000000E-01,
  6325. 'gal' => 6.25000000000000E-02,
  6326. 'l' => 2.36639716032341E-01,
  6327. 'lt' => 2.36639716032341E-01
  6328. ),
  6329. 'pt' => array( 'tsp' => 9.60000000000000E+01,
  6330. 'tbs' => 3.20000000000000E+01,
  6331. 'oz' => 1.60000000000000E+01,
  6332. 'cup' => 2.00000000000000E+00,
  6333. 'pt' => 1.0,
  6334. 'us_pt' => 1.0,
  6335. 'uk_pt' => 8.32856176149081E-01,
  6336. 'qt' => 5.00000000000000E-01,
  6337. 'gal' => 1.25000000000000E-01,
  6338. 'l' => 4.73279432064682E-01,
  6339. 'lt' => 4.73279432064682E-01
  6340. ),
  6341. 'us_pt' => array( 'tsp' => 9.60000000000000E+01,
  6342. 'tbs' => 3.20000000000000E+01,
  6343. 'oz' => 1.60000000000000E+01,
  6344. 'cup' => 2.00000000000000E+00,
  6345. 'pt' => 1.0,
  6346. 'us_pt' => 1.0,
  6347. 'uk_pt' => 8.32856176149081E-01,
  6348. 'qt' => 5.00000000000000E-01,
  6349. 'gal' => 1.25000000000000E-01,
  6350. 'l' => 4.73279432064682E-01,
  6351. 'lt' => 4.73279432064682E-01
  6352. ),
  6353. 'uk_pt' => array( 'tsp' => 1.15266000000000E+02,
  6354. 'tbs' => 3.84220000000000E+01,
  6355. 'oz' => 1.92110000000000E+01,
  6356. 'cup' => 2.40137500000000E+00,
  6357. 'pt' => 1.20068750000000E+00,
  6358. 'us_pt' => 1.20068750000000E+00,
  6359. 'uk_pt' => 1.0,
  6360. 'qt' => 6.00343750000000E-01,
  6361. 'gal' => 1.50085937500000E-01,
  6362. 'l' => 5.68260698087162E-01,
  6363. 'lt' => 5.68260698087162E-01
  6364. ),
  6365. 'qt' => array( 'tsp' => 1.92000000000000E+02,
  6366. 'tbs' => 6.40000000000000E+01,
  6367. 'oz' => 3.20000000000000E+01,
  6368. 'cup' => 4.00000000000000E+00,
  6369. 'pt' => 2.00000000000000E+00,
  6370. 'us_pt' => 2.00000000000000E+00,
  6371. 'uk_pt' => 1.66571235229816E+00,
  6372. 'qt' => 1.0,
  6373. 'gal' => 2.50000000000000E-01,
  6374. 'l' => 9.46558864129363E-01,
  6375. 'lt' => 9.46558864129363E-01
  6376. ),
  6377. 'gal' => array( 'tsp' => 7.68000000000000E+02,
  6378. 'tbs' => 2.56000000000000E+02,
  6379. 'oz' => 1.28000000000000E+02,
  6380. 'cup' => 1.60000000000000E+01,
  6381. 'pt' => 8.00000000000000E+00,
  6382. 'us_pt' => 8.00000000000000E+00,
  6383. 'uk_pt' => 6.66284940919265E+00,
  6384. 'qt' => 4.00000000000000E+00,
  6385. 'gal' => 1.0,
  6386. 'l' => 3.78623545651745E+00,
  6387. 'lt' => 3.78623545651745E+00
  6388. ),
  6389. 'l' => array( 'tsp' => 2.02840000000000E+02,
  6390. 'tbs' => 6.76133333333333E+01,
  6391. 'oz' => 3.38066666666667E+01,
  6392. 'cup' => 4.22583333333333E+00,
  6393. 'pt' => 2.11291666666667E+00,
  6394. 'us_pt' => 2.11291666666667E+00,
  6395. 'uk_pt' => 1.75975569552166E+00,
  6396. 'qt' => 1.05645833333333E+00,
  6397. 'gal' => 2.64114583333333E-01,
  6398. 'l' => 1.0,
  6399. 'lt' => 1.0
  6400. ),
  6401. 'lt' => array( 'tsp' => 2.02840000000000E+02,
  6402. 'tbs' => 6.76133333333333E+01,
  6403. 'oz' => 3.38066666666667E+01,
  6404. 'cup' => 4.22583333333333E+00,
  6405. 'pt' => 2.11291666666667E+00,
  6406. 'us_pt' => 2.11291666666667E+00,
  6407. 'uk_pt' => 1.75975569552166E+00,
  6408. 'qt' => 1.05645833333333E+00,
  6409. 'gal' => 2.64114583333333E-01,
  6410. 'l' => 1.0,
  6411. 'lt' => 1.0
  6412. )
  6413. )
  6414. );
  6415. /**
  6416. * getConversionGroups
  6417. *
  6418. * @return array
  6419. */
  6420. public static function getConversionGroups() {
  6421. $conversionGroups = array();
  6422. foreach(self::$conversionUnits as $conversionUnit) {
  6423. $conversionGroups[] = $conversionUnit['Group'];
  6424. }
  6425. return array_merge(array_unique($conversionGroups));
  6426. } // function getConversionGroups()
  6427. /**
  6428. * getConversionGroupUnits
  6429. *
  6430. * @return array
  6431. */
  6432. public static function getConversionGroupUnits($group = NULL) {
  6433. $conversionGroups = array();
  6434. foreach(self::$conversionUnits as $conversionUnit => $conversionGroup) {
  6435. if ((is_null($group)) || ($conversionGroup['Group'] == $group)) {
  6436. $conversionGroups[$conversionGroup['Group']][] = $conversionUnit;
  6437. }
  6438. }
  6439. return $conversionGroups;
  6440. } // function getConversionGroupUnits()
  6441. /**
  6442. * getConversionGroupUnitDetails
  6443. *
  6444. * @return array
  6445. */
  6446. public static function getConversionGroupUnitDetails($group = NULL) {
  6447. $conversionGroups = array();
  6448. foreach(self::$conversionUnits as $conversionUnit => $conversionGroup) {
  6449. if ((is_null($group)) || ($conversionGroup['Group'] == $group)) {
  6450. $conversionGroups[$conversionGroup['Group']][] = array( 'unit' => $conversionUnit,
  6451. 'description' => $conversionGroup['Unit Name']
  6452. );
  6453. }
  6454. }
  6455. return $conversionGroups;
  6456. } // function getConversionGroupUnitDetails()
  6457. /**
  6458. * getConversionGroups
  6459. *
  6460. * @return array
  6461. */
  6462. public static function getConversionMultipliers() {
  6463. return self::$conversionMultipliers;
  6464. } // function getConversionGroups()
  6465. /**
  6466. * CONVERTUOM
  6467. *
  6468. * @param float $value
  6469. * @param string $fromUOM
  6470. * @param string $toUOM
  6471. * @return float
  6472. */
  6473. public static function CONVERTUOM($value, $fromUOM, $toUOM) {
  6474. $value = self::flattenSingleValue($value);
  6475. $fromUOM = self::flattenSingleValue($fromUOM);
  6476. $toUOM = self::flattenSingleValue($toUOM);
  6477. if (!is_numeric($value)) {
  6478. return self::$_errorCodes['value'];
  6479. }
  6480. $fromMultiplier = 1;
  6481. if (isset(self::$conversionUnits[$fromUOM])) {
  6482. $unitGroup1 = self::$conversionUnits[$fromUOM]['Group'];
  6483. } else {
  6484. $fromMultiplier = substr($fromUOM,0,1);
  6485. $fromUOM = substr($fromUOM,1);
  6486. if (isset(self::$conversionMultipliers[$fromMultiplier])) {
  6487. $fromMultiplier = self::$conversionMultipliers[$fromMultiplier]['multiplier'];
  6488. } else {
  6489. return self::$_errorCodes['na'];
  6490. }
  6491. if ((isset(self::$conversionUnits[$fromUOM])) && (self::$conversionUnits[$fromUOM]['AllowPrefix'])) {
  6492. $unitGroup1 = self::$conversionUnits[$fromUOM]['Group'];
  6493. } else {
  6494. return self::$_errorCodes['na'];
  6495. }
  6496. }
  6497. $value *= $fromMultiplier;
  6498. $toMultiplier = 1;
  6499. if (isset(self::$conversionUnits[$toUOM])) {
  6500. $unitGroup2 = self::$conversionUnits[$toUOM]['Group'];
  6501. } else {
  6502. $toMultiplier = substr($toUOM,0,1);
  6503. $toUOM = substr($toUOM,1);
  6504. if (isset(self::$conversionMultipliers[$toMultiplier])) {
  6505. $toMultiplier = self::$conversionMultipliers[$toMultiplier]['multiplier'];
  6506. } else {
  6507. return self::$_errorCodes['na'];
  6508. }
  6509. if ((isset(self::$conversionUnits[$toUOM])) && (self::$conversionUnits[$toUOM]['AllowPrefix'])) {
  6510. $unitGroup2 = self::$conversionUnits[$toUOM]['Group'];
  6511. } else {
  6512. return self::$_errorCodes['na'];
  6513. }
  6514. }
  6515. if ($unitGroup1 != $unitGroup2) {
  6516. return self::$_errorCodes['na'];
  6517. }
  6518. if ($fromUOM == $toUOM) {
  6519. return 1.0;
  6520. } elseif ($unitGroup1 == 'Temperature') {
  6521. if (($fromUOM == 'F') || ($fromUOM == 'fah')) {
  6522. if (($toUOM == 'F') || ($toUOM == 'fah')) {
  6523. return 1.0;
  6524. } else {
  6525. $value = (($value - 32) / 1.8);
  6526. if (($toUOM == 'K') || ($toUOM == 'kel')) {
  6527. $value += 273.15;
  6528. }
  6529. return $value;
  6530. }
  6531. } elseif ((($fromUOM == 'K') || ($fromUOM == 'kel')) &&
  6532. (($toUOM == 'K') || ($toUOM == 'kel'))) {
  6533. return 1.0;
  6534. } elseif ((($fromUOM == 'C') || ($fromUOM == 'cel')) &&
  6535. (($toUOM == 'C') || ($toUOM == 'cel'))) {
  6536. return 1.0;
  6537. }
  6538. if (($toUOM == 'F') || ($toUOM == 'fah')) {
  6539. if (($fromUOM == 'K') || ($fromUOM == 'kel')) {
  6540. $value -= 273.15;
  6541. }
  6542. return ($value * 1.8) + 32;
  6543. }
  6544. if (($toUOM == 'C') || ($toUOM == 'cel')) {
  6545. return $value - 273.15;
  6546. }
  6547. return $value + 273.15;
  6548. }
  6549. return ($value * self::$unitConversions[$unitGroup1][$fromUOM][$toUOM]) / $toMultiplier;
  6550. }
  6551. /**
  6552. * BESSELI
  6553. *
  6554. * Returns the modified Bessel function, which is equivalent to the Bessel function evaluated for purely imaginary arguments
  6555. *
  6556. * @param float $x
  6557. * @param float $n
  6558. * @return int
  6559. */
  6560. public static function BESSELI($x, $n) {
  6561. $x = self::flattenSingleValue($x);
  6562. $n = floor(self::flattenSingleValue($n));
  6563. if ((is_numeric($x)) && (is_numeric($n))) {
  6564. if ($n < 0) {
  6565. return self::$_errorCodes['num'];
  6566. }
  6567. $f_2_PI = 2 * pi();
  6568. if (abs($x) <= 30) {
  6569. $fTerm = pow($x / 2, $n) / self::FACT($n);
  6570. $nK = 1;
  6571. $fResult = $fTerm;
  6572. $fSqrX = pow($x,2) / 4;
  6573. do {
  6574. $fTerm *= $fSqrX;
  6575. $fTerm /= ($nK * ($nK + $n));
  6576. $fResult += $fTerm;
  6577. } while ((abs($fTerm) > 1e-10) && (++$nK < 100));
  6578. } else {
  6579. $fXAbs = abs($x);
  6580. $fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs);
  6581. if (($n && 1) && ($x < 0)) {
  6582. $fResult = -$fResult;
  6583. }
  6584. }
  6585. return $fResult;
  6586. }
  6587. return self::$_errorCodes['value'];
  6588. }
  6589. /**
  6590. * BESSELJ
  6591. *
  6592. * Returns the Bessel function
  6593. *
  6594. * @param float $x
  6595. * @param float $n
  6596. * @return int
  6597. */
  6598. public static function BESSELJ($x, $n) {
  6599. $x = self::flattenSingleValue($x);
  6600. $n = floor(self::flattenSingleValue($n));
  6601. if ((is_numeric($x)) && (is_numeric($n))) {
  6602. if ($n < 0) {
  6603. return self::$_errorCodes['num'];
  6604. }
  6605. $f_2_DIV_PI = 2 / pi();
  6606. $f_PI_DIV_2 = pi() / 2;
  6607. $f_PI_DIV_4 = pi() / 4;
  6608. $fResult = 0;
  6609. if (abs($x) <= 30) {
  6610. $fTerm = pow($x / 2, $n) / self::FACT($n);
  6611. $nK = 1;
  6612. $fResult = $fTerm;
  6613. $fSqrX = pow($x,2) / -4;
  6614. do {
  6615. $fTerm *= $fSqrX;
  6616. $fTerm /= ($nK * ($nK + $n));
  6617. $fResult += $fTerm;
  6618. } while ((abs($fTerm) > 1e-10) && (++$nK < 100));
  6619. } else {
  6620. $fXAbs = abs($x);
  6621. $fResult = sqrt($f_2_DIV_PI / $fXAbs) * cos($fXAbs - $n * $f_PI_DIV_2 - $f_PI_DIV_4);
  6622. if (($n && 1) && ($x < 0)) {
  6623. $fResult = -$fResult;
  6624. }
  6625. }
  6626. return $fResult;
  6627. }
  6628. return self::$_errorCodes['value'];
  6629. }
  6630. private static function Besselk0($fNum) {
  6631. if ($fNum <= 2) {
  6632. $fNum2 = $fNum * 0.5;
  6633. $y = pow($fNum2,2);
  6634. $fRet = -log($fNum2) * self::BESSELI($fNum, 0) +
  6635. (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y *
  6636. (0.10750e-3 + $y * 0.74e-5))))));
  6637. } else {
  6638. $y = 2 / $fNum;
  6639. $fRet = exp(-$fNum) / sqrt($fNum) *
  6640. (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y *
  6641. (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
  6642. }
  6643. return $fRet;
  6644. }
  6645. private static function Besselk1($fNum) {
  6646. if ($fNum <= 2) {
  6647. $fNum2 = $fNum * 0.5;
  6648. $y = pow($fNum2,2);
  6649. $fRet = log($fNum2) * self::BESSELI($fNum, 1) +
  6650. (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y *
  6651. (-0.110404e-2 + $y * (-0.4686e-4))))))) / $fNum;
  6652. } else {
  6653. $y = 2 / $fNum;
  6654. $fRet = exp(-$fNum) / sqrt($fNum) *
  6655. (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y *
  6656. (0.325614e-2 + $y * (-0.68245e-3)))))));
  6657. }
  6658. return $fRet;
  6659. }
  6660. /**
  6661. * BESSELK
  6662. *
  6663. * Returns the modified Bessel function, which is equivalent to the Bessel functions evaluated for purely imaginary arguments.
  6664. *
  6665. * @param float $x
  6666. * @param float $n
  6667. * @return int
  6668. */
  6669. public static function BESSELK($x, $ord) {
  6670. $x = self::flattenSingleValue($x);
  6671. $n = floor(self::flattenSingleValue($ord));
  6672. if ((is_numeric($x)) && (is_numeric($ord))) {
  6673. if ($ord < 0) {
  6674. return self::$_errorCodes['num'];
  6675. }
  6676. switch($ord) {
  6677. case 0 : return self::Besselk0($x);
  6678. break;
  6679. case 1 : return self::Besselk1($x);
  6680. break;
  6681. default : $fTox = 2 / $x;
  6682. $fBkm = self::Besselk0($x);
  6683. $fBk = self::Besselk1($x);
  6684. for ($n = 1; $n < $ord; ++$n) {
  6685. $fBkp = $fBkm + $n * $fTox * $fBk;
  6686. $fBkm = $fBk;
  6687. $fBk = $fBkp;
  6688. }
  6689. }
  6690. return $fBk;
  6691. }
  6692. return self::$_errorCodes['value'];
  6693. }
  6694. private static function Bessely0($fNum) {
  6695. if ($fNum < 8) {
  6696. $y = pow($fNum,2);
  6697. $f1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * (-86327.92757 + $y * 228.4622733))));
  6698. $f2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * (47447.26470 + $y * (226.1030244 + $y))));
  6699. $fRet = $f1 / $f2 + 0.636619772 * self::BESSELJ($fNum, 0) * log($fNum);
  6700. } else {
  6701. $z = 8 / $fNum;
  6702. $y = pow($z,2);
  6703. $xx = $fNum - 0.785398164;
  6704. $f1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
  6705. $f2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * (-0.934945152e-7))));
  6706. $fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
  6707. }
  6708. return $fRet;
  6709. }
  6710. private static function Bessely1($fNum) {
  6711. if ($fNum < 8) {
  6712. $y = pow($fNum,2);
  6713. $f1 = $fNum * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * (0.7349264551e9 + $y *
  6714. (-0.4237922726e7 + $y * 0.8511937935e4)))));
  6715. $f2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
  6716. (0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
  6717. $fRet = $f1 / $f2 + 0.636619772 * ( self::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum);
  6718. } else {
  6719. $z = 8 / $fNum;
  6720. $y = $z * $z;
  6721. $xx = $fNum - 2.356194491;
  6722. $f1 = 1 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e6))));
  6723. $f2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * (-0.88228987e-6 + $y * 0.105787412e-6)));
  6724. $fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
  6725. #i12430# ...but this seems to work much better.
  6726. // $fRet = sqrt(0.636619772 / $fNum) * sin($fNum - 2.356194491);
  6727. }
  6728. return $fRet;
  6729. }
  6730. /**
  6731. * BESSELY
  6732. *
  6733. * Returns the Bessel function, which is also called the Weber function or the Neumann function.
  6734. *
  6735. * @param float $x
  6736. * @param float $n
  6737. * @return int
  6738. */
  6739. public static function BESSELY($x, $ord) {
  6740. $x = self::flattenSingleValue($x);
  6741. $n = floor(self::flattenSingleValue($ord));
  6742. if ((is_numeric($x)) && (is_numeric($ord))) {
  6743. if ($ord < 0) {
  6744. return self::$_errorCodes['num'];
  6745. }
  6746. switch($ord) {
  6747. case 0 : return self::Bessely0($x);
  6748. break;
  6749. case 1 : return self::Bessely1($x);
  6750. break;
  6751. default: $fTox = 2 / $x;
  6752. $fBym = self::Bessely0($x);
  6753. $fBy = self::Bessely1($x);
  6754. for ($n = 1; $n < $ord; ++$n) {
  6755. $fByp = $n * $fTox * $fBy - $fBym;
  6756. $fBym = $fBy;
  6757. $fBy = $fByp;
  6758. }
  6759. }
  6760. return $fBy;
  6761. }
  6762. return self::$_errorCodes['value'];
  6763. }
  6764. /**
  6765. * DELTA
  6766. *
  6767. * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
  6768. *
  6769. * @param float $a
  6770. * @param float $b
  6771. * @return int
  6772. */
  6773. public static function DELTA($a, $b=0) {
  6774. $a = self::flattenSingleValue($a);
  6775. $b = self::flattenSingleValue($b);
  6776. return (int) ($a == $b);
  6777. }
  6778. /**
  6779. * GESTEP
  6780. *
  6781. * Returns 1 if number = step; returns 0 (zero) otherwise
  6782. *
  6783. * @param float $number
  6784. * @param float $step
  6785. * @return int
  6786. */
  6787. public static function GESTEP($number, $step=0) {
  6788. $number = self::flattenSingleValue($number);
  6789. $step = self::flattenSingleValue($step);
  6790. return (int) ($number >= $step);
  6791. }
  6792. //
  6793. // Private method to calculate the erf value
  6794. //
  6795. private static $two_sqrtpi = 1.128379167095512574;
  6796. private static $rel_error = 1E-15;
  6797. private static function erfVal($x) {
  6798. if (abs($x) > 2.2) {
  6799. return 1 - self::erfcVal($x);
  6800. }
  6801. $sum = $term = $x;
  6802. $xsqr = pow($x,2);
  6803. $j = 1;
  6804. do {
  6805. $term *= $xsqr / $j;
  6806. $sum -= $term / (2 * $j + 1);
  6807. ++$j;
  6808. $term *= $xsqr / $j;
  6809. $sum += $term / (2 * $j + 1);
  6810. ++$j;
  6811. if ($sum == 0) {
  6812. break;
  6813. }
  6814. } while (abs($term / $sum) > self::$rel_error);
  6815. return self::$two_sqrtpi * $sum;
  6816. }
  6817. /**
  6818. * ERF
  6819. *
  6820. * Returns the error function integrated between lower_limit and upper_limit
  6821. *
  6822. * @param float $lower lower bound for integrating ERF
  6823. * @param float $upper upper bound for integrating ERF.
  6824. * If omitted, ERF integrates between zero and lower_limit
  6825. * @return int
  6826. */
  6827. public static function ERF($lower, $upper = 0) {
  6828. $lower = self::flattenSingleValue($lower);
  6829. $upper = self::flattenSingleValue($upper);
  6830. if ((is_numeric($lower)) && (is_numeric($upper))) {
  6831. if (($lower < 0) || ($upper < 0)) {
  6832. return self::$_errorCodes['num'];
  6833. }
  6834. if ($upper > $lower) {
  6835. return self::erfVal($upper) - self::erfVal($lower);
  6836. } else {
  6837. return self::erfVal($lower) - self::erfVal($upper);
  6838. }
  6839. }
  6840. return self::$_errorCodes['value'];
  6841. }
  6842. //
  6843. // Private method to calculate the erfc value
  6844. //
  6845. private static $one_sqrtpi = 0.564189583547756287;
  6846. private static function erfcVal($x) {
  6847. if (abs($x) < 2.2) {
  6848. return 1 - self::erfVal($x);
  6849. }
  6850. if ($x < 0) {
  6851. return 2 - self::erfc(-$x);
  6852. }
  6853. $a = $n = 1;
  6854. $b = $c = $x;
  6855. $d = pow($x,2) + 0.5;
  6856. $q1 = $q2 = $b / $d;
  6857. $t = 0;
  6858. do {
  6859. $t = $a * $n + $b * $x;
  6860. $a = $b;
  6861. $b = $t;
  6862. $t = $c * $n + $d * $x;
  6863. $c = $d;
  6864. $d = $t;
  6865. $n += 0.5;
  6866. $q1 = $q2;
  6867. $q2 = $b / $d;
  6868. } while ((abs($q1 - $q2) / $q2) > self::$rel_error);
  6869. return self::$one_sqrtpi * exp(-$x * $x) * $q2;
  6870. }
  6871. /**
  6872. * ERFC
  6873. *
  6874. * Returns the complementary ERF function integrated between x and infinity
  6875. *
  6876. * @param float $x The lower bound for integrating ERF
  6877. * @return int
  6878. */
  6879. public static function ERFC($x) {
  6880. $x = self::flattenSingleValue($x);
  6881. if (is_numeric($x)) {
  6882. if ($x < 0) {
  6883. return self::$_errorCodes['num'];
  6884. }
  6885. return self::erfcVal($x);
  6886. }
  6887. return self::$_errorCodes['value'];
  6888. }
  6889. /**
  6890. * DOLLAR
  6891. *
  6892. * This function converts a number to text using currency format, with the decimals rounded to the specified place.
  6893. * The format used is $#,##0.00_);($#,##0.00)..
  6894. *
  6895. * @param float $value The value to format
  6896. * @param int $decimals The number of digits to display to the right of the decimal point.
  6897. * If decimals is negative, number is rounded to the left of the decimal point.
  6898. * If you omit decimals, it is assumed to be 2
  6899. * @return string
  6900. */
  6901. public static function DOLLAR($value = 0, $decimals = 2) {
  6902. $value = self::flattenSingleValue($value);
  6903. $decimals = self::flattenSingleValue($decimals);
  6904. // Validate parameters
  6905. if (!is_numeric($value) || !is_numeric($decimals)) {
  6906. return self::$_errorCodes['num'];
  6907. }
  6908. $decimals = floor($decimals);
  6909. if ($decimals > 0) {
  6910. return money_format('%.'.$decimals.'n',$value);
  6911. } else {
  6912. $round = pow(10,abs($decimals));
  6913. if ($value < 0) { $round = 0-$round; }
  6914. $value = self::MROUND($value,$round);
  6915. // The implementation of money_format used if the standard PHP function is not available can't handle decimal places of 0,
  6916. // so we display to 1 dp and chop off that character and the decimal separator using substr
  6917. return substr(money_format('%.1n',$value),0,-2);
  6918. }
  6919. }
  6920. /**
  6921. * DOLLARDE
  6922. *
  6923. * Converts a dollar price expressed as an integer part and a fraction part into a dollar price expressed as a decimal number.
  6924. * Fractional dollar numbers are sometimes used for security prices.
  6925. *
  6926. * @param float $fractional_dollar Fractional Dollar
  6927. * @param int $fraction Fraction
  6928. * @return float
  6929. */
  6930. public static function DOLLARDE($fractional_dollar = Null, $fraction = 0) {
  6931. $fractional_dollar = self::flattenSingleValue($fractional_dollar);
  6932. $fraction = (int)self::flattenSingleValue($fraction);
  6933. // Validate parameters
  6934. if (is_null($fractional_dollar) || $fraction < 0) {
  6935. return self::$_errorCodes['num'];
  6936. }
  6937. if ($fraction == 0) {
  6938. return self::$_errorCodes['divisionbyzero'];
  6939. }
  6940. return floor($fractional_dollar) + ((($fractional_dollar - floor($fractional_dollar)) * 100) / $fraction);
  6941. }
  6942. /**
  6943. * DOLLARFR
  6944. *
  6945. * Converts a dollar price expressed as a decimal number into a dollar price expressed as a fraction.
  6946. * Fractional dollar numbers are sometimes used for security prices.
  6947. *
  6948. * @param float $decimal_dollar Decimal Dollar
  6949. * @param int $fraction Fraction
  6950. * @return float
  6951. */
  6952. public static function DOLLARFR($decimal_dollar = Null, $fraction = 0) {
  6953. $decimal_dollar = self::flattenSingleValue($decimal_dollar);
  6954. $fraction = (int)self::flattenSingleValue($fraction);
  6955. // Validate parameters
  6956. if (is_null($decimal_dollar) || $fraction < 0) {
  6957. return self::$_errorCodes['num'];
  6958. }
  6959. if ($fraction == 0) {
  6960. return self::$_errorCodes['divisionbyzero'];
  6961. }
  6962. return floor($decimal_dollar) + ((($decimal_dollar - floor($decimal_dollar)) * $fraction) / 100);
  6963. }
  6964. /**
  6965. * EFFECT
  6966. *
  6967. * Returns the effective interest rate given the nominal rate and the number of compounding payments per year.
  6968. *
  6969. * @param float $nominal_rate Nominal interest rate
  6970. * @param int $npery Number of compounding payments per year
  6971. * @return float
  6972. */
  6973. public static function EFFECT($nominal_rate = 0, $npery = 0) {
  6974. $nominal_rate = self::flattenSingleValue($$nominal_rate);
  6975. $npery = (int)self::flattenSingleValue($npery);
  6976. // Validate parameters
  6977. if ($nominal_rate <= 0 || $npery < 1) {
  6978. return self::$_errorCodes['num'];
  6979. }
  6980. return pow((1 + $nominal_rate / $npery), $npery) - 1;
  6981. }
  6982. /**
  6983. * NOMINAL
  6984. *
  6985. * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
  6986. *
  6987. * @param float $effect_rate Effective interest rate
  6988. * @param int $npery Number of compounding payments per year
  6989. * @return float
  6990. */
  6991. public static function NOMINAL($effect_rate = 0, $npery = 0) {
  6992. $effect_rate = self::flattenSingleValue($effect_rate);
  6993. $npery = (int)self::flattenSingleValue($npery);
  6994. // Validate parameters
  6995. if ($effect_rate <= 0 || $npery < 1) {
  6996. return self::$_errorCodes['num'];
  6997. }
  6998. // Calculate
  6999. return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
  7000. }
  7001. /**
  7002. * PV
  7003. *
  7004. * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
  7005. *
  7006. * @param float $rate Interest rate per period
  7007. * @param int $nper Number of periods
  7008. * @param float $pmt Periodic payment (annuity)
  7009. * @param float $fv Future Value
  7010. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7011. * @return float
  7012. */
  7013. public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0) {
  7014. $rate = self::flattenSingleValue($rate);
  7015. $nper = self::flattenSingleValue($nper);
  7016. $pmt = self::flattenSingleValue($pmt);
  7017. $fv = self::flattenSingleValue($fv);
  7018. $type = self::flattenSingleValue($type);
  7019. // Validate parameters
  7020. if ($type != 0 && $type != 1) {
  7021. return self::$_errorCodes['num'];
  7022. }
  7023. // Calculate
  7024. if (!is_null($rate) && $rate != 0) {
  7025. return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
  7026. } else {
  7027. return -$fv - $pmt * $nper;
  7028. }
  7029. }
  7030. /**
  7031. * FV
  7032. *
  7033. * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
  7034. *
  7035. * @param float $rate Interest rate per period
  7036. * @param int $nper Number of periods
  7037. * @param float $pmt Periodic payment (annuity)
  7038. * @param float $pv Present Value
  7039. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7040. * @return float
  7041. */
  7042. public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0) {
  7043. $rate = self::flattenSingleValue($rate);
  7044. $nper = self::flattenSingleValue($nper);
  7045. $pmt = self::flattenSingleValue($pmt);
  7046. $pv = self::flattenSingleValue($pv);
  7047. $type = self::flattenSingleValue($type);
  7048. // Validate parameters
  7049. if ($type != 0 && $type != 1) {
  7050. return self::$_errorCodes['num'];
  7051. }
  7052. // Calculate
  7053. if (!is_null($rate) && $rate != 0) {
  7054. return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
  7055. } else {
  7056. return -$pv - $pmt * $nper;
  7057. }
  7058. }
  7059. /**
  7060. * PMT
  7061. *
  7062. * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
  7063. *
  7064. * @param float $rate Interest rate per period
  7065. * @param int $nper Number of periods
  7066. * @param float $pv Present Value
  7067. * @param float $fv Future Value
  7068. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7069. * @return float
  7070. */
  7071. public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) {
  7072. $rate = self::flattenSingleValue($rate);
  7073. $nper = self::flattenSingleValue($nper);
  7074. $pv = self::flattenSingleValue($pv);
  7075. $fv = self::flattenSingleValue($fv);
  7076. $type = self::flattenSingleValue($type);
  7077. // Validate parameters
  7078. if ($type != 0 && $type != 1) {
  7079. return self::$_errorCodes['num'];
  7080. }
  7081. // Calculate
  7082. if (!is_null($rate) && $rate != 0) {
  7083. return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
  7084. } else {
  7085. return (-$pv - $fv) / $nper;
  7086. }
  7087. }
  7088. /**
  7089. * NPER
  7090. *
  7091. * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
  7092. *
  7093. * @param float $rate Interest rate per period
  7094. * @param int $pmt Periodic payment (annuity)
  7095. * @param float $pv Present Value
  7096. * @param float $fv Future Value
  7097. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7098. * @return float
  7099. */
  7100. public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0) {
  7101. $rate = self::flattenSingleValue($rate);
  7102. $pmt = self::flattenSingleValue($pmt);
  7103. $pv = self::flattenSingleValue($pv);
  7104. $fv = self::flattenSingleValue($fv);
  7105. $type = self::flattenSingleValue($type);
  7106. // Validate parameters
  7107. if ($type != 0 && $type != 1) {
  7108. return self::$_errorCodes['num'];
  7109. }
  7110. // Calculate
  7111. if (!is_null($rate) && $rate != 0) {
  7112. if ($pmt == 0 && $pv == 0) {
  7113. return self::$_errorCodes['num'];
  7114. }
  7115. return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
  7116. } else {
  7117. if ($pmt == 0) {
  7118. return self::$_errorCodes['num'];
  7119. }
  7120. return (-$pv -$fv) / $pmt;
  7121. }
  7122. }
  7123. private static function _interestAndPrincipal($rate=0, $per=0, $nper=0, $pv=0, $fv=0, $type=0) {
  7124. $pmt = self::PMT($rate, $nper, $pv, $fv, $type);
  7125. $capital = $pv;
  7126. for ($i = 1; $i<= $per; $i++) {
  7127. $interest = ($type && $i == 1)? 0 : -$capital * $rate;
  7128. $principal = $pmt - $interest;
  7129. $capital += $principal;
  7130. }
  7131. return array($interest, $principal);
  7132. } // function _interestAndPrincipal()
  7133. /**
  7134. * IPMT
  7135. *
  7136. * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
  7137. *
  7138. * @param float $rate Interest rate per period
  7139. * @param int $per Period for which we want to find the interest
  7140. * @param int $nper Number of periods
  7141. * @param float $pv Present Value
  7142. * @param float $fv Future Value
  7143. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7144. * @return float
  7145. */
  7146. public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) {
  7147. $rate = self::flattenSingleValue($rate);
  7148. $per = (int) self::flattenSingleValue($per);
  7149. $nper = (int) self::flattenSingleValue($nper);
  7150. $pv = self::flattenSingleValue($pv);
  7151. $fv = self::flattenSingleValue($fv);
  7152. $type = (int) self::flattenSingleValue($type);
  7153. // Validate parameters
  7154. if ($type != 0 && $type != 1) {
  7155. return self::$_errorCodes['num'];
  7156. }
  7157. if ($per <= 0 || $per > $nper) {
  7158. return self::$_errorCodes['value'];
  7159. }
  7160. // Calculate
  7161. $interestAndPrincipal = self::_interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
  7162. return $interestAndPrincipal[0];
  7163. } // function IPMT()
  7164. /**
  7165. * CUMIPMT
  7166. *
  7167. * Returns the cumulative interest paid on a loan between start_period and end_period.
  7168. *
  7169. * @param float $rate Interest rate per period
  7170. * @param int $nper Number of periods
  7171. * @param float $pv Present Value
  7172. * @param int start The first period in the calculation.
  7173. * Payment periods are numbered beginning with 1.
  7174. * @param int end The last period in the calculation.
  7175. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7176. * @return float
  7177. */
  7178. public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0) {
  7179. $rate = self::flattenSingleValue($rate);
  7180. $nper = (int) self::flattenSingleValue($nper);
  7181. $pv = self::flattenSingleValue($pv);
  7182. $start = (int) self::flattenSingleValue($start);
  7183. $end = (int) self::flattenSingleValue($end);
  7184. $type = (int) self::flattenSingleValue($type);
  7185. // Validate parameters
  7186. if ($type != 0 && $type != 1) {
  7187. return self::$_errorCodes['num'];
  7188. }
  7189. if ($start < 1 || $start > $end) {
  7190. return self::$_errorCodes['value'];
  7191. }
  7192. // Calculate
  7193. $interest = 0;
  7194. for ($per = $start; $per <= $end; ++$per) {
  7195. $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type);
  7196. }
  7197. return $interest;
  7198. } // function CUMIPMT()
  7199. /**
  7200. * PPMT
  7201. *
  7202. * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
  7203. *
  7204. * @param float $rate Interest rate per period
  7205. * @param int $per Period for which we want to find the interest
  7206. * @param int $nper Number of periods
  7207. * @param float $pv Present Value
  7208. * @param float $fv Future Value
  7209. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7210. * @return float
  7211. */
  7212. public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) {
  7213. $rate = self::flattenSingleValue($rate);
  7214. $per = (int) self::flattenSingleValue($per);
  7215. $nper = (int) self::flattenSingleValue($nper);
  7216. $pv = self::flattenSingleValue($pv);
  7217. $fv = self::flattenSingleValue($fv);
  7218. $type = (int) self::flattenSingleValue($type);
  7219. // Validate parameters
  7220. if ($type != 0 && $type != 1) {
  7221. return self::$_errorCodes['num'];
  7222. }
  7223. if ($per <= 0 || $per > $nper) {
  7224. return self::$_errorCodes['value'];
  7225. }
  7226. // Calculate
  7227. $interestAndPrincipal = self::_interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
  7228. return $interestAndPrincipal[1];
  7229. } // function PPMT()
  7230. /**
  7231. * CUMPRINC
  7232. *
  7233. * Returns the cumulative principal paid on a loan between start_period and end_period.
  7234. *
  7235. * @param float $rate Interest rate per period
  7236. * @param int $nper Number of periods
  7237. * @param float $pv Present Value
  7238. * @param int start The first period in the calculation.
  7239. * Payment periods are numbered beginning with 1.
  7240. * @param int end The last period in the calculation.
  7241. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  7242. * @return float
  7243. */
  7244. public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0) {
  7245. $rate = self::flattenSingleValue($rate);
  7246. $nper = (int) self::flattenSingleValue($nper);
  7247. $pv = self::flattenSingleValue($pv);
  7248. $start = (int) self::flattenSingleValue($start);
  7249. $end = (int) self::flattenSingleValue($end);
  7250. $type = (int) self::flattenSingleValue($type);
  7251. // Validate parameters
  7252. if ($type != 0 && $type != 1) {
  7253. return self::$_errorCodes['num'];
  7254. }
  7255. if ($start < 1 || $start > $end) {
  7256. return self::$_errorCodes['value'];
  7257. }
  7258. // Calculate
  7259. $principal = 0;
  7260. for ($per = $start; $per <= $end; ++$per) {
  7261. $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type);
  7262. }
  7263. return $principal;
  7264. } // function CUMPRINC()
  7265. /**
  7266. * NPV
  7267. *
  7268. * Returns the Net Present Value of a cash flow series given a discount rate.
  7269. *
  7270. * @param float Discount interest rate
  7271. * @param array Cash flow series
  7272. * @return float
  7273. */
  7274. public static function NPV() {
  7275. // Return value
  7276. $returnValue = 0;
  7277. // Loop trough arguments
  7278. $aArgs = self::flattenArray(func_get_args());
  7279. // Calculate
  7280. $rate = array_shift($aArgs);
  7281. for ($i = 1; $i <= count($aArgs); ++$i) {
  7282. // Is it a numeric value?
  7283. if (is_numeric($aArgs[$i - 1])) {
  7284. $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
  7285. }
  7286. }
  7287. // Return
  7288. return $returnValue;
  7289. } // function NPV()
  7290. /**
  7291. * ACCRINT
  7292. *
  7293. * Computes the accrued interest for a security that pays periodic interest.
  7294. *
  7295. * @param int $issue
  7296. * @param int $firstInterest
  7297. * @param int $settlement
  7298. * @param int $rate
  7299. * @param int $par
  7300. * @param int $frequency
  7301. * @param int $basis
  7302. * @return int The accrued interest for a security that pays periodic interest.
  7303. */
  7304. /*
  7305. public static function ACCRINT($issue = 0, $firstInterest = 0, $settlement = 0, $rate = 0, $par = 1000, $frequency = 1, $basis = 0) {
  7306. $issue = self::flattenSingleValue($issue);
  7307. $firstInterest = self::flattenSingleValue($firstInterest);
  7308. $settlement = self::flattenSingleValue($settlement);
  7309. $rate = self::flattenSingleValue($rate);
  7310. $par = self::flattenSingleValue($par);
  7311. $frequency = self::flattenSingleValue($frequency);
  7312. $basis = self::flattenSingleValue($basis);
  7313. // Perform checks
  7314. if ($issue >= $settlement || $rate <= 0 || $par <= 0 || !($frequency == 1 || $frequency == 2 || $frequency == 4) || $basis < 0 || $basis > 4) return self::$_errorCodes['num'];
  7315. // Calculate value
  7316. return $par * ($rate / $frequency) *
  7317. }
  7318. */
  7319. /**
  7320. * DB
  7321. *
  7322. * Returns the depreciation of an asset for a specified period using the fixed-declining balance method.
  7323. * This form of depreciation is used if you want to get a higher depreciation value at the beginning of the depreciation
  7324. * (as opposed to linear depreciation). The depreciation value is reduced with every depreciation period by the
  7325. * depreciation already deducted from the initial cost.
  7326. *
  7327. * @param float cost Initial cost of the asset.
  7328. * @param float salvage Value at the end of the depreciation. (Sometimes called the salvage value of the asset)
  7329. * @param int life Number of periods over which the asset is depreciated. (Sometimes called the useful life of the asset)
  7330. * @param int period The period for which you want to calculate the depreciation. Period must use the same units as life.
  7331. * @param float month Number of months in the first year. If month is omitted, it defaults to 12.
  7332. * @return float
  7333. */
  7334. public static function DB($cost, $salvage, $life, $period, $month=12) {
  7335. $cost = (float) self::flattenSingleValue($cost);
  7336. $salvage = (float) self::flattenSingleValue($salvage);
  7337. $life = (int) self::flattenSingleValue($life);
  7338. $period = (int) self::flattenSingleValue($period);
  7339. $month = (int) self::flattenSingleValue($month);
  7340. // Validate
  7341. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) {
  7342. if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1) || ($period > $life)) {
  7343. return self::$_errorCodes['num'];
  7344. }
  7345. // Set Fixed Depreciation Rate
  7346. $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
  7347. $fixedDepreciationRate = round($fixedDepreciationRate, 3);
  7348. // Loop through each period calculating the depreciation
  7349. $previousDepreciation = 0;
  7350. for ($per = 1; $per <= $period; $per++) {
  7351. if ($per == 1) {
  7352. $depreciation = $cost * $fixedDepreciationRate * $month / 12;
  7353. } elseif ($per == ($life + 1)) {
  7354. $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
  7355. } else {
  7356. $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
  7357. }
  7358. $previousDepreciation += $depreciation;
  7359. }
  7360. return $depreciation;
  7361. }
  7362. return self::$_errorCodes['value'];
  7363. } // function DB()
  7364. /**
  7365. * DDB
  7366. *
  7367. * Returns the depreciation of an asset for a specified period using the double-declining balance method or some other method you specify.
  7368. *
  7369. * @param float cost Initial cost of the asset.
  7370. * @param float salvage Value at the end of the depreciation. (Sometimes called the salvage value of the asset)
  7371. * @param int life Number of periods over which the asset is depreciated. (Sometimes called the useful life of the asset)
  7372. * @param int period The period for which you want to calculate the depreciation. Period must use the same units as life.
  7373. * @param float factor The rate at which the balance declines.
  7374. * If factor is omitted, it is assumed to be 2 (the double-declining balance method).
  7375. * @return float
  7376. */
  7377. public static function DDB($cost, $salvage, $life, $period, $factor=2.0) {
  7378. $cost = (float) self::flattenSingleValue($cost);
  7379. $salvage = (float) self::flattenSingleValue($salvage);
  7380. $life = (int) self::flattenSingleValue($life);
  7381. $period = (int) self::flattenSingleValue($period);
  7382. $factor = (float) self::flattenSingleValue($factor);
  7383. // Validate
  7384. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) {
  7385. if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) {
  7386. return self::$_errorCodes['num'];
  7387. }
  7388. // Set Fixed Depreciation Rate
  7389. $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
  7390. $fixedDepreciationRate = round($fixedDepreciationRate, 3);
  7391. // Loop through each period calculating the depreciation
  7392. $previousDepreciation = 0;
  7393. for ($per = 1; $per <= $period; $per++) {
  7394. $depreciation = min( ($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation) );
  7395. $previousDepreciation += $depreciation;
  7396. }
  7397. return $depreciation;
  7398. }
  7399. return self::$_errorCodes['value'];
  7400. } // function DDB()
  7401. private static function _daysPerYear($year,$basis) {
  7402. switch ($basis) {
  7403. case 0 :
  7404. case 2 :
  7405. case 4 :
  7406. $daysPerYear = 360;
  7407. break;
  7408. case 3 :
  7409. $daysPerYear = 365;
  7410. break;
  7411. case 1 :
  7412. if (self::isLeapYear(self::YEAR($year))) {
  7413. $daysPerYear = 366;
  7414. } else {
  7415. $daysPerYear = 365;
  7416. }
  7417. break;
  7418. default :
  7419. return self::$_errorCodes['num'];
  7420. }
  7421. return $daysPerYear;
  7422. } // function _daysPerYear()
  7423. /**
  7424. * ACCRINTM
  7425. *
  7426. * Returns the discount rate for a security.
  7427. *
  7428. * @param mixed issue The security's issue date.
  7429. * @param mixed settlement The security's settlement date.
  7430. * @param float rate The security's annual coupon rate.
  7431. * @param float par The security's par value.
  7432. * @param int basis The type of day count to use.
  7433. * 0 or omitted US (NASD) 30/360
  7434. * 1 Actual/actual
  7435. * 2 Actual/360
  7436. * 3 Actual/365
  7437. * 4 European 30/360
  7438. * @return float
  7439. */
  7440. public static function ACCRINTM($issue, $settlement, $rate, $par=1000, $basis=0) {
  7441. $issue = self::flattenSingleValue($issue);
  7442. $settlement = self::flattenSingleValue($settlement);
  7443. $rate = (float) self::flattenSingleValue($rate);
  7444. $par = (float) self::flattenSingleValue($par);
  7445. $basis = (int) self::flattenSingleValue($basis);
  7446. // Validate
  7447. if ((is_numeric($rate)) && (is_numeric($par))) {
  7448. if (($rate <= 0) || ($par <= 0)) {
  7449. return self::$_errorCodes['num'];
  7450. }
  7451. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  7452. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  7453. return $daysBetweenIssueAndSettlement;
  7454. }
  7455. $daysPerYear = self::_daysPerYear(self::YEAR($issue),$basis);
  7456. if (!is_numeric($daysPerYear)) {
  7457. return $daysPerYear;
  7458. }
  7459. $daysBetweenIssueAndSettlement *= $daysPerYear;
  7460. return $par * $rate * ($daysBetweenIssueAndSettlement / $daysPerYear);
  7461. }
  7462. return self::$_errorCodes['value'];
  7463. } // function ACCRINTM()
  7464. /**
  7465. * DISC
  7466. *
  7467. * Returns the discount rate for a security.
  7468. *
  7469. * @param mixed settlement The security's settlement date.
  7470. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  7471. * @param mixed maturity The security's maturity date.
  7472. * The maturity date is the date when the security expires.
  7473. * @param int price The security's price per $100 face value.
  7474. * @param int redemption the security's redemption value per $100 face value.
  7475. * @param int basis The type of day count to use.
  7476. * 0 or omitted US (NASD) 30/360
  7477. * 1 Actual/actual
  7478. * 2 Actual/360
  7479. * 3 Actual/365
  7480. * 4 European 30/360
  7481. * @return float
  7482. */
  7483. public static function DISC($settlement, $maturity, $price, $redemption, $basis=0) {
  7484. $settlement = self::flattenSingleValue($settlement);
  7485. $maturity = self::flattenSingleValue($maturity);
  7486. $price = (float) self::flattenSingleValue($price);
  7487. $redemption = (float) self::flattenSingleValue($redemption);
  7488. $basis = (int) self::flattenSingleValue($basis);
  7489. // Validate
  7490. if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) {
  7491. if (($price <= 0) || ($redemption <= 0)) {
  7492. return self::$_errorCodes['num'];
  7493. }
  7494. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  7495. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7496. return $daysBetweenSettlementAndMaturity;
  7497. }
  7498. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7499. if (!is_numeric($daysPerYear)) {
  7500. return $daysPerYear;
  7501. }
  7502. return (($redemption - $price) / $redemption) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
  7503. }
  7504. return self::$_errorCodes['value'];
  7505. } // function DB()
  7506. /**
  7507. * PRICEDISC
  7508. *
  7509. * Returns the price per $100 face value of a discounted security.
  7510. *
  7511. * @param mixed settlement The security's settlement date.
  7512. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  7513. * @param mixed maturity The security's maturity date.
  7514. * The maturity date is the date when the security expires.
  7515. * @param int discount The security's discount rate.
  7516. * @param int redemption The security's redemption value per $100 face value.
  7517. * @param int basis The type of day count to use.
  7518. * 0 or omitted US (NASD) 30/360
  7519. * 1 Actual/actual
  7520. * 2 Actual/360
  7521. * 3 Actual/365
  7522. * 4 European 30/360
  7523. * @return float
  7524. */
  7525. public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis=0) {
  7526. $settlement = self::flattenSingleValue($settlement);
  7527. $maturity = self::flattenSingleValue($maturity);
  7528. $discount = (float) self::flattenSingleValue($discount);
  7529. $redemption = (float) self::flattenSingleValue($redemption);
  7530. $basis = (int) self::flattenSingleValue($basis);
  7531. // Validate
  7532. if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) {
  7533. if (($discount <= 0) || ($redemption <= 0)) {
  7534. return self::$_errorCodes['num'];
  7535. }
  7536. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  7537. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7538. return $daysBetweenSettlementAndMaturity;
  7539. }
  7540. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7541. if (!is_numeric($daysPerYear)) {
  7542. return $daysPerYear;
  7543. }
  7544. return $redemption - $discount * $redemption * ($daysBetweenSettlementAndMaturity / $daysPerYear);
  7545. }
  7546. return self::$_errorCodes['value'];
  7547. } // function DB()
  7548. /**
  7549. * PRICEMAT
  7550. *
  7551. * Returns the price per $100 face value of a security that pays interest at maturity.
  7552. *
  7553. * @param mixed settlement The security's settlement date.
  7554. * The security's settlement date is the date after the issue date when the security is traded to the buyer.
  7555. * @param mixed maturity The security's maturity date.
  7556. * The maturity date is the date when the security expires.
  7557. * @param mixed issue The security's issue date.
  7558. * @param int rate The security's interest rate at date of issue.
  7559. * @param int yield The security's annual yield.
  7560. * @param int basis The type of day count to use.
  7561. * 0 or omitted US (NASD) 30/360
  7562. * 1 Actual/actual
  7563. * 2 Actual/360
  7564. * 3 Actual/365
  7565. * 4 European 30/360
  7566. * @return float
  7567. */
  7568. public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis=0) {
  7569. $settlement = self::flattenSingleValue($settlement);
  7570. $maturity = self::flattenSingleValue($maturity);
  7571. $issue = self::flattenSingleValue($issue);
  7572. $rate = self::flattenSingleValue($rate);
  7573. $yield = self::flattenSingleValue($yield);
  7574. $basis = (int) self::flattenSingleValue($basis);
  7575. // Validate
  7576. if (is_numeric($rate) && is_numeric($yield)) {
  7577. if (($rate <= 0) || ($yield <= 0)) {
  7578. return self::$_errorCodes['num'];
  7579. }
  7580. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7581. if (!is_numeric($daysPerYear)) {
  7582. return $daysPerYear;
  7583. }
  7584. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  7585. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  7586. return $daysBetweenIssueAndSettlement;
  7587. }
  7588. $daysBetweenIssueAndSettlement *= $daysPerYear;
  7589. $daysBetweenIssueAndMaturity = self::YEARFRAC($issue, $maturity, $basis);
  7590. if (!is_numeric($daysBetweenIssueAndMaturity)) {
  7591. return $daysBetweenIssueAndMaturity;
  7592. }
  7593. $daysBetweenIssueAndMaturity *= $daysPerYear;
  7594. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  7595. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7596. return $daysBetweenSettlementAndMaturity;
  7597. }
  7598. $daysBetweenSettlementAndMaturity *= $daysPerYear;
  7599. return ((100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
  7600. (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
  7601. (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100));
  7602. }
  7603. return self::$_errorCodes['value'];
  7604. } // function PRICEMAT()
  7605. /**
  7606. * RECEIVED
  7607. *
  7608. * Returns the price per $100 face value of a discounted security.
  7609. *
  7610. * @param mixed settlement The security's settlement date.
  7611. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  7612. * @param mixed maturity The security's maturity date.
  7613. * The maturity date is the date when the security expires.
  7614. * @param int investment The amount invested in the security.
  7615. * @param int discount The security's discount rate.
  7616. * @param int basis The type of day count to use.
  7617. * 0 or omitted US (NASD) 30/360
  7618. * 1 Actual/actual
  7619. * 2 Actual/360
  7620. * 3 Actual/365
  7621. * 4 European 30/360
  7622. * @return float
  7623. */
  7624. public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis=0) {
  7625. $settlement = self::flattenSingleValue($settlement);
  7626. $maturity = self::flattenSingleValue($maturity);
  7627. $investment = (float) self::flattenSingleValue($investment);
  7628. $discount = (float) self::flattenSingleValue($discount);
  7629. $basis = (int) self::flattenSingleValue($basis);
  7630. // Validate
  7631. if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) {
  7632. if (($investment <= 0) || ($discount <= 0)) {
  7633. return self::$_errorCodes['num'];
  7634. }
  7635. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  7636. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7637. return $daysBetweenSettlementAndMaturity;
  7638. }
  7639. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7640. if (!is_numeric($daysPerYear)) {
  7641. return $daysPerYear;
  7642. }
  7643. return $investment / ( 1 - ($discount * ($daysBetweenSettlementAndMaturity / $daysPerYear)));
  7644. }
  7645. return self::$_errorCodes['value'];
  7646. } // function RECEIVED()
  7647. /**
  7648. * INTRATE
  7649. *
  7650. * Returns the interest rate for a fully invested security.
  7651. *
  7652. * @param mixed settlement The security's settlement date.
  7653. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  7654. * @param mixed maturity The security's maturity date.
  7655. * The maturity date is the date when the security expires.
  7656. * @param int investment The amount invested in the security.
  7657. * @param int redemption The amount to be received at maturity.
  7658. * @param int basis The type of day count to use.
  7659. * 0 or omitted US (NASD) 30/360
  7660. * 1 Actual/actual
  7661. * 2 Actual/360
  7662. * 3 Actual/365
  7663. * 4 European 30/360
  7664. * @return float
  7665. */
  7666. public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis=0) {
  7667. $settlement = self::flattenSingleValue($settlement);
  7668. $maturity = self::flattenSingleValue($maturity);
  7669. $investment = (float) self::flattenSingleValue($investment);
  7670. $redemption = (float) self::flattenSingleValue($redemption);
  7671. $basis = (int) self::flattenSingleValue($basis);
  7672. // Validate
  7673. if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) {
  7674. if (($investment <= 0) || ($discount <= 0)) {
  7675. return self::$_errorCodes['num'];
  7676. }
  7677. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  7678. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7679. return $daysBetweenSettlementAndMaturity;
  7680. }
  7681. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7682. if (!is_numeric($daysPerYear)) {
  7683. return $daysPerYear;
  7684. }
  7685. return (($redemption - $investment) / $investment) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
  7686. }
  7687. return self::$_errorCodes['value'];
  7688. } // function INTRATE()
  7689. /**
  7690. * TBILLEQ
  7691. *
  7692. * Returns the bond-equivalent yield for a Treasury bill.
  7693. *
  7694. * @param mixed settlement The Treasury bill's settlement date.
  7695. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
  7696. * @param mixed maturity The Treasury bill's maturity date.
  7697. * The maturity date is the date when the Treasury bill expires.
  7698. * @param int discount The Treasury bill's discount rate.
  7699. * @return float
  7700. */
  7701. public static function TBILLEQ($settlement, $maturity, $discount) {
  7702. $settlement = self::flattenSingleValue($settlement);
  7703. $maturity = self::flattenSingleValue($maturity);
  7704. $discount = self::flattenSingleValue($discount);
  7705. // Use TBILLPRICE for validation
  7706. $testValue = self::TBILLPRICE($settlement, $maturity, $discount);
  7707. if (is_string($testValue)) {
  7708. return $testValue;
  7709. }
  7710. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity);
  7711. $daysBetweenSettlementAndMaturity *= 360;
  7712. return (365 * $discount) / (360 - ($discount * $daysBetweenSettlementAndMaturity));
  7713. } // function TBILLEQ()
  7714. /**
  7715. * TBILLPRICE
  7716. *
  7717. * Returns the yield for a Treasury bill.
  7718. *
  7719. * @param mixed settlement The Treasury bill's settlement date.
  7720. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
  7721. * @param mixed maturity The Treasury bill's maturity date.
  7722. * The maturity date is the date when the Treasury bill expires.
  7723. * @param int discount The Treasury bill's discount rate.
  7724. * @return float
  7725. */
  7726. public static function TBILLPRICE($settlement, $maturity, $discount) {
  7727. $settlement = self::flattenSingleValue($settlement);
  7728. $maturity = self::flattenSingleValue($maturity);
  7729. $discount = self::flattenSingleValue($discount);
  7730. // Validate
  7731. if (is_numeric($discount)) {
  7732. if ($discount <= 0) {
  7733. return self::$_errorCodes['num'];
  7734. }
  7735. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity);
  7736. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7737. return $daysBetweenSettlementAndMaturity;
  7738. }
  7739. $daysBetweenSettlementAndMaturity *= 360;
  7740. if ($daysBetweenSettlementAndMaturity > 360) {
  7741. return self::$_errorCodes['num'];
  7742. }
  7743. $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
  7744. if ($price <= 0) {
  7745. return self::$_errorCodes['num'];
  7746. }
  7747. return $price;
  7748. }
  7749. return self::$_errorCodes['value'];
  7750. } // function TBILLPRICE()
  7751. /**
  7752. * TBILLYIELD
  7753. *
  7754. * Returns the yield for a Treasury bill.
  7755. *
  7756. * @param mixed settlement The Treasury bill's settlement date.
  7757. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
  7758. * @param mixed maturity The Treasury bill's maturity date.
  7759. * The maturity date is the date when the Treasury bill expires.
  7760. * @param int price The Treasury bill's price per $100 face value.
  7761. * @return float
  7762. */
  7763. public static function TBILLYIELD($settlement, $maturity, $price) {
  7764. $settlement = self::flattenSingleValue($settlement);
  7765. $maturity = self::flattenSingleValue($maturity);
  7766. $price = self::flattenSingleValue($price);
  7767. // Validate
  7768. if (is_numeric($price)) {
  7769. if ($price <= 0) {
  7770. return self::$_errorCodes['num'];
  7771. }
  7772. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity);
  7773. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7774. return $daysBetweenSettlementAndMaturity;
  7775. }
  7776. $daysBetweenSettlementAndMaturity *= 360;
  7777. if ($daysBetweenSettlementAndMaturity > 360) {
  7778. return self::$_errorCodes['num'];
  7779. }
  7780. return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
  7781. }
  7782. return self::$_errorCodes['value'];
  7783. } // function TBILLYIELD()
  7784. /**
  7785. * SLN
  7786. *
  7787. * Returns the straight-line depreciation of an asset for one period
  7788. *
  7789. * @param cost Initial cost of the asset
  7790. * @param salvage Value at the end of the depreciation
  7791. * @param life Number of periods over which the asset is depreciated
  7792. * @return float
  7793. */
  7794. public static function SLN($cost, $salvage, $life) {
  7795. $cost = self::flattenSingleValue($cost);
  7796. $salvage = self::flattenSingleValue($salvage);
  7797. $life = self::flattenSingleValue($life);
  7798. // Calculate
  7799. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
  7800. if ($life < 0) {
  7801. return self::$_errorCodes['num'];
  7802. }
  7803. return ($cost - $salvage) / $life;
  7804. }
  7805. return self::$_errorCodes['value'];
  7806. }
  7807. /**
  7808. * YIELDMAT
  7809. *
  7810. * Returns the annual yield of a security that pays interest at maturity.
  7811. *
  7812. * @param mixed settlement The security's settlement date.
  7813. * The security's settlement date is the date after the issue date when the security is traded to the buyer.
  7814. * @param mixed maturity The security's maturity date.
  7815. * The maturity date is the date when the security expires.
  7816. * @param mixed issue The security's issue date.
  7817. * @param int rate The security's interest rate at date of issue.
  7818. * @param int price The security's price per $100 face value.
  7819. * @param int basis The type of day count to use.
  7820. * 0 or omitted US (NASD) 30/360
  7821. * 1 Actual/actual
  7822. * 2 Actual/360
  7823. * 3 Actual/365
  7824. * 4 European 30/360
  7825. * @return float
  7826. */
  7827. public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis=0) {
  7828. $settlement = self::flattenSingleValue($settlement);
  7829. $maturity = self::flattenSingleValue($maturity);
  7830. $issue = self::flattenSingleValue($issue);
  7831. $rate = self::flattenSingleValue($rate);
  7832. $price = self::flattenSingleValue($price);
  7833. $basis = (int) self::flattenSingleValue($basis);
  7834. // Validate
  7835. if (is_numeric($rate) && is_numeric($price)) {
  7836. if (($rate <= 0) || ($price <= 0)) {
  7837. return self::$_errorCodes['num'];
  7838. }
  7839. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7840. if (!is_numeric($daysPerYear)) {
  7841. return $daysPerYear;
  7842. }
  7843. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  7844. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  7845. return $daysBetweenIssueAndSettlement;
  7846. }
  7847. $daysBetweenIssueAndSettlement *= $daysPerYear;
  7848. $daysBetweenIssueAndMaturity = self::YEARFRAC($issue, $maturity, $basis);
  7849. if (!is_numeric($daysBetweenIssueAndMaturity)) {
  7850. return $daysBetweenIssueAndMaturity;
  7851. }
  7852. $daysBetweenIssueAndMaturity *= $daysPerYear;
  7853. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  7854. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7855. return $daysBetweenSettlementAndMaturity;
  7856. }
  7857. $daysBetweenSettlementAndMaturity *= $daysPerYear;
  7858. return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
  7859. (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
  7860. ($daysPerYear / $daysBetweenSettlementAndMaturity);
  7861. }
  7862. return self::$_errorCodes['value'];
  7863. } // function YIELDMAT()
  7864. /**
  7865. * YIELDDISC
  7866. *
  7867. * Returns the annual yield of a security that pays interest at maturity.
  7868. *
  7869. * @param mixed settlement The security's settlement date.
  7870. * The security's settlement date is the date after the issue date when the security is traded to the buyer.
  7871. * @param mixed maturity The security's maturity date.
  7872. * The maturity date is the date when the security expires.
  7873. * @param int price The security's price per $100 face value.
  7874. * @param int redemption The security's redemption value per $100 face value.
  7875. * @param int basis The type of day count to use.
  7876. * 0 or omitted US (NASD) 30/360
  7877. * 1 Actual/actual
  7878. * 2 Actual/360
  7879. * 3 Actual/365
  7880. * 4 European 30/360
  7881. * @return float
  7882. */
  7883. public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis=0) {
  7884. $settlement = self::flattenSingleValue($settlement);
  7885. $maturity = self::flattenSingleValue($maturity);
  7886. $price = self::flattenSingleValue($price);
  7887. $redemption = self::flattenSingleValue($redemption);
  7888. $basis = (int) self::flattenSingleValue($basis);
  7889. // Validate
  7890. if (is_numeric($price) && is_numeric($redemption)) {
  7891. if (($price <= 0) || ($redemption <= 0)) {
  7892. return self::$_errorCodes['num'];
  7893. }
  7894. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  7895. if (!is_numeric($daysPerYear)) {
  7896. return $daysPerYear;
  7897. }
  7898. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity,$basis);
  7899. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  7900. return $daysBetweenSettlementAndMaturity;
  7901. }
  7902. $daysBetweenSettlementAndMaturity *= $daysPerYear;
  7903. return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
  7904. }
  7905. return self::$_errorCodes['value'];
  7906. } // function YIELDDISC()
  7907. /**
  7908. * CELL_ADDRESS
  7909. *
  7910. * Returns the straight-line depreciation of an asset for one period
  7911. *
  7912. * @param row Row number to use in the cell reference
  7913. * @param column Column number to use in the cell reference
  7914. * @param relativity Flag indicating the type of reference to return
  7915. * @param sheetText Name of worksheet to use
  7916. * @return string
  7917. */
  7918. public static function CELL_ADDRESS($row, $column, $relativity=1, $referenceStyle=True, $sheetText='') {
  7919. $row = self::flattenSingleValue($row);
  7920. $column = self::flattenSingleValue($column);
  7921. $relativity = self::flattenSingleValue($relativity);
  7922. $sheetText = self::flattenSingleValue($sheetText);
  7923. if ($sheetText > '') {
  7924. if (strpos($sheetText,' ') !== False) { $sheetText = "'".$sheetText."'"; }
  7925. $sheetText .='!';
  7926. }
  7927. if (!$referenceStyle) {
  7928. if (($relativity == 2) || ($relativity == 4)) { $column = '['.$column.']'; }
  7929. if (($relativity == 3) || ($relativity == 4)) { $row = '['.$row.']'; }
  7930. return $sheetText.'R'.$row.'C'.$column;
  7931. } else {
  7932. $rowRelative = $columnRelative = '$';
  7933. $column = PHPExcel_Cell::stringFromColumnIndex($column-1);
  7934. if (($relativity == 2) || ($relativity == 4)) { $columnRelative = ''; }
  7935. if (($relativity == 3) || ($relativity == 4)) { $rowRelative = ''; }
  7936. return $sheetText.$columnRelative.$column.$rowRelative.$row;
  7937. }
  7938. }
  7939. public static function COLUMN($cellAddress=Null) {
  7940. if (is_null($cellAddress) || $cellAddress === '') {
  7941. return 0;
  7942. }
  7943. foreach($cellAddress as $columnKey => $value) {
  7944. return PHPExcel_Cell::columnIndexFromString($columnKey);
  7945. }
  7946. } // function COLUMN()
  7947. public static function ROW($cellAddress=Null) {
  7948. if ($cellAddress === Null) {
  7949. return 0;
  7950. }
  7951. foreach($cellAddress as $columnKey => $rowValue) {
  7952. foreach($rowValue as $rowKey => $cellValue) {
  7953. return $rowKey;
  7954. }
  7955. }
  7956. } // function ROW()
  7957. public static function OFFSET($cellAddress=Null,$rows=0,$columns=0,$height=null,$width=null) {
  7958. if ($cellAddress == Null) {
  7959. return 0;
  7960. }
  7961. foreach($cellAddress as $startColumnKey => $rowValue) {
  7962. $startColumnIndex = PHPExcel_Cell::columnIndexFromString($startColumnKey);
  7963. foreach($rowValue as $startRowKey => $cellValue) {
  7964. break 2;
  7965. }
  7966. }
  7967. foreach($cellAddress as $endColumnKey => $rowValue) {
  7968. foreach($rowValue as $endRowKey => $cellValue) {
  7969. }
  7970. }
  7971. $endColumnIndex = PHPExcel_Cell::columnIndexFromString($endColumnKey);
  7972. $startColumnIndex += --$columns;
  7973. $startRowKey += $rows;
  7974. if ($width == null) {
  7975. $endColumnIndex += $columns -1;
  7976. } else {
  7977. $endColumnIndex = $startColumnIndex + $width;
  7978. }
  7979. if ($height == null) {
  7980. $endRowKey += $rows;
  7981. } else {
  7982. $endRowKey = $startRowKey + $height -1;
  7983. }
  7984. if (($startColumnIndex < 0) || ($startRowKey <= 0)) {
  7985. return self::$_errorCodes['reference'];
  7986. }
  7987. $startColumnKey = PHPExcel_Cell::stringFromColumnIndex($startColumnIndex);
  7988. $endColumnKey = PHPExcel_Cell::stringFromColumnIndex($endColumnIndex);
  7989. $startCell = $startColumnKey.$startRowKey;
  7990. $endCell = $endColumnKey.$endRowKey;
  7991. if ($startCell == $endCell) {
  7992. return $startColumnKey.$startRowKey;
  7993. } else {
  7994. return $startColumnKey.$startRowKey.':'.$endColumnKey.$endRowKey;
  7995. }
  7996. } // function COLUMN()
  7997. public static function CHOOSE() {
  7998. $chooseArgs = func_get_args();
  7999. $chosenEntry = self::flattenSingleValue(array_shift($chooseArgs));
  8000. $entryCount = count($chooseArgs) - 1;
  8001. if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
  8002. --$chosenEntry;
  8003. } else {
  8004. return self::$_errorCodes['value'];
  8005. }
  8006. $chosenEntry = floor($chosenEntry);
  8007. if (($chosenEntry <= 0) || ($chosenEntry > $entryCount)) {
  8008. return self::$_errorCodes['value'];
  8009. }
  8010. if (is_array($chooseArgs[$chosenEntry])) {
  8011. return self::flattenArray($chooseArgs[$chosenEntry]);
  8012. } else {
  8013. return $chooseArgs[$chosenEntry];
  8014. }
  8015. }
  8016. /**
  8017. * MATCH
  8018. * The MATCH function searches for a specified item in a range of cells
  8019. * @param lookup_value The value that you want to match in lookup_array
  8020. * @param lookup_array The range of cells being searched
  8021. * @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.
  8022. * @return integer the relative position of the found item
  8023. */
  8024. public static function MATCH($lookup_value, $lookup_array, $match_type=1) {
  8025. // flatten the lookup_array
  8026. $lookup_array = self::flattenArray($lookup_array);
  8027. // flatten lookup_value since it may be a cell reference to a value or the value itself
  8028. $lookup_value = self::flattenSingleValue($lookup_value);
  8029. // MATCH is not case sensitive
  8030. $lookup_value = strtolower($lookup_value);
  8031. /*
  8032. echo "--------------------<br>looking for $lookup_value in <br>";
  8033. print_r($lookup_array);
  8034. echo "<br>";
  8035. //return 1;
  8036. /**/
  8037. // **
  8038. // check inputs
  8039. // **
  8040. // lookup_value type has to be number, text, or logical values
  8041. if (!is_numeric($lookup_value) && !is_string($lookup_value) && !is_bool($lookup_value)){
  8042. // error: lookup_array should contain only number, text, or logical values
  8043. //echo "error: lookup_array should contain only number, text, or logical values<br>";
  8044. return self::$_errorCodes['na'];
  8045. }
  8046. // match_type is 0, 1 or -1
  8047. if ($match_type!==0 && $match_type!==-1 && $match_type!==1){
  8048. // error: wrong value for match_type
  8049. //echo "error: wrong value for match_type<br>";
  8050. return self::$_errorCodes['na'];
  8051. }
  8052. // lookup_array should not be empty
  8053. if (sizeof($lookup_array)<=0){
  8054. // error: empty range
  8055. //echo "error: empty range ".sizeof($lookup_array)."<br>";
  8056. return self::$_errorCodes['na'];
  8057. }
  8058. // lookup_array should contain only number, text, or logical values
  8059. for ($i=0;$i<sizeof($lookup_array);++$i){
  8060. // check the type of the value
  8061. if (!is_numeric($lookup_array[$i]) && !is_string($lookup_array[$i]) && !is_bool($lookup_array[$i])){
  8062. // error: lookup_array should contain only number, text, or logical values
  8063. //echo "error: lookup_array should contain only number, text, or logical values<br>";
  8064. return self::$_errorCodes['na'];
  8065. }
  8066. // convert tpo lowercase
  8067. if (is_string($lookup_array[$i]))
  8068. $lookup_array[$i] = strtolower($lookup_array[$i]);
  8069. }
  8070. // if match_type is 1 or -1, the list has to be ordered
  8071. if($match_type==1 || $match_type==-1){
  8072. // **
  8073. // iniitialization
  8074. // store the last value
  8075. $iLastValue=$lookup_array[0];
  8076. // **
  8077. // loop on the cells
  8078. for ($i=0;$i<sizeof($lookup_array);++$i){
  8079. // check ascending order
  8080. if(($match_type==1 && $lookup_array[$i]<$iLastValue)
  8081. // OR check descending order
  8082. || ($match_type==-1 && $lookup_array[$i]>$iLastValue)){
  8083. // error: list is not ordered correctly
  8084. //echo "error: list is not ordered correctly<br>";
  8085. return self::$_errorCodes['na'];
  8086. }
  8087. }
  8088. }
  8089. // **
  8090. // find the match
  8091. // **
  8092. // loop on the cells
  8093. for ($i=0; $i < sizeof($lookup_array); ++$i){
  8094. // if match_type is 0 <=> find the first value that is exactly equal to lookup_value
  8095. if ($match_type==0 && $lookup_array[$i]==$lookup_value){
  8096. // this is the exact match
  8097. return $i+1;
  8098. }
  8099. // if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
  8100. if ($match_type==-1 && $lookup_array[$i] < $lookup_value){
  8101. if ($i<1){
  8102. // 1st cell was allready smaller than the lookup_value
  8103. break;
  8104. }
  8105. else
  8106. // the previous cell was the match
  8107. return $i;
  8108. }
  8109. // if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
  8110. if ($match_type==1 && $lookup_array[$i] > $lookup_value){
  8111. if ($i<1){
  8112. // 1st cell was allready bigger than the lookup_value
  8113. break;
  8114. }
  8115. else
  8116. // the previous cell was the match
  8117. return $i;
  8118. }
  8119. }
  8120. // unsuccessful in finding a match, return #N/A error value
  8121. //echo "unsuccessful in finding a match<br>";
  8122. return self::$_errorCodes['na'];
  8123. }
  8124. /**
  8125. * Uses an index to choose a value from a reference or array
  8126. * implemented: Return the value of a specified cell or array of cells Array form
  8127. * not implemented: Return a reference to specified cells Reference form
  8128. *
  8129. * @param range_array a range of cells or an array constant
  8130. * @param row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required.
  8131. * @param column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required.
  8132. */
  8133. public static function INDEX($range_array,$row_num=null,$column_num=null) {
  8134. // **
  8135. // check inputs
  8136. // **
  8137. // at least one of row_num and column_num is required
  8138. if ($row_num==null && $column_num==null){
  8139. // error: row_num and column_num are both undefined
  8140. //echo "error: row_num and column_num are both undefined<br>";
  8141. return self::$_errorCodes['value'];
  8142. }
  8143. // default values for row_num and column_num
  8144. if ($row_num==null){
  8145. $row_num = 1;
  8146. }
  8147. if ($column_num==null){
  8148. $column_num = 1;
  8149. }
  8150. /* debug
  8151. print_r($range_array);
  8152. echo '<br />';
  8153. echo '$column_num='.$column_num.'; $row_num='.$row_num.'<br />';
  8154. /**/
  8155. // row_num and column_num may not have negative values
  8156. if (($row_num!=null && $row_num < 0) || ($column_num!=null && $column_num < 0)) {
  8157. // error: row_num or column_num has negative value
  8158. //echo "error: row_num or column_num has negative value<br>";
  8159. return self::$_errorCodes['value'];
  8160. }
  8161. // **
  8162. // convert column and row numbers into array indeces
  8163. // **
  8164. // array is zero based
  8165. --$column_num;
  8166. --$row_num;
  8167. // retrieve the columns
  8168. $columnKeys = array_keys($range_array);
  8169. // retrieve the rows
  8170. $rowKeys = array_keys($range_array[$columnKeys[0]]);
  8171. // test ranges
  8172. if ($column_num >= sizeof($columnKeys)){
  8173. // error: column_num is out of range
  8174. //echo "error: column_num is out of range - $column_num > ".sizeof($columnKeys)."<br>";
  8175. return self::$_errorCodes['reference'];
  8176. }
  8177. if ($row_num >= sizeof($rowKeys)){
  8178. // error: row_num is out of range
  8179. //echo "error: row_num is out of range - $row_num > ".sizeof($rowKeys)."<br>";
  8180. return self::$_errorCodes['reference'];
  8181. }
  8182. // compute and return result
  8183. return $range_array[$columnKeys[$column_num]][$rowKeys[$row_num]];
  8184. }
  8185. /*
  8186. public static function INDEX($arrayValues,$rowNum = 0,$columnNum = 0) {
  8187. if (($rowNum < 0) || ($columnNum < 0)) {
  8188. return self::$_errorCodes['value'];
  8189. }
  8190. $columnKeys = array_keys($arrayValues);
  8191. $rowKeys = array_keys($arrayValues[$columnKeys[0]]);
  8192. if ($columnNum > count($columnKeys)) {
  8193. return self::$_errorCodes['value'];
  8194. } elseif ($columnNum == 0) {
  8195. if ($rowNum == 0) {
  8196. return $arrayValues;
  8197. }
  8198. $rowNum = $rowKeys[--$rowNum];
  8199. $returnArray = array();
  8200. foreach($arrayValues as $arrayColumn) {
  8201. $returnArray[] = $arrayColumn[$rowNum];
  8202. }
  8203. return $returnArray;
  8204. }
  8205. $columnNum = $columnKeys[--$columnNum];
  8206. if ($rowNum > count($rowKeys)) {
  8207. return self::$_errorCodes['value'];
  8208. } elseif ($rowNum == 0) {
  8209. return $arrayValues[$columnNum];
  8210. }
  8211. $rowNum = $rowKeys[--$rowNum];
  8212. return $arrayValues[$columnNum][$rowNum];
  8213. }
  8214. */
  8215. /**
  8216. * SYD
  8217. *
  8218. * Returns the sum-of-years' digits depreciation of an asset for a specified period.
  8219. *
  8220. * @param cost Initial cost of the asset
  8221. * @param salvage Value at the end of the depreciation
  8222. * @param life Number of periods over which the asset is depreciated
  8223. * @param period Period
  8224. * @return float
  8225. */
  8226. public static function SYD($cost, $salvage, $life, $period) {
  8227. $cost = self::flattenSingleValue($cost);
  8228. $salvage = self::flattenSingleValue($salvage);
  8229. $life = self::flattenSingleValue($life);
  8230. $period = self::flattenSingleValue($period);
  8231. // Calculate
  8232. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
  8233. if (($life < 1) || ($salvage < $life)) {
  8234. return self::$_errorCodes['num'];
  8235. }
  8236. return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
  8237. }
  8238. return self::$_errorCodes['value'];
  8239. } // function SYD()
  8240. /**
  8241. * TRANSPOSE
  8242. *
  8243. * @param array $matrixData A matrix of values
  8244. * @return array
  8245. *
  8246. * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix.
  8247. */
  8248. public static function TRANSPOSE($matrixData) {
  8249. $returnMatrix = array();
  8250. $column = 0;
  8251. foreach($matrixData as $matrixRow) {
  8252. $row = 0;
  8253. foreach($matrixRow as $matrixCell) {
  8254. $returnMatrix[$column][$row] = $matrixCell;
  8255. ++$row;
  8256. }
  8257. ++$column;
  8258. }
  8259. return $returnMatrix;
  8260. } // function TRANSPOSE()
  8261. /**
  8262. * MMULT
  8263. *
  8264. * @param array $matrixData1 A matrix of values
  8265. * @param array $matrixData2 A matrix of values
  8266. * @return array
  8267. */
  8268. public static function MMULT($matrixData1,$matrixData2) {
  8269. $matrixAData = $matrixBData = array();
  8270. $rowA = 0;
  8271. foreach($matrixData1 as $matrixRow) {
  8272. $columnA = 0;
  8273. foreach($matrixRow as $matrixCell) {
  8274. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  8275. return self::$_errorCodes['value'];
  8276. }
  8277. $matrixAData[$columnA][$rowA] = $matrixCell;
  8278. ++$columnA;
  8279. }
  8280. ++$rowA;
  8281. }
  8282. $matrixA = new Matrix($matrixAData);
  8283. $rowB = 0;
  8284. foreach($matrixData2 as $matrixRow) {
  8285. $columnB = 0;
  8286. foreach($matrixRow as $matrixCell) {
  8287. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  8288. return self::$_errorCodes['value'];
  8289. }
  8290. $matrixBData[$columnB][$rowB] = $matrixCell;
  8291. ++$columnB;
  8292. }
  8293. ++$rowB;
  8294. }
  8295. $matrixB = new Matrix($matrixBData);
  8296. if (($rowA != $columnB) || ($rowB != $columnA)) {
  8297. return self::$_errorCodes['value'];
  8298. }
  8299. return $matrixA->times($matrixB)->getArray();
  8300. } // function MMULT()
  8301. /**
  8302. * MINVERSE
  8303. *
  8304. * @param array $matrixValues A matrix of values
  8305. * @return array
  8306. */
  8307. public static function MINVERSE($matrixValues) {
  8308. $matrixData = array();
  8309. $row = 0;
  8310. foreach($matrixValues as $matrixRow) {
  8311. $column = 0;
  8312. foreach($matrixRow as $matrixCell) {
  8313. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  8314. return self::$_errorCodes['value'];
  8315. }
  8316. $matrixData[$column][$row] = $matrixCell;
  8317. ++$column;
  8318. }
  8319. ++$row;
  8320. }
  8321. $matrix = new Matrix($matrixData);
  8322. return $matrix->inverse()->getArray();
  8323. } // function MINVERSE()
  8324. /**
  8325. * MDETERM
  8326. *
  8327. * @param array $matrixValues A matrix of values
  8328. * @return float
  8329. */
  8330. public static function MDETERM($matrixValues) {
  8331. $matrixData = array();
  8332. $row = 0;
  8333. foreach($matrixValues as $matrixRow) {
  8334. $column = 0;
  8335. foreach($matrixRow as $matrixCell) {
  8336. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  8337. return self::$_errorCodes['value'];
  8338. }
  8339. $matrixData[$column][$row] = $matrixCell;
  8340. ++$column;
  8341. }
  8342. ++$row;
  8343. }
  8344. $matrix = new Matrix($matrixData);
  8345. return $matrix->det();
  8346. } // function MDETERM()
  8347. /**
  8348. * SUMX2MY2
  8349. *
  8350. * @param mixed $value Value to check
  8351. * @return float
  8352. */
  8353. public static function SUMX2MY2($matrixData1,$matrixData2) {
  8354. $array1 = self::flattenArray($matrixData1);
  8355. $array2 = self::flattenArray($matrixData2);
  8356. $count1 = count($array1);
  8357. $count2 = count($array2);
  8358. if ($count1 < $count2) {
  8359. $count = $count1;
  8360. } else {
  8361. $count = $count2;
  8362. }
  8363. $result = 0;
  8364. for ($i = 0; $i < $count; ++$i) {
  8365. if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
  8366. ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
  8367. $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]);
  8368. }
  8369. }
  8370. return $result;
  8371. } // function SUMX2MY2()
  8372. /**
  8373. * SUMX2PY2
  8374. *
  8375. * @param mixed $value Value to check
  8376. * @return float
  8377. */
  8378. public static function SUMX2PY2($matrixData1,$matrixData2) {
  8379. $array1 = self::flattenArray($matrixData1);
  8380. $array2 = self::flattenArray($matrixData2);
  8381. $count1 = count($array1);
  8382. $count2 = count($array2);
  8383. if ($count1 < $count2) {
  8384. $count = $count1;
  8385. } else {
  8386. $count = $count2;
  8387. }
  8388. $result = 0;
  8389. for ($i = 0; $i < $count; ++$i) {
  8390. if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
  8391. ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
  8392. $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]);
  8393. }
  8394. }
  8395. return $result;
  8396. } // function SUMX2PY2()
  8397. /**
  8398. * SUMXMY2
  8399. *
  8400. * @param mixed $value Value to check
  8401. * @return float
  8402. */
  8403. public static function SUMXMY2($matrixData1,$matrixData2) {
  8404. $array1 = self::flattenArray($matrixData1);
  8405. $array2 = self::flattenArray($matrixData2);
  8406. $count1 = count($array1);
  8407. $count2 = count($array2);
  8408. if ($count1 < $count2) {
  8409. $count = $count1;
  8410. } else {
  8411. $count = $count2;
  8412. }
  8413. $result = 0;
  8414. for ($i = 0; $i < $count; ++$i) {
  8415. if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
  8416. ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
  8417. $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]);
  8418. }
  8419. }
  8420. return $result;
  8421. } // function SUMXMY2()
  8422. /**
  8423. * VLOOKUP
  8424. * 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.
  8425. * @param lookup_value The value that you want to match in lookup_array
  8426. * @param lookup_array The range of cells being searched
  8427. * @param index_number The column number in table_array from which the matching value must be returned. The first column is 1.
  8428. * @param not_exact_match Determines if you are looking for an exact match based on lookup_value.
  8429. * @return mixed The value of the found cell
  8430. */
  8431. public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match=true) {
  8432. // index_number must be greater than or equal to 1
  8433. if ($index_number < 1) {
  8434. return self::$_errorCodes['value'];
  8435. }
  8436. // index_number must be less than or equal to the number of columns in lookup_array
  8437. if ($index_number > count($lookup_array)) {
  8438. return self::$_errorCodes['reference'];
  8439. }
  8440. // re-index lookup_array with numeric keys starting at 1
  8441. array_unshift($lookup_array, array());
  8442. $lookup_array = array_slice(array_values($lookup_array), 1, count($lookup_array), true);
  8443. // look for an exact match
  8444. $row_number = array_search($lookup_value, $lookup_array[1]);
  8445. // if an exact match is required, we have what we need to return an appropriate response
  8446. if ($not_exact_match == false) {
  8447. if ($row_number === false) {
  8448. return self::$_errorCodes['na'];
  8449. } else {
  8450. return $lookup_array[$index_number][$row_number];
  8451. }
  8452. }
  8453. // TODO: The VLOOKUP spec in Excel states that, at this point, we should search for
  8454. // the highest value that is less than lookup_value. However, documentation on how string
  8455. // values should be treated here is sparse.
  8456. return self::$_errorCodes['na'];
  8457. }
  8458. /**
  8459. * LOOKUP
  8460. * The LOOKUP function searches for value either from a one-row or one-column range or from an array.
  8461. * @param lookup_value The value that you want to match in lookup_array
  8462. * @param lookup_vector The range of cells being searched
  8463. * @param result_vector The column from which the matching value must be returned
  8464. * @return mixed The value of the found cell
  8465. */
  8466. public static function LOOKUP($lookup_value, $lookup_vector, $result_vector=null) {
  8467. // check for LOOKUP Syntax (view Excel documentation)
  8468. if( is_null($result_vector) )
  8469. {
  8470. // TODO: Syntax 2 (array)
  8471. } else {
  8472. // Syntax 1 (vector)
  8473. // get key (column or row) of lookup_vector
  8474. $kl = key($lookup_vector);
  8475. // check if lookup_value exists in lookup_vector
  8476. if( in_array($lookup_value, $lookup_vector[$kl]) )
  8477. {
  8478. // FOUND IT! Get key of lookup_vector
  8479. $k_res = array_search($lookup_value, $lookup_vector[$kl]);
  8480. } else {
  8481. // value NOT FOUND
  8482. // Get the smallest value in lookup_vector
  8483. // The LOOKUP spec in Excel states --> IMPORTANT - The values in lookup_vector must be placed in ascending order!
  8484. $ksv = key($lookup_vector[$kl]);
  8485. $smallest_value = $lookup_vector[$kl][$ksv];
  8486. // If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP gives the #N/A error value.
  8487. if( $lookup_value < $smallest_value )
  8488. {
  8489. return self::$_errorCodes['na'];
  8490. } else {
  8491. // 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.
  8492. // IMPORTANT : In Excel Documentation is not clear what happen if lookup_value is text!
  8493. foreach( $lookup_vector[$kl] AS $kk => $value )
  8494. {
  8495. if( $lookup_value >= $value )
  8496. {
  8497. $k_res = $kk;
  8498. }
  8499. }
  8500. }
  8501. }
  8502. // Returns a value from the same position in result_vector
  8503. // get key (column or row) of result_vector
  8504. $kr = key($result_vector);
  8505. if( isset($result_vector[$kr][$k_res]) )
  8506. {
  8507. return $result_vector[$kr][$k_res];
  8508. } else {
  8509. // TODO: In Excel Documentation is not clear what happen here...
  8510. }
  8511. }
  8512. }
  8513. /**
  8514. * Flatten multidemensional array
  8515. *
  8516. * @param array $array Array to be flattened
  8517. * @return array Flattened array
  8518. */
  8519. public static function flattenArray($array) {
  8520. if(!is_array ( $array ) ){
  8521. $array = array ( $array );
  8522. }
  8523. $arrayValues = array();
  8524. foreach ($array as $value) {
  8525. if (is_scalar($value)) {
  8526. $arrayValues[] = self::flattenSingleValue($value);
  8527. } elseif (is_array($value)) {
  8528. $arrayValues = array_merge($arrayValues, self::flattenArray($value));
  8529. } else {
  8530. $arrayValues[] = $value;
  8531. }
  8532. }
  8533. return $arrayValues;
  8534. }
  8535. /**
  8536. * Convert an array with one element to a flat value
  8537. *
  8538. * @param mixed $value Array or flat value
  8539. * @return mixed
  8540. */
  8541. public static function flattenSingleValue($value = '') {
  8542. if (is_array($value)) {
  8543. $value = self::flattenSingleValue(array_pop($value));
  8544. }
  8545. return $value;
  8546. }
  8547. }
  8548. //
  8549. // There are a few mathematical functions that aren't available on all versions of PHP for all platforms
  8550. // These functions aren't available in Windows implementations of PHP prior to version 5.3.0
  8551. // So we test if they do exist for this version of PHP/operating platform; and if not we create them
  8552. //
  8553. if (!function_exists('acosh')) {
  8554. function acosh($x) {
  8555. return 2 * log(sqrt(($x + 1) / 2) + sqrt(($x - 1) / 2));
  8556. }
  8557. }
  8558. if (!function_exists('asinh')) {
  8559. function asinh($x) {
  8560. return log($x + sqrt(1 + $x * $x));
  8561. }
  8562. }
  8563. if (!function_exists('atanh')) {
  8564. function atanh($x) {
  8565. return (log(1 + $x) - log(1 - $x)) / 2;
  8566. }
  8567. }
  8568. if (!function_exists('money_format')) {
  8569. function money_format($format, $number) {
  8570. $regex = array( '/%((?:[\^!\-]|\+|\(|\=.)*)([0-9]+)?(?:#([0-9]+))?',
  8571. '(?:\.([0-9]+))?([in%])/'
  8572. );
  8573. $regex = implode('', $regex);
  8574. if (setlocale(LC_MONETARY, null) == '') {
  8575. setlocale(LC_MONETARY, '');
  8576. }
  8577. $locale = localeconv();
  8578. $number = floatval($number);
  8579. if (!preg_match($regex, $format, $fmatch)) {
  8580. trigger_error("No format specified or invalid format", E_USER_WARNING);
  8581. return $number;
  8582. }
  8583. $flags = array( 'fillchar' => preg_match('/\=(.)/', $fmatch[1], $match) ? $match[1] : ' ',
  8584. 'nogroup' => preg_match('/\^/', $fmatch[1]) > 0,
  8585. 'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ? $match[0] : '+',
  8586. 'nosimbol' => preg_match('/\!/', $fmatch[1]) > 0,
  8587. 'isleft' => preg_match('/\-/', $fmatch[1]) > 0
  8588. );
  8589. $width = trim($fmatch[2]) ? (int)$fmatch[2] : 0;
  8590. $left = trim($fmatch[3]) ? (int)$fmatch[3] : 0;
  8591. $right = trim($fmatch[4]) ? (int)$fmatch[4] : $locale['int_frac_digits'];
  8592. $conversion = $fmatch[5];
  8593. $positive = true;
  8594. if ($number < 0) {
  8595. $positive = false;
  8596. $number *= -1;
  8597. }
  8598. $letter = $positive ? 'p' : 'n';
  8599. $prefix = $suffix = $cprefix = $csuffix = $signal = '';
  8600. if (!$positive) {
  8601. $signal = $locale['negative_sign'];
  8602. switch (true) {
  8603. case $locale['n_sign_posn'] == 0 || $flags['usesignal'] == '(':
  8604. $prefix = '(';
  8605. $suffix = ')';
  8606. break;
  8607. case $locale['n_sign_posn'] == 1:
  8608. $prefix = $signal;
  8609. break;
  8610. case $locale['n_sign_posn'] == 2:
  8611. $suffix = $signal;
  8612. break;
  8613. case $locale['n_sign_posn'] == 3:
  8614. $cprefix = $signal;
  8615. break;
  8616. case $locale['n_sign_posn'] == 4:
  8617. $csuffix = $signal;
  8618. break;
  8619. }
  8620. }
  8621. if (!$flags['nosimbol']) {
  8622. $currency = $cprefix;
  8623. $currency .= ( $conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol'] );
  8624. $currency .= $csuffix;
  8625. } else {
  8626. $currency = '';
  8627. }
  8628. $space = $locale["{$letter}_sep_by_space"] ? ' ' : '';
  8629. $number = number_format($number, $right, $locale['mon_decimal_point'], $flags['nogroup'] ? '' : $locale['mon_thousands_sep'] );
  8630. $number = explode($locale['mon_decimal_point'], $number);
  8631. $n = strlen($prefix) + strlen($currency);
  8632. if ($left > 0 && $left > $n) {
  8633. if ($flags['isleft']) {
  8634. $number[0] .= str_repeat($flags['fillchar'], $left - $n);
  8635. } else {
  8636. $number[0] = str_repeat($flags['fillchar'], $left - $n) . $number[0];
  8637. }
  8638. }
  8639. $number = implode($locale['mon_decimal_point'], $number);
  8640. if ($locale["{$letter}_cs_precedes"]) {
  8641. $number = $prefix . $currency . $space . $number . $suffix;
  8642. } else {
  8643. $number = $prefix . $number . $space . $currency . $suffix;
  8644. }
  8645. if ($width > 0) {
  8646. $number = str_pad($number, $width, $flags['fillchar'], $flags['isleft'] ? STR_PAD_RIGHT : STR_PAD_LEFT);
  8647. }
  8648. $format = str_replace($fmatch[0], $number, $format);
  8649. return $format;
  8650. }
  8651. }