PageRenderTime 108ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 2ms

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

#
PHP | 12055 lines | 7250 code | 1375 blank | 3430 comment | 2119 complexity | 6067ba76e0b0d052c80ae52f88a30cb8 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 - 2010 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 - 2010 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. /** PHPExcel root directory */
  28. if (!defined('PHPEXCEL_ROOT')) {
  29. /**
  30. * @ignore
  31. */
  32. define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
  33. require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
  34. PHPExcel_Autoloader::Register();
  35. PHPExcel_Shared_ZipStreamWrapper::register();
  36. // check mbstring.func_overload
  37. if (ini_get('mbstring.func_overload') & 2) {
  38. throw new Exception('Multibyte function overloading in PHP must be disabled for string functions (2).');
  39. }
  40. }
  41. /** EPS */
  42. define('EPS', 2.22e-16);
  43. /** MAX_VALUE */
  44. define('MAX_VALUE', 1.2e308);
  45. /** LOG_GAMMA_X_MAX_VALUE */
  46. define('LOG_GAMMA_X_MAX_VALUE', 2.55e305);
  47. /** SQRT2PI */
  48. define('SQRT2PI', 2.5066282746310005024157652848110452530069867406099);
  49. /** 2 / PI */
  50. define('M_2DIVPI', 0.63661977236758134307553505349006);
  51. /** XMININ */
  52. define('XMININ', 2.23e-308);
  53. /** MAX_ITERATIONS */
  54. define('MAX_ITERATIONS', 256);
  55. /** FINANCIAL_MAX_ITERATIONS */
  56. define('FINANCIAL_MAX_ITERATIONS', 128);
  57. /** PRECISION */
  58. define('PRECISION', 8.88E-016);
  59. /** FINANCIAL_PRECISION */
  60. define('FINANCIAL_PRECISION', 1.0e-08);
  61. /** EULER */
  62. define('EULER', 2.71828182845904523536);
  63. $savedPrecision = ini_get('precision');
  64. if ($savedPrecision < 16) {
  65. ini_set('precision',16);
  66. }
  67. /** Matrix */
  68. require_once PHPEXCEL_ROOT . 'PHPExcel/Shared/JAMA/Matrix.php';
  69. require_once PHPEXCEL_ROOT . 'PHPExcel/Shared/trend/trendClass.php';
  70. /**
  71. * PHPExcel_Calculation_Functions
  72. *
  73. * @category PHPExcel
  74. * @package PHPExcel_Calculation
  75. * @copyright Copyright (c) 2006 - 2010 PHPExcel (http://www.codeplex.com/PHPExcel)
  76. */
  77. class PHPExcel_Calculation_Functions {
  78. /** constants */
  79. const COMPATIBILITY_EXCEL = 'Excel';
  80. const COMPATIBILITY_GNUMERIC = 'Gnumeric';
  81. const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
  82. const RETURNDATE_PHP_NUMERIC = 'P';
  83. const RETURNDATE_PHP_OBJECT = 'O';
  84. const RETURNDATE_EXCEL = 'E';
  85. /**
  86. * Compatibility mode to use for error checking and responses
  87. *
  88. * @access private
  89. * @var string
  90. */
  91. private static $compatibilityMode = self::COMPATIBILITY_EXCEL;
  92. /**
  93. * Data Type to use when returning date values
  94. *
  95. * @access private
  96. * @var string
  97. */
  98. private static $ReturnDateType = self::RETURNDATE_EXCEL;
  99. /**
  100. * List of error codes
  101. *
  102. * @access private
  103. * @var array
  104. */
  105. private static $_errorCodes = array( 'null' => '#NULL!',
  106. 'divisionbyzero' => '#DIV/0!',
  107. 'value' => '#VALUE!',
  108. 'reference' => '#REF!',
  109. 'name' => '#NAME?',
  110. 'num' => '#NUM!',
  111. 'na' => '#N/A',
  112. 'gettingdata' => '#GETTING_DATA'
  113. );
  114. /**
  115. * Set the Compatibility Mode
  116. *
  117. * @access public
  118. * @category Function Configuration
  119. * @param string $compatibilityMode Compatibility Mode
  120. * Permitted values are:
  121. * PHPExcel_Calculation_Functions::COMPATIBILITY_EXCEL 'Excel'
  122. * PHPExcel_Calculation_Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
  123. * PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
  124. * @return boolean (Success or Failure)
  125. */
  126. public static function setCompatibilityMode($compatibilityMode) {
  127. if (($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
  128. ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
  129. ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
  130. self::$compatibilityMode = $compatibilityMode;
  131. return True;
  132. }
  133. return False;
  134. } // function setCompatibilityMode()
  135. /**
  136. * Return the current Compatibility Mode
  137. *
  138. * @access public
  139. * @category Function Configuration
  140. * @return string Compatibility Mode
  141. * Possible Return values are:
  142. * PHPExcel_Calculation_Functions::COMPATIBILITY_EXCEL 'Excel'
  143. * PHPExcel_Calculation_Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
  144. * PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
  145. */
  146. public static function getCompatibilityMode() {
  147. return self::$compatibilityMode;
  148. } // function getCompatibilityMode()
  149. /**
  150. * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object)
  151. *
  152. * @access public
  153. * @category Function Configuration
  154. * @param string $returnDateType Return Date Format
  155. * Permitted values are:
  156. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_NUMERIC 'P'
  157. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_OBJECT 'O'
  158. * PHPExcel_Calculation_Functions::RETURNDATE_EXCEL 'E'
  159. * @return boolean Success or failure
  160. */
  161. public static function setReturnDateType($returnDateType) {
  162. if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) ||
  163. ($returnDateType == self::RETURNDATE_PHP_OBJECT) ||
  164. ($returnDateType == self::RETURNDATE_EXCEL)) {
  165. self::$ReturnDateType = $returnDateType;
  166. return True;
  167. }
  168. return False;
  169. } // function setReturnDateType()
  170. /**
  171. * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object)
  172. *
  173. * @access public
  174. * @category Function Configuration
  175. * @return string Return Date Format
  176. * Possible Return values are:
  177. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_NUMERIC 'P'
  178. * PHPExcel_Calculation_Functions::RETURNDATE_PHP_OBJECT 'O'
  179. * PHPExcel_Calculation_Functions::RETURNDATE_EXCEL 'E'
  180. */
  181. public static function getReturnDateType() {
  182. return self::$ReturnDateType;
  183. } // function getReturnDateType()
  184. /**
  185. * DUMMY
  186. *
  187. * @access public
  188. * @category Error Returns
  189. * @return string #Not Yet Implemented
  190. */
  191. public static function DUMMY() {
  192. return '#Not Yet Implemented';
  193. } // function DUMMY()
  194. /**
  195. * NA
  196. *
  197. * Excel Function:
  198. * =NA()
  199. *
  200. * Returns the error value #N/A
  201. * #N/A is the error value that means "no value is available."
  202. *
  203. * @access public
  204. * @category Logical Functions
  205. * @return string #N/A!
  206. */
  207. public static function NA() {
  208. return self::$_errorCodes['na'];
  209. } // function NA()
  210. /**
  211. * NAN
  212. *
  213. * Returns the error value #NUM!
  214. *
  215. * @access public
  216. * @category Error Returns
  217. * @return string #NUM!
  218. */
  219. public static function NaN() {
  220. return self::$_errorCodes['num'];
  221. } // function NAN()
  222. /**
  223. * NAME
  224. *
  225. * Returns the error value #NAME?
  226. *
  227. * @access public
  228. * @category Error Returns
  229. * @return string #NAME?
  230. */
  231. public static function NAME() {
  232. return self::$_errorCodes['name'];
  233. } // function NAME()
  234. /**
  235. * REF
  236. *
  237. * Returns the error value #REF!
  238. *
  239. * @access public
  240. * @category Error Returns
  241. * @return string #REF!
  242. */
  243. public static function REF() {
  244. return self::$_errorCodes['reference'];
  245. } // function REF()
  246. /**
  247. * VALUE
  248. *
  249. * Returns the error value #VALUE!
  250. *
  251. * @access public
  252. * @category Error Returns
  253. * @return string #VALUE!
  254. */
  255. public static function VALUE() {
  256. return self::$_errorCodes['value'];
  257. } // function VALUE()
  258. private static function isMatrixValue($idx) {
  259. return ((substr_count($idx,'.') <= 1) || (preg_match('/\.[A-Z]/',$idx) > 0));
  260. }
  261. private static function isValue($idx) {
  262. return (substr_count($idx,'.') == 0);
  263. }
  264. private static function isCellValue($idx) {
  265. return (substr_count($idx,'.') > 1);
  266. }
  267. /**
  268. * LOGICAL_AND
  269. *
  270. * Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
  271. *
  272. * Excel Function:
  273. * =AND(logical1[,logical2[, ...]])
  274. *
  275. * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
  276. * or references that contain logical values.
  277. *
  278. * Boolean arguments are treated as True or False as appropriate
  279. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  280. * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
  281. * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
  282. *
  283. * @access public
  284. * @category Logical Functions
  285. * @param mixed $arg,... Data values
  286. * @return boolean The logical AND of the arguments.
  287. */
  288. public static function LOGICAL_AND() {
  289. // Return value
  290. $returnValue = True;
  291. // Loop through the arguments
  292. $aArgs = self::flattenArray(func_get_args());
  293. $argCount = 0;
  294. foreach ($aArgs as $arg) {
  295. // Is it a boolean value?
  296. if (is_bool($arg)) {
  297. $returnValue = $returnValue && $arg;
  298. } elseif ((is_numeric($arg)) && (!is_string($arg))) {
  299. $returnValue = $returnValue && ($arg != 0);
  300. } elseif (is_string($arg)) {
  301. $arg = strtoupper($arg);
  302. if ($arg == 'TRUE') {
  303. $arg = 1;
  304. } elseif ($arg == 'FALSE') {
  305. $arg = 0;
  306. } else {
  307. return self::$_errorCodes['value'];
  308. }
  309. $returnValue = $returnValue && ($arg != 0);
  310. }
  311. ++$argCount;
  312. }
  313. // Return
  314. if ($argCount == 0) {
  315. return self::$_errorCodes['value'];
  316. }
  317. return $returnValue;
  318. } // function LOGICAL_AND()
  319. /**
  320. * LOGICAL_OR
  321. *
  322. * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
  323. *
  324. * Excel Function:
  325. * =OR(logical1[,logical2[, ...]])
  326. *
  327. * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
  328. * or references that contain logical values.
  329. *
  330. * Boolean arguments are treated as True or False as appropriate
  331. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  332. * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
  333. * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
  334. *
  335. * @access public
  336. * @category Logical Functions
  337. * @param mixed $arg,... Data values
  338. * @return boolean The logical OR of the arguments.
  339. */
  340. public static function LOGICAL_OR() {
  341. // Return value
  342. $returnValue = False;
  343. // Loop through the arguments
  344. $aArgs = self::flattenArray(func_get_args());
  345. $argCount = 0;
  346. foreach ($aArgs as $arg) {
  347. // Is it a boolean value?
  348. if (is_bool($arg)) {
  349. $returnValue = $returnValue || $arg;
  350. } elseif ((is_numeric($arg)) && (!is_string($arg))) {
  351. $returnValue = $returnValue || ($arg != 0);
  352. } elseif (is_string($arg)) {
  353. $arg = strtoupper($arg);
  354. if ($arg == 'TRUE') {
  355. $arg = 1;
  356. } elseif ($arg == 'FALSE') {
  357. $arg = 0;
  358. } else {
  359. return self::$_errorCodes['value'];
  360. }
  361. $returnValue = $returnValue || ($arg != 0);
  362. }
  363. ++$argCount;
  364. }
  365. // Return
  366. if ($argCount == 0) {
  367. return self::$_errorCodes['value'];
  368. }
  369. return $returnValue;
  370. } // function LOGICAL_OR()
  371. /**
  372. * LOGICAL_FALSE
  373. *
  374. * Returns the boolean FALSE.
  375. *
  376. * Excel Function:
  377. * =FALSE()
  378. *
  379. * @access public
  380. * @category Logical Functions
  381. * @return boolean False
  382. */
  383. public static function LOGICAL_FALSE() {
  384. return False;
  385. } // function LOGICAL_FALSE()
  386. /**
  387. * LOGICAL_TRUE
  388. *
  389. * Returns the boolean TRUE.
  390. *
  391. * Excel Function:
  392. * =TRUE()
  393. *
  394. * @access public
  395. * @category Logical Functions
  396. * @return boolean True
  397. */
  398. public static function LOGICAL_TRUE() {
  399. return True;
  400. } // function LOGICAL_TRUE()
  401. /**
  402. * LOGICAL_NOT
  403. *
  404. * Returns the boolean inverse of the argument.
  405. *
  406. * Excel Function:
  407. * =NOT(logical)
  408. *
  409. * The argument must evaluate to a logical value such as TRUE or FALSE
  410. *
  411. * Boolean arguments are treated as True or False as appropriate
  412. * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
  413. * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds
  414. * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
  415. *
  416. * @access public
  417. * @category Logical Functions
  418. * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
  419. * @return boolean The boolean inverse of the argument.
  420. */
  421. public static function LOGICAL_NOT($logical) {
  422. $logical = self::flattenSingleValue($logical);
  423. if (is_string($logical)) {
  424. $logical = strtoupper($logical);
  425. if ($logical == 'TRUE') {
  426. return False;
  427. } elseif ($logical == 'FALSE') {
  428. return True;
  429. } else {
  430. return self::$_errorCodes['value'];
  431. }
  432. }
  433. return !$logical;
  434. } // function LOGICAL_NOT()
  435. /**
  436. * STATEMENT_IF
  437. *
  438. * Returns one value if a condition you specify evaluates to TRUE and another value if it evaluates to FALSE.
  439. *
  440. * Excel Function:
  441. * =IF(condition[,returnIfTrue[,returnIfFalse]])
  442. *
  443. * Condition is any value or expression that can be evaluated to TRUE or FALSE.
  444. * For example, A10=100 is a logical expression; if the value in cell A10 is equal to 100,
  445. * the expression evaluates to TRUE. Otherwise, the expression evaluates to FALSE.
  446. * This argument can use any comparison calculation operator.
  447. * ReturnIfTrue is the value that is returned if condition evaluates to TRUE.
  448. * For example, if this argument is the text string "Within budget" and the condition argument evaluates to TRUE,
  449. * then the IF function returns the text "Within budget"
  450. * If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). To display the word TRUE, use
  451. * the logical value TRUE for this argument.
  452. * ReturnIfTrue can be another formula.
  453. * ReturnIfFalse is the value that is returned if condition evaluates to FALSE.
  454. * For example, if this argument is the text string "Over budget" and the condition argument evaluates to FALSE,
  455. * then the IF function returns the text "Over budget".
  456. * If condition is FALSE and ReturnIfFalse is omitted, then the logical value FALSE is returned.
  457. * If condition is FALSE and ReturnIfFalse is blank, then the value 0 (zero) is returned.
  458. * ReturnIfFalse can be another formula.
  459. *
  460. * @access public
  461. * @category Logical Functions
  462. * @param mixed $condition Condition to evaluate
  463. * @param mixed $returnIfTrue Value to return when condition is true
  464. * @param mixed $returnIfFalse Optional value to return when condition is false
  465. * @return mixed The value of returnIfTrue or returnIfFalse determined by condition
  466. */
  467. public static function STATEMENT_IF($condition = true, $returnIfTrue = 0, $returnIfFalse = False) {
  468. $condition = (is_null($condition)) ? True : (boolean) self::flattenSingleValue($condition);
  469. $returnIfTrue = (is_null($returnIfTrue)) ? 0 : self::flattenSingleValue($returnIfTrue);
  470. $returnIfFalse = (is_null($returnIfFalse)) ? False : self::flattenSingleValue($returnIfFalse);
  471. return ($condition ? $returnIfTrue : $returnIfFalse);
  472. } // function STATEMENT_IF()
  473. /**
  474. * STATEMENT_IFERROR
  475. *
  476. * Excel Function:
  477. * =IFERROR(testValue,errorpart)
  478. *
  479. * @access public
  480. * @category Logical Functions
  481. * @param mixed $testValue Value to check, is also the value returned when no error
  482. * @param mixed $errorpart Value to return when testValue is an error condition
  483. * @return mixed The value of errorpart or testValue determined by error condition
  484. */
  485. public static function STATEMENT_IFERROR($testValue = '', $errorpart = '') {
  486. $testValue = (is_null($testValue)) ? '' : self::flattenSingleValue($testValue);
  487. $errorpart = (is_null($errorpart)) ? '' : self::flattenSingleValue($errorpart);
  488. return self::STATEMENT_IF(self::IS_ERROR($testValue), $errorpart, $testValue);
  489. } // function STATEMENT_IFERROR()
  490. /**
  491. * HYPERLINK
  492. *
  493. * Excel Function:
  494. * =HYPERLINK(linkURL,displayName)
  495. *
  496. * @access public
  497. * @category Logical Functions
  498. * @param string $linkURL Value to check, is also the value returned when no error
  499. * @param string $displayName Value to return when testValue is an error condition
  500. * @return mixed The value of errorpart or testValue determined by error condition
  501. */
  502. public static function HYPERLINK($linkURL = '', $displayName = null, PHPExcel_Cell $pCell = null) {
  503. $args = func_get_args();
  504. $pCell = array_pop($args);
  505. $linkURL = (is_null($linkURL)) ? '' : self::flattenSingleValue($linkURL);
  506. $displayName = (is_null($displayName)) ? '' : self::flattenSingleValue($displayName);
  507. if ((!is_object($pCell)) || (trim($linkURL) == '')) {
  508. return self::$_errorCodes['reference'];
  509. }
  510. if ((is_object($displayName)) || trim($displayName) == '') {
  511. $displayName = $linkURL;
  512. }
  513. $pCell->getHyperlink()->setUrl($linkURL);
  514. return $displayName;
  515. } // function HYPERLINK()
  516. /**
  517. * ATAN2
  518. *
  519. * This function calculates the arc tangent of the two variables x and y. It is similar to
  520. * calculating the arc tangent of y ÷ x, except that the signs of both arguments are used
  521. * to determine the quadrant of the result.
  522. * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a
  523. * point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between
  524. * -pi and pi, excluding -pi.
  525. *
  526. * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard
  527. * PHP atan2() function, so we need to reverse them here before calling the PHP atan() function.
  528. *
  529. * Excel Function:
  530. * ATAN2(xCoordinate,yCoordinate)
  531. *
  532. * @access public
  533. * @category Mathematical and Trigonometric Functions
  534. * @param float $xCoordinate The x-coordinate of the point.
  535. * @param float $yCoordinate The y-coordinate of the point.
  536. * @return float The inverse tangent of the specified x- and y-coordinates.
  537. */
  538. public static function REVERSE_ATAN2($xCoordinate, $yCoordinate) {
  539. $xCoordinate = (float) self::flattenSingleValue($xCoordinate);
  540. $yCoordinate = (float) self::flattenSingleValue($yCoordinate);
  541. if (($xCoordinate == 0) && ($yCoordinate == 0)) {
  542. return self::$_errorCodes['divisionbyzero'];
  543. }
  544. return atan2($yCoordinate, $xCoordinate);
  545. } // function REVERSE_ATAN2()
  546. /**
  547. * LOG_BASE
  548. *
  549. * Returns the logarithm of a number to a specified base. The default base is 10.
  550. *
  551. * Excel Function:
  552. * LOG(number[,base])
  553. *
  554. * @access public
  555. * @category Mathematical and Trigonometric Functions
  556. * @param float $value The positive real number for which you want the logarithm
  557. * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10.
  558. * @return float
  559. */
  560. public static function LOG_BASE($number, $base=10) {
  561. $number = self::flattenSingleValue($number);
  562. $base = (is_null($base)) ? 10 : (float) self::flattenSingleValue($base);
  563. return log($number, $base);
  564. } // function LOG_BASE()
  565. /**
  566. * SUM
  567. *
  568. * SUM computes the sum of all the values and cells referenced in the argument list.
  569. *
  570. * Excel Function:
  571. * SUM(value1[,value2[, ...]])
  572. *
  573. * @access public
  574. * @category Mathematical and Trigonometric Functions
  575. * @param mixed $arg,... Data values
  576. * @return float
  577. */
  578. public static function SUM() {
  579. // Return value
  580. $returnValue = 0;
  581. // Loop through the arguments
  582. $aArgs = self::flattenArray(func_get_args());
  583. foreach ($aArgs as $arg) {
  584. // Is it a numeric value?
  585. if ((is_numeric($arg)) && (!is_string($arg))) {
  586. $returnValue += $arg;
  587. }
  588. }
  589. // Return
  590. return $returnValue;
  591. } // function SUM()
  592. /**
  593. * SUMSQ
  594. *
  595. * SUMSQ returns the sum of the squares of the arguments
  596. *
  597. * Excel Function:
  598. * SUMSQ(value1[,value2[, ...]])
  599. *
  600. * @access public
  601. * @category Mathematical and Trigonometric Functions
  602. * @param mixed $arg,... Data values
  603. * @return float
  604. */
  605. public static function SUMSQ() {
  606. // Return value
  607. $returnValue = 0;
  608. // Loop through arguments
  609. $aArgs = self::flattenArray(func_get_args());
  610. foreach ($aArgs as $arg) {
  611. // Is it a numeric value?
  612. if ((is_numeric($arg)) && (!is_string($arg))) {
  613. $returnValue += ($arg * $arg);
  614. }
  615. }
  616. // Return
  617. return $returnValue;
  618. } // function SUMSQ()
  619. /**
  620. * PRODUCT
  621. *
  622. * PRODUCT returns the product of all the values and cells referenced in the argument list.
  623. *
  624. * Excel Function:
  625. * PRODUCT(value1[,value2[, ...]])
  626. *
  627. * @access public
  628. * @category Mathematical and Trigonometric Functions
  629. * @param mixed $arg,... Data values
  630. * @return float
  631. */
  632. public static function PRODUCT() {
  633. // Return value
  634. $returnValue = null;
  635. // Loop through arguments
  636. $aArgs = self::flattenArray(func_get_args());
  637. foreach ($aArgs as $arg) {
  638. // Is it a numeric value?
  639. if ((is_numeric($arg)) && (!is_string($arg))) {
  640. if (is_null($returnValue)) {
  641. $returnValue = $arg;
  642. } else {
  643. $returnValue *= $arg;
  644. }
  645. }
  646. }
  647. // Return
  648. if (is_null($returnValue)) {
  649. return 0;
  650. }
  651. return $returnValue;
  652. } // function PRODUCT()
  653. /**
  654. * QUOTIENT
  655. *
  656. * QUOTIENT function returns the integer portion of a division. Numerator is the divided number
  657. * and denominator is the divisor.
  658. *
  659. * Excel Function:
  660. * QUOTIENT(value1[,value2[, ...]])
  661. *
  662. * @access public
  663. * @category Mathematical and Trigonometric Functions
  664. * @param mixed $arg,... Data values
  665. * @return float
  666. */
  667. public static function QUOTIENT() {
  668. // Return value
  669. $returnValue = null;
  670. // Loop through arguments
  671. $aArgs = self::flattenArray(func_get_args());
  672. foreach ($aArgs as $arg) {
  673. // Is it a numeric value?
  674. if ((is_numeric($arg)) && (!is_string($arg))) {
  675. if (is_null($returnValue)) {
  676. $returnValue = ($arg == 0) ? 0 : $arg;
  677. } else {
  678. if (($returnValue == 0) || ($arg == 0)) {
  679. $returnValue = 0;
  680. } else {
  681. $returnValue /= $arg;
  682. }
  683. }
  684. }
  685. }
  686. // Return
  687. return intval($returnValue);
  688. } // function QUOTIENT()
  689. /**
  690. * MIN
  691. *
  692. * MIN returns the value of the element of the values passed that has the smallest value,
  693. * with negative numbers considered smaller than positive numbers.
  694. *
  695. * Excel Function:
  696. * MIN(value1[,value2[, ...]])
  697. *
  698. * @access public
  699. * @category Statistical Functions
  700. * @param mixed $arg,... Data values
  701. * @return float
  702. */
  703. public static function MIN() {
  704. // Return value
  705. $returnValue = null;
  706. // Loop through arguments
  707. $aArgs = self::flattenArray(func_get_args());
  708. foreach ($aArgs as $arg) {
  709. // Is it a numeric value?
  710. if ((is_numeric($arg)) && (!is_string($arg))) {
  711. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  712. $returnValue = $arg;
  713. }
  714. }
  715. }
  716. // Return
  717. if(is_null($returnValue)) {
  718. return 0;
  719. }
  720. return $returnValue;
  721. } // function MIN()
  722. /**
  723. * MINA
  724. *
  725. * Returns the smallest value in a list of arguments, including numbers, text, and logical values
  726. *
  727. * Excel Function:
  728. * MINA(value1[,value2[, ...]])
  729. *
  730. * @access public
  731. * @category Statistical Functions
  732. * @param mixed $arg,... Data values
  733. * @return float
  734. */
  735. public static function MINA() {
  736. // Return value
  737. $returnValue = null;
  738. // Loop through arguments
  739. $aArgs = self::flattenArray(func_get_args());
  740. foreach ($aArgs as $arg) {
  741. // Is it a numeric value?
  742. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  743. if (is_bool($arg)) {
  744. $arg = (integer) $arg;
  745. } elseif (is_string($arg)) {
  746. $arg = 0;
  747. }
  748. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  749. $returnValue = $arg;
  750. }
  751. }
  752. }
  753. // Return
  754. if(is_null($returnValue)) {
  755. return 0;
  756. }
  757. return $returnValue;
  758. } // function MINA()
  759. /**
  760. * MINIF
  761. *
  762. * Returns the minimum value within a range of cells that contain numbers within the list of arguments
  763. *
  764. * Excel Function:
  765. * MINIF(value1[,value2[, ...]],condition)
  766. *
  767. * @access public
  768. * @category Mathematical and Trigonometric Functions
  769. * @param mixed $arg,... Data values
  770. * @param string $condition The criteria that defines which cells will be checked.
  771. * @return float
  772. */
  773. public static function MINIF($aArgs,$condition,$sumArgs = array()) {
  774. // Return value
  775. $returnValue = null;
  776. $aArgs = self::flattenArray($aArgs);
  777. $sumArgs = self::flattenArray($sumArgs);
  778. if (count($sumArgs) == 0) {
  779. $sumArgs = $aArgs;
  780. }
  781. $condition = self::_ifCondition($condition);
  782. // Loop through arguments
  783. foreach ($aArgs as $key => $arg) {
  784. if (!is_numeric($arg)) { $arg = PHPExcel_Calculation::_wrapResult(strtoupper($arg)); }
  785. $testCondition = '='.$arg.$condition;
  786. if (PHPExcel_Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
  787. if ((is_null($returnValue)) || ($arg < $returnValue)) {
  788. $returnValue = $arg;
  789. }
  790. }
  791. }
  792. // Return
  793. return $returnValue;
  794. } // function MINIF()
  795. /**
  796. * SMALL
  797. *
  798. * Returns the nth smallest value in a data set. You can use this function to
  799. * select a value based on its relative standing.
  800. *
  801. * Excel Function:
  802. * SMALL(value1[,value2[, ...]],entry)
  803. *
  804. * @access public
  805. * @category Statistical Functions
  806. * @param mixed $arg,... Data values
  807. * @param int $entry Position (ordered from the smallest) in the array or range of data to return
  808. * @return float
  809. */
  810. public static function SMALL() {
  811. $aArgs = self::flattenArray(func_get_args());
  812. // Calculate
  813. $entry = array_pop($aArgs);
  814. if ((is_numeric($entry)) && (!is_string($entry))) {
  815. $mArgs = array();
  816. foreach ($aArgs as $arg) {
  817. // Is it a numeric value?
  818. if ((is_numeric($arg)) && (!is_string($arg))) {
  819. $mArgs[] = $arg;
  820. }
  821. }
  822. $count = self::COUNT($mArgs);
  823. $entry = floor(--$entry);
  824. if (($entry < 0) || ($entry >= $count) || ($count == 0)) {
  825. return self::$_errorCodes['num'];
  826. }
  827. sort($mArgs);
  828. return $mArgs[$entry];
  829. }
  830. return self::$_errorCodes['value'];
  831. } // function SMALL()
  832. /**
  833. * MAX
  834. *
  835. * MAX returns the value of the element of the values passed that has the highest value,
  836. * with negative numbers considered smaller than positive numbers.
  837. *
  838. * Excel Function:
  839. * MAX(value1[,value2[, ...]])
  840. *
  841. * @access public
  842. * @category Statistical Functions
  843. * @param mixed $arg,... Data values
  844. * @return float
  845. */
  846. public static function MAX() {
  847. // Return value
  848. $returnValue = null;
  849. // Loop through arguments
  850. $aArgs = self::flattenArray(func_get_args());
  851. foreach ($aArgs as $arg) {
  852. // Is it a numeric value?
  853. if ((is_numeric($arg)) && (!is_string($arg))) {
  854. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  855. $returnValue = $arg;
  856. }
  857. }
  858. }
  859. // Return
  860. if(is_null($returnValue)) {
  861. return 0;
  862. }
  863. return $returnValue;
  864. } // function MAX()
  865. /**
  866. * MAXA
  867. *
  868. * Returns the greatest value in a list of arguments, including numbers, text, and logical values
  869. *
  870. * Excel Function:
  871. * MAXA(value1[,value2[, ...]])
  872. *
  873. * @access public
  874. * @category Statistical Functions
  875. * @param mixed $arg,... Data values
  876. * @return float
  877. */
  878. public static function MAXA() {
  879. // Return value
  880. $returnValue = null;
  881. // Loop through arguments
  882. $aArgs = self::flattenArray(func_get_args());
  883. foreach ($aArgs as $arg) {
  884. // Is it a numeric value?
  885. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  886. if (is_bool($arg)) {
  887. $arg = (integer) $arg;
  888. } elseif (is_string($arg)) {
  889. $arg = 0;
  890. }
  891. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  892. $returnValue = $arg;
  893. }
  894. }
  895. }
  896. // Return
  897. if(is_null($returnValue)) {
  898. return 0;
  899. }
  900. return $returnValue;
  901. } // function MAXA()
  902. private static function _ifCondition($condition) {
  903. $condition = self::flattenSingleValue($condition);
  904. if (!in_array($condition{0},array('>', '<', '='))) {
  905. if (!is_numeric($condition)) { $condition = PHPExcel_Calculation::_wrapResult(strtoupper($condition)); }
  906. return '='.$condition;
  907. } else {
  908. preg_match('/([<>=]+)(.*)/',$condition,$matches);
  909. list(,$operator,$operand) = $matches;
  910. if (!is_numeric($operand)) { $operand = PHPExcel_Calculation::_wrapResult(strtoupper($operand)); }
  911. return $operator.$operand;
  912. }
  913. } // function _ifCondition()
  914. /**
  915. * MAXIF
  916. *
  917. * Counts the maximum value within a range of cells that contain numbers within the list of arguments
  918. *
  919. * Excel Function:
  920. * MAXIF(value1[,value2[, ...]],condition)
  921. *
  922. * @access public
  923. * @category Mathematical and Trigonometric Functions
  924. * @param mixed $arg,... Data values
  925. * @param string $condition The criteria that defines which cells will be checked.
  926. * @return float
  927. */
  928. public static function MAXIF($aArgs,$condition,$sumArgs = array()) {
  929. // Return value
  930. $returnValue = null;
  931. $aArgs = self::flattenArray($aArgs);
  932. $sumArgs = self::flattenArray($sumArgs);
  933. if (count($sumArgs) == 0) {
  934. $sumArgs = $aArgs;
  935. }
  936. $condition = self::_ifCondition($condition);
  937. // Loop through arguments
  938. foreach ($aArgs as $key => $arg) {
  939. if (!is_numeric($arg)) { $arg = PHPExcel_Calculation::_wrapResult(strtoupper($arg)); }
  940. $testCondition = '='.$arg.$condition;
  941. if (PHPExcel_Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
  942. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  943. $returnValue = $arg;
  944. }
  945. }
  946. }
  947. // Return
  948. return $returnValue;
  949. } // function MAXIF()
  950. /**
  951. * LARGE
  952. *
  953. * Returns the nth largest value in a data set. You can use this function to
  954. * select a value based on its relative standing.
  955. *
  956. * Excel Function:
  957. * LARGE(value1[,value2[, ...]],entry)
  958. *
  959. * @access public
  960. * @category Statistical Functions
  961. * @param mixed $arg,... Data values
  962. * @param int $entry Position (ordered from the largest) in the array or range of data to return
  963. * @return float
  964. *
  965. */
  966. public static function LARGE() {
  967. $aArgs = self::flattenArray(func_get_args());
  968. // Calculate
  969. $entry = floor(array_pop($aArgs));
  970. if ((is_numeric($entry)) && (!is_string($entry))) {
  971. $mArgs = array();
  972. foreach ($aArgs as $arg) {
  973. // Is it a numeric value?
  974. if ((is_numeric($arg)) && (!is_string($arg))) {
  975. $mArgs[] = $arg;
  976. }
  977. }
  978. $count = self::COUNT($mArgs);
  979. $entry = floor(--$entry);
  980. if (($entry < 0) || ($entry >= $count) || ($count == 0)) {
  981. return self::$_errorCodes['num'];
  982. }
  983. rsort($mArgs);
  984. return $mArgs[$entry];
  985. }
  986. return self::$_errorCodes['value'];
  987. } // function LARGE()
  988. /**
  989. * PERCENTILE
  990. *
  991. * Returns the nth percentile of values in a range..
  992. *
  993. * Excel Function:
  994. * PERCENTILE(value1[,value2[, ...]],entry)
  995. *
  996. * @access public
  997. * @category Statistical Functions
  998. * @param mixed $arg,... Data values
  999. * @param float $entry Percentile value in the range 0..1, inclusive.
  1000. * @return float
  1001. */
  1002. public static function PERCENTILE() {
  1003. $aArgs = self::flattenArray(func_get_args());
  1004. // Calculate
  1005. $entry = array_pop($aArgs);
  1006. if ((is_numeric($entry)) && (!is_string($entry))) {
  1007. if (($entry < 0) || ($entry > 1)) {
  1008. return self::$_errorCodes['num'];
  1009. }
  1010. $mArgs = array();
  1011. foreach ($aArgs as $arg) {
  1012. // Is it a numeric value?
  1013. if ((is_numeric($arg)) && (!is_string($arg))) {
  1014. $mArgs[] = $arg;
  1015. }
  1016. }
  1017. $mValueCount = count($mArgs);
  1018. if ($mValueCount > 0) {
  1019. sort($mArgs);
  1020. $count = self::COUNT($mArgs);
  1021. $index = $entry * ($count-1);
  1022. $iBase = floor($index);
  1023. if ($index == $iBase) {
  1024. return $mArgs[$index];
  1025. } else {
  1026. $iNext = $iBase + 1;
  1027. $iProportion = $index - $iBase;
  1028. return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion) ;
  1029. }
  1030. }
  1031. }
  1032. return self::$_errorCodes['value'];
  1033. } // function PERCENTILE()
  1034. /**
  1035. * QUARTILE
  1036. *
  1037. * Returns the quartile of a data set.
  1038. *
  1039. * Excel Function:
  1040. * QUARTILE(value1[,value2[, ...]],entry)
  1041. *
  1042. * @access public
  1043. * @category Statistical Functions
  1044. * @param mixed $arg,... Data values
  1045. * @param int $entry Quartile value in the range 1..3, inclusive.
  1046. * @return float
  1047. */
  1048. public static function QUARTILE() {
  1049. $aArgs = self::flattenArray(func_get_args());
  1050. // Calculate
  1051. $entry = floor(array_pop($aArgs));
  1052. if ((is_numeric($entry)) && (!is_string($entry))) {
  1053. $entry /= 4;
  1054. if (($entry < 0) || ($entry > 1)) {
  1055. return self::$_errorCodes['num'];
  1056. }
  1057. return self::PERCENTILE($aArgs,$entry);
  1058. }
  1059. return self::$_errorCodes['value'];
  1060. } // function QUARTILE()
  1061. /**
  1062. * COUNT
  1063. *
  1064. * Counts the number of cells that contain numbers within the list of arguments
  1065. *
  1066. * Excel Function:
  1067. * COUNT(value1[,value2[, ...]])
  1068. *
  1069. * @access public
  1070. * @category Statistical Functions
  1071. * @param mixed $arg,... Data values
  1072. * @return int
  1073. */
  1074. public static function COUNT() {
  1075. // Return value
  1076. $returnValue = 0;
  1077. // Loop through arguments
  1078. $aArgs = self::flattenArrayIndexed(func_get_args());
  1079. foreach ($aArgs as $k => $arg) {
  1080. if ((is_bool($arg)) &&
  1081. ((!self::isCellValue($k)) || (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE))) {
  1082. $arg = (integer) $arg;
  1083. }
  1084. // Is it a numeric value?
  1085. if ((is_numeric($arg)) && (!is_string($arg))) {
  1086. ++$returnValue;
  1087. }
  1088. }
  1089. // Return
  1090. return $returnValue;
  1091. } // function COUNT()
  1092. /**
  1093. * COUNTBLANK
  1094. *
  1095. * Counts the number of empty cells within the list of arguments
  1096. *
  1097. * Excel Function:
  1098. * COUNTBLANK(value1[,value2[, ...]])
  1099. *
  1100. * @access public
  1101. * @category Statistical Functions
  1102. * @param mixed $arg,... Data values
  1103. * @return int
  1104. */
  1105. public static function COUNTBLANK() {
  1106. // Return value
  1107. $returnValue = 0;
  1108. // Loop through arguments
  1109. $aArgs = self::flattenArray(func_get_args());
  1110. foreach ($aArgs as $arg) {
  1111. // Is it a blank cell?
  1112. if ((is_null($arg)) || ((is_string($arg)) && ($arg == ''))) {
  1113. ++$returnValue;
  1114. }
  1115. }
  1116. // Return
  1117. return $returnValue;
  1118. } // function COUNTBLANK()
  1119. /**
  1120. * COUNTA
  1121. *
  1122. * Counts the number of cells that are not empty within the list of arguments
  1123. *
  1124. * Excel Function:
  1125. * COUNTA(value1[,value2[, ...]])
  1126. *
  1127. * @access public
  1128. * @category Statistical Functions
  1129. * @param mixed $arg,... Data values
  1130. * @return int
  1131. */
  1132. public static function COUNTA() {
  1133. // Return value
  1134. $returnValue = 0;
  1135. // Loop through arguments
  1136. $aArgs = self::flattenArray(func_get_args());
  1137. foreach ($aArgs as $arg) {
  1138. // Is it a numeric, boolean or string value?
  1139. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  1140. ++$returnValue;
  1141. }
  1142. }
  1143. // Return
  1144. return $returnValue;
  1145. } // function COUNTA()
  1146. /**
  1147. * COUNTIF
  1148. *
  1149. * Counts the number of cells that contain numbers within the list of arguments
  1150. *
  1151. * Excel Function:
  1152. * COUNTIF(value1[,value2[, ...]],condition)
  1153. *
  1154. * @access public
  1155. * @category Statistical Functions
  1156. * @param mixed $arg,... Data values
  1157. * @param string $condition The criteria that defines which cells will be counted.
  1158. * @return int
  1159. */
  1160. public static function COUNTIF($aArgs,$condition) {
  1161. // Return value
  1162. $returnValue = 0;
  1163. $aArgs = self::flattenArray($aArgs);
  1164. $condition = self::_ifCondition($condition);
  1165. // Loop through arguments
  1166. foreach ($aArgs as $arg) {
  1167. if (!is_numeric($arg)) { $arg = PHPExcel_Calculation::_wrapResult(strtoupper($arg)); }
  1168. $testCondition = '='.$arg.$condition;
  1169. if (PHPExcel_Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
  1170. // Is it a value within our criteria
  1171. ++$returnValue;
  1172. }
  1173. }
  1174. // Return
  1175. return $returnValue;
  1176. } // function COUNTIF()
  1177. /**
  1178. * SUMIF
  1179. *
  1180. * Counts the number of cells that contain numbers within the list of arguments
  1181. *
  1182. * Excel Function:
  1183. * SUMIF(value1[,value2[, ...]],condition)
  1184. *
  1185. * @access public
  1186. * @category Mathematical and Trigonometric Functions
  1187. * @param mixed $arg,... Data values
  1188. * @param string $condition The criteria that defines which cells will be summed.
  1189. * @return float
  1190. */
  1191. public static function SUMIF($aArgs,$condition,$sumArgs = array()) {
  1192. // Return value
  1193. $returnValue = 0;
  1194. $aArgs = self::flattenArray($aArgs);
  1195. $sumArgs = self::flattenArray($sumArgs);
  1196. if (count($sumArgs) == 0) {
  1197. $sumArgs = $aArgs;
  1198. }
  1199. $condition = self::_ifCondition($condition);
  1200. // Loop through arguments
  1201. foreach ($aArgs as $key => $arg) {
  1202. if (!is_numeric($arg)) { $arg = PHPExcel_Calculation::_wrapResult(strtoupper($arg)); }
  1203. $testCondition = '='.$arg.$condition;
  1204. if (PHPExcel_Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
  1205. // Is it a value within our criteria
  1206. $returnValue += $sumArgs[$key];
  1207. }
  1208. }
  1209. // Return
  1210. return $returnValue;
  1211. } // function SUMIF()
  1212. /**
  1213. * AVERAGE
  1214. *
  1215. * Returns the average (arithmetic mean) of the arguments
  1216. *
  1217. * Excel Function:
  1218. * AVERAGE(value1[,value2[, ...]])
  1219. *
  1220. * @access public
  1221. * @category Statistical Functions
  1222. * @param mixed $arg,... Data values
  1223. * @return float
  1224. */
  1225. public static function AVERAGE() {
  1226. $aArgs = self::flattenArrayIndexed(func_get_args());
  1227. $returnValue = $aCount = 0;
  1228. // Loop through arguments
  1229. foreach ($aArgs as $k => $arg) {
  1230. if ((is_bool($arg)) &&
  1231. ((!self::isCellValue($k)) || (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE))) {
  1232. $arg = (integer) $arg;
  1233. }
  1234. // Is it a numeric value?
  1235. if ((is_numeric($arg)) && (!is_string($arg))) {
  1236. if (is_null($returnValue)) {
  1237. $returnValue = $arg;
  1238. } else {
  1239. $returnValue += $arg;
  1240. }
  1241. ++$aCount;
  1242. }
  1243. }
  1244. // Return
  1245. if ($aCount > 0) {
  1246. return $returnValue / $aCount;
  1247. } else {
  1248. return self::$_errorCodes['divisionbyzero'];
  1249. }
  1250. } // function AVERAGE()
  1251. /**
  1252. * AVERAGEA
  1253. *
  1254. * Returns the average of its arguments, including numbers, text, and logical values
  1255. *
  1256. * Excel Function:
  1257. * AVERAGEA(value1[,value2[, ...]])
  1258. *
  1259. * @access public
  1260. * @category Statistical Functions
  1261. * @param mixed $arg,... Data values
  1262. * @return float
  1263. */
  1264. public static function AVERAGEA() {
  1265. // Return value
  1266. $returnValue = null;
  1267. // Loop through arguments
  1268. $aArgs = self::flattenArrayIndexed(func_get_args());
  1269. $aCount = 0;
  1270. foreach ($aArgs as $k => $arg) {
  1271. if ((is_bool($arg)) &&
  1272. (!self::isMatrixValue($k))) {
  1273. } else {
  1274. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
  1275. if (is_bool($arg)) {
  1276. $arg = (integer) $arg;
  1277. } elseif (is_string($arg)) {
  1278. $arg = 0;
  1279. }
  1280. if (is_null($returnValue)) {
  1281. $returnValue = $arg;
  1282. } else {
  1283. $returnValue += $arg;
  1284. }
  1285. ++$aCount;
  1286. }
  1287. }
  1288. }
  1289. // Return
  1290. if ($aCount > 0) {
  1291. return $returnValue / $aCount;
  1292. } else {
  1293. return self::$_errorCodes['divisionbyzero'];
  1294. }
  1295. } // function AVERAGEA()
  1296. /**
  1297. * AVERAGEIF
  1298. *
  1299. * Returns the average value from a range of cells that contain numbers within the list of arguments
  1300. *
  1301. * Excel Function:
  1302. * AVERAGEIF(value1[,value2[, ...]],condition)
  1303. *
  1304. * @access public
  1305. * @category Mathematical and Trigonometric Functions
  1306. * @param mixed $arg,... Data values
  1307. * @param string $condition The criteria that defines which cells will be checked.
  1308. * @return float
  1309. */
  1310. public static function AVERAGEIF($aArgs,$condition,$averageArgs = array()) {
  1311. // Return value
  1312. $returnValue = 0;
  1313. $aArgs = self::flattenArray($aArgs);
  1314. $averageArgs = self::flattenArray($averageArgs);
  1315. if (count($averageArgs) == 0) {
  1316. $averageArgs = $aArgs;
  1317. }
  1318. $condition = self::_ifCondition($condition);
  1319. // Loop through arguments
  1320. $aCount = 0;
  1321. foreach ($aArgs as $key => $arg) {
  1322. if (!is_numeric($arg)) { $arg = PHPExcel_Calculation::_wrapResult(strtoupper($arg)); }
  1323. $testCondition = '='.$arg.$condition;
  1324. if (PHPExcel_Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
  1325. if ((is_null($returnValue)) || ($arg > $returnValue)) {
  1326. $returnValue += $arg;
  1327. ++$aCount;
  1328. }
  1329. }
  1330. }
  1331. // Return
  1332. if ($aCount > 0) {
  1333. return $returnValue / $aCount;
  1334. } else {
  1335. return self::$_errorCodes['divisionbyzero'];
  1336. }
  1337. } // function AVERAGEIF()
  1338. /**
  1339. * MEDIAN
  1340. *
  1341. * Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
  1342. *
  1343. * Excel Function:
  1344. * MEDIAN(value1[,value2[, ...]])
  1345. *
  1346. * @access public
  1347. * @category Statistical Functions
  1348. * @param mixed $arg,... Data values
  1349. * @return float
  1350. */
  1351. public static function MEDIAN() {
  1352. // Return value
  1353. $returnValue = self::$_errorCodes['num'];
  1354. $mArgs = array();
  1355. // Loop through arguments
  1356. $aArgs = self::flattenArray(func_get_args());
  1357. foreach ($aArgs as $arg) {
  1358. // Is it a numeric value?
  1359. if ((is_numeric($arg)) && (!is_string($arg))) {
  1360. $mArgs[] = $arg;
  1361. }
  1362. }
  1363. $mValueCount = count($mArgs);
  1364. if ($mValueCount > 0) {
  1365. sort($mArgs,SORT_NUMERIC);
  1366. $mValueCount = $mValueCount / 2;
  1367. if ($mValueCount == floor($mValueCount)) {
  1368. $returnValue = ($mArgs[$mValueCount--] + $mArgs[$mValueCount]) / 2;
  1369. } else {
  1370. $mValueCount == floor($mValueCount);
  1371. $returnValue = $mArgs[$mValueCount];
  1372. }
  1373. }
  1374. // Return
  1375. return $returnValue;
  1376. } // function MEDIAN()
  1377. //
  1378. // Special variant of array_count_values that isn't limited to strings and integers,
  1379. // but can work with floating point numbers as values
  1380. //
  1381. private static function _modeCalc($data) {
  1382. $frequencyArray = array();
  1383. foreach($data as $datum) {
  1384. $found = False;
  1385. foreach($frequencyArray as $key => $value) {
  1386. if ((string) $value['value'] == (string) $datum) {
  1387. ++$frequencyArray[$key]['frequency'];
  1388. $found = True;
  1389. break;
  1390. }
  1391. }
  1392. if (!$found) {
  1393. $frequencyArray[] = array('value' => $datum,
  1394. 'frequency' => 1 );
  1395. }
  1396. }
  1397. foreach($frequencyArray as $key => $value) {
  1398. $frequencyList[$key] = $value['frequency'];
  1399. $valueList[$key] = $value['value'];
  1400. }
  1401. array_multisort($frequencyList, SORT_DESC, $valueList, SORT_ASC, SORT_NUMERIC, $frequencyArray);
  1402. if ($frequencyArray[0]['frequency'] == 1) {
  1403. return self::NA();
  1404. }
  1405. return $frequencyArray[0]['value'];
  1406. } // function _modeCalc()
  1407. /**
  1408. * MODE
  1409. *
  1410. * Returns the most frequently occurring, or repetitive, value in an array or range of data
  1411. *
  1412. * Excel Function:
  1413. * MODE(value1[,value2[, ...]])
  1414. *
  1415. * @access public
  1416. * @category Statistical Functions
  1417. * @param mixed $arg,... Data values
  1418. * @return float
  1419. */
  1420. public static function MODE() {
  1421. // Return value
  1422. $returnValue = self::NA();
  1423. // Loop through arguments
  1424. $aArgs = self::flattenArray(func_get_args());
  1425. $mArgs = array();
  1426. foreach ($aArgs as $arg) {
  1427. // Is it a numeric value?
  1428. if ((is_numeric($arg)) && (!is_string($arg))) {
  1429. $mArgs[] = $arg;
  1430. }
  1431. }
  1432. if (count($mArgs) > 0) {
  1433. return self::_modeCalc($mArgs);
  1434. }
  1435. // Return
  1436. return $returnValue;
  1437. } // function MODE()
  1438. /**
  1439. * DEVSQ
  1440. *
  1441. * Returns the sum of squares of deviations of data points from their sample mean.
  1442. *
  1443. * Excel Function:
  1444. * DEVSQ(value1[,value2[, ...]])
  1445. *
  1446. * @access public
  1447. * @category Statistical Functions
  1448. * @param mixed $arg,... Data values
  1449. * @return float
  1450. */
  1451. public static function DEVSQ() {
  1452. $aArgs = self::flattenArrayIndexed(func_get_args());
  1453. // Return value
  1454. $returnValue = null;
  1455. $aMean = self::AVERAGE($aArgs);
  1456. if ($aMean != self::$_errorCodes['divisionbyzero']) {
  1457. $aCount = -1;
  1458. foreach ($aArgs as $k => $arg) {
  1459. // Is it a numeric value?
  1460. if ((is_bool($arg)) &&
  1461. ((!self::isCellValue($k)) || (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE))) {
  1462. $arg = (integer) $arg;
  1463. }
  1464. if ((is_numeric($arg)) && (!is_string($arg))) {
  1465. if (is_null($returnValue)) {
  1466. $returnValue = pow(($arg - $aMean),2);
  1467. } else {
  1468. $returnValue += pow(($arg - $aMean),2);
  1469. }
  1470. ++$aCount;
  1471. }
  1472. }
  1473. // Return
  1474. if (is_null($returnValue)) {
  1475. return self::$_errorCodes['num'];
  1476. } else {
  1477. return $returnValue;
  1478. }
  1479. }
  1480. return self::NA();
  1481. } // function DEVSQ()
  1482. /**
  1483. * AVEDEV
  1484. *
  1485. * Returns the average of the absolute deviations of data points from their mean.
  1486. * AVEDEV is a measure of the variability in a data set.
  1487. *
  1488. * Excel Function:
  1489. * AVEDEV(value1[,value2[, ...]])
  1490. *
  1491. * @access public
  1492. * @category Statistical Functions
  1493. * @param mixed $arg,... Data values
  1494. * @return float
  1495. */
  1496. public static function AVEDEV() {
  1497. $aArgs = self::flattenArrayIndexed(func_get_args());
  1498. // Return value
  1499. $returnValue = null;
  1500. $aMean = self::AVERAGE($aArgs);
  1501. if ($aMean != self::$_errorCodes['divisionbyzero']) {
  1502. $aCount = 0;
  1503. foreach ($aArgs as $k => $arg) {
  1504. if ((is_bool($arg)) &&
  1505. ((!self::isCellValue($k)) || (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE))) {
  1506. $arg = (integer) $arg;
  1507. }
  1508. // Is it a numeric value?
  1509. if ((is_numeric($arg)) && (!is_string($arg))) {
  1510. if (is_null($returnValue)) {
  1511. $returnValue = abs($arg - $aMean);
  1512. } else {
  1513. $returnValue += abs($arg - $aMean);
  1514. }
  1515. ++$aCount;
  1516. }
  1517. }
  1518. // Return
  1519. if ($aCount == 0) {
  1520. return self::$_errorCodes['divisionbyzero'];
  1521. }
  1522. return $returnValue / $aCount;
  1523. }
  1524. return self::$_errorCodes['num'];
  1525. } // function AVEDEV()
  1526. /**
  1527. * GEOMEAN
  1528. *
  1529. * Returns the geometric mean of an array or range of positive data. For example, you
  1530. * can use GEOMEAN to calculate average growth rate given compound interest with
  1531. * variable rates.
  1532. *
  1533. * Excel Function:
  1534. * GEOMEAN(value1[,value2[, ...]])
  1535. *
  1536. * @access public
  1537. * @category Statistical Functions
  1538. * @param mixed $arg,... Data values
  1539. * @return float
  1540. */
  1541. public static function GEOMEAN() {
  1542. $aArgs = self::flattenArray(func_get_args());
  1543. $aMean = self::PRODUCT($aArgs);
  1544. if (is_numeric($aMean) && ($aMean > 0)) {
  1545. $aCount = self::COUNT($aArgs) ;
  1546. if (self::MIN($aArgs) > 0) {
  1547. return pow($aMean, (1 / $aCount));
  1548. }
  1549. }
  1550. return self::$_errorCodes['num'];
  1551. } // GEOMEAN()
  1552. /**
  1553. * HARMEAN
  1554. *
  1555. * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the
  1556. * arithmetic mean of reciprocals.
  1557. *
  1558. * Excel Function:
  1559. * HARMEAN(value1[,value2[, ...]])
  1560. *
  1561. * @access public
  1562. * @category Statistical Functions
  1563. * @param mixed $arg,... Data values
  1564. * @return float
  1565. */
  1566. public static function HARMEAN() {
  1567. // Return value
  1568. $returnValue = self::NA();
  1569. // Loop through arguments
  1570. $aArgs = self::flattenArray(func_get_args());
  1571. if (self::MIN($aArgs) < 0) {
  1572. return self::$_errorCodes['num'];
  1573. }
  1574. $aCount = 0;
  1575. foreach ($aArgs as $arg) {
  1576. // Is it a numeric value?
  1577. if ((is_numeric($arg)) && (!is_string($arg))) {
  1578. if ($arg <= 0) {
  1579. return self::$_errorCodes['num'];
  1580. }
  1581. if (is_null($returnValue)) {
  1582. $returnValue = (1 / $arg);
  1583. } else {
  1584. $returnValue += (1 / $arg);
  1585. }
  1586. ++$aCount;
  1587. }
  1588. }
  1589. // Return
  1590. if ($aCount > 0) {
  1591. return 1 / ($returnValue / $aCount);
  1592. } else {
  1593. return $returnValue;
  1594. }
  1595. } // function HARMEAN()
  1596. /**
  1597. * TRIMMEAN
  1598. *
  1599. * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean
  1600. * taken by excluding a percentage of data points from the top and bottom tails
  1601. * of a data set.
  1602. *
  1603. * Excel Function:
  1604. * TRIMEAN(value1[,value2[, ...]],$discard)
  1605. *
  1606. * @access public
  1607. * @category Statistical Functions
  1608. * @param mixed $arg,... Data values
  1609. * @param float $discard Percentage to discard
  1610. * @return float
  1611. */
  1612. public static function TRIMMEAN() {
  1613. $aArgs = self::flattenArray(func_get_args());
  1614. // Calculate
  1615. $percent = array_pop($aArgs);
  1616. if ((is_numeric($percent)) && (!is_string($percent))) {
  1617. if (($percent < 0) || ($percent > 1)) {
  1618. return self::$_errorCodes['num'];
  1619. }
  1620. $mArgs = array();
  1621. foreach ($aArgs as $arg) {
  1622. // Is it a numeric value?
  1623. if ((is_numeric($arg)) && (!is_string($arg))) {
  1624. $mArgs[] = $arg;
  1625. }
  1626. }
  1627. $discard = floor(self::COUNT($mArgs) * $percent / 2);
  1628. sort($mArgs);
  1629. for ($i=0; $i < $discard; ++$i) {
  1630. array_pop($mArgs);
  1631. array_shift($mArgs);
  1632. }
  1633. return self::AVERAGE($mArgs);
  1634. }
  1635. return self::$_errorCodes['value'];
  1636. } // function TRIMMEAN()
  1637. /**
  1638. * STDEV
  1639. *
  1640. * Estimates standard deviation based on a sample. The standard deviation is a measure of how
  1641. * widely values are dispersed from the average value (the mean).
  1642. *
  1643. * Excel Function:
  1644. * STDEV(value1[,value2[, ...]])
  1645. *
  1646. * @access public
  1647. * @category Statistical Functions
  1648. * @param mixed $arg,... Data values
  1649. * @return float
  1650. */
  1651. public static function STDEV() {
  1652. $aArgs = self::flattenArrayIndexed(func_get_args());
  1653. // Return value
  1654. $returnValue = null;
  1655. $aMean = self::AVERAGE($aArgs);
  1656. if (!is_null($aMean)) {
  1657. $aCount = -1;
  1658. foreach ($aArgs as $k => $arg) {
  1659. if ((is_bool($arg)) &&
  1660. ((!self::isCellValue($k)) || (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE))) {
  1661. $arg = (integer) $arg;
  1662. }
  1663. // Is it a numeric value?
  1664. if ((is_numeric($arg)) && (!is_string($arg))) {
  1665. if (is_null($returnValue)) {
  1666. $returnValue = pow(($arg - $aMean),2);
  1667. } else {
  1668. $returnValue += pow(($arg - $aMean),2);
  1669. }
  1670. ++$aCount;
  1671. }
  1672. }
  1673. // Return
  1674. if (($aCount > 0) && ($returnValue > 0)) {
  1675. return sqrt($returnValue / $aCount);
  1676. }
  1677. }
  1678. return self::$_errorCodes['divisionbyzero'];
  1679. } // function STDEV()
  1680. /**
  1681. * STDEVA
  1682. *
  1683. * Estimates standard deviation based on a sample, including numbers, text, and logical values
  1684. *
  1685. * Excel Function:
  1686. * STDEVA(value1[,value2[, ...]])
  1687. *
  1688. * @access public
  1689. * @category Statistical Functions
  1690. * @param mixed $arg,... Data values
  1691. * @return float
  1692. */
  1693. public static function STDEVA() {
  1694. $aArgs = self::flattenArrayIndexed(func_get_args());
  1695. // Return value
  1696. $returnValue = null;
  1697. $aMean = self::AVERAGEA($aArgs);
  1698. if (!is_null($aMean)) {
  1699. $aCount = -1;
  1700. foreach ($aArgs as $k => $arg) {
  1701. if ((is_bool($arg)) &&
  1702. (!self::isMatrixValue($k))) {
  1703. } else {
  1704. // Is it a numeric value?
  1705. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1706. if (is_bool($arg)) {
  1707. $arg = (integer) $arg;
  1708. } elseif (is_string($arg)) {
  1709. $arg = 0;
  1710. }
  1711. if (is_null($returnValue)) {
  1712. $returnValue = pow(($arg - $aMean),2);
  1713. } else {
  1714. $returnValue += pow(($arg - $aMean),2);
  1715. }
  1716. ++$aCount;
  1717. }
  1718. }
  1719. }
  1720. // Return
  1721. if (($aCount > 0) && ($returnValue > 0)) {
  1722. return sqrt($returnValue / $aCount);
  1723. }
  1724. }
  1725. return self::$_errorCodes['divisionbyzero'];
  1726. } // function STDEVA()
  1727. /**
  1728. * STDEVP
  1729. *
  1730. * Calculates standard deviation based on the entire population
  1731. *
  1732. * Excel Function:
  1733. * STDEVP(value1[,value2[, ...]])
  1734. *
  1735. * @access public
  1736. * @category Statistical Functions
  1737. * @param mixed $arg,... Data values
  1738. * @return float
  1739. */
  1740. public static function STDEVP() {
  1741. $aArgs = self::flattenArrayIndexed(func_get_args());
  1742. // Return value
  1743. $returnValue = null;
  1744. $aMean = self::AVERAGE($aArgs);
  1745. if (!is_null($aMean)) {
  1746. $aCount = 0;
  1747. foreach ($aArgs as $k => $arg) {
  1748. if ((is_bool($arg)) &&
  1749. ((!self::isCellValue($k)) || (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE))) {
  1750. $arg = (integer) $arg;
  1751. }
  1752. // Is it a numeric value?
  1753. if ((is_numeric($arg)) && (!is_string($arg))) {
  1754. if (is_null($returnValue)) {
  1755. $returnValue = pow(($arg - $aMean),2);
  1756. } else {
  1757. $returnValue += pow(($arg - $aMean),2);
  1758. }
  1759. ++$aCount;
  1760. }
  1761. }
  1762. // Return
  1763. if (($aCount > 0) && ($returnValue > 0)) {
  1764. return sqrt($returnValue / $aCount);
  1765. }
  1766. }
  1767. return self::$_errorCodes['divisionbyzero'];
  1768. } // function STDEVP()
  1769. /**
  1770. * STDEVPA
  1771. *
  1772. * Calculates standard deviation based on the entire population, including numbers, text, and logical values
  1773. *
  1774. * Excel Function:
  1775. * STDEVPA(value1[,value2[, ...]])
  1776. *
  1777. * @access public
  1778. * @category Statistical Functions
  1779. * @param mixed $arg,... Data values
  1780. * @return float
  1781. */
  1782. public static function STDEVPA() {
  1783. $aArgs = self::flattenArrayIndexed(func_get_args());
  1784. // Return value
  1785. $returnValue = null;
  1786. $aMean = self::AVERAGEA($aArgs);
  1787. if (!is_null($aMean)) {
  1788. $aCount = 0;
  1789. foreach ($aArgs as $k => $arg) {
  1790. if ((is_bool($arg)) &&
  1791. (!self::isMatrixValue($k))) {
  1792. } else {
  1793. // Is it a numeric value?
  1794. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1795. if (is_bool($arg)) {
  1796. $arg = (integer) $arg;
  1797. } elseif (is_string($arg)) {
  1798. $arg = 0;
  1799. }
  1800. if (is_null($returnValue)) {
  1801. $returnValue = pow(($arg - $aMean),2);
  1802. } else {
  1803. $returnValue += pow(($arg - $aMean),2);
  1804. }
  1805. ++$aCount;
  1806. }
  1807. }
  1808. }
  1809. // Return
  1810. if (($aCount > 0) && ($returnValue > 0)) {
  1811. return sqrt($returnValue / $aCount);
  1812. }
  1813. }
  1814. return self::$_errorCodes['divisionbyzero'];
  1815. } // function STDEVPA()
  1816. /**
  1817. * VARFunc
  1818. *
  1819. * Estimates variance based on a sample.
  1820. *
  1821. * Excel Function:
  1822. * VAR(value1[,value2[, ...]])
  1823. *
  1824. * @access public
  1825. * @category Statistical Functions
  1826. * @param mixed $arg,... Data values
  1827. * @return float
  1828. */
  1829. public static function VARFunc() {
  1830. // Return value
  1831. $returnValue = self::$_errorCodes['divisionbyzero'];
  1832. $summerA = $summerB = 0;
  1833. // Loop through arguments
  1834. $aArgs = self::flattenArray(func_get_args());
  1835. $aCount = 0;
  1836. foreach ($aArgs as $arg) {
  1837. if (is_bool($arg)) { $arg = (integer) $arg; }
  1838. // Is it a numeric value?
  1839. if ((is_numeric($arg)) && (!is_string($arg))) {
  1840. $summerA += ($arg * $arg);
  1841. $summerB += $arg;
  1842. ++$aCount;
  1843. }
  1844. }
  1845. // Return
  1846. if ($aCount > 1) {
  1847. $summerA *= $aCount;
  1848. $summerB *= $summerB;
  1849. $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
  1850. }
  1851. return $returnValue;
  1852. } // function VARFunc()
  1853. /**
  1854. * VARA
  1855. *
  1856. * Estimates variance based on a sample, including numbers, text, and logical values
  1857. *
  1858. * Excel Function:
  1859. * VARA(value1[,value2[, ...]])
  1860. *
  1861. * @access public
  1862. * @category Statistical Functions
  1863. * @param mixed $arg,... Data values
  1864. * @return float
  1865. */
  1866. public static function VARA() {
  1867. // Return value
  1868. $returnValue = self::$_errorCodes['divisionbyzero'];
  1869. $summerA = $summerB = 0;
  1870. // Loop through arguments
  1871. $aArgs = self::flattenArrayIndexed(func_get_args());
  1872. $aCount = 0;
  1873. foreach ($aArgs as $k => $arg) {
  1874. if ((is_string($arg)) &&
  1875. (self::isValue($k))) {
  1876. return self::$_errorCodes['value'];
  1877. } elseif ((is_string($arg)) &&
  1878. (!self::isMatrixValue($k))) {
  1879. } else {
  1880. // Is it a numeric value?
  1881. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1882. if (is_bool($arg)) {
  1883. $arg = (integer) $arg;
  1884. } elseif (is_string($arg)) {
  1885. $arg = 0;
  1886. }
  1887. $summerA += ($arg * $arg);
  1888. $summerB += $arg;
  1889. ++$aCount;
  1890. }
  1891. }
  1892. }
  1893. // Return
  1894. if ($aCount > 1) {
  1895. $summerA *= $aCount;
  1896. $summerB *= $summerB;
  1897. $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
  1898. }
  1899. return $returnValue;
  1900. } // function VARA()
  1901. /**
  1902. * VARP
  1903. *
  1904. * Calculates variance based on the entire population
  1905. *
  1906. * Excel Function:
  1907. * VARP(value1[,value2[, ...]])
  1908. *
  1909. * @access public
  1910. * @category Statistical Functions
  1911. * @param mixed $arg,... Data values
  1912. * @return float
  1913. */
  1914. public static function VARP() {
  1915. // Return value
  1916. $returnValue = self::$_errorCodes['divisionbyzero'];
  1917. $summerA = $summerB = 0;
  1918. // Loop through arguments
  1919. $aArgs = self::flattenArray(func_get_args());
  1920. $aCount = 0;
  1921. foreach ($aArgs as $arg) {
  1922. if (is_bool($arg)) { $arg = (integer) $arg; }
  1923. // Is it a numeric value?
  1924. if ((is_numeric($arg)) && (!is_string($arg))) {
  1925. $summerA += ($arg * $arg);
  1926. $summerB += $arg;
  1927. ++$aCount;
  1928. }
  1929. }
  1930. // Return
  1931. if ($aCount > 0) {
  1932. $summerA *= $aCount;
  1933. $summerB *= $summerB;
  1934. $returnValue = ($summerA - $summerB) / ($aCount * $aCount);
  1935. }
  1936. return $returnValue;
  1937. } // function VARP()
  1938. /**
  1939. * VARPA
  1940. *
  1941. * Calculates variance based on the entire population, including numbers, text, and logical values
  1942. *
  1943. * Excel Function:
  1944. * VARPA(value1[,value2[, ...]])
  1945. *
  1946. * @access public
  1947. * @category Statistical Functions
  1948. * @param mixed $arg,... Data values
  1949. * @return float
  1950. */
  1951. public static function VARPA() {
  1952. // Return value
  1953. $returnValue = self::$_errorCodes['divisionbyzero'];
  1954. $summerA = $summerB = 0;
  1955. // Loop through arguments
  1956. $aArgs = self::flattenArrayIndexed(func_get_args());
  1957. $aCount = 0;
  1958. foreach ($aArgs as $k => $arg) {
  1959. if ((is_string($arg)) &&
  1960. (self::isValue($k))) {
  1961. return self::$_errorCodes['value'];
  1962. } elseif ((is_string($arg)) &&
  1963. (!self::isMatrixValue($k))) {
  1964. } else {
  1965. // Is it a numeric value?
  1966. if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) {
  1967. if (is_bool($arg)) {
  1968. $arg = (integer) $arg;
  1969. } elseif (is_string($arg)) {
  1970. $arg = 0;
  1971. }
  1972. $summerA += ($arg * $arg);
  1973. $summerB += $arg;
  1974. ++$aCount;
  1975. }
  1976. }
  1977. }
  1978. // Return
  1979. if ($aCount > 0) {
  1980. $summerA *= $aCount;
  1981. $summerB *= $summerB;
  1982. $returnValue = ($summerA - $summerB) / ($aCount * $aCount);
  1983. }
  1984. return $returnValue;
  1985. } // function VARPA()
  1986. /**
  1987. * RANK
  1988. *
  1989. * Returns the rank of a number in a list of numbers.
  1990. *
  1991. * @param number The number whose rank you want to find.
  1992. * @param array of number An array of, or a reference to, a list of numbers.
  1993. * @param mixed Order to sort the values in the value set
  1994. * @return float
  1995. */
  1996. public static function RANK($value,$valueSet,$order=0) {
  1997. $value = self::flattenSingleValue($value);
  1998. $valueSet = self::flattenArray($valueSet);
  1999. $order = (is_null($order)) ? 0 : (integer) self::flattenSingleValue($order);
  2000. foreach($valueSet as $key => $valueEntry) {
  2001. if (!is_numeric($valueEntry)) {
  2002. unset($valueSet[$key]);
  2003. }
  2004. }
  2005. if ($order == 0) {
  2006. rsort($valueSet,SORT_NUMERIC);
  2007. } else {
  2008. sort($valueSet,SORT_NUMERIC);
  2009. }
  2010. $pos = array_search($value,$valueSet);
  2011. if ($pos === False) {
  2012. return self::$_errorCodes['na'];
  2013. }
  2014. return ++$pos;
  2015. } // function RANK()
  2016. /**
  2017. * PERCENTRANK
  2018. *
  2019. * Returns the rank of a value in a data set as a percentage of the data set.
  2020. *
  2021. * @param array of number An array of, or a reference to, a list of numbers.
  2022. * @param number The number whose rank you want to find.
  2023. * @param number The number of significant digits for the returned percentage value.
  2024. * @return float
  2025. */
  2026. public static function PERCENTRANK($valueSet,$value,$significance=3) {
  2027. $valueSet = self::flattenArray($valueSet);
  2028. $value = self::flattenSingleValue($value);
  2029. $significance = (is_null($significance)) ? 3 : (integer) self::flattenSingleValue($significance);
  2030. foreach($valueSet as $key => $valueEntry) {
  2031. if (!is_numeric($valueEntry)) {
  2032. unset($valueSet[$key]);
  2033. }
  2034. }
  2035. sort($valueSet,SORT_NUMERIC);
  2036. $valueCount = count($valueSet);
  2037. if ($valueCount == 0) {
  2038. return self::$_errorCodes['num'];
  2039. }
  2040. $valueAdjustor = $valueCount - 1;
  2041. if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
  2042. return self::$_errorCodes['na'];
  2043. }
  2044. $pos = array_search($value,$valueSet);
  2045. if ($pos === False) {
  2046. $pos = 0;
  2047. $testValue = $valueSet[0];
  2048. while ($testValue < $value) {
  2049. $testValue = $valueSet[++$pos];
  2050. }
  2051. --$pos;
  2052. $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos]));
  2053. }
  2054. return round($pos / $valueAdjustor,$significance);
  2055. } // function PERCENTRANK()
  2056. private static function _checkTrendArrays(&$array1,&$array2) {
  2057. if (!is_array($array1)) { $array1 = array($array1); }
  2058. if (!is_array($array2)) { $array2 = array($array2); }
  2059. $array1 = self::flattenArray($array1);
  2060. $array2 = self::flattenArray($array2);
  2061. foreach($array1 as $key => $value) {
  2062. if ((is_bool($value)) || (is_string($value)) || (is_null($value))) {
  2063. unset($array1[$key]);
  2064. unset($array2[$key]);
  2065. }
  2066. }
  2067. foreach($array2 as $key => $value) {
  2068. if ((is_bool($value)) || (is_string($value)) || (is_null($value))) {
  2069. unset($array1[$key]);
  2070. unset($array2[$key]);
  2071. }
  2072. }
  2073. $array1 = array_merge($array1);
  2074. $array2 = array_merge($array2);
  2075. return True;
  2076. } // function _checkTrendArrays()
  2077. /**
  2078. * INTERCEPT
  2079. *
  2080. * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values.
  2081. *
  2082. * @param array of mixed Data Series Y
  2083. * @param array of mixed Data Series X
  2084. * @return float
  2085. */
  2086. public static function INTERCEPT($yValues,$xValues) {
  2087. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2088. return self::$_errorCodes['value'];
  2089. }
  2090. $yValueCount = count($yValues);
  2091. $xValueCount = count($xValues);
  2092. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2093. return self::$_errorCodes['na'];
  2094. } elseif ($yValueCount == 1) {
  2095. return self::$_errorCodes['divisionbyzero'];
  2096. }
  2097. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2098. return $bestFitLinear->getIntersect();
  2099. } // function INTERCEPT()
  2100. /**
  2101. * RSQ
  2102. *
  2103. * Returns the square of the Pearson product moment correlation coefficient through data points in known_y's and known_x's.
  2104. *
  2105. * @param array of mixed Data Series Y
  2106. * @param array of mixed Data Series X
  2107. * @return float
  2108. */
  2109. public static function RSQ($yValues,$xValues) {
  2110. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2111. return self::$_errorCodes['value'];
  2112. }
  2113. $yValueCount = count($yValues);
  2114. $xValueCount = count($xValues);
  2115. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2116. return self::$_errorCodes['na'];
  2117. } elseif ($yValueCount == 1) {
  2118. return self::$_errorCodes['divisionbyzero'];
  2119. }
  2120. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2121. return $bestFitLinear->getGoodnessOfFit();
  2122. } // function RSQ()
  2123. /**
  2124. * SLOPE
  2125. *
  2126. * Returns the slope of the linear regression line through data points in known_y's and known_x's.
  2127. *
  2128. * @param array of mixed Data Series Y
  2129. * @param array of mixed Data Series X
  2130. * @return float
  2131. */
  2132. public static function SLOPE($yValues,$xValues) {
  2133. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2134. return self::$_errorCodes['value'];
  2135. }
  2136. $yValueCount = count($yValues);
  2137. $xValueCount = count($xValues);
  2138. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2139. return self::$_errorCodes['na'];
  2140. } elseif ($yValueCount == 1) {
  2141. return self::$_errorCodes['divisionbyzero'];
  2142. }
  2143. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2144. return $bestFitLinear->getSlope();
  2145. } // function SLOPE()
  2146. /**
  2147. * STEYX
  2148. *
  2149. * Returns the standard error of the predicted y-value for each x in the regression.
  2150. *
  2151. * @param array of mixed Data Series Y
  2152. * @param array of mixed Data Series X
  2153. * @return float
  2154. */
  2155. public static function STEYX($yValues,$xValues) {
  2156. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2157. return self::$_errorCodes['value'];
  2158. }
  2159. $yValueCount = count($yValues);
  2160. $xValueCount = count($xValues);
  2161. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2162. return self::$_errorCodes['na'];
  2163. } elseif ($yValueCount == 1) {
  2164. return self::$_errorCodes['divisionbyzero'];
  2165. }
  2166. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2167. return $bestFitLinear->getStdevOfResiduals();
  2168. } // function STEYX()
  2169. /**
  2170. * COVAR
  2171. *
  2172. * Returns covariance, the average of the products of deviations for each data point pair.
  2173. *
  2174. * @param array of mixed Data Series Y
  2175. * @param array of mixed Data Series X
  2176. * @return float
  2177. */
  2178. public static function COVAR($yValues,$xValues) {
  2179. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2180. return self::$_errorCodes['value'];
  2181. }
  2182. $yValueCount = count($yValues);
  2183. $xValueCount = count($xValues);
  2184. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2185. return self::$_errorCodes['na'];
  2186. } elseif ($yValueCount == 1) {
  2187. return self::$_errorCodes['divisionbyzero'];
  2188. }
  2189. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2190. return $bestFitLinear->getCovariance();
  2191. } // function COVAR()
  2192. /**
  2193. * CORREL
  2194. *
  2195. * Returns covariance, the average of the products of deviations for each data point pair.
  2196. *
  2197. * @param array of mixed Data Series Y
  2198. * @param array of mixed Data Series X
  2199. * @return float
  2200. */
  2201. public static function CORREL($yValues,$xValues=null) {
  2202. if ((is_null($xValues)) || (!is_array($yValues)) || (!is_array($xValues))) {
  2203. return self::$_errorCodes['value'];
  2204. }
  2205. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2206. return self::$_errorCodes['value'];
  2207. }
  2208. $yValueCount = count($yValues);
  2209. $xValueCount = count($xValues);
  2210. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2211. return self::$_errorCodes['na'];
  2212. } elseif ($yValueCount == 1) {
  2213. return self::$_errorCodes['divisionbyzero'];
  2214. }
  2215. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2216. return $bestFitLinear->getCorrelation();
  2217. } // function CORREL()
  2218. /**
  2219. * LINEST
  2220. *
  2221. * Calculates the statistics for a line by using the "least squares" method to calculate a straight line that best fits your data,
  2222. * and then returns an array that describes the line.
  2223. *
  2224. * @param array of mixed Data Series Y
  2225. * @param array of mixed Data Series X
  2226. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  2227. * @param boolean A logical value specifying whether to return additional regression statistics.
  2228. * @return array
  2229. */
  2230. public static function LINEST($yValues,$xValues=null,$const=True,$stats=False) {
  2231. $const = (is_null($const)) ? True : (boolean) self::flattenSingleValue($const);
  2232. $stats = (is_null($stats)) ? False : (boolean) self::flattenSingleValue($stats);
  2233. if (is_null($xValues)) $xValues = range(1,count(self::flattenArray($yValues)));
  2234. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2235. return self::$_errorCodes['value'];
  2236. }
  2237. $yValueCount = count($yValues);
  2238. $xValueCount = count($xValues);
  2239. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2240. return self::$_errorCodes['na'];
  2241. } elseif ($yValueCount == 1) {
  2242. return 0;
  2243. }
  2244. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues,$const);
  2245. if ($stats) {
  2246. return array( array( $bestFitLinear->getSlope(),
  2247. $bestFitLinear->getSlopeSE(),
  2248. $bestFitLinear->getGoodnessOfFit(),
  2249. $bestFitLinear->getF(),
  2250. $bestFitLinear->getSSRegression(),
  2251. ),
  2252. array( $bestFitLinear->getIntersect(),
  2253. $bestFitLinear->getIntersectSE(),
  2254. $bestFitLinear->getStdevOfResiduals(),
  2255. $bestFitLinear->getDFResiduals(),
  2256. $bestFitLinear->getSSResiduals()
  2257. )
  2258. );
  2259. } else {
  2260. return array( $bestFitLinear->getSlope(),
  2261. $bestFitLinear->getIntersect()
  2262. );
  2263. }
  2264. } // function LINEST()
  2265. /**
  2266. * LOGEST
  2267. *
  2268. * Calculates an exponential curve that best fits the X and Y data series,
  2269. * and then returns an array that describes the line.
  2270. *
  2271. * @param array of mixed Data Series Y
  2272. * @param array of mixed Data Series X
  2273. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  2274. * @param boolean A logical value specifying whether to return additional regression statistics.
  2275. * @return array
  2276. */
  2277. public static function LOGEST($yValues,$xValues=null,$const=True,$stats=False) {
  2278. $const = (is_null($const)) ? True : (boolean) self::flattenSingleValue($const);
  2279. $stats = (is_null($stats)) ? False : (boolean) self::flattenSingleValue($stats);
  2280. if (is_null($xValues)) $xValues = range(1,count(self::flattenArray($yValues)));
  2281. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2282. return self::$_errorCodes['value'];
  2283. }
  2284. $yValueCount = count($yValues);
  2285. $xValueCount = count($xValues);
  2286. foreach($yValues as $value) {
  2287. if ($value <= 0.0) {
  2288. return self::$_errorCodes['num'];
  2289. }
  2290. }
  2291. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2292. return self::$_errorCodes['na'];
  2293. } elseif ($yValueCount == 1) {
  2294. return 1;
  2295. }
  2296. $bestFitExponential = trendClass::calculate(trendClass::TREND_EXPONENTIAL,$yValues,$xValues,$const);
  2297. if ($stats) {
  2298. return array( array( $bestFitExponential->getSlope(),
  2299. $bestFitExponential->getSlopeSE(),
  2300. $bestFitExponential->getGoodnessOfFit(),
  2301. $bestFitExponential->getF(),
  2302. $bestFitExponential->getSSRegression(),
  2303. ),
  2304. array( $bestFitExponential->getIntersect(),
  2305. $bestFitExponential->getIntersectSE(),
  2306. $bestFitExponential->getStdevOfResiduals(),
  2307. $bestFitExponential->getDFResiduals(),
  2308. $bestFitExponential->getSSResiduals()
  2309. )
  2310. );
  2311. } else {
  2312. return array( $bestFitExponential->getSlope(),
  2313. $bestFitExponential->getIntersect()
  2314. );
  2315. }
  2316. } // function LOGEST()
  2317. /**
  2318. * FORECAST
  2319. *
  2320. * Calculates, or predicts, a future value by using existing values. The predicted value is a y-value for a given x-value.
  2321. *
  2322. * @param float Value of X for which we want to find Y
  2323. * @param array of mixed Data Series Y
  2324. * @param array of mixed Data Series X
  2325. * @return float
  2326. */
  2327. public static function FORECAST($xValue,$yValues,$xValues) {
  2328. $xValue = self::flattenSingleValue($xValue);
  2329. if (!is_numeric($xValue)) {
  2330. return self::$_errorCodes['value'];
  2331. }
  2332. if (!self::_checkTrendArrays($yValues,$xValues)) {
  2333. return self::$_errorCodes['value'];
  2334. }
  2335. $yValueCount = count($yValues);
  2336. $xValueCount = count($xValues);
  2337. if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
  2338. return self::$_errorCodes['na'];
  2339. } elseif ($yValueCount == 1) {
  2340. return self::$_errorCodes['divisionbyzero'];
  2341. }
  2342. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues);
  2343. return $bestFitLinear->getValueOfYForX($xValue);
  2344. } // function FORECAST()
  2345. /**
  2346. * TREND
  2347. *
  2348. * Returns values along a linear trend
  2349. *
  2350. * @param array of mixed Data Series Y
  2351. * @param array of mixed Data Series X
  2352. * @param array of mixed Values of X for which we want to find Y
  2353. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  2354. * @return array of float
  2355. */
  2356. public static function TREND($yValues,$xValues=array(),$newValues=array(),$const=True) {
  2357. $yValues = self::flattenArray($yValues);
  2358. $xValues = self::flattenArray($xValues);
  2359. $newValues = self::flattenArray($newValues);
  2360. $const = (is_null($const)) ? True : (boolean) self::flattenSingleValue($const);
  2361. $bestFitLinear = trendClass::calculate(trendClass::TREND_LINEAR,$yValues,$xValues,$const);
  2362. if (count($newValues) == 0) {
  2363. $newValues = $bestFitLinear->getXValues();
  2364. }
  2365. $returnArray = array();
  2366. foreach($newValues as $xValue) {
  2367. $returnArray[0][] = $bestFitLinear->getValueOfYForX($xValue);
  2368. }
  2369. return $returnArray;
  2370. } // function TREND()
  2371. /**
  2372. * GROWTH
  2373. *
  2374. * Returns values along a predicted emponential trend
  2375. *
  2376. * @param array of mixed Data Series Y
  2377. * @param array of mixed Data Series X
  2378. * @param array of mixed Values of X for which we want to find Y
  2379. * @param boolean A logical value specifying whether to force the intersect to equal 0.
  2380. * @return array of float
  2381. */
  2382. public static function GROWTH($yValues,$xValues=array(),$newValues=array(),$const=True) {
  2383. $yValues = self::flattenArray($yValues);
  2384. $xValues = self::flattenArray($xValues);
  2385. $newValues = self::flattenArray($newValues);
  2386. $const = (is_null($const)) ? True : (boolean) self::flattenSingleValue($const);
  2387. $bestFitExponential = trendClass::calculate(trendClass::TREND_EXPONENTIAL,$yValues,$xValues,$const);
  2388. if (count($newValues) == 0) {
  2389. $newValues = $bestFitExponential->getXValues();
  2390. }
  2391. $returnArray = array();
  2392. foreach($newValues as $xValue) {
  2393. $returnArray[0][] = $bestFitExponential->getValueOfYForX($xValue);
  2394. }
  2395. return $returnArray;
  2396. } // function GROWTH()
  2397. private static function _romanCut($num, $n) {
  2398. return ($num - ($num % $n ) ) / $n;
  2399. } // function _romanCut()
  2400. public static function ROMAN($aValue, $style=0) {
  2401. $aValue = (integer) self::flattenSingleValue($aValue);
  2402. $style = (is_null($style)) ? 0 : (integer) self::flattenSingleValue($style);
  2403. if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) {
  2404. return self::$_errorCodes['value'];
  2405. }
  2406. if ($aValue == 0) {
  2407. return '';
  2408. }
  2409. $mill = Array('', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM');
  2410. $cent = Array('', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM');
  2411. $tens = Array('', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC');
  2412. $ones = Array('', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX');
  2413. $roman = '';
  2414. while ($aValue > 5999) {
  2415. $roman .= 'M';
  2416. $aValue -= 1000;
  2417. }
  2418. $m = self::_romanCut($aValue, 1000); $aValue %= 1000;
  2419. $c = self::_romanCut($aValue, 100); $aValue %= 100;
  2420. $t = self::_romanCut($aValue, 10); $aValue %= 10;
  2421. return $roman.$mill[$m].$cent[$c].$tens[$t].$ones[$aValue];
  2422. } // function ROMAN()
  2423. /**
  2424. * SUBTOTAL
  2425. *
  2426. * Returns a subtotal in a list or database.
  2427. *
  2428. * @param int the number 1 to 11 that specifies which function to
  2429. * use in calculating subtotals within a list.
  2430. * @param array of mixed Data Series
  2431. * @return float
  2432. */
  2433. public static function SUBTOTAL() {
  2434. $aArgs = self::flattenArray(func_get_args());
  2435. // Calculate
  2436. $subtotal = array_shift($aArgs);
  2437. if ((is_numeric($subtotal)) && (!is_string($subtotal))) {
  2438. switch($subtotal) {
  2439. case 1 :
  2440. return self::AVERAGE($aArgs);
  2441. break;
  2442. case 2 :
  2443. return self::COUNT($aArgs);
  2444. break;
  2445. case 3 :
  2446. return self::COUNTA($aArgs);
  2447. break;
  2448. case 4 :
  2449. return self::MAX($aArgs);
  2450. break;
  2451. case 5 :
  2452. return self::MIN($aArgs);
  2453. break;
  2454. case 6 :
  2455. return self::PRODUCT($aArgs);
  2456. break;
  2457. case 7 :
  2458. return self::STDEV($aArgs);
  2459. break;
  2460. case 8 :
  2461. return self::STDEVP($aArgs);
  2462. break;
  2463. case 9 :
  2464. return self::SUM($aArgs);
  2465. break;
  2466. case 10 :
  2467. return self::VARFunc($aArgs);
  2468. break;
  2469. case 11 :
  2470. return self::VARP($aArgs);
  2471. break;
  2472. }
  2473. }
  2474. return self::$_errorCodes['value'];
  2475. } // function SUBTOTAL()
  2476. /**
  2477. * SQRTPI
  2478. *
  2479. * Returns the square root of (number * pi).
  2480. *
  2481. * @param float $number Number
  2482. * @return float Square Root of Number * Pi
  2483. */
  2484. public static function SQRTPI($number) {
  2485. $number = self::flattenSingleValue($number);
  2486. if (is_numeric($number)) {
  2487. if ($number < 0) {
  2488. return self::$_errorCodes['num'];
  2489. }
  2490. return sqrt($number * M_PI) ;
  2491. }
  2492. return self::$_errorCodes['value'];
  2493. } // function SQRTPI()
  2494. /**
  2495. * FACT
  2496. *
  2497. * Returns the factorial of a number.
  2498. *
  2499. * @param float $factVal Factorial Value
  2500. * @return int Factorial
  2501. */
  2502. public static function FACT($factVal) {
  2503. $factVal = self::flattenSingleValue($factVal);
  2504. if (is_numeric($factVal)) {
  2505. if ($factVal < 0) {
  2506. return self::$_errorCodes['num'];
  2507. }
  2508. $factLoop = floor($factVal);
  2509. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  2510. if ($factVal > $factLoop) {
  2511. return self::$_errorCodes['num'];
  2512. }
  2513. }
  2514. $factorial = 1;
  2515. while ($factLoop > 1) {
  2516. $factorial *= $factLoop--;
  2517. }
  2518. return $factorial ;
  2519. }
  2520. return self::$_errorCodes['value'];
  2521. } // function FACT()
  2522. /**
  2523. * FACTDOUBLE
  2524. *
  2525. * Returns the double factorial of a number.
  2526. *
  2527. * @param float $factVal Factorial Value
  2528. * @return int Double Factorial
  2529. */
  2530. public static function FACTDOUBLE($factVal) {
  2531. $factLoop = floor(self::flattenSingleValue($factVal));
  2532. if (is_numeric($factLoop)) {
  2533. if ($factVal < 0) {
  2534. return self::$_errorCodes['num'];
  2535. }
  2536. $factorial = 1;
  2537. while ($factLoop > 1) {
  2538. $factorial *= $factLoop--;
  2539. --$factLoop;
  2540. }
  2541. return $factorial ;
  2542. }
  2543. return self::$_errorCodes['value'];
  2544. } // function FACTDOUBLE()
  2545. /**
  2546. * MULTINOMIAL
  2547. *
  2548. * Returns the ratio of the factorial of a sum of values to the product of factorials.
  2549. *
  2550. * @param array of mixed Data Series
  2551. * @return float
  2552. */
  2553. public static function MULTINOMIAL() {
  2554. // Loop through arguments
  2555. $aArgs = self::flattenArray(func_get_args());
  2556. $summer = 0;
  2557. $divisor = 1;
  2558. foreach ($aArgs as $arg) {
  2559. // Is it a numeric value?
  2560. if (is_numeric($arg)) {
  2561. if ($arg < 1) {
  2562. return self::$_errorCodes['num'];
  2563. }
  2564. $summer += floor($arg);
  2565. $divisor *= self::FACT($arg);
  2566. } else {
  2567. return self::$_errorCodes['value'];
  2568. }
  2569. }
  2570. // Return
  2571. if ($summer > 0) {
  2572. $summer = self::FACT($summer);
  2573. return $summer / $divisor;
  2574. }
  2575. return 0;
  2576. } // function MULTINOMIAL()
  2577. /**
  2578. * CEILING
  2579. *
  2580. * Returns number rounded up, away from zero, to the nearest multiple of significance.
  2581. *
  2582. * @param float $number Number to round
  2583. * @param float $significance Significance
  2584. * @return float Rounded Number
  2585. */
  2586. public static function CEILING($number,$significance=null) {
  2587. $number = self::flattenSingleValue($number);
  2588. $significance = self::flattenSingleValue($significance);
  2589. if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  2590. $significance = $number/abs($number);
  2591. }
  2592. if ((is_numeric($number)) && (is_numeric($significance))) {
  2593. if (self::SIGN($number) == self::SIGN($significance)) {
  2594. if ($significance == 0.0) {
  2595. return 0;
  2596. }
  2597. return ceil($number / $significance) * $significance;
  2598. } else {
  2599. return self::$_errorCodes['num'];
  2600. }
  2601. }
  2602. return self::$_errorCodes['value'];
  2603. } // function CEILING()
  2604. /**
  2605. * EVEN
  2606. *
  2607. * Returns number rounded up to the nearest even integer.
  2608. *
  2609. * @param float $number Number to round
  2610. * @return int Rounded Number
  2611. */
  2612. public static function EVEN($number) {
  2613. $number = self::flattenSingleValue($number);
  2614. if (is_numeric($number)) {
  2615. $significance = 2 * self::SIGN($number);
  2616. return self::CEILING($number,$significance);
  2617. }
  2618. return self::$_errorCodes['value'];
  2619. } // function EVEN()
  2620. /**
  2621. * ODD
  2622. *
  2623. * Returns number rounded up to the nearest odd integer.
  2624. *
  2625. * @param float $number Number to round
  2626. * @return int Rounded Number
  2627. */
  2628. public static function ODD($number) {
  2629. $number = self::flattenSingleValue($number);
  2630. if (is_numeric($number)) {
  2631. $significance = self::SIGN($number);
  2632. if ($significance == 0) {
  2633. return 1;
  2634. }
  2635. $result = self::CEILING($number,$significance);
  2636. if (self::IS_EVEN($result)) {
  2637. $result += $significance;
  2638. }
  2639. return $result;
  2640. }
  2641. return self::$_errorCodes['value'];
  2642. } // function ODD()
  2643. /**
  2644. * INTVALUE
  2645. *
  2646. * Casts a floating point value to an integer
  2647. *
  2648. * @param float $number Number to cast to an integer
  2649. * @return integer Integer value
  2650. */
  2651. public static function INTVALUE($number) {
  2652. $number = self::flattenSingleValue($number);
  2653. if (is_numeric($number)) {
  2654. return (int) floor($number);
  2655. }
  2656. return self::$_errorCodes['value'];
  2657. } // function INTVALUE()
  2658. /**
  2659. * ROUNDUP
  2660. *
  2661. * Rounds a number up to a specified number of decimal places
  2662. *
  2663. * @param float $number Number to round
  2664. * @param int $digits Number of digits to which you want to round $number
  2665. * @return float Rounded Number
  2666. */
  2667. public static function ROUNDUP($number,$digits) {
  2668. $number = self::flattenSingleValue($number);
  2669. $digits = self::flattenSingleValue($digits);
  2670. if ((is_numeric($number)) && (is_numeric($digits))) {
  2671. $significance = pow(10,$digits);
  2672. if ($number < 0.0) {
  2673. return floor($number * $significance) / $significance;
  2674. } else {
  2675. return ceil($number * $significance) / $significance;
  2676. }
  2677. }
  2678. return self::$_errorCodes['value'];
  2679. } // function ROUNDUP()
  2680. /**
  2681. * ROUNDDOWN
  2682. *
  2683. * Rounds a number down to a specified number of decimal places
  2684. *
  2685. * @param float $number Number to round
  2686. * @param int $digits Number of digits to which you want to round $number
  2687. * @return float Rounded Number
  2688. */
  2689. public static function ROUNDDOWN($number,$digits) {
  2690. $number = self::flattenSingleValue($number);
  2691. $digits = self::flattenSingleValue($digits);
  2692. if ((is_numeric($number)) && (is_numeric($digits))) {
  2693. $significance = pow(10,$digits);
  2694. if ($number < 0.0) {
  2695. return ceil($number * $significance) / $significance;
  2696. } else {
  2697. return floor($number * $significance) / $significance;
  2698. }
  2699. }
  2700. return self::$_errorCodes['value'];
  2701. } // function ROUNDDOWN()
  2702. /**
  2703. * MROUND
  2704. *
  2705. * Rounds a number to the nearest multiple of a specified value
  2706. *
  2707. * @param float $number Number to round
  2708. * @param int $multiple Multiple to which you want to round $number
  2709. * @return float Rounded Number
  2710. */
  2711. public static function MROUND($number,$multiple) {
  2712. $number = self::flattenSingleValue($number);
  2713. $multiple = self::flattenSingleValue($multiple);
  2714. if ((is_numeric($number)) && (is_numeric($multiple))) {
  2715. if ($multiple == 0) {
  2716. return 0;
  2717. }
  2718. if ((self::SIGN($number)) == (self::SIGN($multiple))) {
  2719. $multiplier = 1 / $multiple;
  2720. return round($number * $multiplier) / $multiplier;
  2721. }
  2722. return self::$_errorCodes['num'];
  2723. }
  2724. return self::$_errorCodes['value'];
  2725. } // function MROUND()
  2726. /**
  2727. * SIGN
  2728. *
  2729. * Determines the sign of a number. Returns 1 if the number is positive, zero (0)
  2730. * if the number is 0, and -1 if the number is negative.
  2731. *
  2732. * @param float $number Number to round
  2733. * @return int sign value
  2734. */
  2735. public static function SIGN($number) {
  2736. $number = self::flattenSingleValue($number);
  2737. if (is_numeric($number)) {
  2738. if ($number == 0.0) {
  2739. return 0;
  2740. }
  2741. return $number / abs($number);
  2742. }
  2743. return self::$_errorCodes['value'];
  2744. } // function SIGN()
  2745. /**
  2746. * FLOOR
  2747. *
  2748. * Rounds number down, toward zero, to the nearest multiple of significance.
  2749. *
  2750. * @param float $number Number to round
  2751. * @param float $significance Significance
  2752. * @return float Rounded Number
  2753. */
  2754. public static function FLOOR($number,$significance=null) {
  2755. $number = self::flattenSingleValue($number);
  2756. $significance = self::flattenSingleValue($significance);
  2757. if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  2758. $significance = $number/abs($number);
  2759. }
  2760. if ((is_numeric($number)) && (is_numeric($significance))) {
  2761. if ((float) $significance == 0.0) {
  2762. return self::$_errorCodes['divisionbyzero'];
  2763. }
  2764. if (self::SIGN($number) == self::SIGN($significance)) {
  2765. return floor($number / $significance) * $significance;
  2766. } else {
  2767. return self::$_errorCodes['num'];
  2768. }
  2769. }
  2770. return self::$_errorCodes['value'];
  2771. } // function FLOOR()
  2772. /**
  2773. * PERMUT
  2774. *
  2775. * Returns the number of permutations for a given number of objects that can be
  2776. * selected from number objects. A permutation is any set or subset of objects or
  2777. * events where internal order is significant. Permutations are different from
  2778. * combinations, for which the internal order is not significant. Use this function
  2779. * for lottery-style probability calculations.
  2780. *
  2781. * @param int $numObjs Number of different objects
  2782. * @param int $numInSet Number of objects in each permutation
  2783. * @return int Number of permutations
  2784. */
  2785. public static function PERMUT($numObjs,$numInSet) {
  2786. $numObjs = self::flattenSingleValue($numObjs);
  2787. $numInSet = self::flattenSingleValue($numInSet);
  2788. if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
  2789. $numInSet = floor($numInSet);
  2790. if ($numObjs < $numInSet) {
  2791. return self::$_errorCodes['num'];
  2792. }
  2793. return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet));
  2794. }
  2795. return self::$_errorCodes['value'];
  2796. } // function PERMUT()
  2797. /**
  2798. * COMBIN
  2799. *
  2800. * Returns the number of combinations for a given number of items. Use COMBIN to
  2801. * determine the total possible number of groups for a given number of items.
  2802. *
  2803. * @param int $numObjs Number of different objects
  2804. * @param int $numInSet Number of objects in each combination
  2805. * @return int Number of combinations
  2806. */
  2807. public static function COMBIN($numObjs,$numInSet) {
  2808. $numObjs = self::flattenSingleValue($numObjs);
  2809. $numInSet = self::flattenSingleValue($numInSet);
  2810. if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
  2811. if ($numObjs < $numInSet) {
  2812. return self::$_errorCodes['num'];
  2813. } elseif ($numInSet < 0) {
  2814. return self::$_errorCodes['num'];
  2815. }
  2816. return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
  2817. }
  2818. return self::$_errorCodes['value'];
  2819. } // function COMBIN()
  2820. /**
  2821. * SERIESSUM
  2822. *
  2823. * Returns the sum of a power series
  2824. *
  2825. * @param float $x Input value to the power series
  2826. * @param float $n Initial power to which you want to raise $x
  2827. * @param float $m Step by which to increase $n for each term in the series
  2828. * @param array of mixed Data Series
  2829. * @return float
  2830. */
  2831. public static function SERIESSUM() {
  2832. // Return value
  2833. $returnValue = 0;
  2834. // Loop through arguments
  2835. $aArgs = self::flattenArray(func_get_args());
  2836. $x = array_shift($aArgs);
  2837. $n = array_shift($aArgs);
  2838. $m = array_shift($aArgs);
  2839. if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) {
  2840. // Calculate
  2841. $i = 0;
  2842. foreach($aArgs as $arg) {
  2843. // Is it a numeric value?
  2844. if ((is_numeric($arg)) && (!is_string($arg))) {
  2845. $returnValue += $arg * pow($x,$n + ($m * $i++));
  2846. } else {
  2847. return self::$_errorCodes['value'];
  2848. }
  2849. }
  2850. // Return
  2851. return $returnValue;
  2852. }
  2853. return self::$_errorCodes['value'];
  2854. } // function SERIESSUM()
  2855. /**
  2856. * STANDARDIZE
  2857. *
  2858. * Returns a normalized value from a distribution characterized by mean and standard_dev.
  2859. *
  2860. * @param float $value Value to normalize
  2861. * @param float $mean Mean Value
  2862. * @param float $stdDev Standard Deviation
  2863. * @return float Standardized value
  2864. */
  2865. public static function STANDARDIZE($value,$mean,$stdDev) {
  2866. $value = self::flattenSingleValue($value);
  2867. $mean = self::flattenSingleValue($mean);
  2868. $stdDev = self::flattenSingleValue($stdDev);
  2869. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  2870. if ($stdDev <= 0) {
  2871. return self::$_errorCodes['num'];
  2872. }
  2873. return ($value - $mean) / $stdDev ;
  2874. }
  2875. return self::$_errorCodes['value'];
  2876. } // function STANDARDIZE()
  2877. //
  2878. // Private method to return an array of the factors of the input value
  2879. //
  2880. private static function _factors($value) {
  2881. $startVal = floor(sqrt($value));
  2882. $factorArray = array();
  2883. for ($i = $startVal; $i > 1; --$i) {
  2884. if (($value % $i) == 0) {
  2885. $factorArray = array_merge($factorArray,self::_factors($value / $i));
  2886. $factorArray = array_merge($factorArray,self::_factors($i));
  2887. if ($i <= sqrt($value)) {
  2888. break;
  2889. }
  2890. }
  2891. }
  2892. if (count($factorArray) > 0) {
  2893. rsort($factorArray);
  2894. return $factorArray;
  2895. } else {
  2896. return array((integer) $value);
  2897. }
  2898. } // function _factors()
  2899. /**
  2900. * LCM
  2901. *
  2902. * Returns the lowest common multiplier of a series of numbers
  2903. *
  2904. * @param $array Values to calculate the Lowest Common Multiplier
  2905. * @return int Lowest Common Multiplier
  2906. */
  2907. public static function LCM() {
  2908. $aArgs = self::flattenArray(func_get_args());
  2909. $returnValue = 1;
  2910. $allPoweredFactors = array();
  2911. foreach($aArgs as $value) {
  2912. if (!is_numeric($value)) {
  2913. return self::$_errorCodes['value'];
  2914. }
  2915. if ($value == 0) {
  2916. return 0;
  2917. } elseif ($value < 0) {
  2918. return self::$_errorCodes['num'];
  2919. }
  2920. $myFactors = self::_factors(floor($value));
  2921. $myCountedFactors = array_count_values($myFactors);
  2922. $myPoweredFactors = array();
  2923. foreach($myCountedFactors as $myCountedFactor => $myCountedPower) {
  2924. $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor,$myCountedPower);
  2925. }
  2926. foreach($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
  2927. if (array_key_exists($myPoweredValue,$allPoweredFactors)) {
  2928. if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
  2929. $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
  2930. }
  2931. } else {
  2932. $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
  2933. }
  2934. }
  2935. }
  2936. foreach($allPoweredFactors as $allPoweredFactor) {
  2937. $returnValue *= (integer) $allPoweredFactor;
  2938. }
  2939. return $returnValue;
  2940. } // function LCM()
  2941. /**
  2942. * GCD
  2943. *
  2944. * Returns the greatest common divisor of a series of numbers
  2945. *
  2946. * @param $array Values to calculate the Greatest Common Divisor
  2947. * @return int Greatest Common Divisor
  2948. */
  2949. public static function GCD() {
  2950. $aArgs = self::flattenArray(func_get_args());
  2951. $returnValue = 1;
  2952. $allPoweredFactors = array();
  2953. foreach($aArgs as $value) {
  2954. if ($value == 0) {
  2955. break;
  2956. }
  2957. $myFactors = self::_factors($value);
  2958. $myCountedFactors = array_count_values($myFactors);
  2959. $allValuesFactors[] = $myCountedFactors;
  2960. }
  2961. $allValuesCount = count($allValuesFactors);
  2962. $mergedArray = $allValuesFactors[0];
  2963. for ($i=1;$i < $allValuesCount; ++$i) {
  2964. $mergedArray = array_intersect_key($mergedArray,$allValuesFactors[$i]);
  2965. }
  2966. $mergedArrayValues = count($mergedArray);
  2967. if ($mergedArrayValues == 0) {
  2968. return $returnValue;
  2969. } elseif ($mergedArrayValues > 1) {
  2970. foreach($mergedArray as $mergedKey => $mergedValue) {
  2971. foreach($allValuesFactors as $highestPowerTest) {
  2972. foreach($highestPowerTest as $testKey => $testValue) {
  2973. if (($testKey == $mergedKey) && ($testValue < $mergedValue)) {
  2974. $mergedArray[$mergedKey] = $testValue;
  2975. $mergedValue = $testValue;
  2976. }
  2977. }
  2978. }
  2979. }
  2980. $returnValue = 1;
  2981. foreach($mergedArray as $key => $value) {
  2982. $returnValue *= pow($key,$value);
  2983. }
  2984. return $returnValue;
  2985. } else {
  2986. $keys = array_keys($mergedArray);
  2987. $key = $keys[0];
  2988. $value = $mergedArray[$key];
  2989. foreach($allValuesFactors as $testValue) {
  2990. foreach($testValue as $mergedKey => $mergedValue) {
  2991. if (($mergedKey == $key) && ($mergedValue < $value)) {
  2992. $value = $mergedValue;
  2993. }
  2994. }
  2995. }
  2996. return pow($key,$value);
  2997. }
  2998. } // function GCD()
  2999. /**
  3000. * BINOMDIST
  3001. *
  3002. * Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
  3003. * a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
  3004. * when trials are independent, and when the probability of success is constant throughout the
  3005. * experiment. For example, BINOMDIST can calculate the probability that two of the next three
  3006. * babies born are male.
  3007. *
  3008. * @param float $value Number of successes in trials
  3009. * @param float $trials Number of trials
  3010. * @param float $probability Probability of success on each trial
  3011. * @param boolean $cumulative
  3012. * @return float
  3013. *
  3014. * @todo Cumulative distribution function
  3015. *
  3016. */
  3017. public static function BINOMDIST($value, $trials, $probability, $cumulative) {
  3018. $value = floor(self::flattenSingleValue($value));
  3019. $trials = floor(self::flattenSingleValue($trials));
  3020. $probability = self::flattenSingleValue($probability);
  3021. if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) {
  3022. if (($value < 0) || ($value > $trials)) {
  3023. return self::$_errorCodes['num'];
  3024. }
  3025. if (($probability < 0) || ($probability > 1)) {
  3026. return self::$_errorCodes['num'];
  3027. }
  3028. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3029. if ($cumulative) {
  3030. $summer = 0;
  3031. for ($i = 0; $i <= $value; ++$i) {
  3032. $summer += self::COMBIN($trials,$i) * pow($probability,$i) * pow(1 - $probability,$trials - $i);
  3033. }
  3034. return $summer;
  3035. } else {
  3036. return self::COMBIN($trials,$value) * pow($probability,$value) * pow(1 - $probability,$trials - $value) ;
  3037. }
  3038. }
  3039. }
  3040. return self::$_errorCodes['value'];
  3041. } // function BINOMDIST()
  3042. /**
  3043. * NEGBINOMDIST
  3044. *
  3045. * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
  3046. * there will be number_f failures before the number_s-th success, when the constant
  3047. * probability of a success is probability_s. This function is similar to the binomial
  3048. * distribution, except that the number of successes is fixed, and the number of trials is
  3049. * variable. Like the binomial, trials are assumed to be independent.
  3050. *
  3051. * @param float $failures Number of Failures
  3052. * @param float $successes Threshold number of Successes
  3053. * @param float $probability Probability of success on each trial
  3054. * @return float
  3055. *
  3056. */
  3057. public static function NEGBINOMDIST($failures, $successes, $probability) {
  3058. $failures = floor(self::flattenSingleValue($failures));
  3059. $successes = floor(self::flattenSingleValue($successes));
  3060. $probability = self::flattenSingleValue($probability);
  3061. if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) {
  3062. if (($failures < 0) || ($successes < 1)) {
  3063. return self::$_errorCodes['num'];
  3064. }
  3065. if (($probability < 0) || ($probability > 1)) {
  3066. return self::$_errorCodes['num'];
  3067. }
  3068. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  3069. if (($failures + $successes - 1) <= 0) {
  3070. return self::$_errorCodes['num'];
  3071. }
  3072. }
  3073. return (self::COMBIN($failures + $successes - 1,$successes - 1)) * (pow($probability,$successes)) * (pow(1 - $probability,$failures)) ;
  3074. }
  3075. return self::$_errorCodes['value'];
  3076. } // function NEGBINOMDIST()
  3077. /**
  3078. * CRITBINOM
  3079. *
  3080. * Returns the smallest value for which the cumulative binomial distribution is greater
  3081. * than or equal to a criterion value
  3082. *
  3083. * See http://support.microsoft.com/kb/828117/ for details of the algorithm used
  3084. *
  3085. * @param float $trials number of Bernoulli trials
  3086. * @param float $probability probability of a success on each trial
  3087. * @param float $alpha criterion value
  3088. * @return int
  3089. *
  3090. * @todo Warning. This implementation differs from the algorithm detailed on the MS
  3091. * web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess
  3092. * This eliminates a potential endless loop error, but may have an adverse affect on the
  3093. * accuracy of the function (although all my tests have so far returned correct results).
  3094. *
  3095. */
  3096. public static function CRITBINOM($trials, $probability, $alpha) {
  3097. $trials = floor(self::flattenSingleValue($trials));
  3098. $probability = self::flattenSingleValue($probability);
  3099. $alpha = self::flattenSingleValue($alpha);
  3100. if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) {
  3101. if ($trials < 0) {
  3102. return self::$_errorCodes['num'];
  3103. }
  3104. if (($probability < 0) || ($probability > 1)) {
  3105. return self::$_errorCodes['num'];
  3106. }
  3107. if (($alpha < 0) || ($alpha > 1)) {
  3108. return self::$_errorCodes['num'];
  3109. }
  3110. if ($alpha <= 0.5) {
  3111. $t = sqrt(log(1 / ($alpha * $alpha)));
  3112. $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));
  3113. } else {
  3114. $t = sqrt(log(1 / pow(1 - $alpha,2)));
  3115. $trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t);
  3116. }
  3117. $Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability)));
  3118. if ($Guess < 0) {
  3119. $Guess = 0;
  3120. } elseif ($Guess > $trials) {
  3121. $Guess = $trials;
  3122. }
  3123. $TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0;
  3124. $EssentiallyZero = 10e-12;
  3125. $m = floor($trials * $probability);
  3126. ++$TotalUnscaledProbability;
  3127. if ($m == $Guess) { ++$UnscaledPGuess; }
  3128. if ($m <= $Guess) { ++$UnscaledCumPGuess; }
  3129. $PreviousValue = 1;
  3130. $Done = False;
  3131. $k = $m + 1;
  3132. while ((!$Done) && ($k <= $trials)) {
  3133. $CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability));
  3134. $TotalUnscaledProbability += $CurrentValue;
  3135. if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
  3136. if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
  3137. if ($CurrentValue <= $EssentiallyZero) { $Done = True; }
  3138. $PreviousValue = $CurrentValue;
  3139. ++$k;
  3140. }
  3141. $PreviousValue = 1;
  3142. $Done = False;
  3143. $k = $m - 1;
  3144. while ((!$Done) && ($k >= 0)) {
  3145. $CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability);
  3146. $TotalUnscaledProbability += $CurrentValue;
  3147. if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
  3148. if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
  3149. if ($CurrentValue <= $EssentiallyZero) { $Done = True; }
  3150. $PreviousValue = $CurrentValue;
  3151. --$k;
  3152. }
  3153. $PGuess = $UnscaledPGuess / $TotalUnscaledProbability;
  3154. $CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability;
  3155. // $CumPGuessMinus1 = $CumPGuess - $PGuess;
  3156. $CumPGuessMinus1 = $CumPGuess - 1;
  3157. while (True) {
  3158. if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) {
  3159. return $Guess;
  3160. } elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) {
  3161. $PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability);
  3162. $CumPGuessMinus1 = $CumPGuess;
  3163. $CumPGuess = $CumPGuess + $PGuessPlus1;
  3164. $PGuess = $PGuessPlus1;
  3165. ++$Guess;
  3166. } elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) {
  3167. $PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability;
  3168. $CumPGuess = $CumPGuessMinus1;
  3169. $CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess;
  3170. $PGuess = $PGuessMinus1;
  3171. --$Guess;
  3172. }
  3173. }
  3174. }
  3175. return self::$_errorCodes['value'];
  3176. } // function CRITBINOM()
  3177. /**
  3178. * CHIDIST
  3179. *
  3180. * Returns the one-tailed probability of the chi-squared distribution.
  3181. *
  3182. * @param float $value Value for the function
  3183. * @param float $degrees degrees of freedom
  3184. * @return float
  3185. */
  3186. public static function CHIDIST($value, $degrees) {
  3187. $value = self::flattenSingleValue($value);
  3188. $degrees = floor(self::flattenSingleValue($degrees));
  3189. if ((is_numeric($value)) && (is_numeric($degrees))) {
  3190. if ($degrees < 1) {
  3191. return self::$_errorCodes['num'];
  3192. }
  3193. if ($value < 0) {
  3194. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  3195. return 1;
  3196. }
  3197. return self::$_errorCodes['num'];
  3198. }
  3199. return 1 - (self::_incompleteGamma($degrees/2,$value/2) / self::_gamma($degrees/2));
  3200. }
  3201. return self::$_errorCodes['value'];
  3202. } // function CHIDIST()
  3203. /**
  3204. * CHIINV
  3205. *
  3206. * Returns the one-tailed probability of the chi-squared distribution.
  3207. *
  3208. * @param float $probability Probability for the function
  3209. * @param float $degrees degrees of freedom
  3210. * @return float
  3211. */
  3212. public static function CHIINV($probability, $degrees) {
  3213. $probability = self::flattenSingleValue($probability);
  3214. $degrees = floor(self::flattenSingleValue($degrees));
  3215. if ((is_numeric($probability)) && (is_numeric($degrees))) {
  3216. $xLo = 100;
  3217. $xHi = 0;
  3218. $x = $xNew = 1;
  3219. $dx = 1;
  3220. $i = 0;
  3221. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  3222. // Apply Newton-Raphson step
  3223. $result = self::CHIDIST($x, $degrees);
  3224. $error = $result - $probability;
  3225. if ($error == 0.0) {
  3226. $dx = 0;
  3227. } elseif ($error < 0.0) {
  3228. $xLo = $x;
  3229. } else {
  3230. $xHi = $x;
  3231. }
  3232. // Avoid division by zero
  3233. if ($result != 0.0) {
  3234. $dx = $error / $result;
  3235. $xNew = $x - $dx;
  3236. }
  3237. // If the NR fails to converge (which for example may be the
  3238. // case if the initial guess is too rough) we apply a bisection
  3239. // step to determine a more narrow interval around the root.
  3240. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  3241. $xNew = ($xLo + $xHi) / 2;
  3242. $dx = $xNew - $x;
  3243. }
  3244. $x = $xNew;
  3245. }
  3246. if ($i == MAX_ITERATIONS) {
  3247. return self::$_errorCodes['na'];
  3248. }
  3249. return round($x,12);
  3250. }
  3251. return self::$_errorCodes['value'];
  3252. } // function CHIINV()
  3253. /**
  3254. * EXPONDIST
  3255. *
  3256. * Returns the exponential distribution. Use EXPONDIST to model the time between events,
  3257. * such as how long an automated bank teller takes to deliver cash. For example, you can
  3258. * use EXPONDIST to determine the probability that the process takes at most 1 minute.
  3259. *
  3260. * @param float $value Value of the function
  3261. * @param float $lambda The parameter value
  3262. * @param boolean $cumulative
  3263. * @return float
  3264. */
  3265. public static function EXPONDIST($value, $lambda, $cumulative) {
  3266. $value = self::flattenSingleValue($value);
  3267. $lambda = self::flattenSingleValue($lambda);
  3268. $cumulative = self::flattenSingleValue($cumulative);
  3269. if ((is_numeric($value)) && (is_numeric($lambda))) {
  3270. if (($value < 0) || ($lambda < 0)) {
  3271. return self::$_errorCodes['num'];
  3272. }
  3273. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3274. if ($cumulative) {
  3275. return 1 - exp(0-$value*$lambda);
  3276. } else {
  3277. return $lambda * exp(0-$value*$lambda);
  3278. }
  3279. }
  3280. }
  3281. return self::$_errorCodes['value'];
  3282. } // function EXPONDIST()
  3283. /**
  3284. * FISHER
  3285. *
  3286. * Returns the Fisher transformation at x. This transformation produces a function that
  3287. * is normally distributed rather than skewed. Use this function to perform hypothesis
  3288. * testing on the correlation coefficient.
  3289. *
  3290. * @param float $value
  3291. * @return float
  3292. */
  3293. public static function FISHER($value) {
  3294. $value = self::flattenSingleValue($value);
  3295. if (is_numeric($value)) {
  3296. if (($value <= -1) || ($value >= 1)) {
  3297. return self::$_errorCodes['num'];
  3298. }
  3299. return 0.5 * log((1+$value)/(1-$value));
  3300. }
  3301. return self::$_errorCodes['value'];
  3302. } // function FISHER()
  3303. /**
  3304. * FISHERINV
  3305. *
  3306. * Returns the inverse of the Fisher transformation. Use this transformation when
  3307. * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
  3308. * FISHERINV(y) = x.
  3309. *
  3310. * @param float $value
  3311. * @return float
  3312. */
  3313. public static function FISHERINV($value) {
  3314. $value = self::flattenSingleValue($value);
  3315. if (is_numeric($value)) {
  3316. return (exp(2 * $value) - 1) / (exp(2 * $value) + 1);
  3317. }
  3318. return self::$_errorCodes['value'];
  3319. } // function FISHERINV()
  3320. // Function cache for _logBeta function
  3321. private static $_logBetaCache_p = 0.0;
  3322. private static $_logBetaCache_q = 0.0;
  3323. private static $_logBetaCache_result = 0.0;
  3324. /**
  3325. * The natural logarithm of the beta function.
  3326. * @param p require p>0
  3327. * @param q require q>0
  3328. * @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
  3329. * @author Jaco van Kooten
  3330. */
  3331. private static function _logBeta($p, $q) {
  3332. if ($p != self::$_logBetaCache_p || $q != self::$_logBetaCache_q) {
  3333. self::$_logBetaCache_p = $p;
  3334. self::$_logBetaCache_q = $q;
  3335. if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
  3336. self::$_logBetaCache_result = 0.0;
  3337. } else {
  3338. self::$_logBetaCache_result = self::_logGamma($p) + self::_logGamma($q) - self::_logGamma($p + $q);
  3339. }
  3340. }
  3341. return self::$_logBetaCache_result;
  3342. } // function _logBeta()
  3343. /**
  3344. * Evaluates of continued fraction part of incomplete beta function.
  3345. * Based on an idea from Numerical Recipes (W.H. Press et al, 1992).
  3346. * @author Jaco van Kooten
  3347. */
  3348. private static function _betaFraction($x, $p, $q) {
  3349. $c = 1.0;
  3350. $sum_pq = $p + $q;
  3351. $p_plus = $p + 1.0;
  3352. $p_minus = $p - 1.0;
  3353. $h = 1.0 - $sum_pq * $x / $p_plus;
  3354. if (abs($h) < XMININ) {
  3355. $h = XMININ;
  3356. }
  3357. $h = 1.0 / $h;
  3358. $frac = $h;
  3359. $m = 1;
  3360. $delta = 0.0;
  3361. while ($m <= MAX_ITERATIONS && abs($delta-1.0) > PRECISION ) {
  3362. $m2 = 2 * $m;
  3363. // even index for d
  3364. $d = $m * ($q - $m) * $x / ( ($p_minus + $m2) * ($p + $m2));
  3365. $h = 1.0 + $d * $h;
  3366. if (abs($h) < XMININ) {
  3367. $h = XMININ;
  3368. }
  3369. $h = 1.0 / $h;
  3370. $c = 1.0 + $d / $c;
  3371. if (abs($c) < XMININ) {
  3372. $c = XMININ;
  3373. }
  3374. $frac *= $h * $c;
  3375. // odd index for d
  3376. $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2));
  3377. $h = 1.0 + $d * $h;
  3378. if (abs($h) < XMININ) {
  3379. $h = XMININ;
  3380. }
  3381. $h = 1.0 / $h;
  3382. $c = 1.0 + $d / $c;
  3383. if (abs($c) < XMININ) {
  3384. $c = XMININ;
  3385. }
  3386. $delta = $h * $c;
  3387. $frac *= $delta;
  3388. ++$m;
  3389. }
  3390. return $frac;
  3391. } // function _betaFraction()
  3392. /**
  3393. * logGamma function
  3394. *
  3395. * @version 1.1
  3396. * @author Jaco van Kooten
  3397. *
  3398. * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
  3399. *
  3400. * The natural logarithm of the gamma function. <br />
  3401. * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
  3402. * Applied Mathematics Division <br />
  3403. * Argonne National Laboratory <br />
  3404. * Argonne, IL 60439 <br />
  3405. * <p>
  3406. * References:
  3407. * <ol>
  3408. * <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
  3409. * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
  3410. * <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
  3411. * <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
  3412. * </ol>
  3413. * </p>
  3414. * <p>
  3415. * From the original documentation:
  3416. * </p>
  3417. * <p>
  3418. * This routine calculates the LOG(GAMMA) function for a positive real argument X.
  3419. * Computation is based on an algorithm outlined in references 1 and 2.
  3420. * The program uses rational functions that theoretically approximate LOG(GAMMA)
  3421. * to at least 18 significant decimal digits. The approximation for X > 12 is from
  3422. * reference 3, while approximations for X < 12.0 are similar to those in reference
  3423. * 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
  3424. * the compiler, the intrinsic functions, and proper selection of the
  3425. * machine-dependent constants.
  3426. * </p>
  3427. * <p>
  3428. * Error returns: <br />
  3429. * The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
  3430. * The computation is believed to be free of underflow and overflow.
  3431. * </p>
  3432. * @return MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
  3433. */
  3434. // Function cache for logGamma
  3435. private static $_logGammaCache_result = 0.0;
  3436. private static $_logGammaCache_x = 0.0;
  3437. private static function _logGamma($x) {
  3438. // Log Gamma related constants
  3439. static $lg_d1 = -0.5772156649015328605195174;
  3440. static $lg_d2 = 0.4227843350984671393993777;
  3441. static $lg_d4 = 1.791759469228055000094023;
  3442. static $lg_p1 = array( 4.945235359296727046734888,
  3443. 201.8112620856775083915565,
  3444. 2290.838373831346393026739,
  3445. 11319.67205903380828685045,
  3446. 28557.24635671635335736389,
  3447. 38484.96228443793359990269,
  3448. 26377.48787624195437963534,
  3449. 7225.813979700288197698961 );
  3450. static $lg_p2 = array( 4.974607845568932035012064,
  3451. 542.4138599891070494101986,
  3452. 15506.93864978364947665077,
  3453. 184793.2904445632425417223,
  3454. 1088204.76946882876749847,
  3455. 3338152.967987029735917223,
  3456. 5106661.678927352456275255,
  3457. 3074109.054850539556250927 );
  3458. static $lg_p4 = array( 14745.02166059939948905062,
  3459. 2426813.369486704502836312,
  3460. 121475557.4045093227939592,
  3461. 2663432449.630976949898078,
  3462. 29403789566.34553899906876,
  3463. 170266573776.5398868392998,
  3464. 492612579337.743088758812,
  3465. 560625185622.3951465078242 );
  3466. static $lg_q1 = array( 67.48212550303777196073036,
  3467. 1113.332393857199323513008,
  3468. 7738.757056935398733233834,
  3469. 27639.87074403340708898585,
  3470. 54993.10206226157329794414,
  3471. 61611.22180066002127833352,
  3472. 36351.27591501940507276287,
  3473. 8785.536302431013170870835 );
  3474. static $lg_q2 = array( 183.0328399370592604055942,
  3475. 7765.049321445005871323047,
  3476. 133190.3827966074194402448,
  3477. 1136705.821321969608938755,
  3478. 5267964.117437946917577538,
  3479. 13467014.54311101692290052,
  3480. 17827365.30353274213975932,
  3481. 9533095.591844353613395747 );
  3482. static $lg_q4 = array( 2690.530175870899333379843,
  3483. 639388.5654300092398984238,
  3484. 41355999.30241388052042842,
  3485. 1120872109.61614794137657,
  3486. 14886137286.78813811542398,
  3487. 101680358627.2438228077304,
  3488. 341747634550.7377132798597,
  3489. 446315818741.9713286462081 );
  3490. static $lg_c = array( -0.001910444077728,
  3491. 8.4171387781295e-4,
  3492. -5.952379913043012e-4,
  3493. 7.93650793500350248e-4,
  3494. -0.002777777777777681622553,
  3495. 0.08333333333333333331554247,
  3496. 0.0057083835261 );
  3497. // Rough estimate of the fourth root of logGamma_xBig
  3498. static $lg_frtbig = 2.25e76;
  3499. static $pnt68 = 0.6796875;
  3500. if ($x == self::$_logGammaCache_x) {
  3501. return self::$_logGammaCache_result;
  3502. }
  3503. $y = $x;
  3504. if ($y > 0.0 && $y <= LOG_GAMMA_X_MAX_VALUE) {
  3505. if ($y <= EPS) {
  3506. $res = -log(y);
  3507. } elseif ($y <= 1.5) {
  3508. // ---------------------
  3509. // EPS .LT. X .LE. 1.5
  3510. // ---------------------
  3511. if ($y < $pnt68) {
  3512. $corr = -log($y);
  3513. $xm1 = $y;
  3514. } else {
  3515. $corr = 0.0;
  3516. $xm1 = $y - 1.0;
  3517. }
  3518. if ($y <= 0.5 || $y >= $pnt68) {
  3519. $xden = 1.0;
  3520. $xnum = 0.0;
  3521. for ($i = 0; $i < 8; ++$i) {
  3522. $xnum = $xnum * $xm1 + $lg_p1[$i];
  3523. $xden = $xden * $xm1 + $lg_q1[$i];
  3524. }
  3525. $res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden));
  3526. } else {
  3527. $xm2 = $y - 1.0;
  3528. $xden = 1.0;
  3529. $xnum = 0.0;
  3530. for ($i = 0; $i < 8; ++$i) {
  3531. $xnum = $xnum * $xm2 + $lg_p2[$i];
  3532. $xden = $xden * $xm2 + $lg_q2[$i];
  3533. }
  3534. $res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
  3535. }
  3536. } elseif ($y <= 4.0) {
  3537. // ---------------------
  3538. // 1.5 .LT. X .LE. 4.0
  3539. // ---------------------
  3540. $xm2 = $y - 2.0;
  3541. $xden = 1.0;
  3542. $xnum = 0.0;
  3543. for ($i = 0; $i < 8; ++$i) {
  3544. $xnum = $xnum * $xm2 + $lg_p2[$i];
  3545. $xden = $xden * $xm2 + $lg_q2[$i];
  3546. }
  3547. $res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
  3548. } elseif ($y <= 12.0) {
  3549. // ----------------------
  3550. // 4.0 .LT. X .LE. 12.0
  3551. // ----------------------
  3552. $xm4 = $y - 4.0;
  3553. $xden = -1.0;
  3554. $xnum = 0.0;
  3555. for ($i = 0; $i < 8; ++$i) {
  3556. $xnum = $xnum * $xm4 + $lg_p4[$i];
  3557. $xden = $xden * $xm4 + $lg_q4[$i];
  3558. }
  3559. $res = $lg_d4 + $xm4 * ($xnum / $xden);
  3560. } else {
  3561. // ---------------------------------
  3562. // Evaluate for argument .GE. 12.0
  3563. // ---------------------------------
  3564. $res = 0.0;
  3565. if ($y <= $lg_frtbig) {
  3566. $res = $lg_c[6];
  3567. $ysq = $y * $y;
  3568. for ($i = 0; $i < 6; ++$i)
  3569. $res = $res / $ysq + $lg_c[$i];
  3570. }
  3571. $res /= $y;
  3572. $corr = log($y);
  3573. $res = $res + log(SQRT2PI) - 0.5 * $corr;
  3574. $res += $y * ($corr - 1.0);
  3575. }
  3576. } else {
  3577. // --------------------------
  3578. // Return for bad arguments
  3579. // --------------------------
  3580. $res = MAX_VALUE;
  3581. }
  3582. // ------------------------------
  3583. // Final adjustments and return
  3584. // ------------------------------
  3585. self::$_logGammaCache_x = $x;
  3586. self::$_logGammaCache_result = $res;
  3587. return $res;
  3588. } // function _logGamma()
  3589. /**
  3590. * Beta function.
  3591. *
  3592. * @author Jaco van Kooten
  3593. *
  3594. * @param p require p>0
  3595. * @param q require q>0
  3596. * @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
  3597. */
  3598. private static function _beta($p, $q) {
  3599. if ($p <= 0.0 || $q <= 0.0 || ($p + $q) > LOG_GAMMA_X_MAX_VALUE) {
  3600. return 0.0;
  3601. } else {
  3602. return exp(self::_logBeta($p, $q));
  3603. }
  3604. } // function _beta()
  3605. /**
  3606. * Incomplete beta function
  3607. *
  3608. * @author Jaco van Kooten
  3609. * @author Paul Meagher
  3610. *
  3611. * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992).
  3612. * @param x require 0<=x<=1
  3613. * @param p require p>0
  3614. * @param q require q>0
  3615. * @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
  3616. */
  3617. private static function _incompleteBeta($x, $p, $q) {
  3618. if ($x <= 0.0) {
  3619. return 0.0;
  3620. } elseif ($x >= 1.0) {
  3621. return 1.0;
  3622. } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
  3623. return 0.0;
  3624. }
  3625. $beta_gam = exp((0 - self::_logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x));
  3626. if ($x < ($p + 1.0) / ($p + $q + 2.0)) {
  3627. return $beta_gam * self::_betaFraction($x, $p, $q) / $p;
  3628. } else {
  3629. return 1.0 - ($beta_gam * self::_betaFraction(1 - $x, $q, $p) / $q);
  3630. }
  3631. } // function _incompleteBeta()
  3632. /**
  3633. * BETADIST
  3634. *
  3635. * Returns the beta distribution.
  3636. *
  3637. * @param float $value Value at which you want to evaluate the distribution
  3638. * @param float $alpha Parameter to the distribution
  3639. * @param float $beta Parameter to the distribution
  3640. * @param boolean $cumulative
  3641. * @return float
  3642. *
  3643. */
  3644. public static function BETADIST($value,$alpha,$beta,$rMin=0,$rMax=1) {
  3645. $value = self::flattenSingleValue($value);
  3646. $alpha = self::flattenSingleValue($alpha);
  3647. $beta = self::flattenSingleValue($beta);
  3648. $rMin = self::flattenSingleValue($rMin);
  3649. $rMax = self::flattenSingleValue($rMax);
  3650. if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) {
  3651. if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) {
  3652. return self::$_errorCodes['num'];
  3653. }
  3654. if ($rMin > $rMax) {
  3655. $tmp = $rMin;
  3656. $rMin = $rMax;
  3657. $rMax = $tmp;
  3658. }
  3659. $value -= $rMin;
  3660. $value /= ($rMax - $rMin);
  3661. return self::_incompleteBeta($value,$alpha,$beta);
  3662. }
  3663. return self::$_errorCodes['value'];
  3664. } // function BETADIST()
  3665. /**
  3666. * BETAINV
  3667. *
  3668. * Returns the inverse of the beta distribution.
  3669. *
  3670. * @param float $probability Probability at which you want to evaluate the distribution
  3671. * @param float $alpha Parameter to the distribution
  3672. * @param float $beta Parameter to the distribution
  3673. * @param boolean $cumulative
  3674. * @return float
  3675. *
  3676. */
  3677. public static function BETAINV($probability,$alpha,$beta,$rMin=0,$rMax=1) {
  3678. $probability = self::flattenSingleValue($probability);
  3679. $alpha = self::flattenSingleValue($alpha);
  3680. $beta = self::flattenSingleValue($beta);
  3681. $rMin = self::flattenSingleValue($rMin);
  3682. $rMax = self::flattenSingleValue($rMax);
  3683. if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) {
  3684. if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) {
  3685. return self::$_errorCodes['num'];
  3686. }
  3687. if ($rMin > $rMax) {
  3688. $tmp = $rMin;
  3689. $rMin = $rMax;
  3690. $rMax = $tmp;
  3691. }
  3692. $a = 0;
  3693. $b = 2;
  3694. $i = 0;
  3695. while ((($b - $a) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  3696. $guess = ($a + $b) / 2;
  3697. $result = self::BETADIST($guess, $alpha, $beta);
  3698. if (($result == $probability) || ($result == 0)) {
  3699. $b = $a;
  3700. } elseif ($result > $probability) {
  3701. $b = $guess;
  3702. } else {
  3703. $a = $guess;
  3704. }
  3705. }
  3706. if ($i == MAX_ITERATIONS) {
  3707. return self::$_errorCodes['na'];
  3708. }
  3709. return round($rMin + $guess * ($rMax - $rMin),12);
  3710. }
  3711. return self::$_errorCodes['value'];
  3712. } // function BETAINV()
  3713. //
  3714. // Private implementation of the incomplete Gamma function
  3715. //
  3716. private static function _incompleteGamma($a,$x) {
  3717. static $max = 32;
  3718. $summer = 0;
  3719. for ($n=0; $n<=$max; ++$n) {
  3720. $divisor = $a;
  3721. for ($i=1; $i<=$n; ++$i) {
  3722. $divisor *= ($a + $i);
  3723. }
  3724. $summer += (pow($x,$n) / $divisor);
  3725. }
  3726. return pow($x,$a) * exp(0-$x) * $summer;
  3727. } // function _incompleteGamma()
  3728. //
  3729. // Private implementation of the Gamma function
  3730. //
  3731. private static function _gamma($data) {
  3732. if ($data == 0.0) return 0;
  3733. static $p0 = 1.000000000190015;
  3734. static $p = array ( 1 => 76.18009172947146,
  3735. 2 => -86.50532032941677,
  3736. 3 => 24.01409824083091,
  3737. 4 => -1.231739572450155,
  3738. 5 => 1.208650973866179e-3,
  3739. 6 => -5.395239384953e-6
  3740. );
  3741. $y = $x = $data;
  3742. $tmp = $x + 5.5;
  3743. $tmp -= ($x + 0.5) * log($tmp);
  3744. $summer = $p0;
  3745. for ($j=1;$j<=6;++$j) {
  3746. $summer += ($p[$j] / ++$y);
  3747. }
  3748. return exp(0 - $tmp + log(SQRT2PI * $summer / $x));
  3749. } // function _gamma()
  3750. /**
  3751. * GAMMADIST
  3752. *
  3753. * Returns the gamma distribution.
  3754. *
  3755. * @param float $value Value at which you want to evaluate the distribution
  3756. * @param float $a Parameter to the distribution
  3757. * @param float $b Parameter to the distribution
  3758. * @param boolean $cumulative
  3759. * @return float
  3760. *
  3761. */
  3762. public static function GAMMADIST($value,$a,$b,$cumulative) {
  3763. $value = self::flattenSingleValue($value);
  3764. $a = self::flattenSingleValue($a);
  3765. $b = self::flattenSingleValue($b);
  3766. if ((is_numeric($value)) && (is_numeric($a)) && (is_numeric($b))) {
  3767. if (($value < 0) || ($a <= 0) || ($b <= 0)) {
  3768. return self::$_errorCodes['num'];
  3769. }
  3770. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3771. if ($cumulative) {
  3772. return self::_incompleteGamma($a,$value / $b) / self::_gamma($a);
  3773. } else {
  3774. return (1 / (pow($b,$a) * self::_gamma($a))) * pow($value,$a-1) * exp(0-($value / $b));
  3775. }
  3776. }
  3777. }
  3778. return self::$_errorCodes['value'];
  3779. } // function GAMMADIST()
  3780. /**
  3781. * GAMMAINV
  3782. *
  3783. * Returns the inverse of the beta distribution.
  3784. *
  3785. * @param float $probability Probability at which you want to evaluate the distribution
  3786. * @param float $alpha Parameter to the distribution
  3787. * @param float $beta Parameter to the distribution
  3788. * @return float
  3789. *
  3790. */
  3791. public static function GAMMAINV($probability,$alpha,$beta) {
  3792. $probability = self::flattenSingleValue($probability);
  3793. $alpha = self::flattenSingleValue($alpha);
  3794. $beta = self::flattenSingleValue($beta);
  3795. if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta))) {
  3796. if (($alpha <= 0) || ($beta <= 0) || ($probability < 0) || ($probability > 1)) {
  3797. return self::$_errorCodes['num'];
  3798. }
  3799. $xLo = 0;
  3800. $xHi = $alpha * $beta * 5;
  3801. $x = $xNew = 1;
  3802. $error = $pdf = 0;
  3803. $dx = 1024;
  3804. $i = 0;
  3805. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  3806. // Apply Newton-Raphson step
  3807. $error = self::GAMMADIST($x, $alpha, $beta, True) - $probability;
  3808. if ($error < 0.0) {
  3809. $xLo = $x;
  3810. } else {
  3811. $xHi = $x;
  3812. }
  3813. $pdf = self::GAMMADIST($x, $alpha, $beta, False);
  3814. // Avoid division by zero
  3815. if ($pdf != 0.0) {
  3816. $dx = $error / $pdf;
  3817. $xNew = $x - $dx;
  3818. }
  3819. // If the NR fails to converge (which for example may be the
  3820. // case if the initial guess is too rough) we apply a bisection
  3821. // step to determine a more narrow interval around the root.
  3822. if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) {
  3823. $xNew = ($xLo + $xHi) / 2;
  3824. $dx = $xNew - $x;
  3825. }
  3826. $x = $xNew;
  3827. }
  3828. if ($i == MAX_ITERATIONS) {
  3829. return self::$_errorCodes['na'];
  3830. }
  3831. return $x;
  3832. }
  3833. return self::$_errorCodes['value'];
  3834. } // function GAMMAINV()
  3835. /**
  3836. * GAMMALN
  3837. *
  3838. * Returns the natural logarithm of the gamma function.
  3839. *
  3840. * @param float $value
  3841. * @return float
  3842. */
  3843. public static function GAMMALN($value) {
  3844. $value = self::flattenSingleValue($value);
  3845. if (is_numeric($value)) {
  3846. if ($value <= 0) {
  3847. return self::$_errorCodes['num'];
  3848. }
  3849. return log(self::_gamma($value));
  3850. }
  3851. return self::$_errorCodes['value'];
  3852. } // function GAMMALN()
  3853. /**
  3854. * NORMDIST
  3855. *
  3856. * Returns the normal distribution for the specified mean and standard deviation. This
  3857. * function has a very wide range of applications in statistics, including hypothesis
  3858. * testing.
  3859. *
  3860. * @param float $value
  3861. * @param float $mean Mean Value
  3862. * @param float $stdDev Standard Deviation
  3863. * @param boolean $cumulative
  3864. * @return float
  3865. *
  3866. */
  3867. public static function NORMDIST($value, $mean, $stdDev, $cumulative) {
  3868. $value = self::flattenSingleValue($value);
  3869. $mean = self::flattenSingleValue($mean);
  3870. $stdDev = self::flattenSingleValue($stdDev);
  3871. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3872. if ($stdDev < 0) {
  3873. return self::$_errorCodes['num'];
  3874. }
  3875. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  3876. if ($cumulative) {
  3877. return 0.5 * (1 + self::_erfVal(($value - $mean) / ($stdDev * sqrt(2))));
  3878. } else {
  3879. return (1 / (SQRT2PI * $stdDev)) * exp(0 - (pow($value - $mean,2) / (2 * ($stdDev * $stdDev))));
  3880. }
  3881. }
  3882. }
  3883. return self::$_errorCodes['value'];
  3884. } // function NORMDIST()
  3885. /**
  3886. * NORMSDIST
  3887. *
  3888. * Returns the standard normal cumulative distribution function. The distribution has
  3889. * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
  3890. * table of standard normal curve areas.
  3891. *
  3892. * @param float $value
  3893. * @return float
  3894. */
  3895. public static function NORMSDIST($value) {
  3896. $value = self::flattenSingleValue($value);
  3897. return self::NORMDIST($value, 0, 1, True);
  3898. } // function NORMSDIST()
  3899. /**
  3900. * LOGNORMDIST
  3901. *
  3902. * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
  3903. * with parameters mean and standard_dev.
  3904. *
  3905. * @param float $value
  3906. * @return float
  3907. */
  3908. public static function LOGNORMDIST($value, $mean, $stdDev) {
  3909. $value = self::flattenSingleValue($value);
  3910. $mean = self::flattenSingleValue($mean);
  3911. $stdDev = self::flattenSingleValue($stdDev);
  3912. if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  3913. if (($value <= 0) || ($stdDev <= 0)) {
  3914. return self::$_errorCodes['num'];
  3915. }
  3916. return self::NORMSDIST((log($value) - $mean) / $stdDev);
  3917. }
  3918. return self::$_errorCodes['value'];
  3919. } // function LOGNORMDIST()
  3920. /***************************************************************************
  3921. * inverse_ncdf.php
  3922. * -------------------
  3923. * begin : Friday, January 16, 2004
  3924. * copyright : (C) 2004 Michael Nickerson
  3925. * email : nickersonm@yahoo.com
  3926. *
  3927. ***************************************************************************/
  3928. private static function _inverse_ncdf($p) {
  3929. // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
  3930. // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
  3931. // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
  3932. // I have not checked the accuracy of this implementation. Be aware that PHP
  3933. // will truncate the coeficcients to 14 digits.
  3934. // You have permission to use and distribute this function freely for
  3935. // whatever purpose you want, but please show common courtesy and give credit
  3936. // where credit is due.
  3937. // Input paramater is $p - probability - where 0 < p < 1.
  3938. // Coefficients in rational approximations
  3939. static $a = array( 1 => -3.969683028665376e+01,
  3940. 2 => 2.209460984245205e+02,
  3941. 3 => -2.759285104469687e+02,
  3942. 4 => 1.383577518672690e+02,
  3943. 5 => -3.066479806614716e+01,
  3944. 6 => 2.506628277459239e+00
  3945. );
  3946. static $b = array( 1 => -5.447609879822406e+01,
  3947. 2 => 1.615858368580409e+02,
  3948. 3 => -1.556989798598866e+02,
  3949. 4 => 6.680131188771972e+01,
  3950. 5 => -1.328068155288572e+01
  3951. );
  3952. static $c = array( 1 => -7.784894002430293e-03,
  3953. 2 => -3.223964580411365e-01,
  3954. 3 => -2.400758277161838e+00,
  3955. 4 => -2.549732539343734e+00,
  3956. 5 => 4.374664141464968e+00,
  3957. 6 => 2.938163982698783e+00
  3958. );
  3959. static $d = array( 1 => 7.784695709041462e-03,
  3960. 2 => 3.224671290700398e-01,
  3961. 3 => 2.445134137142996e+00,
  3962. 4 => 3.754408661907416e+00
  3963. );
  3964. // Define lower and upper region break-points.
  3965. $p_low = 0.02425; //Use lower region approx. below this
  3966. $p_high = 1 - $p_low; //Use upper region approx. above this
  3967. if (0 < $p && $p < $p_low) {
  3968. // Rational approximation for lower region.
  3969. $q = sqrt(-2 * log($p));
  3970. return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
  3971. (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
  3972. } elseif ($p_low <= $p && $p <= $p_high) {
  3973. // Rational approximation for central region.
  3974. $q = $p - 0.5;
  3975. $r = $q * $q;
  3976. return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
  3977. ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
  3978. } elseif ($p_high < $p && $p < 1) {
  3979. // Rational approximation for upper region.
  3980. $q = sqrt(-2 * log(1 - $p));
  3981. return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
  3982. (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
  3983. }
  3984. // If 0 < p < 1, return a null value
  3985. return self::$_errorCodes['null'];
  3986. } // function _inverse_ncdf()
  3987. private static function _inverse_ncdf2($prob) {
  3988. // Approximation of inverse standard normal CDF developed by
  3989. // B. Moro, "The Full Monte," Risk 8(2), Feb 1995, 57-58.
  3990. $a1 = 2.50662823884;
  3991. $a2 = -18.61500062529;
  3992. $a3 = 41.39119773534;
  3993. $a4 = -25.44106049637;
  3994. $b1 = -8.4735109309;
  3995. $b2 = 23.08336743743;
  3996. $b3 = -21.06224101826;
  3997. $b4 = 3.13082909833;
  3998. $c1 = 0.337475482272615;
  3999. $c2 = 0.976169019091719;
  4000. $c3 = 0.160797971491821;
  4001. $c4 = 2.76438810333863E-02;
  4002. $c5 = 3.8405729373609E-03;
  4003. $c6 = 3.951896511919E-04;
  4004. $c7 = 3.21767881768E-05;
  4005. $c8 = 2.888167364E-07;
  4006. $c9 = 3.960315187E-07;
  4007. $y = $prob - 0.5;
  4008. if (abs($y) < 0.42) {
  4009. $z = ($y * $y);
  4010. $z = $y * ((($a4 * $z + $a3) * $z + $a2) * $z + $a1) / (((($b4 * $z + $b3) * $z + $b2) * $z + $b1) * $z + 1);
  4011. } else {
  4012. if ($y > 0) {
  4013. $z = log(-log(1 - $prob));
  4014. } else {
  4015. $z = log(-log($prob));
  4016. }
  4017. $z = $c1 + $z * ($c2 + $z * ($c3 + $z * ($c4 + $z * ($c5 + $z * ($c6 + $z * ($c7 + $z * ($c8 + $z * $c9)))))));
  4018. if ($y < 0) {
  4019. $z = -$z;
  4020. }
  4021. }
  4022. return $z;
  4023. } // function _inverse_ncdf2()
  4024. private static function _inverse_ncdf3($p) {
  4025. // ALGORITHM AS241 APPL. STATIST. (1988) VOL. 37, NO. 3.
  4026. // Produces the normal deviate Z corresponding to a given lower
  4027. // tail area of P; Z is accurate to about 1 part in 10**16.
  4028. //
  4029. // This is a PHP version of the original FORTRAN code that can
  4030. // be found at http://lib.stat.cmu.edu/apstat/
  4031. $split1 = 0.425;
  4032. $split2 = 5;
  4033. $const1 = 0.180625;
  4034. $const2 = 1.6;
  4035. // coefficients for p close to 0.5
  4036. $a0 = 3.3871328727963666080;
  4037. $a1 = 1.3314166789178437745E+2;
  4038. $a2 = 1.9715909503065514427E+3;
  4039. $a3 = 1.3731693765509461125E+4;
  4040. $a4 = 4.5921953931549871457E+4;
  4041. $a5 = 6.7265770927008700853E+4;
  4042. $a6 = 3.3430575583588128105E+4;
  4043. $a7 = 2.5090809287301226727E+3;
  4044. $b1 = 4.2313330701600911252E+1;
  4045. $b2 = 6.8718700749205790830E+2;
  4046. $b3 = 5.3941960214247511077E+3;
  4047. $b4 = 2.1213794301586595867E+4;
  4048. $b5 = 3.9307895800092710610E+4;
  4049. $b6 = 2.8729085735721942674E+4;
  4050. $b7 = 5.2264952788528545610E+3;
  4051. // coefficients for p not close to 0, 0.5 or 1.
  4052. $c0 = 1.42343711074968357734;
  4053. $c1 = 4.63033784615654529590;
  4054. $c2 = 5.76949722146069140550;
  4055. $c3 = 3.64784832476320460504;
  4056. $c4 = 1.27045825245236838258;
  4057. $c5 = 2.41780725177450611770E-1;
  4058. $c6 = 2.27238449892691845833E-2;
  4059. $c7 = 7.74545014278341407640E-4;
  4060. $d1 = 2.05319162663775882187;
  4061. $d2 = 1.67638483018380384940;
  4062. $d3 = 6.89767334985100004550E-1;
  4063. $d4 = 1.48103976427480074590E-1;
  4064. $d5 = 1.51986665636164571966E-2;
  4065. $d6 = 5.47593808499534494600E-4;
  4066. $d7 = 1.05075007164441684324E-9;
  4067. // coefficients for p near 0 or 1.
  4068. $e0 = 6.65790464350110377720;
  4069. $e1 = 5.46378491116411436990;
  4070. $e2 = 1.78482653991729133580;
  4071. $e3 = 2.96560571828504891230E-1;
  4072. $e4 = 2.65321895265761230930E-2;
  4073. $e5 = 1.24266094738807843860E-3;
  4074. $e6 = 2.71155556874348757815E-5;
  4075. $e7 = 2.01033439929228813265E-7;
  4076. $f1 = 5.99832206555887937690E-1;
  4077. $f2 = 1.36929880922735805310E-1;
  4078. $f3 = 1.48753612908506148525E-2;
  4079. $f4 = 7.86869131145613259100E-4;
  4080. $f5 = 1.84631831751005468180E-5;
  4081. $f6 = 1.42151175831644588870E-7;
  4082. $f7 = 2.04426310338993978564E-15;
  4083. $q = $p - 0.5;
  4084. // computation for p close to 0.5
  4085. if (abs($q) <= split1) {
  4086. $R = $const1 - $q * $q;
  4087. $z = $q * ((((((($a7 * $R + $a6) * $R + $a5) * $R + $a4) * $R + $a3) * $R + $a2) * $R + $a1) * $R + $a0) /
  4088. ((((((($b7 * $R + $b6) * $R + $b5) * $R + $b4) * $R + $b3) * $R + $b2) * $R + $b1) * $R + 1);
  4089. } else {
  4090. if ($q < 0) {
  4091. $R = $p;
  4092. } else {
  4093. $R = 1 - $p;
  4094. }
  4095. $R = pow(-log($R),2);
  4096. // computation for p not close to 0, 0.5 or 1.
  4097. If ($R <= $split2) {
  4098. $R = $R - $const2;
  4099. $z = ((((((($c7 * $R + $c6) * $R + $c5) * $R + $c4) * $R + $c3) * $R + $c2) * $R + $c1) * $R + $c0) /
  4100. ((((((($d7 * $R + $d6) * $R + $d5) * $R + $d4) * $R + $d3) * $R + $d2) * $R + $d1) * $R + 1);
  4101. } else {
  4102. // computation for p near 0 or 1.
  4103. $R = $R - $split2;
  4104. $z = ((((((($e7 * $R + $e6) * $R + $e5) * $R + $e4) * $R + $e3) * $R + $e2) * $R + $e1) * $R + $e0) /
  4105. ((((((($f7 * $R + $f6) * $R + $f5) * $R + $f4) * $R + $f3) * $R + $f2) * $R + $f1) * $R + 1);
  4106. }
  4107. if ($q < 0) {
  4108. $z = -$z;
  4109. }
  4110. }
  4111. return $z;
  4112. } // function _inverse_ncdf3()
  4113. /**
  4114. * NORMINV
  4115. *
  4116. * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
  4117. *
  4118. * @param float $value
  4119. * @param float $mean Mean Value
  4120. * @param float $stdDev Standard Deviation
  4121. * @return float
  4122. *
  4123. */
  4124. public static function NORMINV($probability,$mean,$stdDev) {
  4125. $probability = self::flattenSingleValue($probability);
  4126. $mean = self::flattenSingleValue($mean);
  4127. $stdDev = self::flattenSingleValue($stdDev);
  4128. if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  4129. if (($probability < 0) || ($probability > 1)) {
  4130. return self::$_errorCodes['num'];
  4131. }
  4132. if ($stdDev < 0) {
  4133. return self::$_errorCodes['num'];
  4134. }
  4135. return (self::_inverse_ncdf($probability) * $stdDev) + $mean;
  4136. }
  4137. return self::$_errorCodes['value'];
  4138. } // function NORMINV()
  4139. /**
  4140. * NORMSINV
  4141. *
  4142. * Returns the inverse of the standard normal cumulative distribution
  4143. *
  4144. * @param float $value
  4145. * @return float
  4146. */
  4147. public static function NORMSINV($value) {
  4148. return self::NORMINV($value, 0, 1);
  4149. } // function NORMSINV()
  4150. /**
  4151. * LOGINV
  4152. *
  4153. * Returns the inverse of the normal cumulative distribution
  4154. *
  4155. * @param float $value
  4156. * @return float
  4157. *
  4158. * @todo Try implementing P J Acklam's refinement algorithm for greater
  4159. * accuracy if I can get my head round the mathematics
  4160. * (as described at) http://home.online.no/~pjacklam/notes/invnorm/
  4161. */
  4162. public static function LOGINV($probability, $mean, $stdDev) {
  4163. $probability = self::flattenSingleValue($probability);
  4164. $mean = self::flattenSingleValue($mean);
  4165. $stdDev = self::flattenSingleValue($stdDev);
  4166. if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
  4167. if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) {
  4168. return self::$_errorCodes['num'];
  4169. }
  4170. return exp($mean + $stdDev * self::NORMSINV($probability));
  4171. }
  4172. return self::$_errorCodes['value'];
  4173. } // function LOGINV()
  4174. /**
  4175. * HYPGEOMDIST
  4176. *
  4177. * Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of
  4178. * sample successes, given the sample size, population successes, and population size.
  4179. *
  4180. * @param float $sampleSuccesses Number of successes in the sample
  4181. * @param float $sampleNumber Size of the sample
  4182. * @param float $populationSuccesses Number of successes in the population
  4183. * @param float $populationNumber Population size
  4184. * @return float
  4185. *
  4186. */
  4187. public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
  4188. $sampleSuccesses = floor(self::flattenSingleValue($sampleSuccesses));
  4189. $sampleNumber = floor(self::flattenSingleValue($sampleNumber));
  4190. $populationSuccesses = floor(self::flattenSingleValue($populationSuccesses));
  4191. $populationNumber = floor(self::flattenSingleValue($populationNumber));
  4192. if ((is_numeric($sampleSuccesses)) && (is_numeric($sampleNumber)) && (is_numeric($populationSuccesses)) && (is_numeric($populationNumber))) {
  4193. if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
  4194. return self::$_errorCodes['num'];
  4195. }
  4196. if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
  4197. return self::$_errorCodes['num'];
  4198. }
  4199. if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
  4200. return self::$_errorCodes['num'];
  4201. }
  4202. return self::COMBIN($populationSuccesses,$sampleSuccesses) *
  4203. self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
  4204. self::COMBIN($populationNumber,$sampleNumber);
  4205. }
  4206. return self::$_errorCodes['value'];
  4207. } // function HYPGEOMDIST()
  4208. /**
  4209. * TDIST
  4210. *
  4211. * Returns the probability of Student's T distribution.
  4212. *
  4213. * @param float $value Value for the function
  4214. * @param float $degrees degrees of freedom
  4215. * @param float $tails number of tails (1 or 2)
  4216. * @return float
  4217. */
  4218. public static function TDIST($value, $degrees, $tails) {
  4219. $value = self::flattenSingleValue($value);
  4220. $degrees = floor(self::flattenSingleValue($degrees));
  4221. $tails = floor(self::flattenSingleValue($tails));
  4222. if ((is_numeric($value)) && (is_numeric($degrees)) && (is_numeric($tails))) {
  4223. if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
  4224. return self::$_errorCodes['num'];
  4225. }
  4226. // tdist, which finds the probability that corresponds to a given value
  4227. // of t with k degrees of freedom. This algorithm is translated from a
  4228. // pascal function on p81 of "Statistical Computing in Pascal" by D
  4229. // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
  4230. // London). The above Pascal algorithm is itself a translation of the
  4231. // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
  4232. // Laboratory as reported in (among other places) "Applied Statistics
  4233. // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
  4234. // Horwood Ltd.; W. Sussex, England).
  4235. $tterm = $degrees;
  4236. $ttheta = atan2($value,sqrt($tterm));
  4237. $tc = cos($ttheta);
  4238. $ts = sin($ttheta);
  4239. $tsum = 0;
  4240. if (($degrees % 2) == 1) {
  4241. $ti = 3;
  4242. $tterm = $tc;
  4243. } else {
  4244. $ti = 2;
  4245. $tterm = 1;
  4246. }
  4247. $tsum = $tterm;
  4248. while ($ti < $degrees) {
  4249. $tterm *= $tc * $tc * ($ti - 1) / $ti;
  4250. $tsum += $tterm;
  4251. $ti += 2;
  4252. }
  4253. $tsum *= $ts;
  4254. if (($degrees % 2) == 1) { $tsum = M_2DIVPI * ($tsum + $ttheta); }
  4255. $tValue = 0.5 * (1 + $tsum);
  4256. if ($tails == 1) {
  4257. return 1 - abs($tValue);
  4258. } else {
  4259. return 1 - abs((1 - $tValue) - $tValue);
  4260. }
  4261. }
  4262. return self::$_errorCodes['value'];
  4263. } // function TDIST()
  4264. /**
  4265. * TINV
  4266. *
  4267. * Returns the one-tailed probability of the chi-squared distribution.
  4268. *
  4269. * @param float $probability Probability for the function
  4270. * @param float $degrees degrees of freedom
  4271. * @return float
  4272. */
  4273. public static function TINV($probability, $degrees) {
  4274. $probability = self::flattenSingleValue($probability);
  4275. $degrees = floor(self::flattenSingleValue($degrees));
  4276. if ((is_numeric($probability)) && (is_numeric($degrees))) {
  4277. $xLo = 100;
  4278. $xHi = 0;
  4279. $x = $xNew = 1;
  4280. $dx = 1;
  4281. $i = 0;
  4282. while ((abs($dx) > PRECISION) && ($i++ < MAX_ITERATIONS)) {
  4283. // Apply Newton-Raphson step
  4284. $result = self::TDIST($x, $degrees, 2);
  4285. $error = $result - $probability;
  4286. if ($error == 0.0) {
  4287. $dx = 0;
  4288. } elseif ($error < 0.0) {
  4289. $xLo = $x;
  4290. } else {
  4291. $xHi = $x;
  4292. }
  4293. // Avoid division by zero
  4294. if ($result != 0.0) {
  4295. $dx = $error / $result;
  4296. $xNew = $x - $dx;
  4297. }
  4298. // If the NR fails to converge (which for example may be the
  4299. // case if the initial guess is too rough) we apply a bisection
  4300. // step to determine a more narrow interval around the root.
  4301. if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
  4302. $xNew = ($xLo + $xHi) / 2;
  4303. $dx = $xNew - $x;
  4304. }
  4305. $x = $xNew;
  4306. }
  4307. if ($i == MAX_ITERATIONS) {
  4308. return self::$_errorCodes['na'];
  4309. }
  4310. return round($x,12);
  4311. }
  4312. return self::$_errorCodes['value'];
  4313. } // function TINV()
  4314. /**
  4315. * CONFIDENCE
  4316. *
  4317. * Returns the confidence interval for a population mean
  4318. *
  4319. * @param float $alpha
  4320. * @param float $stdDev Standard Deviation
  4321. * @param float $size
  4322. * @return float
  4323. *
  4324. */
  4325. public static function CONFIDENCE($alpha,$stdDev,$size) {
  4326. $alpha = self::flattenSingleValue($alpha);
  4327. $stdDev = self::flattenSingleValue($stdDev);
  4328. $size = floor(self::flattenSingleValue($size));
  4329. if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) {
  4330. if (($alpha <= 0) || ($alpha >= 1)) {
  4331. return self::$_errorCodes['num'];
  4332. }
  4333. if (($stdDev <= 0) || ($size < 1)) {
  4334. return self::$_errorCodes['num'];
  4335. }
  4336. return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
  4337. }
  4338. return self::$_errorCodes['value'];
  4339. } // function CONFIDENCE()
  4340. /**
  4341. * POISSON
  4342. *
  4343. * Returns the Poisson distribution. A common application of the Poisson distribution
  4344. * is predicting the number of events over a specific time, such as the number of
  4345. * cars arriving at a toll plaza in 1 minute.
  4346. *
  4347. * @param float $value
  4348. * @param float $mean Mean Value
  4349. * @param boolean $cumulative
  4350. * @return float
  4351. *
  4352. */
  4353. public static function POISSON($value, $mean, $cumulative) {
  4354. $value = self::flattenSingleValue($value);
  4355. $mean = self::flattenSingleValue($mean);
  4356. if ((is_numeric($value)) && (is_numeric($mean))) {
  4357. if (($value <= 0) || ($mean <= 0)) {
  4358. return self::$_errorCodes['num'];
  4359. }
  4360. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  4361. if ($cumulative) {
  4362. $summer = 0;
  4363. for ($i = 0; $i <= floor($value); ++$i) {
  4364. $summer += pow($mean,$i) / self::FACT($i);
  4365. }
  4366. return exp(0-$mean) * $summer;
  4367. } else {
  4368. return (exp(0-$mean) * pow($mean,$value)) / self::FACT($value);
  4369. }
  4370. }
  4371. }
  4372. return self::$_errorCodes['value'];
  4373. } // function POISSON()
  4374. /**
  4375. * WEIBULL
  4376. *
  4377. * Returns the Weibull distribution. Use this distribution in reliability
  4378. * analysis, such as calculating a device's mean time to failure.
  4379. *
  4380. * @param float $value
  4381. * @param float $alpha Alpha Parameter
  4382. * @param float $beta Beta Parameter
  4383. * @param boolean $cumulative
  4384. * @return float
  4385. *
  4386. */
  4387. public static function WEIBULL($value, $alpha, $beta, $cumulative) {
  4388. $value = self::flattenSingleValue($value);
  4389. $alpha = self::flattenSingleValue($alpha);
  4390. $beta = self::flattenSingleValue($beta);
  4391. if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta))) {
  4392. if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) {
  4393. return self::$_errorCodes['num'];
  4394. }
  4395. if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
  4396. if ($cumulative) {
  4397. return 1 - exp(0 - pow($value / $beta,$alpha));
  4398. } else {
  4399. return ($alpha / pow($beta,$alpha)) * pow($value,$alpha - 1) * exp(0 - pow($value / $beta,$alpha));
  4400. }
  4401. }
  4402. }
  4403. return self::$_errorCodes['value'];
  4404. } // function WEIBULL()
  4405. /**
  4406. * ZTEST
  4407. *
  4408. * Returns the Weibull distribution. Use this distribution in reliability
  4409. * analysis, such as calculating a device's mean time to failure.
  4410. *
  4411. * @param float $value
  4412. * @param float $alpha Alpha Parameter
  4413. * @param float $beta Beta Parameter
  4414. * @param boolean $cumulative
  4415. * @return float
  4416. *
  4417. */
  4418. public static function ZTEST($dataSet, $m0, $sigma=null) {
  4419. $dataSet = self::flattenArrayIndexed($dataSet);
  4420. $m0 = self::flattenSingleValue($m0);
  4421. $sigma = self::flattenSingleValue($sigma);
  4422. if (is_null($sigma)) {
  4423. $sigma = self::STDEV($dataSet);
  4424. }
  4425. $n = count($dataSet);
  4426. return 1 - self::NORMSDIST((self::AVERAGE($dataSet) - $m0)/($sigma/SQRT($n)));
  4427. } // function ZTEST()
  4428. /**
  4429. * SKEW
  4430. *
  4431. * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry
  4432. * of a distribution around its mean. Positive skewness indicates a distribution with an
  4433. * asymmetric tail extending toward more positive values. Negative skewness indicates a
  4434. * distribution with an asymmetric tail extending toward more negative values.
  4435. *
  4436. * @param array Data Series
  4437. * @return float
  4438. */
  4439. public static function SKEW() {
  4440. $aArgs = self::flattenArrayIndexed(func_get_args());
  4441. $mean = self::AVERAGE($aArgs);
  4442. $stdDev = self::STDEV($aArgs);
  4443. $count = $summer = 0;
  4444. // Loop through arguments
  4445. foreach ($aArgs as $k => $arg) {
  4446. if ((is_bool($arg)) &&
  4447. (!self::isMatrixValue($k))) {
  4448. } else {
  4449. // Is it a numeric value?
  4450. if ((is_numeric($arg)) && (!is_string($arg))) {
  4451. $summer += pow((($arg - $mean) / $stdDev),3) ;
  4452. ++$count;
  4453. }
  4454. }
  4455. }
  4456. // Return
  4457. if ($count > 2) {
  4458. return $summer * ($count / (($count-1) * ($count-2)));
  4459. }
  4460. return self::$_errorCodes['divisionbyzero'];
  4461. } // function SKEW()
  4462. /**
  4463. * KURT
  4464. *
  4465. * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness
  4466. * or flatness of a distribution compared with the normal distribution. Positive
  4467. * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a
  4468. * relatively flat distribution.
  4469. *
  4470. * @param array Data Series
  4471. * @return float
  4472. */
  4473. public static function KURT() {
  4474. $aArgs = self::flattenArrayIndexed(func_get_args());
  4475. $mean = self::AVERAGE($aArgs);
  4476. $stdDev = self::STDEV($aArgs);
  4477. if ($stdDev > 0) {
  4478. $count = $summer = 0;
  4479. // Loop through arguments
  4480. foreach ($aArgs as $k => $arg) {
  4481. if ((is_bool($arg)) &&
  4482. (!self::isMatrixValue($k))) {
  4483. } else {
  4484. // Is it a numeric value?
  4485. if ((is_numeric($arg)) && (!is_string($arg))) {
  4486. $summer += pow((($arg - $mean) / $stdDev),4) ;
  4487. ++$count;
  4488. }
  4489. }
  4490. }
  4491. // Return
  4492. if ($count > 3) {
  4493. return $summer * ($count * ($count+1) / (($count-1) * ($count-2) * ($count-3))) - (3 * pow($count-1,2) / (($count-2) * ($count-3)));
  4494. }
  4495. }
  4496. return self::$_errorCodes['divisionbyzero'];
  4497. } // function KURT()
  4498. /**
  4499. * RAND
  4500. *
  4501. * @param int $min Minimal value
  4502. * @param int $max Maximal value
  4503. * @return int Random number
  4504. */
  4505. public static function RAND($min = 0, $max = 0) {
  4506. $min = self::flattenSingleValue($min);
  4507. $max = self::flattenSingleValue($max);
  4508. if ($min == 0 && $max == 0) {
  4509. return (rand(0,10000000)) / 10000000;
  4510. } else {
  4511. return rand($min, $max);
  4512. }
  4513. } // function RAND()
  4514. /**
  4515. * MOD
  4516. *
  4517. * @param int $a Dividend
  4518. * @param int $b Divisor
  4519. * @return int Remainder
  4520. */
  4521. public static function MOD($a = 1, $b = 1) {
  4522. $a = self::flattenSingleValue($a);
  4523. $b = self::flattenSingleValue($b);
  4524. if ($b == 0.0) {
  4525. return self::$_errorCodes['divisionbyzero'];
  4526. } elseif (($a < 0.0) && ($b > 0.0)) {
  4527. return $b - fmod(abs($a),$b);
  4528. } elseif (($a > 0.0) && ($b < 0.0)) {
  4529. return $b + fmod($a,abs($b));
  4530. }
  4531. return fmod($a,$b);
  4532. } // function MOD()
  4533. /**
  4534. * CHARACTER
  4535. *
  4536. * @param string $character Value
  4537. * @return int
  4538. */
  4539. public static function CHARACTER($character) {
  4540. $character = self::flattenSingleValue($character);
  4541. if ((!is_numeric($character)) || ($character < 0)) {
  4542. return self::$_errorCodes['value'];
  4543. }
  4544. if (function_exists('mb_convert_encoding')) {
  4545. return mb_convert_encoding('&#'.intval($character).';', 'UTF-8', 'HTML-ENTITIES');
  4546. } else {
  4547. return chr(intval($character));
  4548. }
  4549. }
  4550. private static function _uniord($c) {
  4551. if (ord($c{0}) >=0 && ord($c{0}) <= 127)
  4552. return ord($c{0});
  4553. if (ord($c{0}) >= 192 && ord($c{0}) <= 223)
  4554. return (ord($c{0})-192)*64 + (ord($c{1})-128);
  4555. if (ord($c{0}) >= 224 && ord($c{0}) <= 239)
  4556. return (ord($c{0})-224)*4096 + (ord($c{1})-128)*64 + (ord($c{2})-128);
  4557. if (ord($c{0}) >= 240 && ord($c{0}) <= 247)
  4558. return (ord($c{0})-240)*262144 + (ord($c{1})-128)*4096 + (ord($c{2})-128)*64 + (ord($c{3})-128);
  4559. if (ord($c{0}) >= 248 && ord($c{0}) <= 251)
  4560. return (ord($c{0})-248)*16777216 + (ord($c{1})-128)*262144 + (ord($c{2})-128)*4096 + (ord($c{3})-128)*64 + (ord($c{4})-128);
  4561. if (ord($c{0}) >= 252 && ord($c{0}) <= 253)
  4562. return (ord($c{0})-252)*1073741824 + (ord($c{1})-128)*16777216 + (ord($c{2})-128)*262144 + (ord($c{3})-128)*4096 + (ord($c{4})-128)*64 + (ord($c{5})-128);
  4563. if (ord($c{0}) >= 254 && ord($c{0}) <= 255) //error
  4564. return self::$_errorCodes['value'];
  4565. return 0;
  4566. } // function _uniord()
  4567. /**
  4568. * ASCIICODE
  4569. *
  4570. * @param string $character Value
  4571. * @return int
  4572. */
  4573. public static function ASCIICODE($characters) {
  4574. $characters = self::flattenSingleValue($characters);
  4575. if (is_bool($characters)) {
  4576. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4577. $characters = (int) $characters;
  4578. } else {
  4579. if ($characters) {
  4580. $characters = 'True';
  4581. } else {
  4582. $characters = 'False';
  4583. }
  4584. }
  4585. }
  4586. $character = $characters;
  4587. if ((function_exists('mb_strlen')) && (function_exists('mb_substr'))) {
  4588. if (mb_strlen($characters, 'UTF-8') > 1) { $character = mb_substr($characters, 0, 1, 'UTF-8'); }
  4589. return self::_uniord($character);
  4590. } else {
  4591. if (strlen($characters) > 0) { $character = substr($characters, 0, 1); }
  4592. return ord($character);
  4593. }
  4594. } // function ASCIICODE()
  4595. /**
  4596. * CONCATENATE
  4597. *
  4598. * @return string
  4599. */
  4600. public static function CONCATENATE() {
  4601. // Return value
  4602. $returnValue = '';
  4603. // Loop through arguments
  4604. $aArgs = self::flattenArray(func_get_args());
  4605. foreach ($aArgs as $arg) {
  4606. if (is_bool($arg)) {
  4607. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  4608. $arg = (int) $arg;
  4609. } else {
  4610. if ($arg) {
  4611. $arg = 'TRUE';
  4612. } else {
  4613. $arg = 'FALSE';
  4614. }
  4615. }
  4616. }
  4617. $returnValue .= $arg;
  4618. }
  4619. // Return
  4620. return $returnValue;
  4621. } // function CONCATENATE()
  4622. /**
  4623. * STRINGLENGTH
  4624. *
  4625. * @param string $value Value
  4626. * @param int $chars Number of characters
  4627. * @return string
  4628. */
  4629. public static function STRINGLENGTH($value = '') {
  4630. $value = self::flattenSingleValue($value);
  4631. if (is_bool($value)) {
  4632. $value = ($value) ? 'TRUE' : 'FALSE';
  4633. }
  4634. if (function_exists('mb_strlen')) {
  4635. return mb_strlen($value, 'UTF-8');
  4636. } else {
  4637. return strlen($value);
  4638. }
  4639. } // function STRINGLENGTH()
  4640. /**
  4641. * SEARCHSENSITIVE
  4642. *
  4643. * @param string $needle The string to look for
  4644. * @param string $haystack The string in which to look
  4645. * @param int $offset Offset within $haystack
  4646. * @return string
  4647. */
  4648. public static function SEARCHSENSITIVE($needle,$haystack,$offset=1) {
  4649. $needle = self::flattenSingleValue($needle);
  4650. $haystack = self::flattenSingleValue($haystack);
  4651. $offset = self::flattenSingleValue($offset);
  4652. if (!is_bool($needle)) {
  4653. if (is_bool($haystack)) {
  4654. $haystack = ($haystack) ? 'TRUE' : 'FALSE';
  4655. }
  4656. if (($offset > 0) && (strlen($haystack) > $offset)) {
  4657. if (function_exists('mb_strpos')) {
  4658. $pos = mb_strpos($haystack, $needle, --$offset,'UTF-8');
  4659. } else {
  4660. $pos = strpos($haystack, $needle, --$offset);
  4661. }
  4662. if ($pos !== false) {
  4663. return ++$pos;
  4664. }
  4665. }
  4666. }
  4667. return self::$_errorCodes['value'];
  4668. } // function SEARCHSENSITIVE()
  4669. /**
  4670. * SEARCHINSENSITIVE
  4671. *
  4672. * @param string $needle The string to look for
  4673. * @param string $haystack The string in which to look
  4674. * @param int $offset Offset within $haystack
  4675. * @return string
  4676. */
  4677. public static function SEARCHINSENSITIVE($needle,$haystack,$offset=1) {
  4678. $needle = self::flattenSingleValue($needle);
  4679. $haystack = self::flattenSingleValue($haystack);
  4680. $offset = self::flattenSingleValue($offset);
  4681. if (!is_bool($needle)) {
  4682. if (is_bool($haystack)) {
  4683. $haystack = ($haystack) ? 'TRUE' : 'FALSE';
  4684. }
  4685. if (($offset > 0) && (strlen($haystack) > $offset)) {
  4686. if (function_exists('mb_stripos')) {
  4687. $pos = mb_stripos($haystack, $needle, --$offset,'UTF-8');
  4688. } else {
  4689. $pos = stripos($haystack, $needle, --$offset);
  4690. }
  4691. if ($pos !== false) {
  4692. return ++$pos;
  4693. }
  4694. }
  4695. }
  4696. return self::$_errorCodes['value'];
  4697. } // function SEARCHINSENSITIVE()
  4698. /**
  4699. * LEFT
  4700. *
  4701. * @param string $value Value
  4702. * @param int $chars Number of characters
  4703. * @return string
  4704. */
  4705. public static function LEFT($value = '', $chars = 1) {
  4706. $value = self::flattenSingleValue($value);
  4707. $chars = self::flattenSingleValue($chars);
  4708. if ($chars < 0) {
  4709. return self::$_errorCodes['value'];
  4710. }
  4711. if (is_bool($value)) {
  4712. $value = ($value) ? 'TRUE' : 'FALSE';
  4713. }
  4714. if (function_exists('mb_substr')) {
  4715. return mb_substr($value, 0, $chars, 'UTF-8');
  4716. } else {
  4717. return substr($value, 0, $chars);
  4718. }
  4719. } // function LEFT()
  4720. /**
  4721. * RIGHT
  4722. *
  4723. * @param string $value Value
  4724. * @param int $chars Number of characters
  4725. * @return string
  4726. */
  4727. public static function RIGHT($value = '', $chars = 1) {
  4728. $value = self::flattenSingleValue($value);
  4729. $chars = self::flattenSingleValue($chars);
  4730. if ($chars < 0) {
  4731. return self::$_errorCodes['value'];
  4732. }
  4733. if (is_bool($value)) {
  4734. $value = ($value) ? 'TRUE' : 'FALSE';
  4735. }
  4736. if ((function_exists('mb_substr')) && (function_exists('mb_strlen'))) {
  4737. return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
  4738. } else {
  4739. return substr($value, strlen($value) - $chars);
  4740. }
  4741. } // function RIGHT()
  4742. /**
  4743. * MID
  4744. *
  4745. * @param string $value Value
  4746. * @param int $start Start character
  4747. * @param int $chars Number of characters
  4748. * @return string
  4749. */
  4750. public static function MID($value = '', $start = 1, $chars = null) {
  4751. $value = self::flattenSingleValue($value);
  4752. $start = self::flattenSingleValue($start);
  4753. $chars = self::flattenSingleValue($chars);
  4754. if (($start < 1) || ($chars < 0)) {
  4755. return self::$_errorCodes['value'];
  4756. }
  4757. if (is_bool($value)) {
  4758. $value = ($value) ? 'TRUE' : 'FALSE';
  4759. }
  4760. if (function_exists('mb_substr')) {
  4761. return mb_substr($value, --$start, $chars, 'UTF-8');
  4762. } else {
  4763. return substr($value, --$start, $chars);
  4764. }
  4765. } // function MID()
  4766. /**
  4767. * REPLACE
  4768. *
  4769. * @param string $value Value
  4770. * @param int $start Start character
  4771. * @param int $chars Number of characters
  4772. * @return string
  4773. */
  4774. public static function REPLACE($oldText = '', $start = 1, $chars = null, $newText) {
  4775. $oldText = self::flattenSingleValue($oldText);
  4776. $start = self::flattenSingleValue($start);
  4777. $chars = self::flattenSingleValue($chars);
  4778. $newText = self::flattenSingleValue($newText);
  4779. $left = self::LEFT($oldText,$start-1);
  4780. $right = self::RIGHT($oldText,self::STRINGLENGTH($oldText)-($start+$chars)+1);
  4781. return $left.$newText.$right;
  4782. } // function REPLACE()
  4783. /**
  4784. * SUBSTITUTE
  4785. *
  4786. * @param string $text Value
  4787. * @param string $fromText From Value
  4788. * @param string $toText To Value
  4789. * @param integer $instance Instance Number
  4790. * @return string
  4791. */
  4792. public static function SUBSTITUTE($text = '', $fromText = '', $toText = '', $instance = 0) {
  4793. $text = self::flattenSingleValue($text);
  4794. $fromText = self::flattenSingleValue($fromText);
  4795. $toText = self::flattenSingleValue($toText);
  4796. $instance = floor(self::flattenSingleValue($instance));
  4797. if ($instance == 0) {
  4798. if(function_exists('mb_str_replace')) {
  4799. return mb_str_replace($fromText,$toText,$text);
  4800. } else {
  4801. return str_replace($fromText,$toText,$text);
  4802. }
  4803. } else {
  4804. $pos = -1;
  4805. while($instance > 0) {
  4806. if (function_exists('mb_strpos')) {
  4807. $pos = mb_strpos($text, $fromText, $pos+1, 'UTF-8');
  4808. } else {
  4809. $pos = strpos($text, $fromText, $pos+1);
  4810. }
  4811. if ($pos === false) {
  4812. break;
  4813. }
  4814. --$instance;
  4815. }
  4816. if ($pos !== false) {
  4817. if (function_exists('mb_strlen')) {
  4818. return self::REPLACE($text,++$pos,mb_strlen($fromText, 'UTF-8'),$toText);
  4819. } else {
  4820. return self::REPLACE($text,++$pos,strlen($fromText),$toText);
  4821. }
  4822. }
  4823. }
  4824. return $left.$newText.$right;
  4825. } // function SUBSTITUTE()
  4826. /**
  4827. * RETURNSTRING
  4828. *
  4829. * @param mixed $value Value to check
  4830. * @return boolean
  4831. */
  4832. public static function RETURNSTRING($testValue = '') {
  4833. $testValue = self::flattenSingleValue($testValue);
  4834. if (is_string($testValue)) {
  4835. return $testValue;
  4836. }
  4837. return Null;
  4838. } // function RETURNSTRING()
  4839. /**
  4840. * FIXEDFORMAT
  4841. *
  4842. * @param mixed $value Value to check
  4843. * @return boolean
  4844. */
  4845. public static function FIXEDFORMAT($value,$decimals=2,$no_commas=false) {
  4846. $value = self::flattenSingleValue($value);
  4847. $decimals = self::flattenSingleValue($decimals);
  4848. $no_commas = self::flattenSingleValue($no_commas);
  4849. $valueResult = round($value,$decimals);
  4850. if ($decimals < 0) { $decimals = 0; }
  4851. if (!$no_commas) {
  4852. $valueResult = number_format($valueResult,$decimals);
  4853. }
  4854. return (string) $valueResult;
  4855. } // function FIXEDFORMAT()
  4856. /**
  4857. * TEXTFORMAT
  4858. *
  4859. * @param mixed $value Value to check
  4860. * @return boolean
  4861. */
  4862. public static function TEXTFORMAT($value,$format) {
  4863. $value = self::flattenSingleValue($value);
  4864. $format = self::flattenSingleValue($format);
  4865. if ((is_string($value)) && (!is_numeric($value)) && PHPExcel_Shared_Date::isDateTimeFormatCode($format)) {
  4866. $value = self::DATEVALUE($value);
  4867. }
  4868. return (string) PHPExcel_Style_NumberFormat::toFormattedString($value,$format);
  4869. } // function TEXTFORMAT()
  4870. /**
  4871. * TRIMSPACES
  4872. *
  4873. * @param mixed $value Value to check
  4874. * @return string
  4875. */
  4876. public static function TRIMSPACES($stringValue = '') {
  4877. $stringValue = self::flattenSingleValue($stringValue);
  4878. if (is_string($stringValue) || is_numeric($stringValue)) {
  4879. return trim(preg_replace('/ +/',' ',$stringValue));
  4880. }
  4881. return Null;
  4882. } // function TRIMSPACES()
  4883. private static $_invalidChars = Null;
  4884. /**
  4885. * TRIMNONPRINTABLE
  4886. *
  4887. * @param mixed $value Value to check
  4888. * @return string
  4889. */
  4890. public static function TRIMNONPRINTABLE($stringValue = '') {
  4891. $stringValue = self::flattenSingleValue($stringValue);
  4892. if (is_bool($stringValue)) {
  4893. $stringValue = ($stringValue) ? 'TRUE' : 'FALSE';
  4894. }
  4895. if (self::$_invalidChars == Null) {
  4896. self::$_invalidChars = range(chr(0),chr(31));
  4897. }
  4898. if (is_string($stringValue) || is_numeric($stringValue)) {
  4899. return str_replace(self::$_invalidChars,'',trim($stringValue,"\x00..\x1F"));
  4900. }
  4901. return Null;
  4902. } // function TRIMNONPRINTABLE()
  4903. /**
  4904. * ERROR_TYPE
  4905. *
  4906. * @param mixed $value Value to check
  4907. * @return boolean
  4908. */
  4909. public static function ERROR_TYPE($value = '') {
  4910. $value = self::flattenSingleValue($value);
  4911. $i = 1;
  4912. foreach(self::$_errorCodes as $errorCode) {
  4913. if ($value == $errorCode) {
  4914. return $i;
  4915. }
  4916. ++$i;
  4917. }
  4918. return self::$_errorCodes['na'];
  4919. } // function ERROR_TYPE()
  4920. /**
  4921. * IS_BLANK
  4922. *
  4923. * @param mixed $value Value to check
  4924. * @return boolean
  4925. */
  4926. public static function IS_BLANK($value=null) {
  4927. if (!is_null($value)) {
  4928. $value = self::flattenSingleValue($value);
  4929. }
  4930. return is_null($value);
  4931. } // function IS_BLANK()
  4932. /**
  4933. * IS_ERR
  4934. *
  4935. * @param mixed $value Value to check
  4936. * @return boolean
  4937. */
  4938. public static function IS_ERR($value = '') {
  4939. $value = self::flattenSingleValue($value);
  4940. return self::IS_ERROR($value) && (!self::IS_NA($value));
  4941. } // function IS_ERR()
  4942. /**
  4943. * IS_ERROR
  4944. *
  4945. * @param mixed $value Value to check
  4946. * @return boolean
  4947. */
  4948. public static function IS_ERROR($value = '') {
  4949. $value = self::flattenSingleValue($value);
  4950. return in_array($value, array_values(self::$_errorCodes));
  4951. } // function IS_ERROR()
  4952. /**
  4953. * IS_NA
  4954. *
  4955. * @param mixed $value Value to check
  4956. * @return boolean
  4957. */
  4958. public static function IS_NA($value = '') {
  4959. $value = self::flattenSingleValue($value);
  4960. return ($value === self::$_errorCodes['na']);
  4961. } // function IS_NA()
  4962. /**
  4963. * IS_EVEN
  4964. *
  4965. * @param mixed $value Value to check
  4966. * @return boolean
  4967. */
  4968. public static function IS_EVEN($value = 0) {
  4969. $value = self::flattenSingleValue($value);
  4970. if ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
  4971. return self::$_errorCodes['value'];
  4972. }
  4973. return ($value % 2 == 0);
  4974. } // function IS_EVEN()
  4975. /**
  4976. * IS_ODD
  4977. *
  4978. * @param mixed $value Value to check
  4979. * @return boolean
  4980. */
  4981. public static function IS_ODD($value = null) {
  4982. $value = self::flattenSingleValue($value);
  4983. if ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
  4984. return self::$_errorCodes['value'];
  4985. }
  4986. return (abs($value) % 2 == 1);
  4987. } // function IS_ODD()
  4988. /**
  4989. * IS_NUMBER
  4990. *
  4991. * @param mixed $value Value to check
  4992. * @return boolean
  4993. */
  4994. public static function IS_NUMBER($value = 0) {
  4995. $value = self::flattenSingleValue($value);
  4996. if (is_string($value)) {
  4997. return False;
  4998. }
  4999. return is_numeric($value);
  5000. } // function IS_NUMBER()
  5001. /**
  5002. * IS_LOGICAL
  5003. *
  5004. * @param mixed $value Value to check
  5005. * @return boolean
  5006. */
  5007. public static function IS_LOGICAL($value = true) {
  5008. $value = self::flattenSingleValue($value);
  5009. return is_bool($value);
  5010. } // function IS_LOGICAL()
  5011. /**
  5012. * IS_TEXT
  5013. *
  5014. * @param mixed $value Value to check
  5015. * @return boolean
  5016. */
  5017. public static function IS_TEXT($value = '') {
  5018. $value = self::flattenSingleValue($value);
  5019. return is_string($value);
  5020. } // function IS_TEXT()
  5021. /**
  5022. * IS_NONTEXT
  5023. *
  5024. * @param mixed $value Value to check
  5025. * @return boolean
  5026. */
  5027. public static function IS_NONTEXT($value = '') {
  5028. return !self::IS_TEXT($value);
  5029. } // function IS_NONTEXT()
  5030. /**
  5031. * VERSION
  5032. *
  5033. * @return string Version information
  5034. */
  5035. public static function VERSION() {
  5036. return 'PHPExcel ##VERSION##, ##DATE##';
  5037. } // function VERSION()
  5038. /**
  5039. * DATE
  5040. *
  5041. * @param long $year
  5042. * @param long $month
  5043. * @param long $day
  5044. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  5045. * depending on the value of the ReturnDateType flag
  5046. */
  5047. public static function DATE($year = 0, $month = 1, $day = 1) {
  5048. $year = (integer) self::flattenSingleValue($year);
  5049. $month = (integer) self::flattenSingleValue($month);
  5050. $day = (integer) self::flattenSingleValue($day);
  5051. $baseYear = PHPExcel_Shared_Date::getExcelCalendar();
  5052. // Validate parameters
  5053. if ($year < ($baseYear-1900)) {
  5054. return self::$_errorCodes['num'];
  5055. }
  5056. if ((($baseYear-1900) != 0) && ($year < $baseYear) && ($year >= 1900)) {
  5057. return self::$_errorCodes['num'];
  5058. }
  5059. if (($year < $baseYear) && ($year >= ($baseYear-1900))) {
  5060. $year += 1900;
  5061. }
  5062. if ($month < 1) {
  5063. // Handle year/month adjustment if month < 1
  5064. --$month;
  5065. $year += ceil($month / 12) - 1;
  5066. $month = 13 - abs($month % 12);
  5067. } elseif ($month > 12) {
  5068. // Handle year/month adjustment if month > 12
  5069. $year += floor($month / 12);
  5070. $month = ($month % 12);
  5071. }
  5072. // Re-validate the year parameter after adjustments
  5073. if (($year < $baseYear) || ($year >= 10000)) {
  5074. return self::$_errorCodes['num'];
  5075. }
  5076. // Execute function
  5077. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($year, $month, $day);
  5078. switch (self::getReturnDateType()) {
  5079. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  5080. break;
  5081. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateValue);
  5082. break;
  5083. case self::RETURNDATE_PHP_OBJECT : return PHPExcel_Shared_Date::ExcelToPHPObject($excelDateValue);
  5084. break;
  5085. }
  5086. } // function DATE()
  5087. /**
  5088. * TIME
  5089. *
  5090. * @param long $hour
  5091. * @param long $minute
  5092. * @param long $second
  5093. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  5094. * depending on the value of the ReturnDateType flag
  5095. */
  5096. public static function TIME($hour = 0, $minute = 0, $second = 0) {
  5097. $hour = self::flattenSingleValue($hour);
  5098. $minute = self::flattenSingleValue($minute);
  5099. $second = self::flattenSingleValue($second);
  5100. if ($hour == '') { $hour = 0; }
  5101. if ($minute == '') { $minute = 0; }
  5102. if ($second == '') { $second = 0; }
  5103. if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) {
  5104. return self::$_errorCodes['value'];
  5105. }
  5106. $hour = (integer) $hour;
  5107. $minute = (integer) $minute;
  5108. $second = (integer) $second;
  5109. if ($second < 0) {
  5110. $minute += floor($second / 60);
  5111. $second = 60 - abs($second % 60);
  5112. if ($second == 60) { $second = 0; }
  5113. } elseif ($second >= 60) {
  5114. $minute += floor($second / 60);
  5115. $second = $second % 60;
  5116. }
  5117. if ($minute < 0) {
  5118. $hour += floor($minute / 60);
  5119. $minute = 60 - abs($minute % 60);
  5120. if ($minute == 60) { $minute = 0; }
  5121. } elseif ($minute >= 60) {
  5122. $hour += floor($minute / 60);
  5123. $minute = $minute % 60;
  5124. }
  5125. if ($hour > 23) {
  5126. $hour = $hour % 24;
  5127. } elseif ($hour < 0) {
  5128. return self::$_errorCodes['num'];
  5129. }
  5130. // Execute function
  5131. switch (self::getReturnDateType()) {
  5132. case self::RETURNDATE_EXCEL : $date = 0;
  5133. $calendar = PHPExcel_Shared_Date::getExcelCalendar();
  5134. if ($calendar != PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900) {
  5135. $date = 1;
  5136. }
  5137. return (float) PHPExcel_Shared_Date::FormattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
  5138. break;
  5139. 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
  5140. break;
  5141. case self::RETURNDATE_PHP_OBJECT : $dayAdjust = 0;
  5142. if ($hour < 0) {
  5143. $dayAdjust = floor($hour / 24);
  5144. $hour = 24 - abs($hour % 24);
  5145. if ($hour == 24) { $hour = 0; }
  5146. } elseif ($hour >= 24) {
  5147. $dayAdjust = floor($hour / 24);
  5148. $hour = $hour % 24;
  5149. }
  5150. $phpDateObject = new DateTime('1900-01-01 '.$hour.':'.$minute.':'.$second);
  5151. if ($dayAdjust != 0) {
  5152. $phpDateObject->modify($dayAdjust.' days');
  5153. }
  5154. return $phpDateObject;
  5155. break;
  5156. }
  5157. } // function TIME()
  5158. /**
  5159. * DATEVALUE
  5160. *
  5161. * @param string $dateValue
  5162. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  5163. * depending on the value of the ReturnDateType flag
  5164. */
  5165. public static function DATEVALUE($dateValue = 1) {
  5166. $dateValue = trim(self::flattenSingleValue($dateValue),'"');
  5167. // Strip any ordinals because they're allowed in Excel (English only)
  5168. $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui','$1$3',$dateValue);
  5169. // Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
  5170. $dateValue = str_replace(array('/','.','-',' '),array(' ',' ',' ',' '),$dateValue);
  5171. $yearFound = false;
  5172. $t1 = explode(' ',$dateValue);
  5173. foreach($t1 as &$t) {
  5174. if ((is_numeric($t)) && ($t > 31)) {
  5175. if ($yearFound) {
  5176. return self::$_errorCodes['value'];
  5177. } else {
  5178. if ($t < 100) { $t += 1900; }
  5179. $yearFound = true;
  5180. }
  5181. }
  5182. }
  5183. if ((count($t1) == 1) && (strpos($t,':') != false)) {
  5184. // We've been fed a time value without any date
  5185. return 0.0;
  5186. } elseif (count($t1) == 2) {
  5187. // We only have two parts of the date: either day/month or month/year
  5188. if ($yearFound) {
  5189. array_unshift($t1,1);
  5190. } else {
  5191. array_push($t1,date('Y'));
  5192. }
  5193. }
  5194. unset($t);
  5195. $dateValue = implode(' ',$t1);
  5196. $PHPDateArray = date_parse($dateValue);
  5197. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  5198. $testVal1 = strtok($dateValue,'- ');
  5199. if ($testVal1 !== False) {
  5200. $testVal2 = strtok('- ');
  5201. if ($testVal2 !== False) {
  5202. $testVal3 = strtok('- ');
  5203. if ($testVal3 === False) {
  5204. $testVal3 = strftime('%Y');
  5205. }
  5206. } else {
  5207. return self::$_errorCodes['value'];
  5208. }
  5209. } else {
  5210. return self::$_errorCodes['value'];
  5211. }
  5212. $PHPDateArray = date_parse($testVal1.'-'.$testVal2.'-'.$testVal3);
  5213. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  5214. $PHPDateArray = date_parse($testVal2.'-'.$testVal1.'-'.$testVal3);
  5215. if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
  5216. return self::$_errorCodes['value'];
  5217. }
  5218. }
  5219. }
  5220. if (($PHPDateArray !== False) && ($PHPDateArray['error_count'] == 0)) {
  5221. // Execute function
  5222. if ($PHPDateArray['year'] == '') { $PHPDateArray['year'] = strftime('%Y'); }
  5223. if ($PHPDateArray['month'] == '') { $PHPDateArray['month'] = strftime('%m'); }
  5224. if ($PHPDateArray['day'] == '') { $PHPDateArray['day'] = strftime('%d'); }
  5225. $excelDateValue = floor(PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']));
  5226. switch (self::getReturnDateType()) {
  5227. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  5228. break;
  5229. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateValue);
  5230. break;
  5231. case self::RETURNDATE_PHP_OBJECT : return new DateTime($PHPDateArray['year'].'-'.$PHPDateArray['month'].'-'.$PHPDateArray['day'].' 00:00:00');
  5232. break;
  5233. }
  5234. }
  5235. return self::$_errorCodes['value'];
  5236. } // function DATEVALUE()
  5237. /**
  5238. * _getDateValue
  5239. *
  5240. * @param string $dateValue
  5241. * @return mixed Excel date/time serial value, or string if error
  5242. */
  5243. private static function _getDateValue($dateValue) {
  5244. if (!is_numeric($dateValue)) {
  5245. if ((is_string($dateValue)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
  5246. return self::$_errorCodes['value'];
  5247. }
  5248. if ((is_object($dateValue)) && ($dateValue instanceof PHPExcel_Shared_Date::$dateTimeObjectType)) {
  5249. $dateValue = PHPExcel_Shared_Date::PHPToExcel($dateValue);
  5250. } else {
  5251. $saveReturnDateType = self::getReturnDateType();
  5252. self::setReturnDateType(self::RETURNDATE_EXCEL);
  5253. $dateValue = self::DATEVALUE($dateValue);
  5254. self::setReturnDateType($saveReturnDateType);
  5255. }
  5256. }
  5257. return $dateValue;
  5258. } // function _getDateValue()
  5259. /**
  5260. * TIMEVALUE
  5261. *
  5262. * @param string $timeValue
  5263. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  5264. * depending on the value of the ReturnDateType flag
  5265. */
  5266. public static function TIMEVALUE($timeValue) {
  5267. $timeValue = trim(self::flattenSingleValue($timeValue),'"');
  5268. $timeValue = str_replace(array('/','.'),array('-','-'),$timeValue);
  5269. $PHPDateArray = date_parse($timeValue);
  5270. if (($PHPDateArray !== False) && ($PHPDateArray['error_count'] == 0)) {
  5271. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  5272. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']);
  5273. } else {
  5274. $excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel(1900,1,1,$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']) - 1;
  5275. }
  5276. switch (self::getReturnDateType()) {
  5277. case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
  5278. break;
  5279. case self::RETURNDATE_PHP_NUMERIC : return (integer) $phpDateValue = PHPExcel_Shared_Date::ExcelToPHP($excelDateValue+25569) - 3600;;
  5280. break;
  5281. case self::RETURNDATE_PHP_OBJECT : return new DateTime('1900-01-01 '.$PHPDateArray['hour'].':'.$PHPDateArray['minute'].':'.$PHPDateArray['second']);
  5282. break;
  5283. }
  5284. }
  5285. return self::$_errorCodes['value'];
  5286. } // function TIMEVALUE()
  5287. /**
  5288. * _getTimeValue
  5289. *
  5290. * @param string $timeValue
  5291. * @return mixed Excel date/time serial value, or string if error
  5292. */
  5293. private static function _getTimeValue($timeValue) {
  5294. $saveReturnDateType = self::getReturnDateType();
  5295. self::setReturnDateType(self::RETURNDATE_EXCEL);
  5296. $timeValue = self::TIMEVALUE($timeValue);
  5297. self::setReturnDateType($saveReturnDateType);
  5298. return $timeValue;
  5299. } // function _getTimeValue()
  5300. /**
  5301. * DATETIMENOW
  5302. *
  5303. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  5304. * depending on the value of the ReturnDateType flag
  5305. */
  5306. public static function DATETIMENOW() {
  5307. $saveTimeZone = date_default_timezone_get();
  5308. date_default_timezone_set('UTC');
  5309. $retValue = False;
  5310. switch (self::getReturnDateType()) {
  5311. case self::RETURNDATE_EXCEL : $retValue = (float) PHPExcel_Shared_Date::PHPToExcel(time());
  5312. break;
  5313. case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) time();
  5314. break;
  5315. case self::RETURNDATE_PHP_OBJECT : $retValue = new DateTime();
  5316. break;
  5317. }
  5318. date_default_timezone_set($saveTimeZone);
  5319. return $retValue;
  5320. } // function DATETIMENOW()
  5321. /**
  5322. * DATENOW
  5323. *
  5324. * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
  5325. * depending on the value of the ReturnDateType flag
  5326. */
  5327. public static function DATENOW() {
  5328. $saveTimeZone = date_default_timezone_get();
  5329. date_default_timezone_set('UTC');
  5330. $retValue = False;
  5331. $excelDateTime = floor(PHPExcel_Shared_Date::PHPToExcel(time()));
  5332. switch (self::getReturnDateType()) {
  5333. case self::RETURNDATE_EXCEL : $retValue = (float) $excelDateTime;
  5334. break;
  5335. case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) PHPExcel_Shared_Date::ExcelToPHP($excelDateTime) - 3600;
  5336. break;
  5337. case self::RETURNDATE_PHP_OBJECT : $retValue = PHPExcel_Shared_Date::ExcelToPHPObject($excelDateTime);
  5338. break;
  5339. }
  5340. date_default_timezone_set($saveTimeZone);
  5341. return $retValue;
  5342. } // function DATENOW()
  5343. private static function _isLeapYear($year) {
  5344. return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0));
  5345. } // function _isLeapYear()
  5346. private static function _dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) {
  5347. if ($startDay == 31) {
  5348. --$startDay;
  5349. } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::_isLeapYear($startYear))))) {
  5350. $startDay = 30;
  5351. }
  5352. if ($endDay == 31) {
  5353. if ($methodUS && $startDay != 30) {
  5354. $endDay = 1;
  5355. if ($endMonth == 12) {
  5356. ++$endYear;
  5357. $endMonth = 1;
  5358. } else {
  5359. ++$endMonth;
  5360. }
  5361. } else {
  5362. $endDay = 30;
  5363. }
  5364. }
  5365. return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
  5366. } // function _dateDiff360()
  5367. /**
  5368. * DAYS360
  5369. *
  5370. * @param long $startDate Excel date serial value or a standard date string
  5371. * @param long $endDate Excel date serial value or a standard date string
  5372. * @param boolean $method US or European Method
  5373. * @return long PHP date/time serial
  5374. */
  5375. public static function DAYS360($startDate = 0, $endDate = 0, $method = false) {
  5376. $startDate = self::flattenSingleValue($startDate);
  5377. $endDate = self::flattenSingleValue($endDate);
  5378. if (is_string($startDate = self::_getDateValue($startDate))) {
  5379. return self::$_errorCodes['value'];
  5380. }
  5381. if (is_string($endDate = self::_getDateValue($endDate))) {
  5382. return self::$_errorCodes['value'];
  5383. }
  5384. // Execute function
  5385. $PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
  5386. $startDay = $PHPStartDateObject->format('j');
  5387. $startMonth = $PHPStartDateObject->format('n');
  5388. $startYear = $PHPStartDateObject->format('Y');
  5389. $PHPEndDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  5390. $endDay = $PHPEndDateObject->format('j');
  5391. $endMonth = $PHPEndDateObject->format('n');
  5392. $endYear = $PHPEndDateObject->format('Y');
  5393. return self::_dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method);
  5394. } // function DAYS360()
  5395. /**
  5396. * DATEDIF
  5397. *
  5398. * @param long $startDate Excel date serial value or a standard date string
  5399. * @param long $endDate Excel date serial value or a standard date string
  5400. * @param string $unit
  5401. * @return long Interval between the dates
  5402. */
  5403. public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') {
  5404. $startDate = self::flattenSingleValue($startDate);
  5405. $endDate = self::flattenSingleValue($endDate);
  5406. $unit = strtoupper(self::flattenSingleValue($unit));
  5407. if (is_string($startDate = self::_getDateValue($startDate))) {
  5408. return self::$_errorCodes['value'];
  5409. }
  5410. if (is_string($endDate = self::_getDateValue($endDate))) {
  5411. return self::$_errorCodes['value'];
  5412. }
  5413. // Validate parameters
  5414. if ($startDate >= $endDate) {
  5415. return self::$_errorCodes['num'];
  5416. }
  5417. // Execute function
  5418. $difference = $endDate - $startDate;
  5419. $PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
  5420. $startDays = $PHPStartDateObject->format('j');
  5421. $startMonths = $PHPStartDateObject->format('n');
  5422. $startYears = $PHPStartDateObject->format('Y');
  5423. $PHPEndDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  5424. $endDays = $PHPEndDateObject->format('j');
  5425. $endMonths = $PHPEndDateObject->format('n');
  5426. $endYears = $PHPEndDateObject->format('Y');
  5427. $retVal = self::$_errorCodes['num'];
  5428. switch ($unit) {
  5429. case 'D':
  5430. $retVal = intval($difference);
  5431. break;
  5432. case 'M':
  5433. $retVal = intval($endMonths - $startMonths) + (intval($endYears - $startYears) * 12);
  5434. // We're only interested in full months
  5435. if ($endDays < $startDays) {
  5436. --$retVal;
  5437. }
  5438. break;
  5439. case 'Y':
  5440. $retVal = intval($endYears - $startYears);
  5441. // We're only interested in full months
  5442. if ($endMonths < $startMonths) {
  5443. --$retVal;
  5444. } elseif (($endMonths == $startMonths) && ($endDays < $startDays)) {
  5445. --$retVal;
  5446. }
  5447. break;
  5448. case 'MD':
  5449. if ($endDays < $startDays) {
  5450. $retVal = $endDays;
  5451. $PHPEndDateObject->modify('-'.$endDays.' days');
  5452. $adjustDays = $PHPEndDateObject->format('j');
  5453. if ($adjustDays > $startDays) {
  5454. $retVal += ($adjustDays - $startDays);
  5455. }
  5456. } else {
  5457. $retVal = $endDays - $startDays;
  5458. }
  5459. break;
  5460. case 'YM':
  5461. $retVal = intval($endMonths - $startMonths);
  5462. if ($retVal < 0) $retVal = 12 + $retVal;
  5463. // We're only interested in full months
  5464. if ($endDays < $startDays) {
  5465. --$retVal;
  5466. }
  5467. break;
  5468. case 'YD':
  5469. $retVal = intval($difference);
  5470. if ($endYears > $startYears) {
  5471. while ($endYears > $startYears) {
  5472. $PHPEndDateObject->modify('-1 year');
  5473. $endYears = $PHPEndDateObject->format('Y');
  5474. }
  5475. $retVal = $PHPEndDateObject->format('z') - $PHPStartDateObject->format('z');
  5476. if ($retVal < 0) { $retVal += 365; }
  5477. }
  5478. break;
  5479. }
  5480. return $retVal;
  5481. } // function DATEDIF()
  5482. /**
  5483. * YEARFRAC
  5484. *
  5485. * Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the
  5486. * end_date). Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or obligations
  5487. * to assign to a specific term.
  5488. *
  5489. * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer) or date object, or a standard date string
  5490. * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer) or date object, or a standard date string
  5491. * @param integer $method Method used for the calculation
  5492. * 0 or omitted US (NASD) 30/360
  5493. * 1 Actual/actual
  5494. * 2 Actual/360
  5495. * 3 Actual/365
  5496. * 4 European 30/360
  5497. * @return float fraction of the year
  5498. */
  5499. public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) {
  5500. $startDate = self::flattenSingleValue($startDate);
  5501. $endDate = self::flattenSingleValue($endDate);
  5502. $method = self::flattenSingleValue($method);
  5503. if (is_string($startDate = self::_getDateValue($startDate))) {
  5504. return self::$_errorCodes['value'];
  5505. }
  5506. if (is_string($endDate = self::_getDateValue($endDate))) {
  5507. return self::$_errorCodes['value'];
  5508. }
  5509. if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) {
  5510. switch($method) {
  5511. case 0 :
  5512. return self::DAYS360($startDate,$endDate) / 360;
  5513. break;
  5514. case 1 :
  5515. $days = self::DATEDIF($startDate,$endDate);
  5516. $startYear = self::YEAR($startDate);
  5517. $endYear = self::YEAR($endDate);
  5518. $years = $endYear - $startYear + 1;
  5519. $leapDays = 0;
  5520. if ($years == 1) {
  5521. if (self::_isLeapYear($endYear)) {
  5522. $startMonth = self::MONTHOFYEAR($startDate);
  5523. $endMonth = self::MONTHOFYEAR($endDate);
  5524. $endDay = self::DAYOFMONTH($endDate);
  5525. if (($startMonth < 3) ||
  5526. (($endMonth * 100 + $endDay) >= (2 * 100 + 29))) {
  5527. $leapDays += 1;
  5528. }
  5529. }
  5530. } else {
  5531. for($year = $startYear; $year <= $endYear; ++$year) {
  5532. if ($year == $startYear) {
  5533. $startMonth = self::MONTHOFYEAR($startDate);
  5534. $startDay = self::DAYOFMONTH($startDate);
  5535. if ($startMonth < 3) {
  5536. $leapDays += (self::_isLeapYear($year)) ? 1 : 0;
  5537. }
  5538. } elseif($year == $endYear) {
  5539. $endMonth = self::MONTHOFYEAR($endDate);
  5540. $endDay = self::DAYOFMONTH($endDate);
  5541. if (($endMonth * 100 + $endDay) >= (2 * 100 + 29)) {
  5542. $leapDays += (self::_isLeapYear($year)) ? 1 : 0;
  5543. }
  5544. } else {
  5545. $leapDays += (self::_isLeapYear($year)) ? 1 : 0;
  5546. }
  5547. }
  5548. if ($years == 2) {
  5549. if (($leapDays == 0) && (self::_isLeapYear($startYear)) && ($days > 365)) {
  5550. $leapDays = 1;
  5551. } elseif ($days < 366) {
  5552. $years = 1;
  5553. }
  5554. }
  5555. $leapDays /= $years;
  5556. }
  5557. return $days / (365 + $leapDays);
  5558. break;
  5559. case 2 :
  5560. return self::DATEDIF($startDate,$endDate) / 360;
  5561. break;
  5562. case 3 :
  5563. return self::DATEDIF($startDate,$endDate) / 365;
  5564. break;
  5565. case 4 :
  5566. return self::DAYS360($startDate,$endDate,True) / 360;
  5567. break;
  5568. }
  5569. }
  5570. return self::$_errorCodes['value'];
  5571. } // function YEARFRAC()
  5572. /**
  5573. * NETWORKDAYS
  5574. *
  5575. * @param mixed Start date
  5576. * @param mixed End date
  5577. * @param array of mixed Optional Date Series
  5578. * @return long Interval between the dates
  5579. */
  5580. public static function NETWORKDAYS($startDate,$endDate) {
  5581. // Retrieve the mandatory start and end date that are referenced in the function definition
  5582. $startDate = self::flattenSingleValue($startDate);
  5583. $endDate = self::flattenSingleValue($endDate);
  5584. // Flush the mandatory start and end date that are referenced in the function definition, and get the optional days
  5585. $dateArgs = self::flattenArray(func_get_args());
  5586. array_shift($dateArgs);
  5587. array_shift($dateArgs);
  5588. // Validate the start and end dates
  5589. if (is_string($startDate = $sDate = self::_getDateValue($startDate))) {
  5590. return self::$_errorCodes['value'];
  5591. }
  5592. $startDate = (float) floor($startDate);
  5593. if (is_string($endDate = $eDate = self::_getDateValue($endDate))) {
  5594. return self::$_errorCodes['value'];
  5595. }
  5596. $endDate = (float) floor($endDate);
  5597. if ($sDate > $eDate) {
  5598. $startDate = $eDate;
  5599. $endDate = $sDate;
  5600. }
  5601. // Execute function
  5602. $startDoW = 6 - self::DAYOFWEEK($startDate,2);
  5603. if ($startDoW < 0) { $startDoW = 0; }
  5604. $endDoW = self::DAYOFWEEK($endDate,2);
  5605. if ($endDoW >= 6) { $endDoW = 0; }
  5606. $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5;
  5607. $partWeekDays = $endDoW + $startDoW;
  5608. if ($partWeekDays > 5) {
  5609. $partWeekDays -= 5;
  5610. }
  5611. // Test any extra holiday parameters
  5612. $holidayCountedArray = array();
  5613. foreach ($dateArgs as $holidayDate) {
  5614. if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
  5615. return self::$_errorCodes['value'];
  5616. }
  5617. if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
  5618. if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
  5619. --$partWeekDays;
  5620. $holidayCountedArray[] = $holidayDate;
  5621. }
  5622. }
  5623. }
  5624. if ($sDate > $eDate) {
  5625. return 0 - ($wholeWeekDays + $partWeekDays);
  5626. }
  5627. return $wholeWeekDays + $partWeekDays;
  5628. } // function NETWORKDAYS()
  5629. /**
  5630. * WORKDAY
  5631. *
  5632. * @param mixed Start date
  5633. * @param mixed number of days for adjustment
  5634. * @param array of mixed Optional Date Series
  5635. * @return long Interval between the dates
  5636. */
  5637. public static function WORKDAY($startDate,$endDays) {
  5638. // Retrieve the mandatory start date and days that are referenced in the function definition
  5639. $startDate = self::flattenSingleValue($startDate);
  5640. $endDays = (int) self::flattenSingleValue($endDays);
  5641. // Flush the mandatory start date and days that are referenced in the function definition, and get the optional days
  5642. $dateArgs = self::flattenArray(func_get_args());
  5643. array_shift($dateArgs);
  5644. array_shift($dateArgs);
  5645. if ((is_string($startDate = self::_getDateValue($startDate))) || (!is_numeric($endDays))) {
  5646. return self::$_errorCodes['value'];
  5647. }
  5648. $startDate = (float) floor($startDate);
  5649. // If endDays is 0, we always return startDate
  5650. if ($endDays == 0) { return $startDate; }
  5651. $decrementing = ($endDays < 0) ? True : False;
  5652. // Adjust the start date if it falls over a weekend
  5653. $startDoW = self::DAYOFWEEK($startDate,3);
  5654. if (self::DAYOFWEEK($startDate,3) >= 5) {
  5655. $startDate += ($decrementing) ? -$startDoW + 4: 7 - $startDoW;
  5656. ($decrementing) ? $endDays++ : $endDays--;
  5657. }
  5658. // Add endDays
  5659. $endDate = (float) $startDate + (intval($endDays / 5) * 7) + ($endDays % 5);
  5660. // Adjust the calculated end date if it falls over a weekend
  5661. $endDoW = self::DAYOFWEEK($endDate,3);
  5662. if ($endDoW >= 5) {
  5663. $endDate += ($decrementing) ? -$endDoW + 4: 7 - $endDoW;
  5664. }
  5665. // Test any extra holiday parameters
  5666. if (count($dateArgs) > 0) {
  5667. $holidayCountedArray = $holidayDates = array();
  5668. foreach ($dateArgs as $holidayDate) {
  5669. if ((!is_null($holidayDate)) && (trim($holidayDate) > '')) {
  5670. if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
  5671. return self::$_errorCodes['value'];
  5672. }
  5673. if (self::DAYOFWEEK($holidayDate,3) < 5) {
  5674. $holidayDates[] = $holidayDate;
  5675. }
  5676. }
  5677. }
  5678. if ($decrementing) {
  5679. rsort($holidayDates, SORT_NUMERIC);
  5680. } else {
  5681. sort($holidayDates, SORT_NUMERIC);
  5682. }
  5683. foreach ($holidayDates as $holidayDate) {
  5684. if ($decrementing) {
  5685. if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
  5686. if (!in_array($holidayDate,$holidayCountedArray)) {
  5687. --$endDate;
  5688. $holidayCountedArray[] = $holidayDate;
  5689. }
  5690. }
  5691. } else {
  5692. if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
  5693. if (!in_array($holidayDate,$holidayCountedArray)) {
  5694. ++$endDate;
  5695. $holidayCountedArray[] = $holidayDate;
  5696. }
  5697. }
  5698. }
  5699. // Adjust the calculated end date if it falls over a weekend
  5700. $endDoW = self::DAYOFWEEK($endDate,3);
  5701. if ($endDoW >= 5) {
  5702. $endDate += ($decrementing) ? -$endDoW + 4: 7 - $endDoW;
  5703. }
  5704. }
  5705. }
  5706. switch (self::getReturnDateType()) {
  5707. case self::RETURNDATE_EXCEL : return (float) $endDate;
  5708. break;
  5709. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP($endDate);
  5710. break;
  5711. case self::RETURNDATE_PHP_OBJECT : return PHPExcel_Shared_Date::ExcelToPHPObject($endDate);
  5712. break;
  5713. }
  5714. } // function WORKDAY()
  5715. /**
  5716. * DAYOFMONTH
  5717. *
  5718. * @param long $dateValue Excel date serial value or a standard date string
  5719. * @return int Day
  5720. */
  5721. public static function DAYOFMONTH($dateValue = 1) {
  5722. $dateValue = self::flattenSingleValue($dateValue);
  5723. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5724. return self::$_errorCodes['value'];
  5725. } elseif ($dateValue == 0.0) {
  5726. return 0;
  5727. } elseif ($dateValue < 0.0) {
  5728. return self::$_errorCodes['num'];
  5729. }
  5730. // Execute function
  5731. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  5732. return (int) $PHPDateObject->format('j');
  5733. } // function DAYOFMONTH()
  5734. /**
  5735. * DAYOFWEEK
  5736. *
  5737. * @param long $dateValue Excel date serial value or a standard date string
  5738. * @return int Day
  5739. */
  5740. public static function DAYOFWEEK($dateValue = 1, $style = 1) {
  5741. $dateValue = self::flattenSingleValue($dateValue);
  5742. $style = floor(self::flattenSingleValue($style));
  5743. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5744. return self::$_errorCodes['value'];
  5745. } elseif ($dateValue < 0.0) {
  5746. return self::$_errorCodes['num'];
  5747. }
  5748. // Execute function
  5749. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  5750. $DoW = $PHPDateObject->format('w');
  5751. $firstDay = 1;
  5752. switch ($style) {
  5753. case 1: ++$DoW;
  5754. break;
  5755. case 2: if ($DoW == 0) { $DoW = 7; }
  5756. break;
  5757. case 3: if ($DoW == 0) { $DoW = 7; }
  5758. $firstDay = 0;
  5759. --$DoW;
  5760. break;
  5761. default:
  5762. }
  5763. if (self::$compatibilityMode == self::COMPATIBILITY_EXCEL) {
  5764. // Test for Excel's 1900 leap year, and introduce the error as required
  5765. if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) {
  5766. --$DoW;
  5767. if ($DoW < $firstDay) {
  5768. $DoW += 7;
  5769. }
  5770. }
  5771. }
  5772. return (int) $DoW;
  5773. } // function DAYOFWEEK()
  5774. /**
  5775. * WEEKOFYEAR
  5776. *
  5777. * @param long $dateValue Excel date serial value or a standard date string
  5778. * @param boolean $method Week begins on Sunday or Monday
  5779. * @return int Week Number
  5780. */
  5781. public static function WEEKOFYEAR($dateValue = 1, $method = 1) {
  5782. $dateValue = self::flattenSingleValue($dateValue);
  5783. $method = floor(self::flattenSingleValue($method));
  5784. if (!is_numeric($method)) {
  5785. return self::$_errorCodes['value'];
  5786. } elseif (($method < 1) || ($method > 2)) {
  5787. return self::$_errorCodes['num'];
  5788. }
  5789. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5790. return self::$_errorCodes['value'];
  5791. } elseif ($dateValue < 0.0) {
  5792. return self::$_errorCodes['num'];
  5793. }
  5794. // Execute function
  5795. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  5796. $dayOfYear = $PHPDateObject->format('z');
  5797. $dow = $PHPDateObject->format('w');
  5798. $PHPDateObject->modify('-'.$dayOfYear.' days');
  5799. $dow = $PHPDateObject->format('w');
  5800. $daysInFirstWeek = 7 - (($dow + (2 - $method)) % 7);
  5801. $dayOfYear -= $daysInFirstWeek;
  5802. $weekOfYear = ceil($dayOfYear / 7) + 1;
  5803. return (int) $weekOfYear;
  5804. } // function WEEKOFYEAR()
  5805. /**
  5806. * MONTHOFYEAR
  5807. *
  5808. * @param long $dateValue Excel date serial value or a standard date string
  5809. * @return int Month
  5810. */
  5811. public static function MONTHOFYEAR($dateValue = 1) {
  5812. $dateValue = self::flattenSingleValue($dateValue);
  5813. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5814. return self::$_errorCodes['value'];
  5815. } elseif ($dateValue < 0.0) {
  5816. return self::$_errorCodes['num'];
  5817. }
  5818. // Execute function
  5819. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  5820. return (int) $PHPDateObject->format('n');
  5821. } // function MONTHOFYEAR()
  5822. /**
  5823. * YEAR
  5824. *
  5825. * @param long $dateValue Excel date serial value or a standard date string
  5826. * @return int Year
  5827. */
  5828. public static function YEAR($dateValue = 1) {
  5829. $dateValue = self::flattenSingleValue($dateValue);
  5830. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5831. return self::$_errorCodes['value'];
  5832. } elseif ($dateValue < 0.0) {
  5833. return self::$_errorCodes['num'];
  5834. }
  5835. // Execute function
  5836. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  5837. return (int) $PHPDateObject->format('Y');
  5838. } // function YEAR()
  5839. /**
  5840. * HOUROFDAY
  5841. *
  5842. * @param mixed $timeValue Excel time serial value or a standard time string
  5843. * @return int Hour
  5844. */
  5845. public static function HOUROFDAY($timeValue = 0) {
  5846. $timeValue = self::flattenSingleValue($timeValue);
  5847. if (!is_numeric($timeValue)) {
  5848. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  5849. $testVal = strtok($timeValue,'/-: ');
  5850. if (strlen($testVal) < strlen($timeValue)) {
  5851. return self::$_errorCodes['value'];
  5852. }
  5853. }
  5854. $timeValue = self::_getTimeValue($timeValue);
  5855. if (is_string($timeValue)) {
  5856. return self::$_errorCodes['value'];
  5857. }
  5858. }
  5859. // Execute function
  5860. if ($timeValue >= 1) {
  5861. $timeValue = fmod($timeValue,1);
  5862. } elseif ($timeValue < 0.0) {
  5863. return self::$_errorCodes['num'];
  5864. }
  5865. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  5866. return (int) gmdate('G',$timeValue);
  5867. } // function HOUROFDAY()
  5868. /**
  5869. * MINUTEOFHOUR
  5870. *
  5871. * @param long $timeValue Excel time serial value or a standard time string
  5872. * @return int Minute
  5873. */
  5874. public static function MINUTEOFHOUR($timeValue = 0) {
  5875. $timeValue = $timeTester = self::flattenSingleValue($timeValue);
  5876. if (!is_numeric($timeValue)) {
  5877. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  5878. $testVal = strtok($timeValue,'/-: ');
  5879. if (strlen($testVal) < strlen($timeValue)) {
  5880. return self::$_errorCodes['value'];
  5881. }
  5882. }
  5883. $timeValue = self::_getTimeValue($timeValue);
  5884. if (is_string($timeValue)) {
  5885. return self::$_errorCodes['value'];
  5886. }
  5887. }
  5888. // Execute function
  5889. if ($timeValue >= 1) {
  5890. $timeValue = fmod($timeValue,1);
  5891. } elseif ($timeValue < 0.0) {
  5892. return self::$_errorCodes['num'];
  5893. }
  5894. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  5895. return (int) gmdate('i',$timeValue);
  5896. } // function MINUTEOFHOUR()
  5897. /**
  5898. * SECONDOFMINUTE
  5899. *
  5900. * @param long $timeValue Excel time serial value or a standard time string
  5901. * @return int Second
  5902. */
  5903. public static function SECONDOFMINUTE($timeValue = 0) {
  5904. $timeValue = self::flattenSingleValue($timeValue);
  5905. if (!is_numeric($timeValue)) {
  5906. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  5907. $testVal = strtok($timeValue,'/-: ');
  5908. if (strlen($testVal) < strlen($timeValue)) {
  5909. return self::$_errorCodes['value'];
  5910. }
  5911. }
  5912. $timeValue = self::_getTimeValue($timeValue);
  5913. if (is_string($timeValue)) {
  5914. return self::$_errorCodes['value'];
  5915. }
  5916. }
  5917. // Execute function
  5918. if ($timeValue >= 1) {
  5919. $timeValue = fmod($timeValue,1);
  5920. } elseif ($timeValue < 0.0) {
  5921. return self::$_errorCodes['num'];
  5922. }
  5923. $timeValue = PHPExcel_Shared_Date::ExcelToPHP($timeValue);
  5924. return (int) gmdate('s',$timeValue);
  5925. } // function SECONDOFMINUTE()
  5926. private static function _adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) {
  5927. // Execute function
  5928. $PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
  5929. $oMonth = (int) $PHPDateObject->format('m');
  5930. $oYear = (int) $PHPDateObject->format('Y');
  5931. $adjustmentMonthsString = (string) $adjustmentMonths;
  5932. if ($adjustmentMonths > 0) {
  5933. $adjustmentMonthsString = '+'.$adjustmentMonths;
  5934. }
  5935. if ($adjustmentMonths != 0) {
  5936. $PHPDateObject->modify($adjustmentMonthsString.' months');
  5937. }
  5938. $nMonth = (int) $PHPDateObject->format('m');
  5939. $nYear = (int) $PHPDateObject->format('Y');
  5940. $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
  5941. if ($monthDiff != $adjustmentMonths) {
  5942. $adjustDays = (int) $PHPDateObject->format('d');
  5943. $adjustDaysString = '-'.$adjustDays.' days';
  5944. $PHPDateObject->modify($adjustDaysString);
  5945. }
  5946. return $PHPDateObject;
  5947. } // function _adjustDateByMonths()
  5948. /**
  5949. * EDATE
  5950. *
  5951. * Returns the serial number that represents the date that is the indicated number of months before or after a specified date
  5952. * (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.
  5953. *
  5954. * @param long $dateValue Excel date serial value or a standard date string
  5955. * @param int $adjustmentMonths Number of months to adjust by
  5956. * @return long Excel date serial value
  5957. */
  5958. public static function EDATE($dateValue = 1, $adjustmentMonths = 0) {
  5959. $dateValue = self::flattenSingleValue($dateValue);
  5960. $adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
  5961. if (!is_numeric($adjustmentMonths)) {
  5962. return self::$_errorCodes['value'];
  5963. }
  5964. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5965. return self::$_errorCodes['value'];
  5966. }
  5967. // Execute function
  5968. $PHPDateObject = self::_adjustDateByMonths($dateValue,$adjustmentMonths);
  5969. switch (self::getReturnDateType()) {
  5970. case self::RETURNDATE_EXCEL : return (float) PHPExcel_Shared_Date::PHPToExcel($PHPDateObject);
  5971. break;
  5972. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::PHPToExcel($PHPDateObject));
  5973. break;
  5974. case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
  5975. break;
  5976. }
  5977. } // function EDATE()
  5978. /**
  5979. * EOMONTH
  5980. *
  5981. * Returns the serial number for the last day of the month that is the indicated number of months before or after start_date.
  5982. * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
  5983. *
  5984. * @param long $dateValue Excel date serial value or a standard date string
  5985. * @param int $adjustmentMonths Number of months to adjust by
  5986. * @return long Excel date serial value
  5987. */
  5988. public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) {
  5989. $dateValue = self::flattenSingleValue($dateValue);
  5990. $adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
  5991. if (!is_numeric($adjustmentMonths)) {
  5992. return self::$_errorCodes['value'];
  5993. }
  5994. if (is_string($dateValue = self::_getDateValue($dateValue))) {
  5995. return self::$_errorCodes['value'];
  5996. }
  5997. // Execute function
  5998. $PHPDateObject = self::_adjustDateByMonths($dateValue,$adjustmentMonths+1);
  5999. $adjustDays = (int) $PHPDateObject->format('d');
  6000. $adjustDaysString = '-'.$adjustDays.' days';
  6001. $PHPDateObject->modify($adjustDaysString);
  6002. switch (self::getReturnDateType()) {
  6003. case self::RETURNDATE_EXCEL : return (float) PHPExcel_Shared_Date::PHPToExcel($PHPDateObject);
  6004. break;
  6005. case self::RETURNDATE_PHP_NUMERIC : return (integer) PHPExcel_Shared_Date::ExcelToPHP(PHPExcel_Shared_Date::PHPToExcel($PHPDateObject));
  6006. break;
  6007. case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
  6008. break;
  6009. }
  6010. } // function EOMONTH()
  6011. /**
  6012. * TRUNC
  6013. *
  6014. * Truncates value to the number of fractional digits by number_digits.
  6015. *
  6016. * @param float $value
  6017. * @param int $number_digits
  6018. * @return float Truncated value
  6019. */
  6020. public static function TRUNC($value = 0, $number_digits = 0) {
  6021. $value = self::flattenSingleValue($value);
  6022. $number_digits = self::flattenSingleValue($number_digits);
  6023. // Validate parameters
  6024. if ($number_digits < 0) {
  6025. return self::$_errorCodes['value'];
  6026. }
  6027. // Truncate
  6028. if ($number_digits > 0) {
  6029. $value = $value * pow(10, $number_digits);
  6030. }
  6031. $value = intval($value);
  6032. if ($number_digits > 0) {
  6033. $value = $value / pow(10, $number_digits);
  6034. }
  6035. // Return
  6036. return $value;
  6037. } // function TRUNC()
  6038. /**
  6039. * POWER
  6040. *
  6041. * Computes x raised to the power y.
  6042. *
  6043. * @param float $x
  6044. * @param float $y
  6045. * @return float
  6046. */
  6047. public static function POWER($x = 0, $y = 2) {
  6048. $x = self::flattenSingleValue($x);
  6049. $y = self::flattenSingleValue($y);
  6050. // Validate parameters
  6051. if ($x == 0 && $y <= 0) {
  6052. return self::$_errorCodes['divisionbyzero'];
  6053. }
  6054. // Return
  6055. return pow($x, $y);
  6056. } // function POWER()
  6057. private static function _nbrConversionFormat($xVal,$places) {
  6058. if (!is_null($places)) {
  6059. if (strlen($xVal) <= $places) {
  6060. return substr(str_pad($xVal,$places,'0',STR_PAD_LEFT),-10);
  6061. } else {
  6062. return self::$_errorCodes['num'];
  6063. }
  6064. }
  6065. return substr($xVal,-10);
  6066. } // function _nbrConversionFormat()
  6067. /**
  6068. * BINTODEC
  6069. *
  6070. * Return a binary value as Decimal.
  6071. *
  6072. * @param string $x
  6073. * @return string
  6074. */
  6075. public static function BINTODEC($x) {
  6076. $x = self::flattenSingleValue($x);
  6077. if (is_bool($x)) {
  6078. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  6079. $x = (int) $x;
  6080. } else {
  6081. return self::$_errorCodes['value'];
  6082. }
  6083. }
  6084. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  6085. $x = floor($x);
  6086. }
  6087. $x = (string) $x;
  6088. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  6089. return self::$_errorCodes['num'];
  6090. }
  6091. if (strlen($x) > 10) {
  6092. return self::$_errorCodes['num'];
  6093. } elseif (strlen($x) == 10) {
  6094. // Two's Complement
  6095. $x = substr($x,-9);
  6096. return '-'.(512-bindec($x));
  6097. }
  6098. return bindec($x);
  6099. } // function BINTODEC()
  6100. /**
  6101. * BINTOHEX
  6102. *
  6103. * Return a binary value as Hex.
  6104. *
  6105. * @param string $x
  6106. * @return string
  6107. */
  6108. public static function BINTOHEX($x, $places=null) {
  6109. $x = floor(self::flattenSingleValue($x));
  6110. $places = self::flattenSingleValue($places);
  6111. if (is_bool($x)) {
  6112. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  6113. $x = (int) $x;
  6114. } else {
  6115. return self::$_errorCodes['value'];
  6116. }
  6117. }
  6118. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  6119. $x = floor($x);
  6120. }
  6121. $x = (string) $x;
  6122. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  6123. return self::$_errorCodes['num'];
  6124. }
  6125. if (strlen($x) > 10) {
  6126. return self::$_errorCodes['num'];
  6127. } elseif (strlen($x) == 10) {
  6128. // Two's Complement
  6129. return str_repeat('F',8).substr(strtoupper(dechex(bindec(substr($x,-9)))),-2);
  6130. }
  6131. $hexVal = (string) strtoupper(dechex(bindec($x)));
  6132. return self::_nbrConversionFormat($hexVal,$places);
  6133. } // function BINTOHEX()
  6134. /**
  6135. * BINTOOCT
  6136. *
  6137. * Return a binary value as Octal.
  6138. *
  6139. * @param string $x
  6140. * @return string
  6141. */
  6142. public static function BINTOOCT($x, $places=null) {
  6143. $x = floor(self::flattenSingleValue($x));
  6144. $places = self::flattenSingleValue($places);
  6145. if (is_bool($x)) {
  6146. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  6147. $x = (int) $x;
  6148. } else {
  6149. return self::$_errorCodes['value'];
  6150. }
  6151. }
  6152. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  6153. $x = floor($x);
  6154. }
  6155. $x = (string) $x;
  6156. if (strlen($x) > preg_match_all('/[01]/',$x,$out)) {
  6157. return self::$_errorCodes['num'];
  6158. }
  6159. if (strlen($x) > 10) {
  6160. return self::$_errorCodes['num'];
  6161. } elseif (strlen($x) == 10) {
  6162. // Two's Complement
  6163. return str_repeat('7',7).substr(strtoupper(decoct(bindec(substr($x,-9)))),-3);
  6164. }
  6165. $octVal = (string) decoct(bindec($x));
  6166. return self::_nbrConversionFormat($octVal,$places);
  6167. } // function BINTOOCT()
  6168. /**
  6169. * DECTOBIN
  6170. *
  6171. * Return an octal value as binary.
  6172. *
  6173. * @param string $x
  6174. * @return string
  6175. */
  6176. public static function DECTOBIN($x, $places=null) {
  6177. $x = self::flattenSingleValue($x);
  6178. $places = self::flattenSingleValue($places);
  6179. if (is_bool($x)) {
  6180. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  6181. $x = (int) $x;
  6182. } else {
  6183. return self::$_errorCodes['value'];
  6184. }
  6185. }
  6186. $x = (string) $x;
  6187. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  6188. return self::$_errorCodes['value'];
  6189. }
  6190. $x = (string) floor($x);
  6191. $r = decbin($x);
  6192. if (strlen($r) == 32) {
  6193. // Two's Complement
  6194. $r = substr($r,-10);
  6195. } elseif (strlen($r) > 11) {
  6196. return self::$_errorCodes['num'];
  6197. }
  6198. return self::_nbrConversionFormat($r,$places);
  6199. } // function DECTOBIN()
  6200. /**
  6201. * DECTOOCT
  6202. *
  6203. * Return an octal value as binary.
  6204. *
  6205. * @param string $x
  6206. * @return string
  6207. */
  6208. public static function DECTOOCT($x, $places=null) {
  6209. $x = self::flattenSingleValue($x);
  6210. $places = self::flattenSingleValue($places);
  6211. if (is_bool($x)) {
  6212. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  6213. $x = (int) $x;
  6214. } else {
  6215. return self::$_errorCodes['value'];
  6216. }
  6217. }
  6218. $x = (string) $x;
  6219. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  6220. return self::$_errorCodes['value'];
  6221. }
  6222. $x = (string) floor($x);
  6223. $r = decoct($x);
  6224. if (strlen($r) == 11) {
  6225. // Two's Complement
  6226. $r = substr($r,-10);
  6227. }
  6228. return self::_nbrConversionFormat($r,$places);
  6229. } // function DECTOOCT()
  6230. /**
  6231. * DECTOHEX
  6232. *
  6233. * Return an octal value as binary.
  6234. *
  6235. * @param string $x
  6236. * @return string
  6237. */
  6238. public static function DECTOHEX($x, $places=null) {
  6239. $x = self::flattenSingleValue($x);
  6240. $places = self::flattenSingleValue($places);
  6241. if (is_bool($x)) {
  6242. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  6243. $x = (int) $x;
  6244. } else {
  6245. return self::$_errorCodes['value'];
  6246. }
  6247. }
  6248. $x = (string) $x;
  6249. if (strlen($x) > preg_match_all('/[-0123456789.]/',$x,$out)) {
  6250. return self::$_errorCodes['value'];
  6251. }
  6252. $x = (string) floor($x);
  6253. $r = strtoupper(dechex($x));
  6254. if (strlen($r) == 8) {
  6255. // Two's Complement
  6256. $r = 'FF'.$r;
  6257. }
  6258. return self::_nbrConversionFormat($r,$places);
  6259. } // function DECTOHEX()
  6260. /**
  6261. * HEXTOBIN
  6262. *
  6263. * Return a hex value as binary.
  6264. *
  6265. * @param string $x
  6266. * @return string
  6267. */
  6268. public static function HEXTOBIN($x, $places=null) {
  6269. $x = self::flattenSingleValue($x);
  6270. $places = self::flattenSingleValue($places);
  6271. if (is_bool($x)) {
  6272. return self::$_errorCodes['value'];
  6273. }
  6274. $x = (string) $x;
  6275. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  6276. return self::$_errorCodes['num'];
  6277. }
  6278. $binVal = decbin(hexdec($x));
  6279. return substr(self::_nbrConversionFormat($binVal,$places),-10);
  6280. } // function HEXTOBIN()
  6281. /**
  6282. * HEXTOOCT
  6283. *
  6284. * Return a hex value as octal.
  6285. *
  6286. * @param string $x
  6287. * @return string
  6288. */
  6289. public static function HEXTOOCT($x, $places=null) {
  6290. $x = self::flattenSingleValue($x);
  6291. $places = self::flattenSingleValue($places);
  6292. if (is_bool($x)) {
  6293. return self::$_errorCodes['value'];
  6294. }
  6295. $x = (string) $x;
  6296. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  6297. return self::$_errorCodes['num'];
  6298. }
  6299. $octVal = decoct(hexdec($x));
  6300. return self::_nbrConversionFormat($octVal,$places);
  6301. } // function HEXTOOCT()
  6302. /**
  6303. * HEXTODEC
  6304. *
  6305. * Return a hex value as octal.
  6306. *
  6307. * @param string $x
  6308. * @return string
  6309. */
  6310. public static function HEXTODEC($x) {
  6311. $x = self::flattenSingleValue($x);
  6312. if (is_bool($x)) {
  6313. return self::$_errorCodes['value'];
  6314. }
  6315. $x = (string) $x;
  6316. if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/',strtoupper($x),$out)) {
  6317. return self::$_errorCodes['num'];
  6318. }
  6319. return hexdec($x);
  6320. } // function HEXTODEC()
  6321. /**
  6322. * OCTTOBIN
  6323. *
  6324. * Return an octal value as binary.
  6325. *
  6326. * @param string $x
  6327. * @return string
  6328. */
  6329. public static function OCTTOBIN($x, $places=null) {
  6330. $x = self::flattenSingleValue($x);
  6331. $places = self::flattenSingleValue($places);
  6332. if (is_bool($x)) {
  6333. return self::$_errorCodes['value'];
  6334. }
  6335. $x = (string) $x;
  6336. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  6337. return self::$_errorCodes['num'];
  6338. }
  6339. $r = decbin(octdec($x));
  6340. return self::_nbrConversionFormat($r,$places);
  6341. } // function OCTTOBIN()
  6342. /**
  6343. * OCTTODEC
  6344. *
  6345. * Return an octal value as binary.
  6346. *
  6347. * @param string $x
  6348. * @return string
  6349. */
  6350. public static function OCTTODEC($x) {
  6351. $x = self::flattenSingleValue($x);
  6352. if (is_bool($x)) {
  6353. return self::$_errorCodes['value'];
  6354. }
  6355. $x = (string) $x;
  6356. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  6357. return self::$_errorCodes['num'];
  6358. }
  6359. return octdec($x);
  6360. } // function OCTTODEC()
  6361. /**
  6362. * OCTTOHEX
  6363. *
  6364. * Return an octal value as hex.
  6365. *
  6366. * @param string $x
  6367. * @return string
  6368. */
  6369. public static function OCTTOHEX($x, $places=null) {
  6370. $x = self::flattenSingleValue($x);
  6371. $places = self::flattenSingleValue($places);
  6372. if (is_bool($x)) {
  6373. return self::$_errorCodes['value'];
  6374. }
  6375. $x = (string) $x;
  6376. if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
  6377. return self::$_errorCodes['num'];
  6378. }
  6379. $hexVal = strtoupper(dechex(octdec($x)));
  6380. return self::_nbrConversionFormat($hexVal,$places);
  6381. } // function OCTTOHEX()
  6382. public static function _parseComplex($complexNumber) {
  6383. $workString = (string) $complexNumber;
  6384. $realNumber = $imaginary = 0;
  6385. // Extract the suffix, if there is one
  6386. $suffix = substr($workString,-1);
  6387. if (!is_numeric($suffix)) {
  6388. $workString = substr($workString,0,-1);
  6389. } else {
  6390. $suffix = '';
  6391. }
  6392. // Split the input into its Real and Imaginary components
  6393. $leadingSign = 0;
  6394. if (strlen($workString) > 0) {
  6395. $leadingSign = (($workString{0} == '+') || ($workString{0} == '-')) ? 1 : 0;
  6396. }
  6397. $power = '';
  6398. $realNumber = strtok($workString, '+-');
  6399. if (strtoupper(substr($realNumber,-1)) == 'E') {
  6400. $power = strtok('+-');
  6401. ++$leadingSign;
  6402. }
  6403. $realNumber = substr($workString,0,strlen($realNumber)+strlen($power)+$leadingSign);
  6404. if ($suffix != '') {
  6405. $imaginary = substr($workString,strlen($realNumber));
  6406. if (($imaginary == '') && (($realNumber == '') || ($realNumber == '+') || ($realNumber == '-'))) {
  6407. $imaginary = $realNumber.'1';
  6408. $realNumber = '0';
  6409. } else if ($imaginary == '') {
  6410. $imaginary = $realNumber;
  6411. $realNumber = '0';
  6412. } elseif (($imaginary == '+') || ($imaginary == '-')) {
  6413. $imaginary .= '1';
  6414. }
  6415. }
  6416. $complexArray = array( 'real' => $realNumber,
  6417. 'imaginary' => $imaginary,
  6418. 'suffix' => $suffix
  6419. );
  6420. return $complexArray;
  6421. } // function _parseComplex()
  6422. private static function _cleanComplex($complexNumber) {
  6423. if ($complexNumber{0} == '+') $complexNumber = substr($complexNumber,1);
  6424. if ($complexNumber{0} == '0') $complexNumber = substr($complexNumber,1);
  6425. if ($complexNumber{0} == '.') $complexNumber = '0'.$complexNumber;
  6426. if ($complexNumber{0} == '+') $complexNumber = substr($complexNumber,1);
  6427. return $complexNumber;
  6428. }
  6429. /**
  6430. * COMPLEX
  6431. *
  6432. * returns a complex number of the form x + yi or x + yj.
  6433. *
  6434. * @param float $realNumber
  6435. * @param float $imaginary
  6436. * @param string $suffix
  6437. * @return string
  6438. */
  6439. public static function COMPLEX($realNumber=0.0, $imaginary=0.0, $suffix='i') {
  6440. $realNumber = (is_null($realNumber)) ? 0.0 : (float) self::flattenSingleValue($realNumber);
  6441. $imaginary = (is_null($imaginary)) ? 0.0 : (float) self::flattenSingleValue($imaginary);
  6442. $suffix = (is_null($suffix)) ? 'i' : self::flattenSingleValue($suffix);
  6443. if (((is_numeric($realNumber)) && (is_numeric($imaginary))) &&
  6444. (($suffix == 'i') || ($suffix == 'j') || ($suffix == ''))) {
  6445. if ($suffix == '') $suffix = 'i';
  6446. if ($realNumber == 0.0) {
  6447. if ($imaginary == 0.0) {
  6448. return (string) '0';
  6449. } elseif ($imaginary == 1.0) {
  6450. return (string) $suffix;
  6451. } elseif ($imaginary == -1.0) {
  6452. return (string) '-'.$suffix;
  6453. }
  6454. return (string) $imaginary.$suffix;
  6455. } elseif ($imaginary == 0.0) {
  6456. return (string) $realNumber;
  6457. } elseif ($imaginary == 1.0) {
  6458. return (string) $realNumber.'+'.$suffix;
  6459. } elseif ($imaginary == -1.0) {
  6460. return (string) $realNumber.'-'.$suffix;
  6461. }
  6462. if ($imaginary > 0) { $imaginary = (string) '+'.$imaginary; }
  6463. return (string) $realNumber.$imaginary.$suffix;
  6464. }
  6465. return self::$_errorCodes['value'];
  6466. } // function COMPLEX()
  6467. /**
  6468. * IMAGINARY
  6469. *
  6470. * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
  6471. *
  6472. * @param string $complexNumber
  6473. * @return real
  6474. */
  6475. public static function IMAGINARY($complexNumber) {
  6476. $complexNumber = self::flattenSingleValue($complexNumber);
  6477. $parsedComplex = self::_parseComplex($complexNumber);
  6478. if (!is_array($parsedComplex)) {
  6479. return $parsedComplex;
  6480. }
  6481. return $parsedComplex['imaginary'];
  6482. } // function IMAGINARY()
  6483. /**
  6484. * IMREAL
  6485. *
  6486. * Returns the real coefficient of a complex number in x + yi or x + yj text format.
  6487. *
  6488. * @param string $complexNumber
  6489. * @return real
  6490. */
  6491. public static function IMREAL($complexNumber) {
  6492. $complexNumber = self::flattenSingleValue($complexNumber);
  6493. $parsedComplex = self::_parseComplex($complexNumber);
  6494. if (!is_array($parsedComplex)) {
  6495. return $parsedComplex;
  6496. }
  6497. return $parsedComplex['real'];
  6498. } // function IMREAL()
  6499. /**
  6500. * IMABS
  6501. *
  6502. * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
  6503. *
  6504. * @param string $complexNumber
  6505. * @return real
  6506. */
  6507. public static function IMABS($complexNumber) {
  6508. $complexNumber = self::flattenSingleValue($complexNumber);
  6509. $parsedComplex = self::_parseComplex($complexNumber);
  6510. if (!is_array($parsedComplex)) {
  6511. return $parsedComplex;
  6512. }
  6513. return sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
  6514. } // function IMABS()
  6515. /**
  6516. * IMARGUMENT
  6517. *
  6518. * 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.
  6519. *
  6520. * @param string $complexNumber
  6521. * @return string
  6522. */
  6523. public static function IMARGUMENT($complexNumber) {
  6524. $complexNumber = self::flattenSingleValue($complexNumber);
  6525. $parsedComplex = self::_parseComplex($complexNumber);
  6526. if (!is_array($parsedComplex)) {
  6527. return $parsedComplex;
  6528. }
  6529. if ($parsedComplex['real'] == 0.0) {
  6530. if ($parsedComplex['imaginary'] == 0.0) {
  6531. return 0.0;
  6532. } elseif($parsedComplex['imaginary'] < 0.0) {
  6533. return M_PI / -2;
  6534. } else {
  6535. return M_PI / 2;
  6536. }
  6537. } elseif ($parsedComplex['real'] > 0.0) {
  6538. return atan($parsedComplex['imaginary'] / $parsedComplex['real']);
  6539. } elseif ($parsedComplex['imaginary'] < 0.0) {
  6540. return 0 - (M_PI - atan(abs($parsedComplex['imaginary']) / abs($parsedComplex['real'])));
  6541. } else {
  6542. return M_PI - atan($parsedComplex['imaginary'] / abs($parsedComplex['real']));
  6543. }
  6544. } // function IMARGUMENT()
  6545. /**
  6546. * IMCONJUGATE
  6547. *
  6548. * Returns the complex conjugate of a complex number in x + yi or x + yj text format.
  6549. *
  6550. * @param string $complexNumber
  6551. * @return string
  6552. */
  6553. public static function IMCONJUGATE($complexNumber) {
  6554. $complexNumber = self::flattenSingleValue($complexNumber);
  6555. $parsedComplex = self::_parseComplex($complexNumber);
  6556. if (!is_array($parsedComplex)) {
  6557. return $parsedComplex;
  6558. }
  6559. if ($parsedComplex['imaginary'] == 0.0) {
  6560. return $parsedComplex['real'];
  6561. } else {
  6562. return self::_cleanComplex(self::COMPLEX($parsedComplex['real'], 0 - $parsedComplex['imaginary'], $parsedComplex['suffix']));
  6563. }
  6564. } // function IMCONJUGATE()
  6565. /**
  6566. * IMCOS
  6567. *
  6568. * Returns the cosine of a complex number in x + yi or x + yj text format.
  6569. *
  6570. * @param string $complexNumber
  6571. * @return string
  6572. */
  6573. public static function IMCOS($complexNumber) {
  6574. $complexNumber = self::flattenSingleValue($complexNumber);
  6575. $parsedComplex = self::_parseComplex($complexNumber);
  6576. if (!is_array($parsedComplex)) {
  6577. return $parsedComplex;
  6578. }
  6579. if ($parsedComplex['imaginary'] == 0.0) {
  6580. return cos($parsedComplex['real']);
  6581. } else {
  6582. return self::IMCONJUGATE(self::COMPLEX(cos($parsedComplex['real']) * cosh($parsedComplex['imaginary']),sin($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']));
  6583. }
  6584. } // function IMCOS()
  6585. /**
  6586. * IMSIN
  6587. *
  6588. * Returns the sine of a complex number in x + yi or x + yj text format.
  6589. *
  6590. * @param string $complexNumber
  6591. * @return string
  6592. */
  6593. public static function IMSIN($complexNumber) {
  6594. $complexNumber = self::flattenSingleValue($complexNumber);
  6595. $parsedComplex = self::_parseComplex($complexNumber);
  6596. if (!is_array($parsedComplex)) {
  6597. return $parsedComplex;
  6598. }
  6599. if ($parsedComplex['imaginary'] == 0.0) {
  6600. return sin($parsedComplex['real']);
  6601. } else {
  6602. return self::COMPLEX(sin($parsedComplex['real']) * cosh($parsedComplex['imaginary']),cos($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']);
  6603. }
  6604. } // function IMSIN()
  6605. /**
  6606. * IMSQRT
  6607. *
  6608. * Returns the square root of a complex number in x + yi or x + yj text format.
  6609. *
  6610. * @param string $complexNumber
  6611. * @return string
  6612. */
  6613. public static function IMSQRT($complexNumber) {
  6614. $complexNumber = self::flattenSingleValue($complexNumber);
  6615. $parsedComplex = self::_parseComplex($complexNumber);
  6616. if (!is_array($parsedComplex)) {
  6617. return $parsedComplex;
  6618. }
  6619. $theta = self::IMARGUMENT($complexNumber);
  6620. $d1 = cos($theta / 2);
  6621. $d2 = sin($theta / 2);
  6622. $r = sqrt(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
  6623. if ($parsedComplex['suffix'] == '') {
  6624. return self::COMPLEX($d1 * $r,$d2 * $r);
  6625. } else {
  6626. return self::COMPLEX($d1 * $r,$d2 * $r,$parsedComplex['suffix']);
  6627. }
  6628. } // function IMSQRT()
  6629. /**
  6630. * IMLN
  6631. *
  6632. * Returns the natural logarithm of a complex number in x + yi or x + yj text format.
  6633. *
  6634. * @param string $complexNumber
  6635. * @return string
  6636. */
  6637. public static function IMLN($complexNumber) {
  6638. $complexNumber = self::flattenSingleValue($complexNumber);
  6639. $parsedComplex = self::_parseComplex($complexNumber);
  6640. if (!is_array($parsedComplex)) {
  6641. return $parsedComplex;
  6642. }
  6643. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  6644. return self::$_errorCodes['num'];
  6645. }
  6646. $logR = log(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
  6647. $t = self::IMARGUMENT($complexNumber);
  6648. if ($parsedComplex['suffix'] == '') {
  6649. return self::COMPLEX($logR,$t);
  6650. } else {
  6651. return self::COMPLEX($logR,$t,$parsedComplex['suffix']);
  6652. }
  6653. } // function IMLN()
  6654. /**
  6655. * IMLOG10
  6656. *
  6657. * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
  6658. *
  6659. * @param string $complexNumber
  6660. * @return string
  6661. */
  6662. public static function IMLOG10($complexNumber) {
  6663. $complexNumber = self::flattenSingleValue($complexNumber);
  6664. $parsedComplex = self::_parseComplex($complexNumber);
  6665. if (!is_array($parsedComplex)) {
  6666. return $parsedComplex;
  6667. }
  6668. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  6669. return self::$_errorCodes['num'];
  6670. } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  6671. return log10($parsedComplex['real']);
  6672. }
  6673. return self::IMPRODUCT(log10(EULER),self::IMLN($complexNumber));
  6674. } // function IMLOG10()
  6675. /**
  6676. * IMLOG2
  6677. *
  6678. * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
  6679. *
  6680. * @param string $complexNumber
  6681. * @return string
  6682. */
  6683. public static function IMLOG2($complexNumber) {
  6684. $complexNumber = self::flattenSingleValue($complexNumber);
  6685. $parsedComplex = self::_parseComplex($complexNumber);
  6686. if (!is_array($parsedComplex)) {
  6687. return $parsedComplex;
  6688. }
  6689. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  6690. return self::$_errorCodes['num'];
  6691. } elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  6692. return log($parsedComplex['real'],2);
  6693. }
  6694. return self::IMPRODUCT(log(EULER,2),self::IMLN($complexNumber));
  6695. } // function IMLOG2()
  6696. /**
  6697. * IMEXP
  6698. *
  6699. * Returns the exponential of a complex number in x + yi or x + yj text format.
  6700. *
  6701. * @param string $complexNumber
  6702. * @return string
  6703. */
  6704. public static function IMEXP($complexNumber) {
  6705. $complexNumber = self::flattenSingleValue($complexNumber);
  6706. $parsedComplex = self::_parseComplex($complexNumber);
  6707. if (!is_array($parsedComplex)) {
  6708. return $parsedComplex;
  6709. }
  6710. if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
  6711. return '1';
  6712. }
  6713. $e = exp($parsedComplex['real']);
  6714. $eX = $e * cos($parsedComplex['imaginary']);
  6715. $eY = $e * sin($parsedComplex['imaginary']);
  6716. if ($parsedComplex['suffix'] == '') {
  6717. return self::COMPLEX($eX,$eY);
  6718. } else {
  6719. return self::COMPLEX($eX,$eY,$parsedComplex['suffix']);
  6720. }
  6721. } // function IMEXP()
  6722. /**
  6723. * IMPOWER
  6724. *
  6725. * Returns a complex number in x + yi or x + yj text format raised to a power.
  6726. *
  6727. * @param string $complexNumber
  6728. * @return string
  6729. */
  6730. public static function IMPOWER($complexNumber,$realNumber) {
  6731. $complexNumber = self::flattenSingleValue($complexNumber);
  6732. $realNumber = self::flattenSingleValue($realNumber);
  6733. if (!is_numeric($realNumber)) {
  6734. return self::$_errorCodes['value'];
  6735. }
  6736. $parsedComplex = self::_parseComplex($complexNumber);
  6737. if (!is_array($parsedComplex)) {
  6738. return $parsedComplex;
  6739. }
  6740. $r = sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
  6741. $rPower = pow($r,$realNumber);
  6742. $theta = self::IMARGUMENT($complexNumber) * $realNumber;
  6743. if ($theta == 0) {
  6744. return 1;
  6745. } elseif ($parsedComplex['imaginary'] == 0.0) {
  6746. return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
  6747. } else {
  6748. return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
  6749. }
  6750. } // function IMPOWER()
  6751. /**
  6752. * IMDIV
  6753. *
  6754. * Returns the quotient of two complex numbers in x + yi or x + yj text format.
  6755. *
  6756. * @param string $complexDividend
  6757. * @param string $complexDivisor
  6758. * @return real
  6759. */
  6760. public static function IMDIV($complexDividend,$complexDivisor) {
  6761. $complexDividend = self::flattenSingleValue($complexDividend);
  6762. $complexDivisor = self::flattenSingleValue($complexDivisor);
  6763. $parsedComplexDividend = self::_parseComplex($complexDividend);
  6764. if (!is_array($parsedComplexDividend)) {
  6765. return $parsedComplexDividend;
  6766. }
  6767. $parsedComplexDivisor = self::_parseComplex($complexDivisor);
  6768. if (!is_array($parsedComplexDivisor)) {
  6769. return $parsedComplexDividend;
  6770. }
  6771. if (($parsedComplexDividend['suffix'] != '') && ($parsedComplexDivisor['suffix'] != '') &&
  6772. ($parsedComplexDividend['suffix'] != $parsedComplexDivisor['suffix'])) {
  6773. return self::$_errorCodes['num'];
  6774. }
  6775. if (($parsedComplexDividend['suffix'] != '') && ($parsedComplexDivisor['suffix'] == '')) {
  6776. $parsedComplexDivisor['suffix'] = $parsedComplexDividend['suffix'];
  6777. }
  6778. $d1 = ($parsedComplexDividend['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['imaginary']);
  6779. $d2 = ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['real']) - ($parsedComplexDividend['real'] * $parsedComplexDivisor['imaginary']);
  6780. $d3 = ($parsedComplexDivisor['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDivisor['imaginary'] * $parsedComplexDivisor['imaginary']);
  6781. $r = $d1/$d3;
  6782. $i = $d2/$d3;
  6783. if ($i > 0.0) {
  6784. return self::_cleanComplex($r.'+'.$i.$parsedComplexDivisor['suffix']);
  6785. } elseif ($i < 0.0) {
  6786. return self::_cleanComplex($r.$i.$parsedComplexDivisor['suffix']);
  6787. } else {
  6788. return $r;
  6789. }
  6790. } // function IMDIV()
  6791. /**
  6792. * IMSUB
  6793. *
  6794. * Returns the difference of two complex numbers in x + yi or x + yj text format.
  6795. *
  6796. * @param string $complexNumber1
  6797. * @param string $complexNumber2
  6798. * @return real
  6799. */
  6800. public static function IMSUB($complexNumber1,$complexNumber2) {
  6801. $complexNumber1 = self::flattenSingleValue($complexNumber1);
  6802. $complexNumber2 = self::flattenSingleValue($complexNumber2);
  6803. $parsedComplex1 = self::_parseComplex($complexNumber1);
  6804. if (!is_array($parsedComplex1)) {
  6805. return $parsedComplex1;
  6806. }
  6807. $parsedComplex2 = self::_parseComplex($complexNumber2);
  6808. if (!is_array($parsedComplex2)) {
  6809. return $parsedComplex2;
  6810. }
  6811. if ((($parsedComplex1['suffix'] != '') && ($parsedComplex2['suffix'] != '')) &&
  6812. ($parsedComplex1['suffix'] != $parsedComplex2['suffix'])) {
  6813. return self::$_errorCodes['num'];
  6814. } elseif (($parsedComplex1['suffix'] == '') && ($parsedComplex2['suffix'] != '')) {
  6815. $parsedComplex1['suffix'] = $parsedComplex2['suffix'];
  6816. }
  6817. $d1 = $parsedComplex1['real'] - $parsedComplex2['real'];
  6818. $d2 = $parsedComplex1['imaginary'] - $parsedComplex2['imaginary'];
  6819. return self::COMPLEX($d1,$d2,$parsedComplex1['suffix']);
  6820. } // function IMSUB()
  6821. /**
  6822. * IMSUM
  6823. *
  6824. * Returns the sum of two or more complex numbers in x + yi or x + yj text format.
  6825. *
  6826. * @param array of mixed Data Series
  6827. * @return real
  6828. */
  6829. public static function IMSUM() {
  6830. // Return value
  6831. $returnValue = self::_parseComplex('0');
  6832. $activeSuffix = '';
  6833. // Loop through the arguments
  6834. $aArgs = self::flattenArray(func_get_args());
  6835. foreach ($aArgs as $arg) {
  6836. $parsedComplex = self::_parseComplex($arg);
  6837. if (!is_array($parsedComplex)) {
  6838. return $parsedComplex;
  6839. }
  6840. if ($activeSuffix == '') {
  6841. $activeSuffix = $parsedComplex['suffix'];
  6842. } elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
  6843. return self::$_errorCodes['value'];
  6844. }
  6845. $returnValue['real'] += $parsedComplex['real'];
  6846. $returnValue['imaginary'] += $parsedComplex['imaginary'];
  6847. }
  6848. if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
  6849. return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
  6850. } // function IMSUM()
  6851. /**
  6852. * IMPRODUCT
  6853. *
  6854. * Returns the product of two or more complex numbers in x + yi or x + yj text format.
  6855. *
  6856. * @param array of mixed Data Series
  6857. * @return real
  6858. */
  6859. public static function IMPRODUCT() {
  6860. // Return value
  6861. $returnValue = self::_parseComplex('1');
  6862. $activeSuffix = '';
  6863. // Loop through the arguments
  6864. $aArgs = self::flattenArray(func_get_args());
  6865. foreach ($aArgs as $arg) {
  6866. $parsedComplex = self::_parseComplex($arg);
  6867. if (!is_array($parsedComplex)) {
  6868. return $parsedComplex;
  6869. }
  6870. $workValue = $returnValue;
  6871. if (($parsedComplex['suffix'] != '') && ($activeSuffix == '')) {
  6872. $activeSuffix = $parsedComplex['suffix'];
  6873. } elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
  6874. return self::$_errorCodes['num'];
  6875. }
  6876. $returnValue['real'] = ($workValue['real'] * $parsedComplex['real']) - ($workValue['imaginary'] * $parsedComplex['imaginary']);
  6877. $returnValue['imaginary'] = ($workValue['real'] * $parsedComplex['imaginary']) + ($workValue['imaginary'] * $parsedComplex['real']);
  6878. }
  6879. if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
  6880. return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
  6881. } // function IMPRODUCT()
  6882. private static $_conversionUnits = array( 'g' => array( 'Group' => 'Mass', 'Unit Name' => 'Gram', 'AllowPrefix' => True ),
  6883. 'sg' => array( 'Group' => 'Mass', 'Unit Name' => 'Slug', 'AllowPrefix' => False ),
  6884. 'lbm' => array( 'Group' => 'Mass', 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => False ),
  6885. 'u' => array( 'Group' => 'Mass', 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => True ),
  6886. 'ozm' => array( 'Group' => 'Mass', 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => False ),
  6887. 'm' => array( 'Group' => 'Distance', 'Unit Name' => 'Meter', 'AllowPrefix' => True ),
  6888. 'mi' => array( 'Group' => 'Distance', 'Unit Name' => 'Statute mile', 'AllowPrefix' => False ),
  6889. 'Nmi' => array( 'Group' => 'Distance', 'Unit Name' => 'Nautical mile', 'AllowPrefix' => False ),
  6890. 'in' => array( 'Group' => 'Distance', 'Unit Name' => 'Inch', 'AllowPrefix' => False ),
  6891. 'ft' => array( 'Group' => 'Distance', 'Unit Name' => 'Foot', 'AllowPrefix' => False ),
  6892. 'yd' => array( 'Group' => 'Distance', 'Unit Name' => 'Yard', 'AllowPrefix' => False ),
  6893. 'ang' => array( 'Group' => 'Distance', 'Unit Name' => 'Angstrom', 'AllowPrefix' => True ),
  6894. 'Pica' => array( 'Group' => 'Distance', 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => False ),
  6895. 'yr' => array( 'Group' => 'Time', 'Unit Name' => 'Year', 'AllowPrefix' => False ),
  6896. 'day' => array( 'Group' => 'Time', 'Unit Name' => 'Day', 'AllowPrefix' => False ),
  6897. 'hr' => array( 'Group' => 'Time', 'Unit Name' => 'Hour', 'AllowPrefix' => False ),
  6898. 'mn' => array( 'Group' => 'Time', 'Unit Name' => 'Minute', 'AllowPrefix' => False ),
  6899. 'sec' => array( 'Group' => 'Time', 'Unit Name' => 'Second', 'AllowPrefix' => True ),
  6900. 'Pa' => array( 'Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => True ),
  6901. 'p' => array( 'Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => True ),
  6902. 'atm' => array( 'Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => True ),
  6903. 'at' => array( 'Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => True ),
  6904. 'mmHg' => array( 'Group' => 'Pressure', 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => True ),
  6905. 'N' => array( 'Group' => 'Force', 'Unit Name' => 'Newton', 'AllowPrefix' => True ),
  6906. 'dyn' => array( 'Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => True ),
  6907. 'dy' => array( 'Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => True ),
  6908. 'lbf' => array( 'Group' => 'Force', 'Unit Name' => 'Pound force', 'AllowPrefix' => False ),
  6909. 'J' => array( 'Group' => 'Energy', 'Unit Name' => 'Joule', 'AllowPrefix' => True ),
  6910. 'e' => array( 'Group' => 'Energy', 'Unit Name' => 'Erg', 'AllowPrefix' => True ),
  6911. 'c' => array( 'Group' => 'Energy', 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => True ),
  6912. 'cal' => array( 'Group' => 'Energy', 'Unit Name' => 'IT calorie', 'AllowPrefix' => True ),
  6913. 'eV' => array( 'Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => True ),
  6914. 'ev' => array( 'Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => True ),
  6915. 'HPh' => array( 'Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => False ),
  6916. 'hh' => array( 'Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => False ),
  6917. 'Wh' => array( 'Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => True ),
  6918. 'wh' => array( 'Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => True ),
  6919. 'flb' => array( 'Group' => 'Energy', 'Unit Name' => 'Foot-pound', 'AllowPrefix' => False ),
  6920. 'BTU' => array( 'Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => False ),
  6921. 'btu' => array( 'Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => False ),
  6922. 'HP' => array( 'Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => False ),
  6923. 'h' => array( 'Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => False ),
  6924. 'W' => array( 'Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => True ),
  6925. 'w' => array( 'Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => True ),
  6926. 'T' => array( 'Group' => 'Magnetism', 'Unit Name' => 'Tesla', 'AllowPrefix' => True ),
  6927. 'ga' => array( 'Group' => 'Magnetism', 'Unit Name' => 'Gauss', 'AllowPrefix' => True ),
  6928. 'C' => array( 'Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => False ),
  6929. 'cel' => array( 'Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => False ),
  6930. 'F' => array( 'Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => False ),
  6931. 'fah' => array( 'Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => False ),
  6932. 'K' => array( 'Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => False ),
  6933. 'kel' => array( 'Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => False ),
  6934. 'tsp' => array( 'Group' => 'Liquid', 'Unit Name' => 'Teaspoon', 'AllowPrefix' => False ),
  6935. 'tbs' => array( 'Group' => 'Liquid', 'Unit Name' => 'Tablespoon', 'AllowPrefix' => False ),
  6936. 'oz' => array( 'Group' => 'Liquid', 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => False ),
  6937. 'cup' => array( 'Group' => 'Liquid', 'Unit Name' => 'Cup', 'AllowPrefix' => False ),
  6938. 'pt' => array( 'Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => False ),
  6939. 'us_pt' => array( 'Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => False ),
  6940. 'uk_pt' => array( 'Group' => 'Liquid', 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => False ),
  6941. 'qt' => array( 'Group' => 'Liquid', 'Unit Name' => 'Quart', 'AllowPrefix' => False ),
  6942. 'gal' => array( 'Group' => 'Liquid', 'Unit Name' => 'Gallon', 'AllowPrefix' => False ),
  6943. 'l' => array( 'Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => True ),
  6944. 'lt' => array( 'Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => True )
  6945. );
  6946. private static $_conversionMultipliers = array( 'Y' => array( 'multiplier' => 1E24, 'name' => 'yotta' ),
  6947. 'Z' => array( 'multiplier' => 1E21, 'name' => 'zetta' ),
  6948. 'E' => array( 'multiplier' => 1E18, 'name' => 'exa' ),
  6949. 'P' => array( 'multiplier' => 1E15, 'name' => 'peta' ),
  6950. 'T' => array( 'multiplier' => 1E12, 'name' => 'tera' ),
  6951. 'G' => array( 'multiplier' => 1E9, 'name' => 'giga' ),
  6952. 'M' => array( 'multiplier' => 1E6, 'name' => 'mega' ),
  6953. 'k' => array( 'multiplier' => 1E3, 'name' => 'kilo' ),
  6954. 'h' => array( 'multiplier' => 1E2, 'name' => 'hecto' ),
  6955. 'e' => array( 'multiplier' => 1E1, 'name' => 'deka' ),
  6956. 'd' => array( 'multiplier' => 1E-1, 'name' => 'deci' ),
  6957. 'c' => array( 'multiplier' => 1E-2, 'name' => 'centi' ),
  6958. 'm' => array( 'multiplier' => 1E-3, 'name' => 'milli' ),
  6959. 'u' => array( 'multiplier' => 1E-6, 'name' => 'micro' ),
  6960. 'n' => array( 'multiplier' => 1E-9, 'name' => 'nano' ),
  6961. 'p' => array( 'multiplier' => 1E-12, 'name' => 'pico' ),
  6962. 'f' => array( 'multiplier' => 1E-15, 'name' => 'femto' ),
  6963. 'a' => array( 'multiplier' => 1E-18, 'name' => 'atto' ),
  6964. 'z' => array( 'multiplier' => 1E-21, 'name' => 'zepto' ),
  6965. 'y' => array( 'multiplier' => 1E-24, 'name' => 'yocto' )
  6966. );
  6967. private static $_unitConversions = array( 'Mass' => array( 'g' => array( 'g' => 1.0,
  6968. 'sg' => 6.85220500053478E-05,
  6969. 'lbm' => 2.20462291469134E-03,
  6970. 'u' => 6.02217000000000E+23,
  6971. 'ozm' => 3.52739718003627E-02
  6972. ),
  6973. 'sg' => array( 'g' => 1.45938424189287E+04,
  6974. 'sg' => 1.0,
  6975. 'lbm' => 3.21739194101647E+01,
  6976. 'u' => 8.78866000000000E+27,
  6977. 'ozm' => 5.14782785944229E+02
  6978. ),
  6979. 'lbm' => array( 'g' => 4.5359230974881148E+02,
  6980. 'sg' => 3.10810749306493E-02,
  6981. 'lbm' => 1.0,
  6982. 'u' => 2.73161000000000E+26,
  6983. 'ozm' => 1.60000023429410E+01
  6984. ),
  6985. 'u' => array( 'g' => 1.66053100460465E-24,
  6986. 'sg' => 1.13782988532950E-28,
  6987. 'lbm' => 3.66084470330684E-27,
  6988. 'u' => 1.0,
  6989. 'ozm' => 5.85735238300524E-26
  6990. ),
  6991. 'ozm' => array( 'g' => 2.83495152079732E+01,
  6992. 'sg' => 1.94256689870811E-03,
  6993. 'lbm' => 6.24999908478882E-02,
  6994. 'u' => 1.70725600000000E+25,
  6995. 'ozm' => 1.0
  6996. )
  6997. ),
  6998. 'Distance' => array( 'm' => array( 'm' => 1.0,
  6999. 'mi' => 6.21371192237334E-04,
  7000. 'Nmi' => 5.39956803455724E-04,
  7001. 'in' => 3.93700787401575E+01,
  7002. 'ft' => 3.28083989501312E+00,
  7003. 'yd' => 1.09361329797891E+00,
  7004. 'ang' => 1.00000000000000E+10,
  7005. 'Pica' => 2.83464566929116E+03
  7006. ),
  7007. 'mi' => array( 'm' => 1.60934400000000E+03,
  7008. 'mi' => 1.0,
  7009. 'Nmi' => 8.68976241900648E-01,
  7010. 'in' => 6.33600000000000E+04,
  7011. 'ft' => 5.28000000000000E+03,
  7012. 'yd' => 1.76000000000000E+03,
  7013. 'ang' => 1.60934400000000E+13,
  7014. 'Pica' => 4.56191999999971E+06
  7015. ),
  7016. 'Nmi' => array( 'm' => 1.85200000000000E+03,
  7017. 'mi' => 1.15077944802354E+00,
  7018. 'Nmi' => 1.0,
  7019. 'in' => 7.29133858267717E+04,
  7020. 'ft' => 6.07611548556430E+03,
  7021. 'yd' => 2.02537182785694E+03,
  7022. 'ang' => 1.85200000000000E+13,
  7023. 'Pica' => 5.24976377952723E+06
  7024. ),
  7025. 'in' => array( 'm' => 2.54000000000000E-02,
  7026. 'mi' => 1.57828282828283E-05,
  7027. 'Nmi' => 1.37149028077754E-05,
  7028. 'in' => 1.0,
  7029. 'ft' => 8.33333333333333E-02,
  7030. 'yd' => 2.77777777686643E-02,
  7031. 'ang' => 2.54000000000000E+08,
  7032. 'Pica' => 7.19999999999955E+01
  7033. ),
  7034. 'ft' => array( 'm' => 3.04800000000000E-01,
  7035. 'mi' => 1.89393939393939E-04,
  7036. 'Nmi' => 1.64578833693305E-04,
  7037. 'in' => 1.20000000000000E+01,
  7038. 'ft' => 1.0,
  7039. 'yd' => 3.33333333223972E-01,
  7040. 'ang' => 3.04800000000000E+09,
  7041. 'Pica' => 8.63999999999946E+02
  7042. ),
  7043. 'yd' => array( 'm' => 9.14400000300000E-01,
  7044. 'mi' => 5.68181818368230E-04,
  7045. 'Nmi' => 4.93736501241901E-04,
  7046. 'in' => 3.60000000118110E+01,
  7047. 'ft' => 3.00000000000000E+00,
  7048. 'yd' => 1.0,
  7049. 'ang' => 9.14400000300000E+09,
  7050. 'Pica' => 2.59200000085023E+03
  7051. ),
  7052. 'ang' => array( 'm' => 1.00000000000000E-10,
  7053. 'mi' => 6.21371192237334E-14,
  7054. 'Nmi' => 5.39956803455724E-14,
  7055. 'in' => 3.93700787401575E-09,
  7056. 'ft' => 3.28083989501312E-10,
  7057. 'yd' => 1.09361329797891E-10,
  7058. 'ang' => 1.0,
  7059. 'Pica' => 2.83464566929116E-07
  7060. ),
  7061. 'Pica' => array( 'm' => 3.52777777777800E-04,
  7062. 'mi' => 2.19205948372629E-07,
  7063. 'Nmi' => 1.90484761219114E-07,
  7064. 'in' => 1.38888888888898E-02,
  7065. 'ft' => 1.15740740740748E-03,
  7066. 'yd' => 3.85802469009251E-04,
  7067. 'ang' => 3.52777777777800E+06,
  7068. 'Pica' => 1.0
  7069. )
  7070. ),
  7071. 'Time' => array( 'yr' => array( 'yr' => 1.0,
  7072. 'day' => 365.25,
  7073. 'hr' => 8766.0,
  7074. 'mn' => 525960.0,
  7075. 'sec' => 31557600.0
  7076. ),
  7077. 'day' => array( 'yr' => 2.73785078713210E-03,
  7078. 'day' => 1.0,
  7079. 'hr' => 24.0,
  7080. 'mn' => 1440.0,
  7081. 'sec' => 86400.0
  7082. ),
  7083. 'hr' => array( 'yr' => 1.14077116130504E-04,
  7084. 'day' => 4.16666666666667E-02,
  7085. 'hr' => 1.0,
  7086. 'mn' => 60.0,
  7087. 'sec' => 3600.0
  7088. ),
  7089. 'mn' => array( 'yr' => 1.90128526884174E-06,
  7090. 'day' => 6.94444444444444E-04,
  7091. 'hr' => 1.66666666666667E-02,
  7092. 'mn' => 1.0,
  7093. 'sec' => 60.0
  7094. ),
  7095. 'sec' => array( 'yr' => 3.16880878140289E-08,
  7096. 'day' => 1.15740740740741E-05,
  7097. 'hr' => 2.77777777777778E-04,
  7098. 'mn' => 1.66666666666667E-02,
  7099. 'sec' => 1.0
  7100. )
  7101. ),
  7102. 'Pressure' => array( 'Pa' => array( 'Pa' => 1.0,
  7103. 'p' => 1.0,
  7104. 'atm' => 9.86923299998193E-06,
  7105. 'at' => 9.86923299998193E-06,
  7106. 'mmHg' => 7.50061707998627E-03
  7107. ),
  7108. 'p' => array( 'Pa' => 1.0,
  7109. 'p' => 1.0,
  7110. 'atm' => 9.86923299998193E-06,
  7111. 'at' => 9.86923299998193E-06,
  7112. 'mmHg' => 7.50061707998627E-03
  7113. ),
  7114. 'atm' => array( 'Pa' => 1.01324996583000E+05,
  7115. 'p' => 1.01324996583000E+05,
  7116. 'atm' => 1.0,
  7117. 'at' => 1.0,
  7118. 'mmHg' => 760.0
  7119. ),
  7120. 'at' => array( 'Pa' => 1.01324996583000E+05,
  7121. 'p' => 1.01324996583000E+05,
  7122. 'atm' => 1.0,
  7123. 'at' => 1.0,
  7124. 'mmHg' => 760.0
  7125. ),
  7126. 'mmHg' => array( 'Pa' => 1.33322363925000E+02,
  7127. 'p' => 1.33322363925000E+02,
  7128. 'atm' => 1.31578947368421E-03,
  7129. 'at' => 1.31578947368421E-03,
  7130. 'mmHg' => 1.0
  7131. )
  7132. ),
  7133. 'Force' => array( 'N' => array( 'N' => 1.0,
  7134. 'dyn' => 1.0E+5,
  7135. 'dy' => 1.0E+5,
  7136. 'lbf' => 2.24808923655339E-01
  7137. ),
  7138. 'dyn' => array( 'N' => 1.0E-5,
  7139. 'dyn' => 1.0,
  7140. 'dy' => 1.0,
  7141. 'lbf' => 2.24808923655339E-06
  7142. ),
  7143. 'dy' => array( 'N' => 1.0E-5,
  7144. 'dyn' => 1.0,
  7145. 'dy' => 1.0,
  7146. 'lbf' => 2.24808923655339E-06
  7147. ),
  7148. 'lbf' => array( 'N' => 4.448222,
  7149. 'dyn' => 4.448222E+5,
  7150. 'dy' => 4.448222E+5,
  7151. 'lbf' => 1.0
  7152. )
  7153. ),
  7154. 'Energy' => array( 'J' => array( 'J' => 1.0,
  7155. 'e' => 9.99999519343231E+06,
  7156. 'c' => 2.39006249473467E-01,
  7157. 'cal' => 2.38846190642017E-01,
  7158. 'eV' => 6.24145700000000E+18,
  7159. 'ev' => 6.24145700000000E+18,
  7160. 'HPh' => 3.72506430801000E-07,
  7161. 'hh' => 3.72506430801000E-07,
  7162. 'Wh' => 2.77777916238711E-04,
  7163. 'wh' => 2.77777916238711E-04,
  7164. 'flb' => 2.37304222192651E+01,
  7165. 'BTU' => 9.47815067349015E-04,
  7166. 'btu' => 9.47815067349015E-04
  7167. ),
  7168. 'e' => array( 'J' => 1.00000048065700E-07,
  7169. 'e' => 1.0,
  7170. 'c' => 2.39006364353494E-08,
  7171. 'cal' => 2.38846305445111E-08,
  7172. 'eV' => 6.24146000000000E+11,
  7173. 'ev' => 6.24146000000000E+11,
  7174. 'HPh' => 3.72506609848824E-14,
  7175. 'hh' => 3.72506609848824E-14,
  7176. 'Wh' => 2.77778049754611E-11,
  7177. 'wh' => 2.77778049754611E-11,
  7178. 'flb' => 2.37304336254586E-06,
  7179. 'BTU' => 9.47815522922962E-11,
  7180. 'btu' => 9.47815522922962E-11
  7181. ),
  7182. 'c' => array( 'J' => 4.18399101363672E+00,
  7183. 'e' => 4.18398900257312E+07,
  7184. 'c' => 1.0,
  7185. 'cal' => 9.99330315287563E-01,
  7186. 'eV' => 2.61142000000000E+19,
  7187. 'ev' => 2.61142000000000E+19,
  7188. 'HPh' => 1.55856355899327E-06,
  7189. 'hh' => 1.55856355899327E-06,
  7190. 'Wh' => 1.16222030532950E-03,
  7191. 'wh' => 1.16222030532950E-03,
  7192. 'flb' => 9.92878733152102E+01,
  7193. 'BTU' => 3.96564972437776E-03,
  7194. 'btu' => 3.96564972437776E-03
  7195. ),
  7196. 'cal' => array( 'J' => 4.18679484613929E+00,
  7197. 'e' => 4.18679283372801E+07,
  7198. 'c' => 1.00067013349059E+00,
  7199. 'cal' => 1.0,
  7200. 'eV' => 2.61317000000000E+19,
  7201. 'ev' => 2.61317000000000E+19,
  7202. 'HPh' => 1.55960800463137E-06,
  7203. 'hh' => 1.55960800463137E-06,
  7204. 'Wh' => 1.16299914807955E-03,
  7205. 'wh' => 1.16299914807955E-03,
  7206. 'flb' => 9.93544094443283E+01,
  7207. 'BTU' => 3.96830723907002E-03,
  7208. 'btu' => 3.96830723907002E-03
  7209. ),
  7210. 'eV' => array( 'J' => 1.60219000146921E-19,
  7211. 'e' => 1.60218923136574E-12,
  7212. 'c' => 3.82933423195043E-20,
  7213. 'cal' => 3.82676978535648E-20,
  7214. 'eV' => 1.0,
  7215. 'ev' => 1.0,
  7216. 'HPh' => 5.96826078912344E-26,
  7217. 'hh' => 5.96826078912344E-26,
  7218. 'Wh' => 4.45053000026614E-23,
  7219. 'wh' => 4.45053000026614E-23,
  7220. 'flb' => 3.80206452103492E-18,
  7221. 'BTU' => 1.51857982414846E-22,
  7222. 'btu' => 1.51857982414846E-22
  7223. ),
  7224. 'ev' => array( 'J' => 1.60219000146921E-19,
  7225. 'e' => 1.60218923136574E-12,
  7226. 'c' => 3.82933423195043E-20,
  7227. 'cal' => 3.82676978535648E-20,
  7228. 'eV' => 1.0,
  7229. 'ev' => 1.0,
  7230. 'HPh' => 5.96826078912344E-26,
  7231. 'hh' => 5.96826078912344E-26,
  7232. 'Wh' => 4.45053000026614E-23,
  7233. 'wh' => 4.45053000026614E-23,
  7234. 'flb' => 3.80206452103492E-18,
  7235. 'BTU' => 1.51857982414846E-22,
  7236. 'btu' => 1.51857982414846E-22
  7237. ),
  7238. 'HPh' => array( 'J' => 2.68451741316170E+06,
  7239. 'e' => 2.68451612283024E+13,
  7240. 'c' => 6.41616438565991E+05,
  7241. 'cal' => 6.41186757845835E+05,
  7242. 'eV' => 1.67553000000000E+25,
  7243. 'ev' => 1.67553000000000E+25,
  7244. 'HPh' => 1.0,
  7245. 'hh' => 1.0,
  7246. 'Wh' => 7.45699653134593E+02,
  7247. 'wh' => 7.45699653134593E+02,
  7248. 'flb' => 6.37047316692964E+07,
  7249. 'BTU' => 2.54442605275546E+03,
  7250. 'btu' => 2.54442605275546E+03
  7251. ),
  7252. 'hh' => array( 'J' => 2.68451741316170E+06,
  7253. 'e' => 2.68451612283024E+13,
  7254. 'c' => 6.41616438565991E+05,
  7255. 'cal' => 6.41186757845835E+05,
  7256. 'eV' => 1.67553000000000E+25,
  7257. 'ev' => 1.67553000000000E+25,
  7258. 'HPh' => 1.0,
  7259. 'hh' => 1.0,
  7260. 'Wh' => 7.45699653134593E+02,
  7261. 'wh' => 7.45699653134593E+02,
  7262. 'flb' => 6.37047316692964E+07,
  7263. 'BTU' => 2.54442605275546E+03,
  7264. 'btu' => 2.54442605275546E+03
  7265. ),
  7266. 'Wh' => array( 'J' => 3.59999820554720E+03,
  7267. 'e' => 3.59999647518369E+10,
  7268. 'c' => 8.60422069219046E+02,
  7269. 'cal' => 8.59845857713046E+02,
  7270. 'eV' => 2.24692340000000E+22,
  7271. 'ev' => 2.24692340000000E+22,
  7272. 'HPh' => 1.34102248243839E-03,
  7273. 'hh' => 1.34102248243839E-03,
  7274. 'Wh' => 1.0,
  7275. 'wh' => 1.0,
  7276. 'flb' => 8.54294774062316E+04,
  7277. 'BTU' => 3.41213254164705E+00,
  7278. 'btu' => 3.41213254164705E+00
  7279. ),
  7280. 'wh' => array( 'J' => 3.59999820554720E+03,
  7281. 'e' => 3.59999647518369E+10,
  7282. 'c' => 8.60422069219046E+02,
  7283. 'cal' => 8.59845857713046E+02,
  7284. 'eV' => 2.24692340000000E+22,
  7285. 'ev' => 2.24692340000000E+22,
  7286. 'HPh' => 1.34102248243839E-03,
  7287. 'hh' => 1.34102248243839E-03,
  7288. 'Wh' => 1.0,
  7289. 'wh' => 1.0,
  7290. 'flb' => 8.54294774062316E+04,
  7291. 'BTU' => 3.41213254164705E+00,
  7292. 'btu' => 3.41213254164705E+00
  7293. ),
  7294. 'flb' => array( 'J' => 4.21400003236424E-02,
  7295. 'e' => 4.21399800687660E+05,
  7296. 'c' => 1.00717234301644E-02,
  7297. 'cal' => 1.00649785509554E-02,
  7298. 'eV' => 2.63015000000000E+17,
  7299. 'ev' => 2.63015000000000E+17,
  7300. 'HPh' => 1.56974211145130E-08,
  7301. 'hh' => 1.56974211145130E-08,
  7302. 'Wh' => 1.17055614802000E-05,
  7303. 'wh' => 1.17055614802000E-05,
  7304. 'flb' => 1.0,
  7305. 'BTU' => 3.99409272448406E-05,
  7306. 'btu' => 3.99409272448406E-05
  7307. ),
  7308. 'BTU' => array( 'J' => 1.05505813786749E+03,
  7309. 'e' => 1.05505763074665E+10,
  7310. 'c' => 2.52165488508168E+02,
  7311. 'cal' => 2.51996617135510E+02,
  7312. 'eV' => 6.58510000000000E+21,
  7313. 'ev' => 6.58510000000000E+21,
  7314. 'HPh' => 3.93015941224568E-04,
  7315. 'hh' => 3.93015941224568E-04,
  7316. 'Wh' => 2.93071851047526E-01,
  7317. 'wh' => 2.93071851047526E-01,
  7318. 'flb' => 2.50369750774671E+04,
  7319. 'BTU' => 1.0,
  7320. 'btu' => 1.0,
  7321. ),
  7322. 'btu' => array( 'J' => 1.05505813786749E+03,
  7323. 'e' => 1.05505763074665E+10,
  7324. 'c' => 2.52165488508168E+02,
  7325. 'cal' => 2.51996617135510E+02,
  7326. 'eV' => 6.58510000000000E+21,
  7327. 'ev' => 6.58510000000000E+21,
  7328. 'HPh' => 3.93015941224568E-04,
  7329. 'hh' => 3.93015941224568E-04,
  7330. 'Wh' => 2.93071851047526E-01,
  7331. 'wh' => 2.93071851047526E-01,
  7332. 'flb' => 2.50369750774671E+04,
  7333. 'BTU' => 1.0,
  7334. 'btu' => 1.0,
  7335. )
  7336. ),
  7337. 'Power' => array( 'HP' => array( 'HP' => 1.0,
  7338. 'h' => 1.0,
  7339. 'W' => 7.45701000000000E+02,
  7340. 'w' => 7.45701000000000E+02
  7341. ),
  7342. 'h' => array( 'HP' => 1.0,
  7343. 'h' => 1.0,
  7344. 'W' => 7.45701000000000E+02,
  7345. 'w' => 7.45701000000000E+02
  7346. ),
  7347. 'W' => array( 'HP' => 1.34102006031908E-03,
  7348. 'h' => 1.34102006031908E-03,
  7349. 'W' => 1.0,
  7350. 'w' => 1.0
  7351. ),
  7352. 'w' => array( 'HP' => 1.34102006031908E-03,
  7353. 'h' => 1.34102006031908E-03,
  7354. 'W' => 1.0,
  7355. 'w' => 1.0
  7356. )
  7357. ),
  7358. 'Magnetism' => array( 'T' => array( 'T' => 1.0,
  7359. 'ga' => 10000.0
  7360. ),
  7361. 'ga' => array( 'T' => 0.0001,
  7362. 'ga' => 1.0
  7363. )
  7364. ),
  7365. 'Liquid' => array( 'tsp' => array( 'tsp' => 1.0,
  7366. 'tbs' => 3.33333333333333E-01,
  7367. 'oz' => 1.66666666666667E-01,
  7368. 'cup' => 2.08333333333333E-02,
  7369. 'pt' => 1.04166666666667E-02,
  7370. 'us_pt' => 1.04166666666667E-02,
  7371. 'uk_pt' => 8.67558516821960E-03,
  7372. 'qt' => 5.20833333333333E-03,
  7373. 'gal' => 1.30208333333333E-03,
  7374. 'l' => 4.92999408400710E-03,
  7375. 'lt' => 4.92999408400710E-03
  7376. ),
  7377. 'tbs' => array( 'tsp' => 3.00000000000000E+00,
  7378. 'tbs' => 1.0,
  7379. 'oz' => 5.00000000000000E-01,
  7380. 'cup' => 6.25000000000000E-02,
  7381. 'pt' => 3.12500000000000E-02,
  7382. 'us_pt' => 3.12500000000000E-02,
  7383. 'uk_pt' => 2.60267555046588E-02,
  7384. 'qt' => 1.56250000000000E-02,
  7385. 'gal' => 3.90625000000000E-03,
  7386. 'l' => 1.47899822520213E-02,
  7387. 'lt' => 1.47899822520213E-02
  7388. ),
  7389. 'oz' => array( 'tsp' => 6.00000000000000E+00,
  7390. 'tbs' => 2.00000000000000E+00,
  7391. 'oz' => 1.0,
  7392. 'cup' => 1.25000000000000E-01,
  7393. 'pt' => 6.25000000000000E-02,
  7394. 'us_pt' => 6.25000000000000E-02,
  7395. 'uk_pt' => 5.20535110093176E-02,
  7396. 'qt' => 3.12500000000000E-02,
  7397. 'gal' => 7.81250000000000E-03,
  7398. 'l' => 2.95799645040426E-02,
  7399. 'lt' => 2.95799645040426E-02
  7400. ),
  7401. 'cup' => array( 'tsp' => 4.80000000000000E+01,
  7402. 'tbs' => 1.60000000000000E+01,
  7403. 'oz' => 8.00000000000000E+00,
  7404. 'cup' => 1.0,
  7405. 'pt' => 5.00000000000000E-01,
  7406. 'us_pt' => 5.00000000000000E-01,
  7407. 'uk_pt' => 4.16428088074541E-01,
  7408. 'qt' => 2.50000000000000E-01,
  7409. 'gal' => 6.25000000000000E-02,
  7410. 'l' => 2.36639716032341E-01,
  7411. 'lt' => 2.36639716032341E-01
  7412. ),
  7413. 'pt' => array( 'tsp' => 9.60000000000000E+01,
  7414. 'tbs' => 3.20000000000000E+01,
  7415. 'oz' => 1.60000000000000E+01,
  7416. 'cup' => 2.00000000000000E+00,
  7417. 'pt' => 1.0,
  7418. 'us_pt' => 1.0,
  7419. 'uk_pt' => 8.32856176149081E-01,
  7420. 'qt' => 5.00000000000000E-01,
  7421. 'gal' => 1.25000000000000E-01,
  7422. 'l' => 4.73279432064682E-01,
  7423. 'lt' => 4.73279432064682E-01
  7424. ),
  7425. 'us_pt' => array( 'tsp' => 9.60000000000000E+01,
  7426. 'tbs' => 3.20000000000000E+01,
  7427. 'oz' => 1.60000000000000E+01,
  7428. 'cup' => 2.00000000000000E+00,
  7429. 'pt' => 1.0,
  7430. 'us_pt' => 1.0,
  7431. 'uk_pt' => 8.32856176149081E-01,
  7432. 'qt' => 5.00000000000000E-01,
  7433. 'gal' => 1.25000000000000E-01,
  7434. 'l' => 4.73279432064682E-01,
  7435. 'lt' => 4.73279432064682E-01
  7436. ),
  7437. 'uk_pt' => array( 'tsp' => 1.15266000000000E+02,
  7438. 'tbs' => 3.84220000000000E+01,
  7439. 'oz' => 1.92110000000000E+01,
  7440. 'cup' => 2.40137500000000E+00,
  7441. 'pt' => 1.20068750000000E+00,
  7442. 'us_pt' => 1.20068750000000E+00,
  7443. 'uk_pt' => 1.0,
  7444. 'qt' => 6.00343750000000E-01,
  7445. 'gal' => 1.50085937500000E-01,
  7446. 'l' => 5.68260698087162E-01,
  7447. 'lt' => 5.68260698087162E-01
  7448. ),
  7449. 'qt' => array( 'tsp' => 1.92000000000000E+02,
  7450. 'tbs' => 6.40000000000000E+01,
  7451. 'oz' => 3.20000000000000E+01,
  7452. 'cup' => 4.00000000000000E+00,
  7453. 'pt' => 2.00000000000000E+00,
  7454. 'us_pt' => 2.00000000000000E+00,
  7455. 'uk_pt' => 1.66571235229816E+00,
  7456. 'qt' => 1.0,
  7457. 'gal' => 2.50000000000000E-01,
  7458. 'l' => 9.46558864129363E-01,
  7459. 'lt' => 9.46558864129363E-01
  7460. ),
  7461. 'gal' => array( 'tsp' => 7.68000000000000E+02,
  7462. 'tbs' => 2.56000000000000E+02,
  7463. 'oz' => 1.28000000000000E+02,
  7464. 'cup' => 1.60000000000000E+01,
  7465. 'pt' => 8.00000000000000E+00,
  7466. 'us_pt' => 8.00000000000000E+00,
  7467. 'uk_pt' => 6.66284940919265E+00,
  7468. 'qt' => 4.00000000000000E+00,
  7469. 'gal' => 1.0,
  7470. 'l' => 3.78623545651745E+00,
  7471. 'lt' => 3.78623545651745E+00
  7472. ),
  7473. 'l' => array( 'tsp' => 2.02840000000000E+02,
  7474. 'tbs' => 6.76133333333333E+01,
  7475. 'oz' => 3.38066666666667E+01,
  7476. 'cup' => 4.22583333333333E+00,
  7477. 'pt' => 2.11291666666667E+00,
  7478. 'us_pt' => 2.11291666666667E+00,
  7479. 'uk_pt' => 1.75975569552166E+00,
  7480. 'qt' => 1.05645833333333E+00,
  7481. 'gal' => 2.64114583333333E-01,
  7482. 'l' => 1.0,
  7483. 'lt' => 1.0
  7484. ),
  7485. 'lt' => array( 'tsp' => 2.02840000000000E+02,
  7486. 'tbs' => 6.76133333333333E+01,
  7487. 'oz' => 3.38066666666667E+01,
  7488. 'cup' => 4.22583333333333E+00,
  7489. 'pt' => 2.11291666666667E+00,
  7490. 'us_pt' => 2.11291666666667E+00,
  7491. 'uk_pt' => 1.75975569552166E+00,
  7492. 'qt' => 1.05645833333333E+00,
  7493. 'gal' => 2.64114583333333E-01,
  7494. 'l' => 1.0,
  7495. 'lt' => 1.0
  7496. )
  7497. )
  7498. );
  7499. /**
  7500. * getConversionGroups
  7501. *
  7502. * @return array
  7503. */
  7504. public static function getConversionGroups() {
  7505. $conversionGroups = array();
  7506. foreach(self::$_conversionUnits as $conversionUnit) {
  7507. $conversionGroups[] = $conversionUnit['Group'];
  7508. }
  7509. return array_merge(array_unique($conversionGroups));
  7510. } // function getConversionGroups()
  7511. /**
  7512. * getConversionGroupUnits
  7513. *
  7514. * @return array
  7515. */
  7516. public static function getConversionGroupUnits($group = NULL) {
  7517. $conversionGroups = array();
  7518. foreach(self::$_conversionUnits as $conversionUnit => $conversionGroup) {
  7519. if ((is_null($group)) || ($conversionGroup['Group'] == $group)) {
  7520. $conversionGroups[$conversionGroup['Group']][] = $conversionUnit;
  7521. }
  7522. }
  7523. return $conversionGroups;
  7524. } // function getConversionGroupUnits()
  7525. /**
  7526. * getConversionGroupUnitDetails
  7527. *
  7528. * @return array
  7529. */
  7530. public static function getConversionGroupUnitDetails($group = NULL) {
  7531. $conversionGroups = array();
  7532. foreach(self::$_conversionUnits as $conversionUnit => $conversionGroup) {
  7533. if ((is_null($group)) || ($conversionGroup['Group'] == $group)) {
  7534. $conversionGroups[$conversionGroup['Group']][] = array( 'unit' => $conversionUnit,
  7535. 'description' => $conversionGroup['Unit Name']
  7536. );
  7537. }
  7538. }
  7539. return $conversionGroups;
  7540. } // function getConversionGroupUnitDetails()
  7541. /**
  7542. * getConversionGroups
  7543. *
  7544. * @return array
  7545. */
  7546. public static function getConversionMultipliers() {
  7547. return self::$_conversionMultipliers;
  7548. } // function getConversionGroups()
  7549. /**
  7550. * CONVERTUOM
  7551. *
  7552. * @param float $value
  7553. * @param string $fromUOM
  7554. * @param string $toUOM
  7555. * @return float
  7556. */
  7557. public static function CONVERTUOM($value, $fromUOM, $toUOM) {
  7558. $value = self::flattenSingleValue($value);
  7559. $fromUOM = self::flattenSingleValue($fromUOM);
  7560. $toUOM = self::flattenSingleValue($toUOM);
  7561. if (!is_numeric($value)) {
  7562. return self::$_errorCodes['value'];
  7563. }
  7564. $fromMultiplier = 1;
  7565. if (isset(self::$_conversionUnits[$fromUOM])) {
  7566. $unitGroup1 = self::$_conversionUnits[$fromUOM]['Group'];
  7567. } else {
  7568. $fromMultiplier = substr($fromUOM,0,1);
  7569. $fromUOM = substr($fromUOM,1);
  7570. if (isset(self::$_conversionMultipliers[$fromMultiplier])) {
  7571. $fromMultiplier = self::$_conversionMultipliers[$fromMultiplier]['multiplier'];
  7572. } else {
  7573. return self::$_errorCodes['na'];
  7574. }
  7575. if ((isset(self::$_conversionUnits[$fromUOM])) && (self::$_conversionUnits[$fromUOM]['AllowPrefix'])) {
  7576. $unitGroup1 = self::$_conversionUnits[$fromUOM]['Group'];
  7577. } else {
  7578. return self::$_errorCodes['na'];
  7579. }
  7580. }
  7581. $value *= $fromMultiplier;
  7582. $toMultiplier = 1;
  7583. if (isset(self::$_conversionUnits[$toUOM])) {
  7584. $unitGroup2 = self::$_conversionUnits[$toUOM]['Group'];
  7585. } else {
  7586. $toMultiplier = substr($toUOM,0,1);
  7587. $toUOM = substr($toUOM,1);
  7588. if (isset(self::$_conversionMultipliers[$toMultiplier])) {
  7589. $toMultiplier = self::$_conversionMultipliers[$toMultiplier]['multiplier'];
  7590. } else {
  7591. return self::$_errorCodes['na'];
  7592. }
  7593. if ((isset(self::$_conversionUnits[$toUOM])) && (self::$_conversionUnits[$toUOM]['AllowPrefix'])) {
  7594. $unitGroup2 = self::$_conversionUnits[$toUOM]['Group'];
  7595. } else {
  7596. return self::$_errorCodes['na'];
  7597. }
  7598. }
  7599. if ($unitGroup1 != $unitGroup2) {
  7600. return self::$_errorCodes['na'];
  7601. }
  7602. if ($fromUOM == $toUOM) {
  7603. return 1.0;
  7604. } elseif ($unitGroup1 == 'Temperature') {
  7605. if (($fromUOM == 'F') || ($fromUOM == 'fah')) {
  7606. if (($toUOM == 'F') || ($toUOM == 'fah')) {
  7607. return 1.0;
  7608. } else {
  7609. $value = (($value - 32) / 1.8);
  7610. if (($toUOM == 'K') || ($toUOM == 'kel')) {
  7611. $value += 273.15;
  7612. }
  7613. return $value;
  7614. }
  7615. } elseif ((($fromUOM == 'K') || ($fromUOM == 'kel')) &&
  7616. (($toUOM == 'K') || ($toUOM == 'kel'))) {
  7617. return 1.0;
  7618. } elseif ((($fromUOM == 'C') || ($fromUOM == 'cel')) &&
  7619. (($toUOM == 'C') || ($toUOM == 'cel'))) {
  7620. return 1.0;
  7621. }
  7622. if (($toUOM == 'F') || ($toUOM == 'fah')) {
  7623. if (($fromUOM == 'K') || ($fromUOM == 'kel')) {
  7624. $value -= 273.15;
  7625. }
  7626. return ($value * 1.8) + 32;
  7627. }
  7628. if (($toUOM == 'C') || ($toUOM == 'cel')) {
  7629. return $value - 273.15;
  7630. }
  7631. return $value + 273.15;
  7632. }
  7633. return ($value * self::$_unitConversions[$unitGroup1][$fromUOM][$toUOM]) / $toMultiplier;
  7634. } // function CONVERTUOM()
  7635. /**
  7636. * BESSELI
  7637. *
  7638. * Returns the modified Bessel function, which is equivalent to the Bessel function evaluated for purely imaginary arguments
  7639. *
  7640. * @param float $x
  7641. * @param float $n
  7642. * @return int
  7643. */
  7644. public static function BESSELI($x, $n) {
  7645. $x = (is_null($x)) ? 0.0 : self::flattenSingleValue($x);
  7646. $n = (is_null($n)) ? 0.0 : self::flattenSingleValue($n);
  7647. if ((is_numeric($x)) && (is_numeric($n))) {
  7648. $n = floor($n);
  7649. if ($n < 0) {
  7650. return self::$_errorCodes['num'];
  7651. }
  7652. $f_2_PI = 2 * M_PI;
  7653. if (abs($x) <= 30) {
  7654. $fTerm = pow($x / 2, $n) / self::FACT($n);
  7655. $nK = 1;
  7656. $fResult = $fTerm;
  7657. $fSqrX = ($x * $x) / 4;
  7658. do {
  7659. $fTerm *= $fSqrX;
  7660. $fTerm /= ($nK * ($nK + $n));
  7661. $fResult += $fTerm;
  7662. } while ((abs($fTerm) > 1e-10) && (++$nK < 100));
  7663. } else {
  7664. $fXAbs = abs($x);
  7665. $fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs);
  7666. if (($n && 1) && ($x < 0)) {
  7667. $fResult = -$fResult;
  7668. }
  7669. }
  7670. return $fResult;
  7671. }
  7672. return self::$_errorCodes['value'];
  7673. } // function BESSELI()
  7674. /**
  7675. * BESSELJ
  7676. *
  7677. * Returns the Bessel function
  7678. *
  7679. * @param float $x
  7680. * @param float $n
  7681. * @return int
  7682. */
  7683. public static function BESSELJ($x, $n) {
  7684. $x = (is_null($x)) ? 0.0 : self::flattenSingleValue($x);
  7685. $n = (is_null($n)) ? 0.0 : self::flattenSingleValue($n);
  7686. if ((is_numeric($x)) && (is_numeric($n))) {
  7687. $n = floor($n);
  7688. if ($n < 0) {
  7689. return self::$_errorCodes['num'];
  7690. }
  7691. $f_PI_DIV_2 = M_PI / 2;
  7692. $f_PI_DIV_4 = M_PI / 4;
  7693. $fResult = 0;
  7694. if (abs($x) <= 30) {
  7695. $fTerm = pow($x / 2, $n) / self::FACT($n);
  7696. $nK = 1;
  7697. $fResult = $fTerm;
  7698. $fSqrX = ($x * $x) / -4;
  7699. do {
  7700. $fTerm *= $fSqrX;
  7701. $fTerm /= ($nK * ($nK + $n));
  7702. $fResult += $fTerm;
  7703. } while ((abs($fTerm) > 1e-10) && (++$nK < 100));
  7704. } else {
  7705. $fXAbs = abs($x);
  7706. $fResult = sqrt(M_2DIVPI / $fXAbs) * cos($fXAbs - $n * $f_PI_DIV_2 - $f_PI_DIV_4);
  7707. if (($n && 1) && ($x < 0)) {
  7708. $fResult = -$fResult;
  7709. }
  7710. }
  7711. return $fResult;
  7712. }
  7713. return self::$_errorCodes['value'];
  7714. } // function BESSELJ()
  7715. private static function _Besselk0($fNum) {
  7716. if ($fNum <= 2) {
  7717. $fNum2 = $fNum * 0.5;
  7718. $y = ($fNum2 * $fNum2);
  7719. $fRet = -log($fNum2) * self::BESSELI($fNum, 0) +
  7720. (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y *
  7721. (0.10750e-3 + $y * 0.74e-5))))));
  7722. } else {
  7723. $y = 2 / $fNum;
  7724. $fRet = exp(-$fNum) / sqrt($fNum) *
  7725. (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y *
  7726. (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
  7727. }
  7728. return $fRet;
  7729. } // function _Besselk0()
  7730. private static function _Besselk1($fNum) {
  7731. if ($fNum <= 2) {
  7732. $fNum2 = $fNum * 0.5;
  7733. $y = ($fNum2 * $fNum2);
  7734. $fRet = log($fNum2) * self::BESSELI($fNum, 1) +
  7735. (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y *
  7736. (-0.110404e-2 + $y * (-0.4686e-4))))))) / $fNum;
  7737. } else {
  7738. $y = 2 / $fNum;
  7739. $fRet = exp(-$fNum) / sqrt($fNum) *
  7740. (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y *
  7741. (0.325614e-2 + $y * (-0.68245e-3)))))));
  7742. }
  7743. return $fRet;
  7744. } // function _Besselk1()
  7745. /**
  7746. * BESSELK
  7747. *
  7748. * Returns the modified Bessel function, which is equivalent to the Bessel functions evaluated for purely imaginary arguments.
  7749. *
  7750. * @param float $x
  7751. * @param float $ord
  7752. * @return float
  7753. */
  7754. public static function BESSELK($x, $ord) {
  7755. $x = (is_null($x)) ? 0.0 : self::flattenSingleValue($x);
  7756. $ord = (is_null($ord)) ? 0.0 : self::flattenSingleValue($ord);
  7757. if ((is_numeric($x)) && (is_numeric($ord))) {
  7758. if (($ord < 0) || ($x == 0.0)) {
  7759. return self::$_errorCodes['num'];
  7760. }
  7761. switch(floor($ord)) {
  7762. case 0 : return self::_Besselk0($x);
  7763. break;
  7764. case 1 : return self::_Besselk1($x);
  7765. break;
  7766. default : $fTox = 2 / $x;
  7767. $fBkm = self::_Besselk0($x);
  7768. $fBk = self::_Besselk1($x);
  7769. for ($n = 1; $n < $ord; ++$n) {
  7770. $fBkp = $fBkm + $n * $fTox * $fBk;
  7771. $fBkm = $fBk;
  7772. $fBk = $fBkp;
  7773. }
  7774. }
  7775. return $fBk;
  7776. }
  7777. return self::$_errorCodes['value'];
  7778. } // function BESSELK()
  7779. private static function _Bessely0($fNum) {
  7780. if ($fNum < 8.0) {
  7781. $y = ($fNum * $fNum);
  7782. $f1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * (-86327.92757 + $y * 228.4622733))));
  7783. $f2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * (47447.26470 + $y * (226.1030244 + $y))));
  7784. $fRet = $f1 / $f2 + M_2DIVPI * self::BESSELJ($fNum, 0) * log($fNum);
  7785. } else {
  7786. $z = 8.0 / $fNum;
  7787. $y = ($z * $z);
  7788. $xx = $fNum - 0.785398164;
  7789. $f1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
  7790. $f2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * (-0.934945152e-7))));
  7791. $fRet = sqrt(M_2DIVPI / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
  7792. }
  7793. return $fRet;
  7794. } // function _Bessely0()
  7795. private static function _Bessely1($fNum) {
  7796. if ($fNum < 8.0) {
  7797. $y = ($fNum * $fNum);
  7798. $f1 = $fNum * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * (0.7349264551e9 + $y *
  7799. (-0.4237922726e7 + $y * 0.8511937935e4)))));
  7800. $f2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
  7801. (0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
  7802. $fRet = $f1 / $f2 + M_2DIVPI * ( self::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum);
  7803. } else {
  7804. $z = 8.0 / $fNum;
  7805. $y = ($z * $z);
  7806. $xx = $fNum - 2.356194491;
  7807. $f1 = 1 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e6))));
  7808. $f2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * (-0.88228987e-6 + $y * 0.105787412e-6)));
  7809. $fRet = sqrt(M_2DIVPI / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
  7810. #i12430# ...but this seems to work much better.
  7811. // $fRet = sqrt(M_2DIVPI / $fNum) * sin($fNum - 2.356194491);
  7812. }
  7813. return $fRet;
  7814. } // function _Bessely1()
  7815. /**
  7816. * BESSELY
  7817. *
  7818. * Returns the Bessel function, which is also called the Weber function or the Neumann function.
  7819. *
  7820. * @param float $x
  7821. * @param float $n
  7822. * @return int
  7823. */
  7824. public static function BESSELY($x, $ord) {
  7825. $x = (is_null($x)) ? 0.0 : self::flattenSingleValue($x);
  7826. $ord = (is_null($ord)) ? 0.0 : self::flattenSingleValue($ord);
  7827. if ((is_numeric($x)) && (is_numeric($ord))) {
  7828. if (($ord < 0) || ($x == 0.0)) {
  7829. return self::$_errorCodes['num'];
  7830. }
  7831. switch(floor($ord)) {
  7832. case 0 : return self::_Bessely0($x);
  7833. break;
  7834. case 1 : return self::_Bessely1($x);
  7835. break;
  7836. default: $fTox = 2 / $x;
  7837. $fBym = self::_Bessely0($x);
  7838. $fBy = self::_Bessely1($x);
  7839. for ($n = 1; $n < $ord; ++$n) {
  7840. $fByp = $n * $fTox * $fBy - $fBym;
  7841. $fBym = $fBy;
  7842. $fBy = $fByp;
  7843. }
  7844. }
  7845. return $fBy;
  7846. }
  7847. return self::$_errorCodes['value'];
  7848. } // function BESSELY()
  7849. /**
  7850. * DELTA
  7851. *
  7852. * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
  7853. *
  7854. * @param float $a
  7855. * @param float $b
  7856. * @return int
  7857. */
  7858. public static function DELTA($a, $b=0) {
  7859. $a = self::flattenSingleValue($a);
  7860. $b = self::flattenSingleValue($b);
  7861. return (int) ($a == $b);
  7862. } // function DELTA()
  7863. /**
  7864. * GESTEP
  7865. *
  7866. * Returns 1 if number = step; returns 0 (zero) otherwise
  7867. *
  7868. * @param float $number
  7869. * @param float $step
  7870. * @return int
  7871. */
  7872. public static function GESTEP($number, $step=0) {
  7873. $number = self::flattenSingleValue($number);
  7874. $step = self::flattenSingleValue($step);
  7875. return (int) ($number >= $step);
  7876. } // function GESTEP()
  7877. //
  7878. // Private method to calculate the erf value
  7879. //
  7880. private static $_two_sqrtpi = 1.128379167095512574;
  7881. private static function _erfVal($x) {
  7882. if (abs($x) > 2.2) {
  7883. return 1 - self::_erfcVal($x);
  7884. }
  7885. $sum = $term = $x;
  7886. $xsqr = ($x * $x);
  7887. $j = 1;
  7888. do {
  7889. $term *= $xsqr / $j;
  7890. $sum -= $term / (2 * $j + 1);
  7891. ++$j;
  7892. $term *= $xsqr / $j;
  7893. $sum += $term / (2 * $j + 1);
  7894. ++$j;
  7895. if ($sum == 0.0) {
  7896. break;
  7897. }
  7898. } while (abs($term / $sum) > PRECISION);
  7899. return self::$_two_sqrtpi * $sum;
  7900. } // function _erfVal()
  7901. /**
  7902. * ERF
  7903. *
  7904. * Returns the error function integrated between lower_limit and upper_limit
  7905. *
  7906. * @param float $lower lower bound for integrating ERF
  7907. * @param float $upper upper bound for integrating ERF.
  7908. * If omitted, ERF integrates between zero and lower_limit
  7909. * @return int
  7910. */
  7911. public static function ERF($lower, $upper = null) {
  7912. $lower = self::flattenSingleValue($lower);
  7913. $upper = self::flattenSingleValue($upper);
  7914. if (is_numeric($lower)) {
  7915. if ($lower < 0) {
  7916. return self::$_errorCodes['num'];
  7917. }
  7918. if (is_null($upper)) {
  7919. return self::_erfVal($lower);
  7920. }
  7921. if (is_numeric($upper)) {
  7922. if ($upper < 0) {
  7923. return self::$_errorCodes['num'];
  7924. }
  7925. return self::_erfVal($upper) - self::_erfVal($lower);
  7926. }
  7927. }
  7928. return self::$_errorCodes['value'];
  7929. } // function ERF()
  7930. //
  7931. // Private method to calculate the erfc value
  7932. //
  7933. private static $_one_sqrtpi = 0.564189583547756287;
  7934. private static function _erfcVal($x) {
  7935. if (abs($x) < 2.2) {
  7936. return 1 - self::_erfVal($x);
  7937. }
  7938. if ($x < 0) {
  7939. return 2 - self::erfc(-$x);
  7940. }
  7941. $a = $n = 1;
  7942. $b = $c = $x;
  7943. $d = ($x * $x) + 0.5;
  7944. $q1 = $q2 = $b / $d;
  7945. $t = 0;
  7946. do {
  7947. $t = $a * $n + $b * $x;
  7948. $a = $b;
  7949. $b = $t;
  7950. $t = $c * $n + $d * $x;
  7951. $c = $d;
  7952. $d = $t;
  7953. $n += 0.5;
  7954. $q1 = $q2;
  7955. $q2 = $b / $d;
  7956. } while ((abs($q1 - $q2) / $q2) > PRECISION);
  7957. return self::$_one_sqrtpi * exp(-$x * $x) * $q2;
  7958. } // function _erfcVal()
  7959. /**
  7960. * ERFC
  7961. *
  7962. * Returns the complementary ERF function integrated between x and infinity
  7963. *
  7964. * @param float $x The lower bound for integrating ERF
  7965. * @return int
  7966. */
  7967. public static function ERFC($x) {
  7968. $x = self::flattenSingleValue($x);
  7969. if (is_numeric($x)) {
  7970. if ($x < 0) {
  7971. return self::$_errorCodes['num'];
  7972. }
  7973. return self::_erfcVal($x);
  7974. }
  7975. return self::$_errorCodes['value'];
  7976. } // function ERFC()
  7977. /**
  7978. * LOWERCASE
  7979. *
  7980. * Converts a string value to upper case.
  7981. *
  7982. * @param string $mixedCaseString
  7983. * @return string
  7984. */
  7985. public static function LOWERCASE($mixedCaseString) {
  7986. $mixedCaseString = self::flattenSingleValue($mixedCaseString);
  7987. if (is_bool($mixedCaseString)) {
  7988. $mixedCaseString = ($mixedCaseString) ? 'TRUE' : 'FALSE';
  7989. }
  7990. if (function_exists('mb_convert_case')) {
  7991. return mb_convert_case($mixedCaseString, MB_CASE_LOWER, 'UTF-8');
  7992. } else {
  7993. return strtoupper($mixedCaseString);
  7994. }
  7995. } // function LOWERCASE()
  7996. /**
  7997. * UPPERCASE
  7998. *
  7999. * Converts a string value to upper case.
  8000. *
  8001. * @param string $mixedCaseString
  8002. * @return string
  8003. */
  8004. public static function UPPERCASE($mixedCaseString) {
  8005. $mixedCaseString = self::flattenSingleValue($mixedCaseString);
  8006. if (is_bool($mixedCaseString)) {
  8007. $mixedCaseString = ($mixedCaseString) ? 'TRUE' : 'FALSE';
  8008. }
  8009. if (function_exists('mb_convert_case')) {
  8010. return mb_convert_case($mixedCaseString, MB_CASE_UPPER, 'UTF-8');
  8011. } else {
  8012. return strtoupper($mixedCaseString);
  8013. }
  8014. } // function UPPERCASE()
  8015. /**
  8016. * PROPERCASE
  8017. *
  8018. * Converts a string value to upper case.
  8019. *
  8020. * @param string $mixedCaseString
  8021. * @return string
  8022. */
  8023. public static function PROPERCASE($mixedCaseString) {
  8024. $mixedCaseString = self::flattenSingleValue($mixedCaseString);
  8025. if (is_bool($mixedCaseString)) {
  8026. $mixedCaseString = ($mixedCaseString) ? 'TRUE' : 'FALSE';
  8027. }
  8028. if (function_exists('mb_convert_case')) {
  8029. return mb_convert_case($mixedCaseString, MB_CASE_TITLE, 'UTF-8');
  8030. } else {
  8031. return ucwords($mixedCaseString);
  8032. }
  8033. } // function PROPERCASE()
  8034. /**
  8035. * DOLLAR
  8036. *
  8037. * This function converts a number to text using currency format, with the decimals rounded to the specified place.
  8038. * The format used is $#,##0.00_);($#,##0.00)..
  8039. *
  8040. * @param float $value The value to format
  8041. * @param int $decimals The number of digits to display to the right of the decimal point.
  8042. * If decimals is negative, number is rounded to the left of the decimal point.
  8043. * If you omit decimals, it is assumed to be 2
  8044. * @return string
  8045. */
  8046. public static function DOLLAR($value = 0, $decimals = 2) {
  8047. $value = self::flattenSingleValue($value);
  8048. $decimals = is_null($decimals) ? 0 : self::flattenSingleValue($decimals);
  8049. // Validate parameters
  8050. if (!is_numeric($value) || !is_numeric($decimals)) {
  8051. return self::$_errorCodes['num'];
  8052. }
  8053. $decimals = floor($decimals);
  8054. if ($decimals > 0) {
  8055. return money_format('%.'.$decimals.'n',$value);
  8056. } else {
  8057. $round = pow(10,abs($decimals));
  8058. if ($value < 0) { $round = 0-$round; }
  8059. $value = self::MROUND($value,$round);
  8060. // The implementation of money_format used if the standard PHP function is not available can't handle decimal places of 0,
  8061. // so we display to 1 dp and chop off that character and the decimal separator using substr
  8062. return substr(money_format('%.1n',$value),0,-2);
  8063. }
  8064. } // function DOLLAR()
  8065. /**
  8066. * DOLLARDE
  8067. *
  8068. * Converts a dollar price expressed as an integer part and a fraction part into a dollar price expressed as a decimal number.
  8069. * Fractional dollar numbers are sometimes used for security prices.
  8070. *
  8071. * @param float $fractional_dollar Fractional Dollar
  8072. * @param int $fraction Fraction
  8073. * @return float
  8074. */
  8075. public static function DOLLARDE($fractional_dollar = Null, $fraction = 0) {
  8076. $fractional_dollar = self::flattenSingleValue($fractional_dollar);
  8077. $fraction = (int)self::flattenSingleValue($fraction);
  8078. // Validate parameters
  8079. if (is_null($fractional_dollar) || $fraction < 0) {
  8080. return self::$_errorCodes['num'];
  8081. }
  8082. if ($fraction == 0) {
  8083. return self::$_errorCodes['divisionbyzero'];
  8084. }
  8085. $dollars = floor($fractional_dollar);
  8086. $cents = fmod($fractional_dollar,1);
  8087. $cents /= $fraction;
  8088. $cents *= pow(10,ceil(log10($fraction)));
  8089. return $dollars + $cents;
  8090. } // function DOLLARDE()
  8091. /**
  8092. * DOLLARFR
  8093. *
  8094. * Converts a dollar price expressed as a decimal number into a dollar price expressed as a fraction.
  8095. * Fractional dollar numbers are sometimes used for security prices.
  8096. *
  8097. * @param float $decimal_dollar Decimal Dollar
  8098. * @param int $fraction Fraction
  8099. * @return float
  8100. */
  8101. public static function DOLLARFR($decimal_dollar = Null, $fraction = 0) {
  8102. $decimal_dollar = self::flattenSingleValue($decimal_dollar);
  8103. $fraction = (int)self::flattenSingleValue($fraction);
  8104. // Validate parameters
  8105. if (is_null($decimal_dollar) || $fraction < 0) {
  8106. return self::$_errorCodes['num'];
  8107. }
  8108. if ($fraction == 0) {
  8109. return self::$_errorCodes['divisionbyzero'];
  8110. }
  8111. $dollars = floor($decimal_dollar);
  8112. $cents = fmod($decimal_dollar,1);
  8113. $cents *= $fraction;
  8114. $cents *= pow(10,-ceil(log10($fraction)));
  8115. return $dollars + $cents;
  8116. } // function DOLLARFR()
  8117. /**
  8118. * EFFECT
  8119. *
  8120. * Returns the effective interest rate given the nominal rate and the number of compounding payments per year.
  8121. *
  8122. * @param float $nominal_rate Nominal interest rate
  8123. * @param int $npery Number of compounding payments per year
  8124. * @return float
  8125. */
  8126. public static function EFFECT($nominal_rate = 0, $npery = 0) {
  8127. $nominal_rate = self::flattenSingleValue($nominal_rate);
  8128. $npery = (int)self::flattenSingleValue($npery);
  8129. // Validate parameters
  8130. if ($nominal_rate <= 0 || $npery < 1) {
  8131. return self::$_errorCodes['num'];
  8132. }
  8133. return pow((1 + $nominal_rate / $npery), $npery) - 1;
  8134. } // function EFFECT()
  8135. /**
  8136. * NOMINAL
  8137. *
  8138. * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
  8139. *
  8140. * @param float $effect_rate Effective interest rate
  8141. * @param int $npery Number of compounding payments per year
  8142. * @return float
  8143. */
  8144. public static function NOMINAL($effect_rate = 0, $npery = 0) {
  8145. $effect_rate = self::flattenSingleValue($effect_rate);
  8146. $npery = (int)self::flattenSingleValue($npery);
  8147. // Validate parameters
  8148. if ($effect_rate <= 0 || $npery < 1) {
  8149. return self::$_errorCodes['num'];
  8150. }
  8151. // Calculate
  8152. return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
  8153. } // function NOMINAL()
  8154. /**
  8155. * PV
  8156. *
  8157. * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
  8158. *
  8159. * @param float $rate Interest rate per period
  8160. * @param int $nper Number of periods
  8161. * @param float $pmt Periodic payment (annuity)
  8162. * @param float $fv Future Value
  8163. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8164. * @return float
  8165. */
  8166. public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0) {
  8167. $rate = self::flattenSingleValue($rate);
  8168. $nper = self::flattenSingleValue($nper);
  8169. $pmt = self::flattenSingleValue($pmt);
  8170. $fv = self::flattenSingleValue($fv);
  8171. $type = self::flattenSingleValue($type);
  8172. // Validate parameters
  8173. if ($type != 0 && $type != 1) {
  8174. return self::$_errorCodes['num'];
  8175. }
  8176. // Calculate
  8177. if (!is_null($rate) && $rate != 0) {
  8178. return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
  8179. } else {
  8180. return -$fv - $pmt * $nper;
  8181. }
  8182. } // function PV()
  8183. /**
  8184. * FV
  8185. *
  8186. * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
  8187. *
  8188. * @param float $rate Interest rate per period
  8189. * @param int $nper Number of periods
  8190. * @param float $pmt Periodic payment (annuity)
  8191. * @param float $pv Present Value
  8192. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8193. * @return float
  8194. */
  8195. public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0) {
  8196. $rate = self::flattenSingleValue($rate);
  8197. $nper = self::flattenSingleValue($nper);
  8198. $pmt = self::flattenSingleValue($pmt);
  8199. $pv = self::flattenSingleValue($pv);
  8200. $type = self::flattenSingleValue($type);
  8201. // Validate parameters
  8202. if ($type != 0 && $type != 1) {
  8203. return self::$_errorCodes['num'];
  8204. }
  8205. // Calculate
  8206. if (!is_null($rate) && $rate != 0) {
  8207. return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
  8208. } else {
  8209. return -$pv - $pmt * $nper;
  8210. }
  8211. } // function FV()
  8212. /**
  8213. * FVSCHEDULE
  8214. *
  8215. */
  8216. public static function FVSCHEDULE($principal, $schedule) {
  8217. $principal = self::flattenSingleValue($principal);
  8218. $schedule = self::flattenArray($schedule);
  8219. foreach($schedule as $n) {
  8220. $principal *= 1 + $n;
  8221. }
  8222. return $principal;
  8223. } // function FVSCHEDULE()
  8224. /**
  8225. * PMT
  8226. *
  8227. * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
  8228. *
  8229. * @param float $rate Interest rate per period
  8230. * @param int $nper Number of periods
  8231. * @param float $pv Present Value
  8232. * @param float $fv Future Value
  8233. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8234. * @return float
  8235. */
  8236. public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) {
  8237. $rate = self::flattenSingleValue($rate);
  8238. $nper = self::flattenSingleValue($nper);
  8239. $pv = self::flattenSingleValue($pv);
  8240. $fv = self::flattenSingleValue($fv);
  8241. $type = self::flattenSingleValue($type);
  8242. // Validate parameters
  8243. if ($type != 0 && $type != 1) {
  8244. return self::$_errorCodes['num'];
  8245. }
  8246. // Calculate
  8247. if (!is_null($rate) && $rate != 0) {
  8248. return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
  8249. } else {
  8250. return (-$pv - $fv) / $nper;
  8251. }
  8252. } // function PMT()
  8253. /**
  8254. * NPER
  8255. *
  8256. * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
  8257. *
  8258. * @param float $rate Interest rate per period
  8259. * @param int $pmt Periodic payment (annuity)
  8260. * @param float $pv Present Value
  8261. * @param float $fv Future Value
  8262. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8263. * @return float
  8264. */
  8265. public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0) {
  8266. $rate = self::flattenSingleValue($rate);
  8267. $pmt = self::flattenSingleValue($pmt);
  8268. $pv = self::flattenSingleValue($pv);
  8269. $fv = self::flattenSingleValue($fv);
  8270. $type = self::flattenSingleValue($type);
  8271. // Validate parameters
  8272. if ($type != 0 && $type != 1) {
  8273. return self::$_errorCodes['num'];
  8274. }
  8275. // Calculate
  8276. if (!is_null($rate) && $rate != 0) {
  8277. if ($pmt == 0 && $pv == 0) {
  8278. return self::$_errorCodes['num'];
  8279. }
  8280. return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
  8281. } else {
  8282. if ($pmt == 0) {
  8283. return self::$_errorCodes['num'];
  8284. }
  8285. return (-$pv -$fv) / $pmt;
  8286. }
  8287. } // function NPER()
  8288. private static function _interestAndPrincipal($rate=0, $per=0, $nper=0, $pv=0, $fv=0, $type=0) {
  8289. $pmt = self::PMT($rate, $nper, $pv, $fv, $type);
  8290. $capital = $pv;
  8291. for ($i = 1; $i<= $per; ++$i) {
  8292. $interest = ($type && $i == 1)? 0 : -$capital * $rate;
  8293. $principal = $pmt - $interest;
  8294. $capital += $principal;
  8295. }
  8296. return array($interest, $principal);
  8297. } // function _interestAndPrincipal()
  8298. /**
  8299. * IPMT
  8300. *
  8301. * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
  8302. *
  8303. * @param float $rate Interest rate per period
  8304. * @param int $per Period for which we want to find the interest
  8305. * @param int $nper Number of periods
  8306. * @param float $pv Present Value
  8307. * @param float $fv Future Value
  8308. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8309. * @return float
  8310. */
  8311. public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) {
  8312. $rate = self::flattenSingleValue($rate);
  8313. $per = (int) self::flattenSingleValue($per);
  8314. $nper = (int) self::flattenSingleValue($nper);
  8315. $pv = self::flattenSingleValue($pv);
  8316. $fv = self::flattenSingleValue($fv);
  8317. $type = (int) self::flattenSingleValue($type);
  8318. // Validate parameters
  8319. if ($type != 0 && $type != 1) {
  8320. return self::$_errorCodes['num'];
  8321. }
  8322. if ($per <= 0 || $per > $nper) {
  8323. return self::$_errorCodes['value'];
  8324. }
  8325. // Calculate
  8326. $interestAndPrincipal = self::_interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
  8327. return $interestAndPrincipal[0];
  8328. } // function IPMT()
  8329. /**
  8330. * CUMIPMT
  8331. *
  8332. * Returns the cumulative interest paid on a loan between start_period and end_period.
  8333. *
  8334. * @param float $rate Interest rate per period
  8335. * @param int $nper Number of periods
  8336. * @param float $pv Present Value
  8337. * @param int start The first period in the calculation.
  8338. * Payment periods are numbered beginning with 1.
  8339. * @param int end The last period in the calculation.
  8340. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8341. * @return float
  8342. */
  8343. public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0) {
  8344. $rate = self::flattenSingleValue($rate);
  8345. $nper = (int) self::flattenSingleValue($nper);
  8346. $pv = self::flattenSingleValue($pv);
  8347. $start = (int) self::flattenSingleValue($start);
  8348. $end = (int) self::flattenSingleValue($end);
  8349. $type = (int) self::flattenSingleValue($type);
  8350. // Validate parameters
  8351. if ($type != 0 && $type != 1) {
  8352. return self::$_errorCodes['num'];
  8353. }
  8354. if ($start < 1 || $start > $end) {
  8355. return self::$_errorCodes['value'];
  8356. }
  8357. // Calculate
  8358. $interest = 0;
  8359. for ($per = $start; $per <= $end; ++$per) {
  8360. $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type);
  8361. }
  8362. return $interest;
  8363. } // function CUMIPMT()
  8364. /**
  8365. * PPMT
  8366. *
  8367. * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
  8368. *
  8369. * @param float $rate Interest rate per period
  8370. * @param int $per Period for which we want to find the interest
  8371. * @param int $nper Number of periods
  8372. * @param float $pv Present Value
  8373. * @param float $fv Future Value
  8374. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8375. * @return float
  8376. */
  8377. public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) {
  8378. $rate = self::flattenSingleValue($rate);
  8379. $per = (int) self::flattenSingleValue($per);
  8380. $nper = (int) self::flattenSingleValue($nper);
  8381. $pv = self::flattenSingleValue($pv);
  8382. $fv = self::flattenSingleValue($fv);
  8383. $type = (int) self::flattenSingleValue($type);
  8384. // Validate parameters
  8385. if ($type != 0 && $type != 1) {
  8386. return self::$_errorCodes['num'];
  8387. }
  8388. if ($per <= 0 || $per > $nper) {
  8389. return self::$_errorCodes['value'];
  8390. }
  8391. // Calculate
  8392. $interestAndPrincipal = self::_interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
  8393. return $interestAndPrincipal[1];
  8394. } // function PPMT()
  8395. /**
  8396. * CUMPRINC
  8397. *
  8398. * Returns the cumulative principal paid on a loan between start_period and end_period.
  8399. *
  8400. * @param float $rate Interest rate per period
  8401. * @param int $nper Number of periods
  8402. * @param float $pv Present Value
  8403. * @param int start The first period in the calculation.
  8404. * Payment periods are numbered beginning with 1.
  8405. * @param int end The last period in the calculation.
  8406. * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
  8407. * @return float
  8408. */
  8409. public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0) {
  8410. $rate = self::flattenSingleValue($rate);
  8411. $nper = (int) self::flattenSingleValue($nper);
  8412. $pv = self::flattenSingleValue($pv);
  8413. $start = (int) self::flattenSingleValue($start);
  8414. $end = (int) self::flattenSingleValue($end);
  8415. $type = (int) self::flattenSingleValue($type);
  8416. // Validate parameters
  8417. if ($type != 0 && $type != 1) {
  8418. return self::$_errorCodes['num'];
  8419. }
  8420. if ($start < 1 || $start > $end) {
  8421. return self::$_errorCodes['value'];
  8422. }
  8423. // Calculate
  8424. $principal = 0;
  8425. for ($per = $start; $per <= $end; ++$per) {
  8426. $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type);
  8427. }
  8428. return $principal;
  8429. } // function CUMPRINC()
  8430. /**
  8431. * ISPMT
  8432. *
  8433. * Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
  8434. *
  8435. * Excel Function:
  8436. * =ISPMT(interest_rate, period, number_payments, PV)
  8437. *
  8438. * interest_rate is the interest rate for the investment
  8439. *
  8440. * period is the period to calculate the interest rate. It must be betweeen 1 and number_payments.
  8441. *
  8442. * number_payments is the number of payments for the annuity
  8443. *
  8444. * PV is the loan amount or present value of the payments
  8445. */
  8446. public static function ISPMT() {
  8447. // Return value
  8448. $returnValue = 0;
  8449. // Get the parameters
  8450. $aArgs = self::flattenArray(func_get_args());
  8451. $interestRate = array_shift($aArgs);
  8452. $period = array_shift($aArgs);
  8453. $numberPeriods = array_shift($aArgs);
  8454. $principleRemaining = array_shift($aArgs);
  8455. // Calculate
  8456. $principlePayment = ($principleRemaining * 1.0) / ($numberPeriods * 1.0);
  8457. for($i=0; $i <= $period; ++$i) {
  8458. $returnValue = $interestRate * $principleRemaining * -1;
  8459. $principleRemaining -= $principlePayment;
  8460. // principle needs to be 0 after the last payment, don't let floating point screw it up
  8461. if($i == $numberPeriods) {
  8462. $returnValue = 0;
  8463. }
  8464. }
  8465. return($returnValue);
  8466. } // function ISPMT()
  8467. /**
  8468. * NPV
  8469. *
  8470. * Returns the Net Present Value of a cash flow series given a discount rate.
  8471. *
  8472. * @param float Discount interest rate
  8473. * @param array Cash flow series
  8474. * @return float
  8475. */
  8476. public static function NPV() {
  8477. // Return value
  8478. $returnValue = 0;
  8479. // Loop through arguments
  8480. $aArgs = self::flattenArray(func_get_args());
  8481. // Calculate
  8482. $rate = array_shift($aArgs);
  8483. for ($i = 1; $i <= count($aArgs); ++$i) {
  8484. // Is it a numeric value?
  8485. if (is_numeric($aArgs[$i - 1])) {
  8486. $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
  8487. }
  8488. }
  8489. // Return
  8490. return $returnValue;
  8491. } // function NPV()
  8492. /**
  8493. * XNPV
  8494. *
  8495. * Returns the net present value for a schedule of cash flows that is not necessarily periodic.
  8496. * To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
  8497. *
  8498. * @param float Discount interest rate
  8499. * @param array Cash flow series
  8500. * @return float
  8501. */
  8502. public static function XNPV($rate, $values, $dates) {
  8503. if ((!is_array($values)) || (!is_array($dates))) return self::$_errorCodes['value'];
  8504. $values = self::flattenArray($values);
  8505. $dates = self::flattenArray($dates);
  8506. $valCount = count($values);
  8507. if ($valCount != count($dates)) return self::$_errorCodes['num'];
  8508. $xnpv = 0.0;
  8509. for ($i = 0; $i < $valCount; ++$i) {
  8510. $xnpv += $values[$i] / pow(1 + $rate, self::DATEDIF($dates[0],$dates[$i],'d') / 365);
  8511. }
  8512. return (is_finite($xnpv) ? $xnpv : self::$_errorCodes['value']);
  8513. } // function XNPV()
  8514. public static function IRR($values, $guess = 0.1) {
  8515. if (!is_array($values)) return self::$_errorCodes['value'];
  8516. $values = self::flattenArray($values);
  8517. $guess = self::flattenSingleValue($guess);
  8518. // create an initial range, with a root somewhere between 0 and guess
  8519. $x1 = 0.0;
  8520. $x2 = $guess;
  8521. $f1 = self::NPV($x1, $values);
  8522. $f2 = self::NPV($x2, $values);
  8523. for ($i = 0; $i < FINANCIAL_MAX_ITERATIONS; ++$i) {
  8524. if (($f1 * $f2) < 0.0) break;
  8525. if (abs($f1) < abs($f2)) {
  8526. $f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values);
  8527. } else {
  8528. $f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values);
  8529. }
  8530. }
  8531. if (($f1 * $f2) > 0.0) return self::$_errorCodes['value'];
  8532. $f = self::NPV($x1, $values);
  8533. if ($f < 0.0) {
  8534. $rtb = $x1;
  8535. $dx = $x2 - $x1;
  8536. } else {
  8537. $rtb = $x2;
  8538. $dx = $x1 - $x2;
  8539. }
  8540. for ($i = 0; $i < FINANCIAL_MAX_ITERATIONS; ++$i) {
  8541. $dx *= 0.5;
  8542. $x_mid = $rtb + $dx;
  8543. $f_mid = self::NPV($x_mid, $values);
  8544. if ($f_mid <= 0.0) $rtb = $x_mid;
  8545. if ((abs($f_mid) < FINANCIAL_PRECISION) || (abs($dx) < FINANCIAL_PRECISION)) return $x_mid;
  8546. }
  8547. return self::$_errorCodes['value'];
  8548. } // function IRR()
  8549. public static function MIRR($values, $finance_rate, $reinvestment_rate) {
  8550. if (!is_array($values)) return self::$_errorCodes['value'];
  8551. $values = self::flattenArray($values);
  8552. $finance_rate = self::flattenSingleValue($finance_rate);
  8553. $reinvestment_rate = self::flattenSingleValue($reinvestment_rate);
  8554. $n = count($values);
  8555. $rr = 1.0 + $reinvestment_rate;
  8556. $fr = 1.0 + $finance_rate;
  8557. $npv_pos = $npv_neg = 0.0;
  8558. foreach($values as $i => $v) {
  8559. if ($v >= 0) {
  8560. $npv_pos += $v / pow($rr, $i);
  8561. } else {
  8562. $npv_neg += $v / pow($fr, $i);
  8563. }
  8564. }
  8565. if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) {
  8566. return self::$_errorCodes['value'];
  8567. }
  8568. $mirr = pow((-$npv_pos * pow($rr, $n))
  8569. / ($npv_neg * ($rr)), (1.0 / ($n - 1))) - 1.0;
  8570. return (is_finite($mirr) ? $mirr : self::$_errorCodes['value']);
  8571. } // function MIRR()
  8572. public static function XIRR($values, $dates, $guess = 0.1) {
  8573. if ((!is_array($values)) && (!is_array($dates))) return self::$_errorCodes['value'];
  8574. $values = self::flattenArray($values);
  8575. $dates = self::flattenArray($dates);
  8576. $guess = self::flattenSingleValue($guess);
  8577. if (count($values) != count($dates)) return self::$_errorCodes['num'];
  8578. // create an initial range, with a root somewhere between 0 and guess
  8579. $x1 = 0.0;
  8580. $x2 = $guess;
  8581. $f1 = self::XNPV($x1, $values, $dates);
  8582. $f2 = self::XNPV($x2, $values, $dates);
  8583. for ($i = 0; $i < FINANCIAL_MAX_ITERATIONS; ++$i) {
  8584. if (($f1 * $f2) < 0.0) break;
  8585. if (abs($f1) < abs($f2)) {
  8586. $f1 = self::XNPV($x1 += 1.6 * ($x1 - $x2), $values, $dates);
  8587. } else {
  8588. $f2 = self::XNPV($x2 += 1.6 * ($x2 - $x1), $values, $dates);
  8589. }
  8590. }
  8591. if (($f1 * $f2) > 0.0) return self::$_errorCodes['value'];
  8592. $f = self::XNPV($x1, $values, $dates);
  8593. if ($f < 0.0) {
  8594. $rtb = $x1;
  8595. $dx = $x2 - $x1;
  8596. } else {
  8597. $rtb = $x2;
  8598. $dx = $x1 - $x2;
  8599. }
  8600. for ($i = 0; $i < FINANCIAL_MAX_ITERATIONS; ++$i) {
  8601. $dx *= 0.5;
  8602. $x_mid = $rtb + $dx;
  8603. $f_mid = self::XNPV($x_mid, $values, $dates);
  8604. if ($f_mid <= 0.0) $rtb = $x_mid;
  8605. if ((abs($f_mid) < FINANCIAL_PRECISION) || (abs($dx) < FINANCIAL_PRECISION)) return $x_mid;
  8606. }
  8607. return self::$_errorCodes['value'];
  8608. }
  8609. /**
  8610. * RATE
  8611. *
  8612. **/
  8613. public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1) {
  8614. $nper = (int) self::flattenSingleValue($nper);
  8615. $pmt = self::flattenSingleValue($pmt);
  8616. $pv = self::flattenSingleValue($pv);
  8617. $fv = (is_null($fv)) ? 0.0 : self::flattenSingleValue($fv);
  8618. $type = (is_null($type)) ? 0 : (int) self::flattenSingleValue($type);
  8619. $guess = (is_null($guess)) ? 0.1 : self::flattenSingleValue($guess);
  8620. $rate = $guess;
  8621. if (abs($rate) < FINANCIAL_PRECISION) {
  8622. $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
  8623. } else {
  8624. $f = exp($nper * log(1 + $rate));
  8625. $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
  8626. }
  8627. $y0 = $pv + $pmt * $nper + $fv;
  8628. $y1 = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
  8629. // find root by secant method
  8630. $i = $x0 = 0.0;
  8631. $x1 = $rate;
  8632. while ((abs($y0 - $y1) > FINANCIAL_PRECISION) && ($i < FINANCIAL_MAX_ITERATIONS)) {
  8633. $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0);
  8634. $x0 = $x1;
  8635. $x1 = $rate;
  8636. if (abs($rate) < FINANCIAL_PRECISION) {
  8637. $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv;
  8638. } else {
  8639. $f = exp($nper * log(1 + $rate));
  8640. $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv;
  8641. }
  8642. $y0 = $y1;
  8643. $y1 = $y;
  8644. ++$i;
  8645. }
  8646. return $rate;
  8647. } // function RATE()
  8648. /**
  8649. * DB
  8650. *
  8651. * Returns the depreciation of an asset for a specified period using the fixed-declining balance method.
  8652. * This form of depreciation is used if you want to get a higher depreciation value at the beginning of the depreciation
  8653. * (as opposed to linear depreciation). The depreciation value is reduced with every depreciation period by the
  8654. * depreciation already deducted from the initial cost.
  8655. *
  8656. * @param float cost Initial cost of the asset.
  8657. * @param float salvage Value at the end of the depreciation. (Sometimes called the salvage value of the asset)
  8658. * @param int life Number of periods over which the asset is depreciated. (Sometimes called the useful life of the asset)
  8659. * @param int period The period for which you want to calculate the depreciation. Period must use the same units as life.
  8660. * @param float month Number of months in the first year. If month is omitted, it defaults to 12.
  8661. * @return float
  8662. */
  8663. public static function DB($cost, $salvage, $life, $period, $month=12) {
  8664. $cost = (float) self::flattenSingleValue($cost);
  8665. $salvage = (float) self::flattenSingleValue($salvage);
  8666. $life = (int) self::flattenSingleValue($life);
  8667. $period = (int) self::flattenSingleValue($period);
  8668. $month = (int) self::flattenSingleValue($month);
  8669. // Validate
  8670. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) {
  8671. if ($cost == 0) {
  8672. return 0.0;
  8673. } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) {
  8674. return self::$_errorCodes['num'];
  8675. }
  8676. // Set Fixed Depreciation Rate
  8677. $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
  8678. $fixedDepreciationRate = round($fixedDepreciationRate, 3);
  8679. // Loop through each period calculating the depreciation
  8680. $previousDepreciation = 0;
  8681. for ($per = 1; $per <= $period; ++$per) {
  8682. if ($per == 1) {
  8683. $depreciation = $cost * $fixedDepreciationRate * $month / 12;
  8684. } elseif ($per == ($life + 1)) {
  8685. $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
  8686. } else {
  8687. $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
  8688. }
  8689. $previousDepreciation += $depreciation;
  8690. }
  8691. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  8692. $depreciation = round($depreciation,2);
  8693. }
  8694. return $depreciation;
  8695. }
  8696. return self::$_errorCodes['value'];
  8697. } // function DB()
  8698. /**
  8699. * DDB
  8700. *
  8701. * Returns the depreciation of an asset for a specified period using the double-declining balance method or some other method you specify.
  8702. *
  8703. * @param float cost Initial cost of the asset.
  8704. * @param float salvage Value at the end of the depreciation. (Sometimes called the salvage value of the asset)
  8705. * @param int life Number of periods over which the asset is depreciated. (Sometimes called the useful life of the asset)
  8706. * @param int period The period for which you want to calculate the depreciation. Period must use the same units as life.
  8707. * @param float factor The rate at which the balance declines.
  8708. * If factor is omitted, it is assumed to be 2 (the double-declining balance method).
  8709. * @return float
  8710. */
  8711. public static function DDB($cost, $salvage, $life, $period, $factor=2.0) {
  8712. $cost = (float) self::flattenSingleValue($cost);
  8713. $salvage = (float) self::flattenSingleValue($salvage);
  8714. $life = (int) self::flattenSingleValue($life);
  8715. $period = (int) self::flattenSingleValue($period);
  8716. $factor = (float) self::flattenSingleValue($factor);
  8717. // Validate
  8718. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) {
  8719. if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) {
  8720. return self::$_errorCodes['num'];
  8721. }
  8722. // Set Fixed Depreciation Rate
  8723. $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
  8724. $fixedDepreciationRate = round($fixedDepreciationRate, 3);
  8725. // Loop through each period calculating the depreciation
  8726. $previousDepreciation = 0;
  8727. for ($per = 1; $per <= $period; ++$per) {
  8728. $depreciation = min( ($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation) );
  8729. $previousDepreciation += $depreciation;
  8730. }
  8731. if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
  8732. $depreciation = round($depreciation,2);
  8733. }
  8734. return $depreciation;
  8735. }
  8736. return self::$_errorCodes['value'];
  8737. } // function DDB()
  8738. private static function _daysPerYear($year,$basis) {
  8739. switch ($basis) {
  8740. case 0 :
  8741. case 2 :
  8742. case 4 :
  8743. $daysPerYear = 360;
  8744. break;
  8745. case 3 :
  8746. $daysPerYear = 365;
  8747. break;
  8748. case 1 :
  8749. if (self::_isLeapYear($year)) {
  8750. $daysPerYear = 366;
  8751. } else {
  8752. $daysPerYear = 365;
  8753. }
  8754. break;
  8755. default :
  8756. return self::$_errorCodes['num'];
  8757. }
  8758. return $daysPerYear;
  8759. } // function _daysPerYear()
  8760. /**
  8761. * ACCRINT
  8762. *
  8763. * Returns the discount rate for a security.
  8764. *
  8765. * @param mixed issue The security's issue date.
  8766. * @param mixed firstinter The security's first interest date.
  8767. * @param mixed settlement The security's settlement date.
  8768. * @param float rate The security's annual coupon rate.
  8769. * @param float par The security's par value.
  8770. * @param int basis The type of day count to use.
  8771. * 0 or omitted US (NASD) 30/360
  8772. * 1 Actual/actual
  8773. * 2 Actual/360
  8774. * 3 Actual/365
  8775. * 4 European 30/360
  8776. * @return float
  8777. */
  8778. public static function ACCRINT($issue, $firstinter, $settlement, $rate, $par=1000, $frequency=1, $basis=0) {
  8779. $issue = self::flattenSingleValue($issue);
  8780. $firstinter = self::flattenSingleValue($firstinter);
  8781. $settlement = self::flattenSingleValue($settlement);
  8782. $rate = (float) self::flattenSingleValue($rate);
  8783. $par = (is_null($par)) ? 1000 : (float) self::flattenSingleValue($par);
  8784. $frequency = (is_null($frequency)) ? 1 : (int) self::flattenSingleValue($frequency);
  8785. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8786. // Validate
  8787. if ((is_numeric($rate)) && (is_numeric($par))) {
  8788. if (($rate <= 0) || ($par <= 0)) {
  8789. return self::$_errorCodes['num'];
  8790. }
  8791. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  8792. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  8793. return $daysBetweenIssueAndSettlement;
  8794. }
  8795. return $par * $rate * $daysBetweenIssueAndSettlement;
  8796. }
  8797. return self::$_errorCodes['value'];
  8798. } // function ACCRINT()
  8799. /**
  8800. * ACCRINTM
  8801. *
  8802. * Returns the discount rate for a security.
  8803. *
  8804. * @param mixed issue The security's issue date.
  8805. * @param mixed settlement The security's settlement date.
  8806. * @param float rate The security's annual coupon rate.
  8807. * @param float par The security's par value.
  8808. * @param int basis The type of day count to use.
  8809. * 0 or omitted US (NASD) 30/360
  8810. * 1 Actual/actual
  8811. * 2 Actual/360
  8812. * 3 Actual/365
  8813. * 4 European 30/360
  8814. * @return float
  8815. */
  8816. public static function ACCRINTM($issue, $settlement, $rate, $par=1000, $basis=0) {
  8817. $issue = self::flattenSingleValue($issue);
  8818. $settlement = self::flattenSingleValue($settlement);
  8819. $rate = (float) self::flattenSingleValue($rate);
  8820. $par = (is_null($par)) ? 1000 : (float) self::flattenSingleValue($par);
  8821. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8822. // Validate
  8823. if ((is_numeric($rate)) && (is_numeric($par))) {
  8824. if (($rate <= 0) || ($par <= 0)) {
  8825. return self::$_errorCodes['num'];
  8826. }
  8827. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  8828. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  8829. return $daysBetweenIssueAndSettlement;
  8830. }
  8831. return $par * $rate * $daysBetweenIssueAndSettlement;
  8832. }
  8833. return self::$_errorCodes['value'];
  8834. } // function ACCRINTM()
  8835. public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis=0) {
  8836. $cost = self::flattenSingleValue($cost);
  8837. $purchased = self::flattenSingleValue($purchased);
  8838. $firstPeriod = self::flattenSingleValue($firstPeriod);
  8839. $salvage = self::flattenSingleValue($salvage);
  8840. $period = floor(self::flattenSingleValue($period));
  8841. $rate = self::flattenSingleValue($rate);
  8842. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8843. $fUsePer = 1.0 / $rate;
  8844. if ($fUsePer < 3.0) {
  8845. $amortiseCoeff = 1.0;
  8846. } elseif ($fUsePer < 5.0) {
  8847. $amortiseCoeff = 1.5;
  8848. } elseif ($fUsePer <= 6.0) {
  8849. $amortiseCoeff = 2.0;
  8850. } else {
  8851. $amortiseCoeff = 2.5;
  8852. }
  8853. $rate *= $amortiseCoeff;
  8854. // $fNRate = floor((self::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost) + 0.5);
  8855. $fNRate = round(self::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost,0);
  8856. $cost -= $fNRate;
  8857. $fRest = $cost - $salvage;
  8858. for ($n = 0; $n < $period; ++$n) {
  8859. // $fNRate = floor(($rate * $cost) + 0.5);
  8860. $fNRate = round($rate * $cost,0);
  8861. $fRest -= $fNRate;
  8862. if ($fRest < 0.0) {
  8863. switch ($period - $n) {
  8864. case 0 :
  8865. case 1 :
  8866. // return floor(($cost * 0.5) + 0.5);
  8867. return round($cost * 0.5,0);
  8868. break;
  8869. default : return 0.0;
  8870. break;
  8871. }
  8872. }
  8873. $cost -= $fNRate;
  8874. }
  8875. return $fNRate;
  8876. } // function AMORDEGRC()
  8877. public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis=0) {
  8878. $cost = self::flattenSingleValue($cost);
  8879. $purchased = self::flattenSingleValue($purchased);
  8880. $firstPeriod = self::flattenSingleValue($firstPeriod);
  8881. $salvage = self::flattenSingleValue($salvage);
  8882. $period = self::flattenSingleValue($period);
  8883. $rate = self::flattenSingleValue($rate);
  8884. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8885. $fOneRate = $cost * $rate;
  8886. $fCostDelta = $cost - $salvage;
  8887. // Note, quirky variation for leap years on the YEARFRAC for this function
  8888. $purchasedYear = self::YEAR($purchased);
  8889. $yearFrac = self::YEARFRAC($purchased, $firstPeriod, $basis);
  8890. if (($basis == 1) && ($yearFrac < 1) && (self::_isLeapYear($purchasedYear))) {
  8891. $yearFrac *= 365 / 366;
  8892. }
  8893. $f0Rate = $yearFrac * $rate * $cost;
  8894. $nNumOfFullPeriods = intval(($cost - $salvage - $f0Rate) / $fOneRate);
  8895. if ($period == 0) {
  8896. return $f0Rate;
  8897. } elseif ($period <= $nNumOfFullPeriods) {
  8898. return $fOneRate;
  8899. } elseif ($period == ($nNumOfFullPeriods + 1)) {
  8900. return ($fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate);
  8901. } else {
  8902. return 0.0;
  8903. }
  8904. } // function AMORLINC()
  8905. private static function _lastDayOfMonth($testDate) {
  8906. $date = clone $testDate;
  8907. $date->modify('+1 day');
  8908. return ($date->format('d') == 1);
  8909. } // function _lastDayOfMonth()
  8910. private static function _firstDayOfMonth($testDate) {
  8911. $date = clone $testDate;
  8912. return ($date->format('d') == 1);
  8913. } // function _lastDayOfMonth()
  8914. private static function _coupFirstPeriodDate($settlement, $maturity, $frequency, $next) {
  8915. $months = 12 / $frequency;
  8916. $result = PHPExcel_Shared_Date::ExcelToPHPObject($maturity);
  8917. $eom = self::_lastDayOfMonth($result);
  8918. while ($settlement < PHPExcel_Shared_Date::PHPToExcel($result)) {
  8919. $result->modify('-'.$months.' months');
  8920. }
  8921. if ($next) {
  8922. $result->modify('+'.$months.' months');
  8923. }
  8924. if ($eom) {
  8925. $result->modify('-1 day');
  8926. }
  8927. return PHPExcel_Shared_Date::PHPToExcel($result);
  8928. } // function _coupFirstPeriodDate()
  8929. private static function _validFrequency($frequency) {
  8930. if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) {
  8931. return true;
  8932. }
  8933. if ((self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) &&
  8934. (($frequency == 6) || ($frequency == 12))) {
  8935. return true;
  8936. }
  8937. return false;
  8938. } // function _validFrequency()
  8939. public static function COUPDAYS($settlement, $maturity, $frequency, $basis=0) {
  8940. $settlement = self::flattenSingleValue($settlement);
  8941. $maturity = self::flattenSingleValue($maturity);
  8942. $frequency = (int) self::flattenSingleValue($frequency);
  8943. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8944. if (is_string($settlement = self::_getDateValue($settlement))) {
  8945. return self::$_errorCodes['value'];
  8946. }
  8947. if (is_string($maturity = self::_getDateValue($maturity))) {
  8948. return self::$_errorCodes['value'];
  8949. }
  8950. if (($settlement > $maturity) ||
  8951. (!self::_validFrequency($frequency)) ||
  8952. (($basis < 0) || ($basis > 4))) {
  8953. return self::$_errorCodes['num'];
  8954. }
  8955. switch ($basis) {
  8956. case 3: // Actual/365
  8957. return 365 / $frequency;
  8958. case 1: // Actual/actual
  8959. if ($frequency == 1) {
  8960. $daysPerYear = self::_daysPerYear(self::YEAR($maturity),$basis);
  8961. return ($daysPerYear / $frequency);
  8962. } else {
  8963. $prev = self::_coupFirstPeriodDate($settlement, $maturity, $frequency, False);
  8964. $next = self::_coupFirstPeriodDate($settlement, $maturity, $frequency, True);
  8965. return ($next - $prev);
  8966. }
  8967. default: // US (NASD) 30/360, Actual/360 or European 30/360
  8968. return 360 / $frequency;
  8969. }
  8970. return self::$_errorCodes['value'];
  8971. } // function COUPDAYS()
  8972. public static function COUPDAYBS($settlement, $maturity, $frequency, $basis=0) {
  8973. $settlement = self::flattenSingleValue($settlement);
  8974. $maturity = self::flattenSingleValue($maturity);
  8975. $frequency = (int) self::flattenSingleValue($frequency);
  8976. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8977. if (is_string($settlement = self::_getDateValue($settlement))) {
  8978. return self::$_errorCodes['value'];
  8979. }
  8980. if (is_string($maturity = self::_getDateValue($maturity))) {
  8981. return self::$_errorCodes['value'];
  8982. }
  8983. if (($settlement > $maturity) ||
  8984. (!self::_validFrequency($frequency)) ||
  8985. (($basis < 0) || ($basis > 4))) {
  8986. return self::$_errorCodes['num'];
  8987. }
  8988. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  8989. $prev = self::_coupFirstPeriodDate($settlement, $maturity, $frequency, False);
  8990. return self::YEARFRAC($prev, $settlement, $basis) * $daysPerYear;
  8991. } // function COUPDAYBS()
  8992. public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis=0) {
  8993. $settlement = self::flattenSingleValue($settlement);
  8994. $maturity = self::flattenSingleValue($maturity);
  8995. $frequency = (int) self::flattenSingleValue($frequency);
  8996. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  8997. if (is_string($settlement = self::_getDateValue($settlement))) {
  8998. return self::$_errorCodes['value'];
  8999. }
  9000. if (is_string($maturity = self::_getDateValue($maturity))) {
  9001. return self::$_errorCodes['value'];
  9002. }
  9003. if (($settlement > $maturity) ||
  9004. (!self::_validFrequency($frequency)) ||
  9005. (($basis < 0) || ($basis > 4))) {
  9006. return self::$_errorCodes['num'];
  9007. }
  9008. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  9009. $next = self::_coupFirstPeriodDate($settlement, $maturity, $frequency, True);
  9010. return self::YEARFRAC($settlement, $next, $basis) * $daysPerYear;
  9011. } // function COUPDAYSNC()
  9012. public static function COUPNCD($settlement, $maturity, $frequency, $basis=0) {
  9013. $settlement = self::flattenSingleValue($settlement);
  9014. $maturity = self::flattenSingleValue($maturity);
  9015. $frequency = (int) self::flattenSingleValue($frequency);
  9016. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  9017. if (is_string($settlement = self::_getDateValue($settlement))) {
  9018. return self::$_errorCodes['value'];
  9019. }
  9020. if (is_string($maturity = self::_getDateValue($maturity))) {
  9021. return self::$_errorCodes['value'];
  9022. }
  9023. if (($settlement > $maturity) ||
  9024. (!self::_validFrequency($frequency)) ||
  9025. (($basis < 0) || ($basis > 4))) {
  9026. return self::$_errorCodes['num'];
  9027. }
  9028. return self::_coupFirstPeriodDate($settlement, $maturity, $frequency, True);
  9029. } // function COUPNCD()
  9030. public static function COUPPCD($settlement, $maturity, $frequency, $basis=0) {
  9031. $settlement = self::flattenSingleValue($settlement);
  9032. $maturity = self::flattenSingleValue($maturity);
  9033. $frequency = (int) self::flattenSingleValue($frequency);
  9034. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  9035. if (is_string($settlement = self::_getDateValue($settlement))) {
  9036. return self::$_errorCodes['value'];
  9037. }
  9038. if (is_string($maturity = self::_getDateValue($maturity))) {
  9039. return self::$_errorCodes['value'];
  9040. }
  9041. if (($settlement > $maturity) ||
  9042. (!self::_validFrequency($frequency)) ||
  9043. (($basis < 0) || ($basis > 4))) {
  9044. return self::$_errorCodes['num'];
  9045. }
  9046. return self::_coupFirstPeriodDate($settlement, $maturity, $frequency, False);
  9047. } // function COUPPCD()
  9048. public static function COUPNUM($settlement, $maturity, $frequency, $basis=0) {
  9049. $settlement = self::flattenSingleValue($settlement);
  9050. $maturity = self::flattenSingleValue($maturity);
  9051. $frequency = (int) self::flattenSingleValue($frequency);
  9052. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  9053. if (is_string($settlement = self::_getDateValue($settlement))) {
  9054. return self::$_errorCodes['value'];
  9055. }
  9056. if (is_string($maturity = self::_getDateValue($maturity))) {
  9057. return self::$_errorCodes['value'];
  9058. }
  9059. if (($settlement > $maturity) ||
  9060. (!self::_validFrequency($frequency)) ||
  9061. (($basis < 0) || ($basis > 4))) {
  9062. return self::$_errorCodes['num'];
  9063. }
  9064. $settlement = self::_coupFirstPeriodDate($settlement, $maturity, $frequency, True);
  9065. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis) * 365;
  9066. switch ($frequency) {
  9067. case 1: // annual payments
  9068. return ceil($daysBetweenSettlementAndMaturity / 360);
  9069. case 2: // half-yearly
  9070. return ceil($daysBetweenSettlementAndMaturity / 180);
  9071. case 4: // quarterly
  9072. return ceil($daysBetweenSettlementAndMaturity / 90);
  9073. case 6: // bimonthly
  9074. return ceil($daysBetweenSettlementAndMaturity / 60);
  9075. case 12: // monthly
  9076. return ceil($daysBetweenSettlementAndMaturity / 30);
  9077. }
  9078. return self::$_errorCodes['value'];
  9079. } // function COUPNUM()
  9080. public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis=0) {
  9081. $settlement = self::flattenSingleValue($settlement);
  9082. $maturity = self::flattenSingleValue($maturity);
  9083. $rate = (float) self::flattenSingleValue($rate);
  9084. $yield = (float) self::flattenSingleValue($yield);
  9085. $redemption = (float) self::flattenSingleValue($redemption);
  9086. $frequency = (int) self::flattenSingleValue($frequency);
  9087. $basis = (is_null($basis)) ? 0 : (int) self::flattenSingleValue($basis);
  9088. if (is_string($settlement = self::_getDateValue($settlement))) {
  9089. return self::$_errorCodes['value'];
  9090. }
  9091. if (is_string($maturity = self::_getDateValue($maturity))) {
  9092. return self::$_errorCodes['value'];
  9093. }
  9094. if (($settlement > $maturity) ||
  9095. (!self::_validFrequency($frequency)) ||
  9096. (($basis < 0) || ($basis > 4))) {
  9097. return self::$_errorCodes['num'];
  9098. }
  9099. $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
  9100. $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis);
  9101. $n = self::COUPNUM($settlement, $maturity, $frequency, $basis);
  9102. $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis);
  9103. $baseYF = 1.0 + ($yield / $frequency);
  9104. $rfp = 100 * ($rate / $frequency);
  9105. $de = $dsc / $e;
  9106. $result = $redemption / pow($baseYF, (--$n + $de));
  9107. for($k = 0; $k <= $n; ++$k) {
  9108. $result += $rfp / (pow($baseYF, ($k + $de)));
  9109. }
  9110. $result -= $rfp * ($a / $e);
  9111. return $result;
  9112. } // function PRICE()
  9113. /**
  9114. * DISC
  9115. *
  9116. * Returns the discount rate for a security.
  9117. *
  9118. * @param mixed settlement The security's settlement date.
  9119. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  9120. * @param mixed maturity The security's maturity date.
  9121. * The maturity date is the date when the security expires.
  9122. * @param int price The security's price per $100 face value.
  9123. * @param int redemption the security's redemption value per $100 face value.
  9124. * @param int basis The type of day count to use.
  9125. * 0 or omitted US (NASD) 30/360
  9126. * 1 Actual/actual
  9127. * 2 Actual/360
  9128. * 3 Actual/365
  9129. * 4 European 30/360
  9130. * @return float
  9131. */
  9132. public static function DISC($settlement, $maturity, $price, $redemption, $basis=0) {
  9133. $settlement = self::flattenSingleValue($settlement);
  9134. $maturity = self::flattenSingleValue($maturity);
  9135. $price = (float) self::flattenSingleValue($price);
  9136. $redemption = (float) self::flattenSingleValue($redemption);
  9137. $basis = (int) self::flattenSingleValue($basis);
  9138. // Validate
  9139. if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) {
  9140. if (($price <= 0) || ($redemption <= 0)) {
  9141. return self::$_errorCodes['num'];
  9142. }
  9143. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  9144. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9145. return $daysBetweenSettlementAndMaturity;
  9146. }
  9147. return ((1 - $price / $redemption) / $daysBetweenSettlementAndMaturity);
  9148. }
  9149. return self::$_errorCodes['value'];
  9150. } // function DISC()
  9151. /**
  9152. * PRICEDISC
  9153. *
  9154. * Returns the price per $100 face value of a discounted security.
  9155. *
  9156. * @param mixed settlement The security's settlement date.
  9157. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  9158. * @param mixed maturity The security's maturity date.
  9159. * The maturity date is the date when the security expires.
  9160. * @param int discount The security's discount rate.
  9161. * @param int redemption The security's redemption value per $100 face value.
  9162. * @param int basis The type of day count to use.
  9163. * 0 or omitted US (NASD) 30/360
  9164. * 1 Actual/actual
  9165. * 2 Actual/360
  9166. * 3 Actual/365
  9167. * 4 European 30/360
  9168. * @return float
  9169. */
  9170. public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis=0) {
  9171. $settlement = self::flattenSingleValue($settlement);
  9172. $maturity = self::flattenSingleValue($maturity);
  9173. $discount = (float) self::flattenSingleValue($discount);
  9174. $redemption = (float) self::flattenSingleValue($redemption);
  9175. $basis = (int) self::flattenSingleValue($basis);
  9176. // Validate
  9177. if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) {
  9178. if (($discount <= 0) || ($redemption <= 0)) {
  9179. return self::$_errorCodes['num'];
  9180. }
  9181. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  9182. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9183. return $daysBetweenSettlementAndMaturity;
  9184. }
  9185. return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
  9186. }
  9187. return self::$_errorCodes['value'];
  9188. } // function PRICEDISC()
  9189. /**
  9190. * PRICEMAT
  9191. *
  9192. * Returns the price per $100 face value of a security that pays interest at maturity.
  9193. *
  9194. * @param mixed settlement The security's settlement date.
  9195. * The security's settlement date is the date after the issue date when the security is traded to the buyer.
  9196. * @param mixed maturity The security's maturity date.
  9197. * The maturity date is the date when the security expires.
  9198. * @param mixed issue The security's issue date.
  9199. * @param int rate The security's interest rate at date of issue.
  9200. * @param int yield The security's annual yield.
  9201. * @param int basis The type of day count to use.
  9202. * 0 or omitted US (NASD) 30/360
  9203. * 1 Actual/actual
  9204. * 2 Actual/360
  9205. * 3 Actual/365
  9206. * 4 European 30/360
  9207. * @return float
  9208. */
  9209. public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis=0) {
  9210. $settlement = self::flattenSingleValue($settlement);
  9211. $maturity = self::flattenSingleValue($maturity);
  9212. $issue = self::flattenSingleValue($issue);
  9213. $rate = self::flattenSingleValue($rate);
  9214. $yield = self::flattenSingleValue($yield);
  9215. $basis = (int) self::flattenSingleValue($basis);
  9216. // Validate
  9217. if (is_numeric($rate) && is_numeric($yield)) {
  9218. if (($rate <= 0) || ($yield <= 0)) {
  9219. return self::$_errorCodes['num'];
  9220. }
  9221. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  9222. if (!is_numeric($daysPerYear)) {
  9223. return $daysPerYear;
  9224. }
  9225. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  9226. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  9227. return $daysBetweenIssueAndSettlement;
  9228. }
  9229. $daysBetweenIssueAndSettlement *= $daysPerYear;
  9230. $daysBetweenIssueAndMaturity = self::YEARFRAC($issue, $maturity, $basis);
  9231. if (!is_numeric($daysBetweenIssueAndMaturity)) {
  9232. return $daysBetweenIssueAndMaturity;
  9233. }
  9234. $daysBetweenIssueAndMaturity *= $daysPerYear;
  9235. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  9236. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9237. return $daysBetweenSettlementAndMaturity;
  9238. }
  9239. $daysBetweenSettlementAndMaturity *= $daysPerYear;
  9240. return ((100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
  9241. (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
  9242. (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100));
  9243. }
  9244. return self::$_errorCodes['value'];
  9245. } // function PRICEMAT()
  9246. /**
  9247. * RECEIVED
  9248. *
  9249. * Returns the price per $100 face value of a discounted security.
  9250. *
  9251. * @param mixed settlement The security's settlement date.
  9252. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  9253. * @param mixed maturity The security's maturity date.
  9254. * The maturity date is the date when the security expires.
  9255. * @param int investment The amount invested in the security.
  9256. * @param int discount The security's discount rate.
  9257. * @param int basis The type of day count to use.
  9258. * 0 or omitted US (NASD) 30/360
  9259. * 1 Actual/actual
  9260. * 2 Actual/360
  9261. * 3 Actual/365
  9262. * 4 European 30/360
  9263. * @return float
  9264. */
  9265. public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis=0) {
  9266. $settlement = self::flattenSingleValue($settlement);
  9267. $maturity = self::flattenSingleValue($maturity);
  9268. $investment = (float) self::flattenSingleValue($investment);
  9269. $discount = (float) self::flattenSingleValue($discount);
  9270. $basis = (int) self::flattenSingleValue($basis);
  9271. // Validate
  9272. if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) {
  9273. if (($investment <= 0) || ($discount <= 0)) {
  9274. return self::$_errorCodes['num'];
  9275. }
  9276. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  9277. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9278. return $daysBetweenSettlementAndMaturity;
  9279. }
  9280. return $investment / ( 1 - ($discount * $daysBetweenSettlementAndMaturity));
  9281. }
  9282. return self::$_errorCodes['value'];
  9283. } // function RECEIVED()
  9284. /**
  9285. * INTRATE
  9286. *
  9287. * Returns the interest rate for a fully invested security.
  9288. *
  9289. * @param mixed settlement The security's settlement date.
  9290. * The security settlement date is the date after the issue date when the security is traded to the buyer.
  9291. * @param mixed maturity The security's maturity date.
  9292. * The maturity date is the date when the security expires.
  9293. * @param int investment The amount invested in the security.
  9294. * @param int redemption The amount to be received at maturity.
  9295. * @param int basis The type of day count to use.
  9296. * 0 or omitted US (NASD) 30/360
  9297. * 1 Actual/actual
  9298. * 2 Actual/360
  9299. * 3 Actual/365
  9300. * 4 European 30/360
  9301. * @return float
  9302. */
  9303. public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis=0) {
  9304. $settlement = self::flattenSingleValue($settlement);
  9305. $maturity = self::flattenSingleValue($maturity);
  9306. $investment = (float) self::flattenSingleValue($investment);
  9307. $redemption = (float) self::flattenSingleValue($redemption);
  9308. $basis = (int) self::flattenSingleValue($basis);
  9309. // Validate
  9310. if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) {
  9311. if (($investment <= 0) || ($redemption <= 0)) {
  9312. return self::$_errorCodes['num'];
  9313. }
  9314. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  9315. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9316. return $daysBetweenSettlementAndMaturity;
  9317. }
  9318. return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);
  9319. }
  9320. return self::$_errorCodes['value'];
  9321. } // function INTRATE()
  9322. /**
  9323. * TBILLEQ
  9324. *
  9325. * Returns the bond-equivalent yield for a Treasury bill.
  9326. *
  9327. * @param mixed settlement The Treasury bill's settlement date.
  9328. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
  9329. * @param mixed maturity The Treasury bill's maturity date.
  9330. * The maturity date is the date when the Treasury bill expires.
  9331. * @param int discount The Treasury bill's discount rate.
  9332. * @return float
  9333. */
  9334. public static function TBILLEQ($settlement, $maturity, $discount) {
  9335. $settlement = self::flattenSingleValue($settlement);
  9336. $maturity = self::flattenSingleValue($maturity);
  9337. $discount = self::flattenSingleValue($discount);
  9338. // Use TBILLPRICE for validation
  9339. $testValue = self::TBILLPRICE($settlement, $maturity, $discount);
  9340. if (is_string($testValue)) {
  9341. return $testValue;
  9342. }
  9343. if (is_string($maturity = self::_getDateValue($maturity))) {
  9344. return self::$_errorCodes['value'];
  9345. }
  9346. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  9347. ++$maturity;
  9348. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity) * 360;
  9349. } else {
  9350. $daysBetweenSettlementAndMaturity = (self::_getDateValue($maturity) - self::_getDateValue($settlement));
  9351. }
  9352. return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
  9353. } // function TBILLEQ()
  9354. /**
  9355. * TBILLPRICE
  9356. *
  9357. * Returns the yield for a Treasury bill.
  9358. *
  9359. * @param mixed settlement The Treasury bill's settlement date.
  9360. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
  9361. * @param mixed maturity The Treasury bill's maturity date.
  9362. * The maturity date is the date when the Treasury bill expires.
  9363. * @param int discount The Treasury bill's discount rate.
  9364. * @return float
  9365. */
  9366. public static function TBILLPRICE($settlement, $maturity, $discount) {
  9367. $settlement = self::flattenSingleValue($settlement);
  9368. $maturity = self::flattenSingleValue($maturity);
  9369. $discount = self::flattenSingleValue($discount);
  9370. if (is_string($maturity = self::_getDateValue($maturity))) {
  9371. return self::$_errorCodes['value'];
  9372. }
  9373. // Validate
  9374. if (is_numeric($discount)) {
  9375. if ($discount <= 0) {
  9376. return self::$_errorCodes['num'];
  9377. }
  9378. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  9379. ++$maturity;
  9380. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity) * 360;
  9381. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9382. return $daysBetweenSettlementAndMaturity;
  9383. }
  9384. } else {
  9385. $daysBetweenSettlementAndMaturity = (self::_getDateValue($maturity) - self::_getDateValue($settlement));
  9386. }
  9387. if ($daysBetweenSettlementAndMaturity > 360) {
  9388. return self::$_errorCodes['num'];
  9389. }
  9390. $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
  9391. if ($price <= 0) {
  9392. return self::$_errorCodes['num'];
  9393. }
  9394. return $price;
  9395. }
  9396. return self::$_errorCodes['value'];
  9397. } // function TBILLPRICE()
  9398. /**
  9399. * TBILLYIELD
  9400. *
  9401. * Returns the yield for a Treasury bill.
  9402. *
  9403. * @param mixed settlement The Treasury bill's settlement date.
  9404. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
  9405. * @param mixed maturity The Treasury bill's maturity date.
  9406. * The maturity date is the date when the Treasury bill expires.
  9407. * @param int price The Treasury bill's price per $100 face value.
  9408. * @return float
  9409. */
  9410. public static function TBILLYIELD($settlement, $maturity, $price) {
  9411. $settlement = self::flattenSingleValue($settlement);
  9412. $maturity = self::flattenSingleValue($maturity);
  9413. $price = self::flattenSingleValue($price);
  9414. // Validate
  9415. if (is_numeric($price)) {
  9416. if ($price <= 0) {
  9417. return self::$_errorCodes['num'];
  9418. }
  9419. if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
  9420. ++$maturity;
  9421. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity) * 360;
  9422. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9423. return $daysBetweenSettlementAndMaturity;
  9424. }
  9425. } else {
  9426. $daysBetweenSettlementAndMaturity = (self::_getDateValue($maturity) - self::_getDateValue($settlement));
  9427. }
  9428. if ($daysBetweenSettlementAndMaturity > 360) {
  9429. return self::$_errorCodes['num'];
  9430. }
  9431. return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
  9432. }
  9433. return self::$_errorCodes['value'];
  9434. } // function TBILLYIELD()
  9435. /**
  9436. * SLN
  9437. *
  9438. * Returns the straight-line depreciation of an asset for one period
  9439. *
  9440. * @param cost Initial cost of the asset
  9441. * @param salvage Value at the end of the depreciation
  9442. * @param life Number of periods over which the asset is depreciated
  9443. * @return float
  9444. */
  9445. public static function SLN($cost, $salvage, $life) {
  9446. $cost = self::flattenSingleValue($cost);
  9447. $salvage = self::flattenSingleValue($salvage);
  9448. $life = self::flattenSingleValue($life);
  9449. // Calculate
  9450. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
  9451. if ($life < 0) {
  9452. return self::$_errorCodes['num'];
  9453. }
  9454. return ($cost - $salvage) / $life;
  9455. }
  9456. return self::$_errorCodes['value'];
  9457. } // function SLN()
  9458. /**
  9459. * YIELDMAT
  9460. *
  9461. * Returns the annual yield of a security that pays interest at maturity.
  9462. *
  9463. * @param mixed settlement The security's settlement date.
  9464. * The security's settlement date is the date after the issue date when the security is traded to the buyer.
  9465. * @param mixed maturity The security's maturity date.
  9466. * The maturity date is the date when the security expires.
  9467. * @param mixed issue The security's issue date.
  9468. * @param int rate The security's interest rate at date of issue.
  9469. * @param int price The security's price per $100 face value.
  9470. * @param int basis The type of day count to use.
  9471. * 0 or omitted US (NASD) 30/360
  9472. * 1 Actual/actual
  9473. * 2 Actual/360
  9474. * 3 Actual/365
  9475. * 4 European 30/360
  9476. * @return float
  9477. */
  9478. public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis=0) {
  9479. $settlement = self::flattenSingleValue($settlement);
  9480. $maturity = self::flattenSingleValue($maturity);
  9481. $issue = self::flattenSingleValue($issue);
  9482. $rate = self::flattenSingleValue($rate);
  9483. $price = self::flattenSingleValue($price);
  9484. $basis = (int) self::flattenSingleValue($basis);
  9485. // Validate
  9486. if (is_numeric($rate) && is_numeric($price)) {
  9487. if (($rate <= 0) || ($price <= 0)) {
  9488. return self::$_errorCodes['num'];
  9489. }
  9490. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  9491. if (!is_numeric($daysPerYear)) {
  9492. return $daysPerYear;
  9493. }
  9494. $daysBetweenIssueAndSettlement = self::YEARFRAC($issue, $settlement, $basis);
  9495. if (!is_numeric($daysBetweenIssueAndSettlement)) {
  9496. return $daysBetweenIssueAndSettlement;
  9497. }
  9498. $daysBetweenIssueAndSettlement *= $daysPerYear;
  9499. $daysBetweenIssueAndMaturity = self::YEARFRAC($issue, $maturity, $basis);
  9500. if (!is_numeric($daysBetweenIssueAndMaturity)) {
  9501. return $daysBetweenIssueAndMaturity;
  9502. }
  9503. $daysBetweenIssueAndMaturity *= $daysPerYear;
  9504. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity, $basis);
  9505. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9506. return $daysBetweenSettlementAndMaturity;
  9507. }
  9508. $daysBetweenSettlementAndMaturity *= $daysPerYear;
  9509. return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
  9510. (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
  9511. ($daysPerYear / $daysBetweenSettlementAndMaturity);
  9512. }
  9513. return self::$_errorCodes['value'];
  9514. } // function YIELDMAT()
  9515. /**
  9516. * YIELDDISC
  9517. *
  9518. * Returns the annual yield of a security that pays interest at maturity.
  9519. *
  9520. * @param mixed settlement The security's settlement date.
  9521. * The security's settlement date is the date after the issue date when the security is traded to the buyer.
  9522. * @param mixed maturity The security's maturity date.
  9523. * The maturity date is the date when the security expires.
  9524. * @param int price The security's price per $100 face value.
  9525. * @param int redemption The security's redemption value per $100 face value.
  9526. * @param int basis The type of day count to use.
  9527. * 0 or omitted US (NASD) 30/360
  9528. * 1 Actual/actual
  9529. * 2 Actual/360
  9530. * 3 Actual/365
  9531. * 4 European 30/360
  9532. * @return float
  9533. */
  9534. public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis=0) {
  9535. $settlement = self::flattenSingleValue($settlement);
  9536. $maturity = self::flattenSingleValue($maturity);
  9537. $price = self::flattenSingleValue($price);
  9538. $redemption = self::flattenSingleValue($redemption);
  9539. $basis = (int) self::flattenSingleValue($basis);
  9540. // Validate
  9541. if (is_numeric($price) && is_numeric($redemption)) {
  9542. if (($price <= 0) || ($redemption <= 0)) {
  9543. return self::$_errorCodes['num'];
  9544. }
  9545. $daysPerYear = self::_daysPerYear(self::YEAR($settlement),$basis);
  9546. if (!is_numeric($daysPerYear)) {
  9547. return $daysPerYear;
  9548. }
  9549. $daysBetweenSettlementAndMaturity = self::YEARFRAC($settlement, $maturity,$basis);
  9550. if (!is_numeric($daysBetweenSettlementAndMaturity)) {
  9551. return $daysBetweenSettlementAndMaturity;
  9552. }
  9553. $daysBetweenSettlementAndMaturity *= $daysPerYear;
  9554. return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
  9555. }
  9556. return self::$_errorCodes['value'];
  9557. } // function YIELDDISC()
  9558. /**
  9559. * CELL_ADDRESS
  9560. *
  9561. * Creates a cell address as text, given specified row and column numbers.
  9562. *
  9563. * @param row Row number to use in the cell reference
  9564. * @param column Column number to use in the cell reference
  9565. * @param relativity Flag indicating the type of reference to return
  9566. * 1 or omitted Absolute
  9567. * 2 Absolute row; relative column
  9568. * 3 Relative row; absolute column
  9569. * 4 Relative
  9570. * @param referenceStyle A logical value that specifies the A1 or R1C1 reference style.
  9571. * TRUE or omitted CELL_ADDRESS returns an A1-style reference
  9572. * FALSE CELL_ADDRESS returns an R1C1-style reference
  9573. * @param sheetText Optional Name of worksheet to use
  9574. * @return string
  9575. */
  9576. public static function CELL_ADDRESS($row, $column, $relativity=1, $referenceStyle=True, $sheetText='') {
  9577. $row = self::flattenSingleValue($row);
  9578. $column = self::flattenSingleValue($column);
  9579. $relativity = self::flattenSingleValue($relativity);
  9580. $sheetText = self::flattenSingleValue($sheetText);
  9581. if (($row < 1) || ($column < 1)) {
  9582. return self::$_errorCodes['value'];
  9583. }
  9584. if ($sheetText > '') {
  9585. if (strpos($sheetText,' ') !== False) { $sheetText = "'".$sheetText."'"; }
  9586. $sheetText .='!';
  9587. }
  9588. if ((!is_bool($referenceStyle)) || $referenceStyle) {
  9589. $rowRelative = $columnRelative = '$';
  9590. $column = PHPExcel_Cell::stringFromColumnIndex($column-1);
  9591. if (($relativity == 2) || ($relativity == 4)) { $columnRelative = ''; }
  9592. if (($relativity == 3) || ($relativity == 4)) { $rowRelative = ''; }
  9593. return $sheetText.$columnRelative.$column.$rowRelative.$row;
  9594. } else {
  9595. if (($relativity == 2) || ($relativity == 4)) { $column = '['.$column.']'; }
  9596. if (($relativity == 3) || ($relativity == 4)) { $row = '['.$row.']'; }
  9597. return $sheetText.'R'.$row.'C'.$column;
  9598. }
  9599. } // function CELL_ADDRESS()
  9600. /**
  9601. * COLUMN
  9602. *
  9603. * Returns the column number of the given cell reference
  9604. * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array.
  9605. * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
  9606. * reference of the cell in which the COLUMN function appears; otherwise this function returns 0.
  9607. *
  9608. * @param cellAddress A reference to a range of cells for which you want the column numbers
  9609. * @return integer or array of integer
  9610. */
  9611. public static function COLUMN($cellAddress=Null) {
  9612. if (is_null($cellAddress) || trim($cellAddress) === '') { return 0; }
  9613. if (is_array($cellAddress)) {
  9614. foreach($cellAddress as $columnKey => $value) {
  9615. $columnKey = preg_replace('/[^a-z]/i','',$columnKey);
  9616. return (integer) PHPExcel_Cell::columnIndexFromString($columnKey);
  9617. }
  9618. } else {
  9619. if (strpos($cellAddress,'!') !== false) {
  9620. list($sheet,$cellAddress) = explode('!',$cellAddress);
  9621. }
  9622. if (strpos($cellAddress,':') !== false) {
  9623. list($startAddress,$endAddress) = explode(':',$cellAddress);
  9624. $startAddress = preg_replace('/[^a-z]/i','',$startAddress);
  9625. $endAddress = preg_replace('/[^a-z]/i','',$endAddress);
  9626. $returnValue = array();
  9627. do {
  9628. $returnValue[] = (integer) PHPExcel_Cell::columnIndexFromString($startAddress);
  9629. } while ($startAddress++ != $endAddress);
  9630. return $returnValue;
  9631. } else {
  9632. $cellAddress = preg_replace('/[^a-z]/i','',$cellAddress);
  9633. return (integer) PHPExcel_Cell::columnIndexFromString($cellAddress);
  9634. }
  9635. }
  9636. } // function COLUMN()
  9637. /**
  9638. * COLUMNS
  9639. *
  9640. * Returns the number of columns in an array or reference.
  9641. *
  9642. * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns
  9643. * @return integer
  9644. */
  9645. public static function COLUMNS($cellAddress=Null) {
  9646. if (is_null($cellAddress) || $cellAddress === '') {
  9647. return 1;
  9648. } elseif (!is_array($cellAddress)) {
  9649. return self::$_errorCodes['value'];
  9650. }
  9651. $x = array_keys($cellAddress);
  9652. $x = array_shift($x);
  9653. $isMatrix = (is_numeric($x));
  9654. list($columns,$rows) = PHPExcel_Calculation::_getMatrixDimensions($cellAddress);
  9655. if ($isMatrix) {
  9656. return $rows;
  9657. } else {
  9658. return $columns;
  9659. }
  9660. } // function COLUMNS()
  9661. /**
  9662. * ROW
  9663. *
  9664. * Returns the row number of the given cell reference
  9665. * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array.
  9666. * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
  9667. * reference of the cell in which the ROW function appears; otherwise this function returns 0.
  9668. *
  9669. * @param cellAddress A reference to a range of cells for which you want the row numbers
  9670. * @return integer or array of integer
  9671. */
  9672. public static function ROW($cellAddress=Null) {
  9673. if (is_null($cellAddress) || trim($cellAddress) === '') { return 0; }
  9674. if (is_array($cellAddress)) {
  9675. foreach($cellAddress as $columnKey => $rowValue) {
  9676. foreach($rowValue as $rowKey => $cellValue) {
  9677. return (integer) preg_replace('/[^0-9]/i','',$rowKey);
  9678. }
  9679. }
  9680. } else {
  9681. if (strpos($cellAddress,'!') !== false) {
  9682. list($sheet,$cellAddress) = explode('!',$cellAddress);
  9683. }
  9684. if (strpos($cellAddress,':') !== false) {
  9685. list($startAddress,$endAddress) = explode(':',$cellAddress);
  9686. $startAddress = preg_replace('/[^0-9]/','',$startAddress);
  9687. $endAddress = preg_replace('/[^0-9]/','',$endAddress);
  9688. $returnValue = array();
  9689. do {
  9690. $returnValue[][] = (integer) $startAddress;
  9691. } while ($startAddress++ != $endAddress);
  9692. return $returnValue;
  9693. } else {
  9694. list($cellAddress) = explode(':',$cellAddress);
  9695. return (integer) preg_replace('/[^0-9]/','',$cellAddress);
  9696. }
  9697. }
  9698. } // function ROW()
  9699. /**
  9700. * ROWS
  9701. *
  9702. * Returns the number of rows in an array or reference.
  9703. *
  9704. * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows
  9705. * @return integer
  9706. */
  9707. public static function ROWS($cellAddress=Null) {
  9708. if (is_null($cellAddress) || $cellAddress === '') {
  9709. return 1;
  9710. } elseif (!is_array($cellAddress)) {
  9711. return self::$_errorCodes['value'];
  9712. }
  9713. $i = array_keys($cellAddress);
  9714. $isMatrix = (is_numeric(array_shift($i)));
  9715. list($columns,$rows) = PHPExcel_Calculation::_getMatrixDimensions($cellAddress);
  9716. if ($isMatrix) {
  9717. return $columns;
  9718. } else {
  9719. return $rows;
  9720. }
  9721. } // function ROWS()
  9722. /**
  9723. * INDIRECT
  9724. *
  9725. * Returns the number of rows in an array or reference.
  9726. *
  9727. * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows
  9728. * @return integer
  9729. */
  9730. public static function INDIRECT($cellAddress=Null, PHPExcel_Cell $pCell = null) {
  9731. $cellAddress = self::flattenSingleValue($cellAddress);
  9732. if (is_null($cellAddress) || $cellAddress === '') {
  9733. return self::REF();
  9734. }
  9735. $cellAddress1 = $cellAddress;
  9736. $cellAddress2 = NULL;
  9737. if (strpos($cellAddress,':') !== false) {
  9738. list($cellAddress1,$cellAddress2) = explode(':',$cellAddress);
  9739. }
  9740. if ((!preg_match('/^'.PHPExcel_Calculation::CALCULATION_REGEXP_CELLREF.'$/i', $cellAddress1, $matches)) ||
  9741. ((!is_null($cellAddress2)) && (!preg_match('/^'.PHPExcel_Calculation::CALCULATION_REGEXP_CELLREF.'$/i', $cellAddress2, $matches)))) {
  9742. return self::REF();
  9743. }
  9744. if (strpos($cellAddress,'!') !== false) {
  9745. list($sheetName,$cellAddress) = explode('!',$cellAddress);
  9746. $pSheet = $pCell->getParent()->getParent()->getSheetByName($sheetName);
  9747. } else {
  9748. $pSheet = $pCell->getParent();
  9749. }
  9750. return PHPExcel_Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, False);
  9751. } // function INDIRECT()
  9752. /**
  9753. * OFFSET
  9754. *
  9755. * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
  9756. * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
  9757. * the number of columns to be returned.
  9758. *
  9759. * @param cellAddress The reference from which you want to base the offset. Reference must refer to a cell or
  9760. * range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value.
  9761. * @param rows The number of rows, up or down, that you want the upper-left cell to refer to.
  9762. * Using 5 as the rows argument specifies that the upper-left cell in the reference is
  9763. * five rows below reference. Rows can be positive (which means below the starting reference)
  9764. * or negative (which means above the starting reference).
  9765. * @param cols The number of columns, to the left or right, that you want the upper-left cell of the result
  9766. * to refer to. Using 5 as the cols argument specifies that the upper-left cell in the
  9767. * reference is five columns to the right of reference. Cols can be positive (which means
  9768. * to the right of the starting reference) or negative (which means to the left of the
  9769. * starting reference).
  9770. * @param height The height, in number of rows, that you want the returned reference to be. Height must be a positive number.
  9771. * @param width The width, in number of columns, that you want the returned reference to be. Width must be a positive number.
  9772. * @return string A reference to a cell or range of cells
  9773. */
  9774. public static function OFFSET($cellAddress=Null,$rows=0,$columns=0,$height=null,$width=null) {
  9775. $rows = self::flattenSingleValue($rows);
  9776. $columns = self::flattenSingleValue($columns);
  9777. $height = self::flattenSingleValue($height);
  9778. $width = self::flattenSingleValue($width);
  9779. if ($cellAddress == Null) {
  9780. return 0;
  9781. }
  9782. $args = func_get_args();
  9783. $pCell = array_pop($args);
  9784. if (!is_object($pCell)) {
  9785. return self::$_errorCodes['reference'];
  9786. }
  9787. $sheetName = null;
  9788. if (strpos($cellAddress,"!")) {
  9789. list($sheetName,$cellAddress) = explode("!",$cellAddress);
  9790. }
  9791. if (strpos($cellAddress,":")) {
  9792. list($startCell,$endCell) = explode(":",$cellAddress);
  9793. } else {
  9794. $startCell = $endCell = $cellAddress;
  9795. }
  9796. list($startCellColumn,$startCellRow) = PHPExcel_Cell::coordinateFromString($startCell);
  9797. list($endCellColumn,$endCellRow) = PHPExcel_Cell::coordinateFromString($endCell);
  9798. $startCellRow += $rows;
  9799. $startCellColumn = PHPExcel_Cell::columnIndexFromString($startCellColumn) - 1;
  9800. $startCellColumn += $columns;
  9801. if (($startCellRow <= 0) || ($startCellColumn < 0)) {
  9802. return self::$_errorCodes['reference'];
  9803. }
  9804. $endCellColumn = PHPExcel_Cell::columnIndexFromString($endCellColumn) - 1;
  9805. if (($width != null) && (!is_object($width))) {
  9806. $endCellColumn = $startCellColumn + $width - 1;
  9807. } else {
  9808. $endCellColumn += $columns;
  9809. }
  9810. $startCellColumn = PHPExcel_Cell::stringFromColumnIndex($startCellColumn);
  9811. if (($height != null) && (!is_object($height))) {
  9812. $endCellRow = $startCellRow + $height - 1;
  9813. } else {
  9814. $endCellRow += $rows;
  9815. }
  9816. if (($endCellRow <= 0) || ($endCellColumn < 0)) {
  9817. return self::$_errorCodes['reference'];
  9818. }
  9819. $endCellColumn = PHPExcel_Cell::stringFromColumnIndex($endCellColumn);
  9820. $cellAddress = $startCellColumn.$startCellRow;
  9821. if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
  9822. $cellAddress .= ':'.$endCellColumn.$endCellRow;
  9823. }
  9824. if ($sheetName !== null) {
  9825. $pSheet = $pCell->getParent()->getParent()->getSheetByName($sheetName);
  9826. } else {
  9827. $pSheet = $pCell->getParent();
  9828. }
  9829. return PHPExcel_Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, False);
  9830. } // function OFFSET()
  9831. public static function CHOOSE() {
  9832. $chooseArgs = func_get_args();
  9833. $chosenEntry = self::flattenArray(array_shift($chooseArgs));
  9834. $entryCount = count($chooseArgs) - 1;
  9835. if(is_array($chosenEntry)) {
  9836. $chosenEntry = array_shift($chosenEntry);
  9837. }
  9838. if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
  9839. --$chosenEntry;
  9840. } else {
  9841. return self::$_errorCodes['value'];
  9842. }
  9843. $chosenEntry = floor($chosenEntry);
  9844. if (($chosenEntry <= 0) || ($chosenEntry > $entryCount)) {
  9845. return self::$_errorCodes['value'];
  9846. }
  9847. if (is_array($chooseArgs[$chosenEntry])) {
  9848. return self::flattenArray($chooseArgs[$chosenEntry]);
  9849. } else {
  9850. return $chooseArgs[$chosenEntry];
  9851. }
  9852. } // function CHOOSE()
  9853. /**
  9854. * MATCH
  9855. *
  9856. * The MATCH function searches for a specified item in a range of cells
  9857. *
  9858. * @param lookup_value The value that you want to match in lookup_array
  9859. * @param lookup_array The range of cells being searched
  9860. * @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.
  9861. * @return integer The relative position of the found item
  9862. */
  9863. public static function MATCH($lookup_value, $lookup_array, $match_type=1) {
  9864. // flatten the lookup_array
  9865. $lookup_array = self::flattenArray($lookup_array);
  9866. // flatten lookup_value since it may be a cell reference to a value or the value itself
  9867. $lookup_value = self::flattenSingleValue($lookup_value);
  9868. // MATCH is not case sensitive
  9869. $lookup_value = strtolower($lookup_value);
  9870. /*
  9871. echo "--------------------<br>looking for $lookup_value in <br>";
  9872. print_r($lookup_array);
  9873. echo "<br>";
  9874. //return 1;
  9875. /**/
  9876. // **
  9877. // check inputs
  9878. // **
  9879. // lookup_value type has to be number, text, or logical values
  9880. if (!is_numeric($lookup_value) && !is_string($lookup_value) && !is_bool($lookup_value)){
  9881. // error: lookup_array should contain only number, text, or logical values
  9882. //echo "error: lookup_array should contain only number, text, or logical values<br>";
  9883. return self::$_errorCodes['na'];
  9884. }
  9885. // match_type is 0, 1 or -1
  9886. if ($match_type!==0 && $match_type!==-1 && $match_type!==1){
  9887. // error: wrong value for match_type
  9888. //echo "error: wrong value for match_type<br>";
  9889. return self::$_errorCodes['na'];
  9890. }
  9891. // lookup_array should not be empty
  9892. if (sizeof($lookup_array)<=0){
  9893. // error: empty range
  9894. //echo "error: empty range ".sizeof($lookup_array)."<br>";
  9895. return self::$_errorCodes['na'];
  9896. }
  9897. // lookup_array should contain only number, text, or logical values
  9898. for ($i=0;$i<sizeof($lookup_array);++$i){
  9899. // check the type of the value
  9900. if (!is_numeric($lookup_array[$i]) && !is_string($lookup_array[$i]) && !is_bool($lookup_array[$i])){
  9901. // error: lookup_array should contain only number, text, or logical values
  9902. //echo "error: lookup_array should contain only number, text, or logical values<br>";
  9903. return self::$_errorCodes['na'];
  9904. }
  9905. // convert tpo lowercase
  9906. if (is_string($lookup_array[$i]))
  9907. $lookup_array[$i] = strtolower($lookup_array[$i]);
  9908. }
  9909. // if match_type is 1 or -1, the list has to be ordered
  9910. if($match_type==1 || $match_type==-1){
  9911. // **
  9912. // iniitialization
  9913. // store the last value
  9914. $iLastValue=$lookup_array[0];
  9915. // **
  9916. // loop on the cells
  9917. for ($i=0;$i<sizeof($lookup_array);++$i){
  9918. // check ascending order
  9919. if(($match_type==1 && $lookup_array[$i]<$iLastValue)
  9920. // OR check descending order
  9921. || ($match_type==-1 && $lookup_array[$i]>$iLastValue)){
  9922. // error: list is not ordered correctly
  9923. //echo "error: list is not ordered correctly<br>";
  9924. return self::$_errorCodes['na'];
  9925. }
  9926. }
  9927. }
  9928. // **
  9929. // find the match
  9930. // **
  9931. // loop on the cells
  9932. for ($i=0; $i < sizeof($lookup_array); ++$i){
  9933. // if match_type is 0 <=> find the first value that is exactly equal to lookup_value
  9934. if ($match_type==0 && $lookup_array[$i]==$lookup_value){
  9935. // this is the exact match
  9936. return $i+1;
  9937. }
  9938. // if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
  9939. if ($match_type==-1 && $lookup_array[$i] < $lookup_value){
  9940. if ($i<1){
  9941. // 1st cell was allready smaller than the lookup_value
  9942. break;
  9943. }
  9944. else
  9945. // the previous cell was the match
  9946. return $i;
  9947. }
  9948. // if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
  9949. if ($match_type==1 && $lookup_array[$i] > $lookup_value){
  9950. if ($i<1){
  9951. // 1st cell was allready bigger than the lookup_value
  9952. break;
  9953. }
  9954. else
  9955. // the previous cell was the match
  9956. return $i;
  9957. }
  9958. }
  9959. // unsuccessful in finding a match, return #N/A error value
  9960. //echo "unsuccessful in finding a match<br>";
  9961. return self::$_errorCodes['na'];
  9962. } // function MATCH()
  9963. /**
  9964. * INDEX
  9965. *
  9966. * Uses an index to choose a value from a reference or array
  9967. * implemented: Return the value of a specified cell or array of cells Array form
  9968. * not implemented: Return a reference to specified cells Reference form
  9969. *
  9970. * @param range_array a range of cells or an array constant
  9971. * @param row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required.
  9972. * @param column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required.
  9973. */
  9974. public static function INDEX($arrayValues,$rowNum = 0,$columnNum = 0) {
  9975. if (($rowNum < 0) || ($columnNum < 0)) {
  9976. return self::$_errorCodes['value'];
  9977. }
  9978. if (!is_array($arrayValues)) {
  9979. return self::$_errorCodes['reference'];
  9980. }
  9981. $rowKeys = array_keys($arrayValues);
  9982. $columnKeys = @array_keys($arrayValues[$rowKeys[0]]);
  9983. if ($columnNum > count($columnKeys)) {
  9984. return self::$_errorCodes['value'];
  9985. } elseif ($columnNum == 0) {
  9986. if ($rowNum == 0) {
  9987. return $arrayValues;
  9988. }
  9989. $rowNum = $rowKeys[--$rowNum];
  9990. $returnArray = array();
  9991. foreach($arrayValues as $arrayColumn) {
  9992. if (is_array($arrayColumn)) {
  9993. if (isset($arrayColumn[$rowNum])) {
  9994. $returnArray[] = $arrayColumn[$rowNum];
  9995. } else {
  9996. return $arrayValues[$rowNum];
  9997. }
  9998. } else {
  9999. return $arrayValues[$rowNum];
  10000. }
  10001. }
  10002. return $returnArray;
  10003. }
  10004. $columnNum = $columnKeys[--$columnNum];
  10005. if ($rowNum > count($rowKeys)) {
  10006. return self::$_errorCodes['value'];
  10007. } elseif ($rowNum == 0) {
  10008. return $arrayValues[$columnNum];
  10009. }
  10010. $rowNum = $rowKeys[--$rowNum];
  10011. return $arrayValues[$rowNum][$columnNum];
  10012. } // function INDEX()
  10013. /**
  10014. * N
  10015. *
  10016. * Returns a value converted to a number
  10017. *
  10018. * @param value The value you want converted
  10019. * @return number N converts values listed in the following table
  10020. * If value is or refers to N returns
  10021. * A number That number
  10022. * A date The serial number of that date
  10023. * TRUE 1
  10024. * FALSE 0
  10025. * An error value The error value
  10026. * Anything else 0
  10027. */
  10028. public static function N($value) {
  10029. while (is_array($value)) {
  10030. $value = array_shift($value);
  10031. }
  10032. switch (gettype($value)) {
  10033. case 'double' :
  10034. case 'float' :
  10035. case 'integer' :
  10036. return $value;
  10037. break;
  10038. case 'boolean' :
  10039. return (integer) $value;
  10040. break;
  10041. case 'string' :
  10042. // Errors
  10043. if ((strlen($value) > 0) && ($value{0} == '#')) {
  10044. return $value;
  10045. }
  10046. break;
  10047. }
  10048. return 0;
  10049. } // function N()
  10050. /**
  10051. * TYPE
  10052. *
  10053. * Returns a number that identifies the type of a value
  10054. *
  10055. * @param value The value you want tested
  10056. * @return number N converts values listed in the following table
  10057. * If value is or refers to N returns
  10058. * A number 1
  10059. * Text 2
  10060. * Logical Value 4
  10061. * An error value 16
  10062. * Array or Matrix 64
  10063. */
  10064. public static function TYPE($value) {
  10065. $value = self::flattenArrayIndexed($value);
  10066. if (is_array($value) && (count($value) > 1)) {
  10067. $a = array_keys($value);
  10068. $a = array_pop($a);
  10069. // Range of cells is an error
  10070. if (self::isCellValue($a)) {
  10071. return 16;
  10072. // Test for Matrix
  10073. } elseif (self::isMatrixValue($a)) {
  10074. return 64;
  10075. }
  10076. } elseif(count($value) == 0) {
  10077. // Empty Cell
  10078. return 1;
  10079. }
  10080. $value = self::flattenSingleValue($value);
  10081. switch (gettype($value)) {
  10082. case 'double' :
  10083. case 'float' :
  10084. case 'integer' :
  10085. return 1;
  10086. break;
  10087. case 'boolean' :
  10088. return 4;
  10089. break;
  10090. case 'array' :
  10091. return 64;
  10092. break;
  10093. case 'string' :
  10094. // Errors
  10095. if ((strlen($value) > 0) && ($value{0} == '#')) {
  10096. return 16;
  10097. }
  10098. return 2;
  10099. break;
  10100. }
  10101. return 0;
  10102. } // function TYPE()
  10103. /**
  10104. * SYD
  10105. *
  10106. * Returns the sum-of-years' digits depreciation of an asset for a specified period.
  10107. *
  10108. * @param cost Initial cost of the asset
  10109. * @param salvage Value at the end of the depreciation
  10110. * @param life Number of periods over which the asset is depreciated
  10111. * @param period Period
  10112. * @return float
  10113. */
  10114. public static function SYD($cost, $salvage, $life, $period) {
  10115. $cost = self::flattenSingleValue($cost);
  10116. $salvage = self::flattenSingleValue($salvage);
  10117. $life = self::flattenSingleValue($life);
  10118. $period = self::flattenSingleValue($period);
  10119. // Calculate
  10120. if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
  10121. if (($life < 1) || ($period > $life)) {
  10122. return self::$_errorCodes['num'];
  10123. }
  10124. return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
  10125. }
  10126. return self::$_errorCodes['value'];
  10127. } // function SYD()
  10128. /**
  10129. * TRANSPOSE
  10130. *
  10131. * @param array $matrixData A matrix of values
  10132. * @return array
  10133. *
  10134. * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix.
  10135. */
  10136. public static function TRANSPOSE($matrixData) {
  10137. $returnMatrix = array();
  10138. if (!is_array($matrixData)) { $matrixData = array(array($matrixData)); }
  10139. $column = 0;
  10140. foreach($matrixData as $matrixRow) {
  10141. $row = 0;
  10142. foreach($matrixRow as $matrixCell) {
  10143. $returnMatrix[$row][$column] = $matrixCell;
  10144. ++$row;
  10145. }
  10146. ++$column;
  10147. }
  10148. return $returnMatrix;
  10149. } // function TRANSPOSE()
  10150. /**
  10151. * MMULT
  10152. *
  10153. * @param array $matrixData1 A matrix of values
  10154. * @param array $matrixData2 A matrix of values
  10155. * @return array
  10156. */
  10157. public static function MMULT($matrixData1,$matrixData2) {
  10158. $matrixAData = $matrixBData = array();
  10159. if (!is_array($matrixData1)) { $matrixData1 = array(array($matrixData1)); }
  10160. if (!is_array($matrixData2)) { $matrixData2 = array(array($matrixData2)); }
  10161. $rowA = 0;
  10162. foreach($matrixData1 as $matrixRow) {
  10163. $columnA = 0;
  10164. foreach($matrixRow as $matrixCell) {
  10165. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  10166. return self::$_errorCodes['value'];
  10167. }
  10168. $matrixAData[$rowA][$columnA] = $matrixCell;
  10169. ++$columnA;
  10170. }
  10171. ++$rowA;
  10172. }
  10173. try {
  10174. $matrixA = new Matrix($matrixAData);
  10175. $rowB = 0;
  10176. foreach($matrixData2 as $matrixRow) {
  10177. $columnB = 0;
  10178. foreach($matrixRow as $matrixCell) {
  10179. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  10180. return self::$_errorCodes['value'];
  10181. }
  10182. $matrixBData[$rowB][$columnB] = $matrixCell;
  10183. ++$columnB;
  10184. }
  10185. ++$rowB;
  10186. }
  10187. $matrixB = new Matrix($matrixBData);
  10188. if (($rowA != $columnB) || ($rowB != $columnA)) {
  10189. return self::$_errorCodes['value'];
  10190. }
  10191. return $matrixA->times($matrixB)->getArray();
  10192. } catch (Exception $ex) {
  10193. return self::$_errorCodes['value'];
  10194. }
  10195. } // function MMULT()
  10196. /**
  10197. * MINVERSE
  10198. *
  10199. * @param array $matrixValues A matrix of values
  10200. * @return array
  10201. */
  10202. public static function MINVERSE($matrixValues) {
  10203. $matrixData = array();
  10204. if (!is_array($matrixValues)) { $matrixValues = array(array($matrixValues)); }
  10205. $row = $maxColumn = 0;
  10206. foreach($matrixValues as $matrixRow) {
  10207. $column = 0;
  10208. foreach($matrixRow as $matrixCell) {
  10209. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  10210. return self::$_errorCodes['value'];
  10211. }
  10212. $matrixData[$column][$row] = $matrixCell;
  10213. ++$column;
  10214. }
  10215. if ($column > $maxColumn) { $maxColumn = $column; }
  10216. ++$row;
  10217. }
  10218. if ($row != $maxColumn) { return self::$_errorCodes['value']; }
  10219. try {
  10220. $matrix = new Matrix($matrixData);
  10221. return $matrix->inverse()->getArray();
  10222. } catch (Exception $ex) {
  10223. return self::$_errorCodes['value'];
  10224. }
  10225. } // function MINVERSE()
  10226. /**
  10227. * MDETERM
  10228. *
  10229. * @param array $matrixValues A matrix of values
  10230. * @return float
  10231. */
  10232. public static function MDETERM($matrixValues) {
  10233. $matrixData = array();
  10234. if (!is_array($matrixValues)) { $matrixValues = array(array($matrixValues)); }
  10235. $row = $maxColumn = 0;
  10236. foreach($matrixValues as $matrixRow) {
  10237. $column = 0;
  10238. foreach($matrixRow as $matrixCell) {
  10239. if ((is_string($matrixCell)) || ($matrixCell === null)) {
  10240. return self::$_errorCodes['value'];
  10241. }
  10242. $matrixData[$column][$row] = $matrixCell;
  10243. ++$column;
  10244. }
  10245. if ($column > $maxColumn) { $maxColumn = $column; }
  10246. ++$row;
  10247. }
  10248. if ($row != $maxColumn) { return self::$_errorCodes['value']; }
  10249. try {
  10250. $matrix = new Matrix($matrixData);
  10251. return $matrix->det();
  10252. } catch (Exception $ex) {
  10253. return self::$_errorCodes['value'];
  10254. }
  10255. } // function MDETERM()
  10256. /**
  10257. * SUMPRODUCT
  10258. *
  10259. * @param mixed $value Value to check
  10260. * @return float
  10261. */
  10262. public static function SUMPRODUCT() {
  10263. $arrayList = func_get_args();
  10264. $wrkArray = self::flattenArray(array_shift($arrayList));
  10265. $wrkCellCount = count($wrkArray);
  10266. foreach($arrayList as $matrixData) {
  10267. $array2 = self::flattenArray($matrixData);
  10268. $count = count($array2);
  10269. if ($wrkCellCount != $count) {
  10270. return self::$_errorCodes['value'];
  10271. }
  10272. foreach ($array2 as $i => $val) {
  10273. if (((is_numeric($wrkArray[$i])) && (!is_string($wrkArray[$i]))) &&
  10274. ((is_numeric($val)) && (!is_string($val)))) {
  10275. $wrkArray[$i] *= $val;
  10276. }
  10277. }
  10278. }
  10279. return array_sum($wrkArray);
  10280. } // function SUMPRODUCT()
  10281. /**
  10282. * SUMX2MY2
  10283. *
  10284. * @param mixed $value Value to check
  10285. * @return float
  10286. */
  10287. public static function SUMX2MY2($matrixData1,$matrixData2) {
  10288. $array1 = self::flattenArray($matrixData1);
  10289. $array2 = self::flattenArray($matrixData2);
  10290. $count1 = count($array1);
  10291. $count2 = count($array2);
  10292. if ($count1 < $count2) {
  10293. $count = $count1;
  10294. } else {
  10295. $count = $count2;
  10296. }
  10297. $result = 0;
  10298. for ($i = 0; $i < $count; ++$i) {
  10299. if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
  10300. ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
  10301. $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]);
  10302. }
  10303. }
  10304. return $result;
  10305. } // function SUMX2MY2()
  10306. /**
  10307. * SUMX2PY2
  10308. *
  10309. * @param mixed $value Value to check
  10310. * @return float
  10311. */
  10312. public static function SUMX2PY2($matrixData1,$matrixData2) {
  10313. $array1 = self::flattenArray($matrixData1);
  10314. $array2 = self::flattenArray($matrixData2);
  10315. $count1 = count($array1);
  10316. $count2 = count($array2);
  10317. if ($count1 < $count2) {
  10318. $count = $count1;
  10319. } else {
  10320. $count = $count2;
  10321. }
  10322. $result = 0;
  10323. for ($i = 0; $i < $count; ++$i) {
  10324. if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
  10325. ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
  10326. $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]);
  10327. }
  10328. }
  10329. return $result;
  10330. } // function SUMX2PY2()
  10331. /**
  10332. * SUMXMY2
  10333. *
  10334. * @param mixed $value Value to check
  10335. * @return float
  10336. */
  10337. public static function SUMXMY2($matrixData1,$matrixData2) {
  10338. $array1 = self::flattenArray($matrixData1);
  10339. $array2 = self::flattenArray($matrixData2);
  10340. $count1 = count($array1);
  10341. $count2 = count($array2);
  10342. if ($count1 < $count2) {
  10343. $count = $count1;
  10344. } else {
  10345. $count = $count2;
  10346. }
  10347. $result = 0;
  10348. for ($i = 0; $i < $count; ++$i) {
  10349. if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) &&
  10350. ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) {
  10351. $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]);
  10352. }
  10353. }
  10354. return $result;
  10355. } // function SUMXMY2()
  10356. private static function _vlookupSort($a,$b) {
  10357. $f = array_keys($a);
  10358. $firstColumn = array_shift($f);
  10359. if (strtolower($a[$firstColumn]) == strtolower($b[$firstColumn])) {
  10360. return 0;
  10361. }
  10362. return (strtolower($a[$firstColumn]) < strtolower($b[$firstColumn])) ? -1 : 1;
  10363. } // function _vlookupSort()
  10364. /**
  10365. * VLOOKUP
  10366. * 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.
  10367. * @param lookup_value The value that you want to match in lookup_array
  10368. * @param lookup_array The range of cells being searched
  10369. * @param index_number The column number in table_array from which the matching value must be returned. The first column is 1.
  10370. * @param not_exact_match Determines if you are looking for an exact match based on lookup_value.
  10371. * @return mixed The value of the found cell
  10372. */
  10373. public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match=true) {
  10374. $lookup_value = self::flattenSingleValue($lookup_value);
  10375. $index_number = self::flattenSingleValue($index_number);
  10376. $not_exact_match = self::flattenSingleValue($not_exact_match);
  10377. // index_number must be greater than or equal to 1
  10378. if ($index_number < 1) {
  10379. return self::$_errorCodes['value'];
  10380. }
  10381. // index_number must be less than or equal to the number of columns in lookup_array
  10382. if ((!is_array($lookup_array)) || (count($lookup_array) < 1)) {
  10383. return self::$_errorCodes['reference'];
  10384. } else {
  10385. $f = array_keys($lookup_array);
  10386. $firstRow = array_pop($f);
  10387. if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
  10388. return self::$_errorCodes['reference'];
  10389. } else {
  10390. $columnKeys = array_keys($lookup_array[$firstRow]);
  10391. $returnColumn = $columnKeys[--$index_number];
  10392. $firstColumn = array_shift($columnKeys);
  10393. }
  10394. }
  10395. if (!$not_exact_match) {
  10396. uasort($lookup_array,array('self','_vlookupSort'));
  10397. }
  10398. $rowNumber = $rowValue = False;
  10399. foreach($lookup_array as $rowKey => $rowData) {
  10400. if (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)) {
  10401. break;
  10402. }
  10403. $rowNumber = $rowKey;
  10404. $rowValue = $rowData[$firstColumn];
  10405. }
  10406. if ($rowNumber !== false) {
  10407. if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
  10408. // if an exact match is required, we have what we need to return an appropriate response
  10409. return self::$_errorCodes['na'];
  10410. } else {
  10411. // otherwise return the appropriate value
  10412. return $lookup_array[$rowNumber][$returnColumn];
  10413. }
  10414. }
  10415. return self::$_errorCodes['na'];
  10416. } // function VLOOKUP()
  10417. /**
  10418. * LOOKUP
  10419. * The LOOKUP function searches for value either from a one-row or one-column range or from an array.
  10420. * @param lookup_value The value that you want to match in lookup_array
  10421. * @param lookup_vector The range of cells being searched
  10422. * @param result_vector The column from which the matching value must be returned
  10423. * @return mixed The value of the found cell
  10424. */
  10425. public static function LOOKUP($lookup_value, $lookup_vector, $result_vector=null) {
  10426. $lookup_value = self::flattenSingleValue($lookup_value);
  10427. if (!is_array($lookup_vector)) {
  10428. return self::$_errorCodes['na'];
  10429. }
  10430. $lookupRows = count($lookup_vector);
  10431. $l = array_keys($lookup_vector);
  10432. $l = array_shift($l);
  10433. $lookupColumns = count($lookup_vector[$l]);
  10434. if ((($lookupRows == 1) && ($lookupColumns > 1)) || (($lookupRows == 2) && ($lookupColumns != 2))) {
  10435. $lookup_vector = self::TRANSPOSE($lookup_vector);
  10436. $lookupRows = count($lookup_vector);
  10437. $l = array_keys($lookup_vector);
  10438. $lookupColumns = count($lookup_vector[array_shift($l)]);
  10439. }
  10440. if (is_null($result_vector)) {
  10441. $result_vector = $lookup_vector;
  10442. }
  10443. $resultRows = count($result_vector);
  10444. $l = array_keys($result_vector);
  10445. $l = array_shift($l);
  10446. $resultColumns = count($result_vector[$l]);
  10447. if ((($resultRows == 1) && ($resultColumns > 1)) || (($resultRows == 2) && ($resultColumns != 2))) {
  10448. $result_vector = self::TRANSPOSE($result_vector);
  10449. $resultRows = count($result_vector);
  10450. $r = array_keys($result_vector);
  10451. $resultColumns = count($result_vector[array_shift($r)]);
  10452. }
  10453. if ($lookupRows == 2) {
  10454. $result_vector = array_pop($lookup_vector);
  10455. $lookup_vector = array_shift($lookup_vector);
  10456. }
  10457. if ($lookupColumns != 2) {
  10458. foreach($lookup_vector as &$value) {
  10459. if (is_array($value)) {
  10460. $k = array_keys($value);
  10461. $key1 = $key2 = array_shift($k);
  10462. $key2++;
  10463. $dataValue1 = $value[$key1];
  10464. } else {
  10465. $key1 = 0;
  10466. $key2 = 1;
  10467. $dataValue1 = $value;
  10468. }
  10469. $dataValue2 = array_shift($result_vector);
  10470. if (is_array($dataValue2)) {
  10471. $dataValue2 = array_shift($dataValue2);
  10472. }
  10473. $value = array($key1 => $dataValue1, $key2 => $dataValue2);
  10474. }
  10475. unset($value);
  10476. }
  10477. return self::VLOOKUP($lookup_value,$lookup_vector,2);
  10478. } // function LOOKUP()
  10479. /**
  10480. * Convert a multi-dimensional array to a simple 1-dimensional array
  10481. *
  10482. * @param array $array Array to be flattened
  10483. * @return array Flattened array
  10484. */
  10485. public static function flattenArray($array) {
  10486. if (!is_array($array)) {
  10487. return (array) $array;
  10488. }
  10489. $arrayValues = array();
  10490. foreach ($array as $value) {
  10491. if (is_array($value)) {
  10492. foreach ($value as $val) {
  10493. if (is_array($val)) {
  10494. foreach ($val as $v) {
  10495. $arrayValues[] = $v;
  10496. }
  10497. } else {
  10498. $arrayValues[] = $val;
  10499. }
  10500. }
  10501. } else {
  10502. $arrayValues[] = $value;
  10503. }
  10504. }
  10505. return $arrayValues;
  10506. } // function flattenArray()
  10507. /**
  10508. * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing
  10509. *
  10510. * @param array $array Array to be flattened
  10511. * @return array Flattened array
  10512. */
  10513. public static function flattenArrayIndexed($array) {
  10514. if (!is_array($array)) {
  10515. return (array) $array;
  10516. }
  10517. $arrayValues = array();
  10518. foreach ($array as $k1 => $value) {
  10519. if (is_array($value)) {
  10520. foreach ($value as $k2 => $val) {
  10521. if (is_array($val)) {
  10522. foreach ($val as $k3 => $v) {
  10523. $arrayValues[$k1.'.'.$k2.'.'.$k3] = $v;
  10524. }
  10525. } else {
  10526. $arrayValues[$k1.'.'.$k2] = $val;
  10527. }
  10528. }
  10529. } else {
  10530. $arrayValues[$k1] = $value;
  10531. }
  10532. }
  10533. return $arrayValues;
  10534. } // function flattenArrayIndexed()
  10535. /**
  10536. * Convert an array to a single scalar value by extracting the first element
  10537. *
  10538. * @param mixed $value Array or scalar value
  10539. * @return mixed
  10540. */
  10541. public static function flattenSingleValue($value = '') {
  10542. while (is_array($value)) {
  10543. $value = array_pop($value);
  10544. }
  10545. return $value;
  10546. } // function flattenSingleValue()
  10547. } // class PHPExcel_Calculation_Functions
  10548. //
  10549. // There are a few mathematical functions that aren't available on all versions of PHP for all platforms
  10550. // These functions aren't available in Windows implementations of PHP prior to version 5.3.0
  10551. // So we test if they do exist for this version of PHP/operating platform; and if not we create them
  10552. //
  10553. if (!function_exists('acosh')) {
  10554. function acosh($x) {
  10555. return 2 * log(sqrt(($x + 1) / 2) + sqrt(($x - 1) / 2));
  10556. } // function acosh()
  10557. }
  10558. if (!function_exists('asinh')) {
  10559. function asinh($x) {
  10560. return log($x + sqrt(1 + $x * $x));
  10561. } // function asinh()
  10562. }
  10563. if (!function_exists('atanh')) {
  10564. function atanh($x) {
  10565. return (log(1 + $x) - log(1 - $x)) / 2;
  10566. } // function atanh()
  10567. }
  10568. if (!function_exists('money_format')) {
  10569. function money_format($format, $number) {
  10570. $regex = array( '/%((?:[\^!\-]|\+|\(|\=.)*)([0-9]+)?(?:#([0-9]+))?',
  10571. '(?:\.([0-9]+))?([in%])/'
  10572. );
  10573. $regex = implode('', $regex);
  10574. if (setlocale(LC_MONETARY, null) == '') {
  10575. setlocale(LC_MONETARY, '');
  10576. }
  10577. $locale = localeconv();
  10578. $number = floatval($number);
  10579. if (!preg_match($regex, $format, $fmatch)) {
  10580. trigger_error("No format specified or invalid format", E_USER_WARNING);
  10581. return $number;
  10582. }
  10583. $flags = array( 'fillchar' => preg_match('/\=(.)/', $fmatch[1], $match) ? $match[1] : ' ',
  10584. 'nogroup' => preg_match('/\^/', $fmatch[1]) > 0,
  10585. 'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ? $match[0] : '+',
  10586. 'nosimbol' => preg_match('/\!/', $fmatch[1]) > 0,
  10587. 'isleft' => preg_match('/\-/', $fmatch[1]) > 0
  10588. );
  10589. $width = trim($fmatch[2]) ? (int)$fmatch[2] : 0;
  10590. $left = trim($fmatch[3]) ? (int)$fmatch[3] : 0;
  10591. $right = trim($fmatch[4]) ? (int)$fmatch[4] : $locale['int_frac_digits'];
  10592. $conversion = $fmatch[5];
  10593. $positive = true;
  10594. if ($number < 0) {
  10595. $positive = false;
  10596. $number *= -1;
  10597. }
  10598. $letter = $positive ? 'p' : 'n';
  10599. $prefix = $suffix = $cprefix = $csuffix = $signal = '';
  10600. if (!$positive) {
  10601. $signal = $locale['negative_sign'];
  10602. switch (true) {
  10603. case $locale['n_sign_posn'] == 0 || $flags['usesignal'] == '(':
  10604. $prefix = '(';
  10605. $suffix = ')';
  10606. break;
  10607. case $locale['n_sign_posn'] == 1:
  10608. $prefix = $signal;
  10609. break;
  10610. case $locale['n_sign_posn'] == 2:
  10611. $suffix = $signal;
  10612. break;
  10613. case $locale['n_sign_posn'] == 3:
  10614. $cprefix = $signal;
  10615. break;
  10616. case $locale['n_sign_posn'] == 4:
  10617. $csuffix = $signal;
  10618. break;
  10619. }
  10620. }
  10621. if (!$flags['nosimbol']) {
  10622. $currency = $cprefix;
  10623. $currency .= ($conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']);
  10624. $currency .= $csuffix;
  10625. $currency = iconv('ISO-8859-1','UTF-8',$currency);
  10626. } else {
  10627. $currency = '';
  10628. }
  10629. $space = $locale["{$letter}_sep_by_space"] ? ' ' : '';
  10630. $number = number_format($number, $right, $locale['mon_decimal_point'], $flags['nogroup'] ? '' : $locale['mon_thousands_sep'] );
  10631. $number = explode($locale['mon_decimal_point'], $number);
  10632. $n = strlen($prefix) + strlen($currency);
  10633. if ($left > 0 && $left > $n) {
  10634. if ($flags['isleft']) {
  10635. $number[0] .= str_repeat($flags['fillchar'], $left - $n);
  10636. } else {
  10637. $number[0] = str_repeat($flags['fillchar'], $left - $n) . $number[0];
  10638. }
  10639. }
  10640. $number = implode($locale['mon_decimal_point'], $number);
  10641. if ($locale["{$letter}_cs_precedes"]) {
  10642. $number = $prefix . $currency . $space . $number . $suffix;
  10643. } else {
  10644. $number = $prefix . $number . $space . $currency . $suffix;
  10645. }
  10646. if ($width > 0) {
  10647. $number = str_pad($number, $width, $flags['fillchar'], $flags['isleft'] ? STR_PAD_RIGHT : STR_PAD_LEFT);
  10648. }
  10649. $format = str_replace($fmatch[0], $number, $format);
  10650. return $format;
  10651. } // function money_format()
  10652. }
  10653. //
  10654. // Strangely, PHP doesn't have a mb_str_replace multibyte function
  10655. // As we'll only ever use this function with UTF-8 characters, we can simply "hard-code" the character set
  10656. //
  10657. if ((!function_exists('mb_str_replace')) &&
  10658. (function_exists('mb_substr')) && (function_exists('mb_strlen')) && (function_exists('mb_strpos'))) {
  10659. function mb_str_replace($search, $replace, $subject) {
  10660. if(is_array($subject)) {
  10661. $ret = array();
  10662. foreach($subject as $key => $val) {
  10663. $ret[$key] = mb_str_replace($search, $replace, $val);
  10664. }
  10665. return $ret;
  10666. }
  10667. foreach((array) $search as $key => $s) {
  10668. if($s == '') {
  10669. continue;
  10670. }
  10671. $r = !is_array($replace) ? $replace : (array_key_exists($key, $replace) ? $replace[$key] : '');
  10672. $pos = mb_strpos($subject, $s, 0, 'UTF-8');
  10673. while($pos !== false) {
  10674. $subject = mb_substr($subject, 0, $pos, 'UTF-8') . $r . mb_substr($subject, $pos + mb_strlen($s, 'UTF-8'), 65535, 'UTF-8');
  10675. $pos = mb_strpos($subject, $s, $pos + mb_strlen($r, 'UTF-8'), 'UTF-8');
  10676. }
  10677. }
  10678. return $subject;
  10679. }
  10680. }