PageRenderTime 31ms CodeModel.GetById 8ms app.highlight 316ms RepoModel.GetById 1ms app.codeStats 3ms

/add-ons/PHPExcel/PHPExcel/Calculation/Functions.php

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