Source for file Functions.php
Documentation is available at Functions.php
* Copyright (c) 2006 - 2009 PHPExcel
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* @package PHPExcel_Calculation
* @copyright Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
* @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
* @version 1.6.5, 2009-01-05
define('LOG_GAMMA_X_MAX_VALUE', 2.55e305);
define('SQRT2PI', 2.5066282746310005024157652848110452530069867406099);
define('MAX_ITERATIONS', 150);
define('PRECISION', 8.88E-016);
define('EULER', 2.71828182845904523536);
$savedPrecision = ini_get('precision');
if ($savedPrecision < 15) {
require_once 'PHPExcel/Cell.php';
/** PHPExcel_Cell_DataType */
require_once 'PHPExcel/Cell/DataType.php';
/** PHPExcel_Shared_Date */
require_once 'PHPExcel/Shared/Date.php';
/** PHPExcel_Shared_Date */
require_once 'PHPExcel/Shared/JAMA/Matrix.php';
* PHPExcel_Calculation_Functions
* @package PHPExcel_Calculation
* @copyright Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
const COMPATIBILITY_EXCEL = 'Excel';
const COMPATIBILITY_GNUMERIC = 'Gnumeric';
const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc';
const RETURNDATE_PHP_NUMERIC = 'P';
const RETURNDATE_PHP_OBJECT = 'O';
const RETURNDATE_EXCEL = 'E';
* Compatibility mode to use for error checking and responses
private static $compatibilityMode = self::COMPATIBILITY_EXCEL;
* Data Type to use when returning date values
private static $ReturnDateType = self::RETURNDATE_PHP_NUMERIC;
private static $_errorCodes = array( 'null' => '#NULL!',
'divisionbyzero' => '#DIV/0!',
'gettingdata' => '#GETTING_DATA'
* Set the Compatibility Mode
* @param string $compatibilityMode Compatibility Mode
* @return boolean (Success or Failure)
if (($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
self::$compatibilityMode = $compatibilityMode;
* Return the current Compatibility Mode
* @return string $compatibilityMode Compatibility Mode
return self::$compatibilityMode;
* Set the Return Date Format (Excel, PHP Serialized or PHP Object)
* @param integer $returnDateType Return Date Format
* @return boolean Success or failure
if (($returnDateType == self::RETURNDATE_PHP_NUMERIC) ||
($returnDateType == self::RETURNDATE_PHP_OBJECT) ||
($returnDateType == self::RETURNDATE_EXCEL)) {
self::$ReturnDateType = $returnDateType;
} // function setReturnDateType()
* Return the Return Date Format (Excel, PHP Serialized or PHP Object)
* @return integer $returnDateType Return Date Format
return self::$ReturnDateType;
} // function getReturnDateType()
public static function DUMMY() {
return self::$_errorCodes['name'];
public static function NA() {
return self::$_errorCodes['na'];
* Returns boolean TRUE if all its arguments are TRUE; returns FALSE if one or more argument is FALSE.
* Booleans arguments are treated as True or False as appropriate
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
* If any argument value is a string, or a Null, it is ignored
* String values passed directly to the function rather than through a cell reference
* will return a #VALUE! error, _not_ ignoring the string.
* This behaviour is not replicated
* @param array of mixed Data Series
// Loop through the arguments
foreach ($aArgs as $arg) {
// Is it a boolean value?
$returnValue = $returnValue && $arg;
$returnValue = $returnValue && ($arg != 0);
return self::$_errorCodes['value'];
* Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
* Booleans arguments are treated as True or False as appropriate
* Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
* If any argument value is a string, or a Null, it is ignored
* @param array of mixed Data Series
// Loop through the arguments
foreach ($aArgs as $arg) {
// Is it a boolean value?
$returnValue = $returnValue || $arg;
$returnValue = $returnValue || ($arg != 0);
return self::$_errorCodes['value'];
* This function calculates the arc tangent of the two variables x and y. It is similar to
* calculating the arc tangent of y / x, except that the signs of both arguments are used
* to determine the quadrant of the result.
* Note that Excel reverses the arguments, so we need to reverse them here before calling the
* standard PHP atan() function
* @return float Square Root of Number * Pi
$x = self::flattenSingleValue($x);
$y = self::flattenSingleValue($y);
* SUM computes the sum of all the values and cells referenced in the argument list.
* @param array of mixed Data Series
public static function SUM() {
// Loop through the arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
* Returns the sum of the squares of the arguments
* @param array of mixed Data Series
public static function SUMSQ() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
$returnValue += pow($arg,2);
* PRODUCT returns the product of all the values and cells referenced in the argument list.
* @param array of mixed Data Series
foreach ($aArgs as $arg) {
// Is it a numeric value?
* QUOTIENT function returns the integer portion of a division.numerator is the divided number
* and denominator is the divisor.
* @param array of mixed Data Series
foreach ($aArgs as $arg) {
// Is it a numeric value?
if (($returnValue == 0) || ($arg == 0)) {
if (($returnValue == 0) || ($arg == 0)) {
* MIN returns the value of the element of the values passed that has the smallest value,
* with negative numbers considered smaller than positive numbers.
* @param array of mixed Data Series
public static function MIN() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_null($returnValue)) || ($arg < $returnValue)) {
* Returns the smallest value in a list of arguments, including numbers, text, and logical values
* @param array of mixed Data Series
public static function MINA() {
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_null($returnValue)) || ($arg < $returnValue)) {
* Returns the nth smallest value in a data set. You can use this function to
* select a value based on its relative standing.
* @param array of mixed Data Series
* @param float Entry in the series to return
public static function SMALL() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
$count = self::COUNT($mArgs);
if (($n < 0) || ($n >= $count) || ($count == 0)) {
return self::$_errorCodes['num'];
return self::$_errorCodes['value'];
* MAX returns the value of the element of the values passed that has the highest value,
* with negative numbers considered smaller than positive numbers.
* @param array of mixed Data Series
public static function MAX() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_null($returnValue)) || ($arg > $returnValue)) {
* Returns the greatest value in a list of arguments, including numbers, text, and logical values
* @param array of mixed Data Series
public static function MAXA() {
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_null($returnValue)) || ($arg > $returnValue)) {
* Returns the nth largest value in a data set. You can use this function to
* select a value based on its relative standing.
* @param array of mixed Data Series
* @param float Entry in the series to return
public static function LARGE() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
$count = self::COUNT($mArgs);
if (($n < 0) || ($n >= $count) || ($count == 0)) {
return self::$_errorCodes['num'];
return self::$_errorCodes['value'];
* Returns the nth percentile of values in a range..
* @param array of mixed Data Series
* @param float $entry Entry in the series to return
if (($entry < 0) || ($entry > 1)) {
return self::$_errorCodes['num'];
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$mValueCount = count($mArgs);
$count = self::COUNT($mArgs);
$index = $entry * ($count- 1);
$iProportion = $index - $iBase;
return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion) ;
return self::$_errorCodes['value'];
* Returns the quartile of a data set.
* @param array of mixed Data Series
* @param float $entry Entry in the series to return
if (($entry < 0) || ($entry > 1)) {
return self::$_errorCodes['num'];
return self::PERCENTILE($aArgs,$entry);
return self::$_errorCodes['value'];
* Counts the number of cells that contain numbers within the list of arguments
* @param array of mixed Data Series
public static function COUNT() {
foreach ($aArgs as $arg) {
if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
// Is it a numeric value?
* Counts the number of empty cells within the list of arguments
* @param array of mixed Data Series
foreach ($aArgs as $arg) {
* Counts the number of cells that are not empty within the list of arguments
* @param array of mixed Data Series
public static function COUNTA() {
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric, boolean or string value?
* Returns the average (arithmetic mean) of the arguments
* @param array of mixed Data Series
// Loop through arguments
foreach ($aArgs as $arg) {
if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
// Is it a numeric value?
return $returnValue / $aCount;
return self::$_errorCodes['divisionbyzero'];
* Returns the average of its arguments, including numbers, text, and logical values
* @param array of mixed Data Series
// Loop through arguments
foreach ($aArgs as $arg) {
return $returnValue / $aCount;
return self::$_errorCodes['divisionbyzero'];
* Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
* @param array of mixed Data Series
public static function MEDIAN() {
$returnValue = self::$_errorCodes['num'];
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$mValueCount = count($mArgs);
sort($mArgs,SORT_NUMERIC);
$mValueCount = $mValueCount / 2;
if ($mValueCount == floor($mValueCount)) {
$returnValue = ($mArgs[$mValueCount-- ] + $mArgs[$mValueCount]) / 2;
$mValueCount == floor($mValueCount);
$returnValue = $mArgs[$mValueCount];
// Special variant of array_count_values that isn't limited to strings and integers,
// but can work with floating point numbers as values
private static function modeCalc($data) {
$frequencyArray = array();
foreach($data as $datum) {
foreach($frequencyArray as $key => $value) {
if ((string) $value['value'] == (string) $datum) {
++ $frequencyArray[$key]['frequency'];
$frequencyArray[] = array('value' => $datum,
foreach($frequencyArray as $key => $value) {
$frequencyList[$key] = $value['frequency'];
$valueList[$key] = $value['value'];
array_multisort($frequencyList, SORT_DESC, $valueList, SORT_ASC, SORT_NUMERIC, $frequencyArray);
if ($frequencyArray[0]['frequency'] == 1) {
return $frequencyArray[0]['value'];
* Returns the most frequently occurring, or repetitive, value in an array or range of data
* @param array of mixed Data Series
public static function MODE() {
$returnValue = self::NA();
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
return self::modeCalc($mArgs);
* Returns the sum of squares of deviations of data points from their sample mean.
* @param array of mixed Data Series
public static function DEVSQ() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
$returnValue = pow(($arg - $aMean),2);
$returnValue += pow(($arg - $aMean),2);
return self::$_errorCodes['num'];
* Returns the average of the absolute deviations of data points from their mean.
* AVEDEV is a measure of the variability in a data set.
* @param array of mixed Data Series
public static function AVEDEV() {
$aMean = self::AVERAGE($aArgs);
if ($aMean != self::$_errorCodes['divisionbyzero']) {
foreach ($aArgs as $arg) {
if ((is_bool($arg)) && (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE)) {
// Is it a numeric value?
$returnValue = abs($arg - $aMean);
$returnValue += abs($arg - $aMean);
return $returnValue / $aCount ;
return self::$_errorCodes['num'];
* Returns the geometric mean of an array or range of positive data. For example, you
* can use GEOMEAN to calculate average growth rate given compound interest with
* @param array of mixed Data Series
$aCount = self::COUNT($aArgs) ;
if (self::MIN($aArgs) > 0) {
return pow($aMean, (1 / $aCount));
return self::$_errorCodes['num'];
* Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the
* arithmetic mean of reciprocals.
* @param array of mixed Data Series
$returnValue = self::NA();
// Loop through arguments
if (self::MIN($aArgs) < 0) {
return self::$_errorCodes['num'];
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
return self::$_errorCodes['num'];
if (is_null($returnValue)) {
$returnValue = (1 / $arg);
$returnValue += (1 / $arg);
return 1 / ($returnValue / $aCount);
* Returns the mean of the interior of a data set. TRIMMEAN calculates the mean
* taken by excluding a percentage of data points from the top and bottom tails
* @param array of mixed Data Series
* @param float Percentage to discard
if (($percent < 0) || ($percent > 1)) {
return self::$_errorCodes['num'];
foreach ($aArgs as $arg) {
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
$discard = floor(self::COUNT($mArgs) * $percent / 2);
for ($i= 0; $i < $discard; ++ $i) {
return self::AVERAGE($mArgs);
return self::$_errorCodes['value'];
* Estimates standard deviation based on a sample. The standard deviation is a measure of how
* widely values are dispersed from the average value (the mean).
* @param array of mixed Data Series
public static function STDEV() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
$returnValue = pow(($arg - $aMean),2);
$returnValue += pow(($arg - $aMean),2);
if (($aCount > 0) && ($returnValue > 0)) {
return sqrt($returnValue / $aCount);
return self::$_errorCodes['divisionbyzero'];
* Estimates standard deviation based on a sample, including numbers, text, and logical values
* @param array of mixed Data Series
public static function STDEVA() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
$returnValue = pow(($arg - $aMean),2);
$returnValue += pow(($arg - $aMean),2);
if (($aCount > 0) && ($returnValue > 0)) {
return sqrt($returnValue / $aCount);
return self::$_errorCodes['divisionbyzero'];
* Calculates standard deviation based on the entire population
* @param array of mixed Data Series
public static function STDEVP() {
foreach ($aArgs as $arg) {
// Is it a numeric value?
$returnValue = pow(($arg - $aMean),2);
$returnValue += pow(($arg - $aMean),2);
if (($aCount > 0) && ($returnValue > 0)) {
return sqrt($returnValue / $aCount);
return self::$_errorCodes['divisionbyzero'];
* Calculates standard deviation based on the entire population, including numbers, text, and logical values
* @param array of mixed Data Series
foreach ($aArgs as $arg) {
// Is it a numeric value?
$returnValue = pow(($arg - $aMean),2);
$returnValue += pow(($arg - $aMean),2);
if (($aCount > 0) && ($returnValue > 0)) {
return sqrt($returnValue / $aCount);
return self::$_errorCodes['divisionbyzero'];
* Estimates variance based on a sample.
* @param array of mixed Data Series
$returnValue = self::$_errorCodes['divisionbyzero'];
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$summerA += ($arg * $arg);
$summerA = $summerA * $aCount;
$summerB = ($summerB * $summerB);
$returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
* Estimates variance based on a sample, including numbers, text, and logical values
* @param array of mixed Data Series
public static function VARA() {
$returnValue = self::$_errorCodes['divisionbyzero'];
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$summerA += ($arg * $arg);
$summerA = $summerA * $aCount;
$summerB = ($summerB * $summerB);
$returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1));
* Calculates variance based on the entire population
* @param array of mixed Data Series
public static function VARP() {
$returnValue = self::$_errorCodes['divisionbyzero'];
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$summerA += ($arg * $arg);
$summerA = $summerA * $aCount;
$summerB = ($summerB * $summerB);
$returnValue = ($summerA - $summerB) / ($aCount * $aCount);
* Calculates variance based on the entire population, including numbers, text, and logical values
* @param array of mixed Data Series
public static function VARPA() {
$returnValue = self::$_errorCodes['divisionbyzero'];
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$summerA += ($arg * $arg);
$summerA = $summerA * $aCount;
$summerB = ($summerB * $summerB);
$returnValue = ($summerA - $summerB) / ($aCount * $aCount);
* Returns a subtotal in a list or database.
* @param int the number 1 to 11 that specifies which function to
* use in calculating subtotals within a list.
* @param array of mixed Data Series
return self::AVERAGE($aArgs);
return self::COUNT($aArgs);
return self::COUNTA($aArgs);
return self::MAX($aArgs);
return self::MIN($aArgs);
return self::PRODUCT($aArgs);
return self::STDEV($aArgs);
return self::STDEVP($aArgs);
return self::SUM($aArgs);
return self::VARFunc($aArgs);
return self::VARP($aArgs);
return self::$_errorCodes['value'];
* Returns the square root of (number * pi).
* @param float $number Number
* @return float Square Root of Number * Pi
public static function SQRTPI($number) {
$number = self::flattenSingleValue($number);
return self::$_errorCodes['num'];
return sqrt($number * pi()) ;
return self::$_errorCodes['value'];
* Returns the factorial of a number.
* @param float $factVal Factorial Value
public static function FACT($factVal) {
$factVal = self::flattenSingleValue($factVal);
return self::$_errorCodes['num'];
$factLoop = floor($factVal);
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
if ($factVal > $factLoop) {
return self::$_errorCodes['num'];
$factorial *= $factLoop-- ;
return self::$_errorCodes['value'];
* Returns the double factorial of a number.
* @param float $factVal Factorial Value
* @return int Double Factorial
$factLoop = floor(self::flattenSingleValue($factVal));
return self::$_errorCodes['num'];
$factorial *= $factLoop-- ;
return self::$_errorCodes['value'];
* Returns the ratio of the factorial of a sum of values to the product of factorials.
* @param array of mixed Data Series
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
return self::$_errorCodes['num'];
$divisor *= self::FACT($arg);
return self::$_errorCodes['value'];
$summer = self::FACT($summer);
return $summer / $divisor;
* Returns number rounded up, away from zero, to the nearest multiple of significance.
* @param float $number Number to round
* @param float $significance Significance
* @return float Rounded Number
public static function CEILING($number,$significance= null) {
$number = self::flattenSingleValue($number);
$significance = self::flattenSingleValue($significance);
if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
$significance = $number/ abs($number);
if (self::SIGN($number) == self::SIGN($significance)) {
if ($significance == 0.0) {
return ceil($number / $significance) * $significance;
return self::$_errorCodes['num'];
return self::$_errorCodes['value'];
* Returns number rounded up to the nearest even integer.
* @param float $number Number to round
* @return int Rounded Number
public static function EVEN($number) {
$number = self::flattenSingleValue($number);
$significance = 2 * self::SIGN($number);
return self::CEILING($number,$significance);
return self::$_errorCodes['value'];
* Returns number rounded up to the nearest odd integer.
* @param float $number Number to round
* @return int Rounded Number
public static function ODD($number) {
$number = self::flattenSingleValue($number);
$significance = self::SIGN($number);
if ($significance == 0) {
$result = self::CEILING($number,$significance);
if (self::IS_EVEN($result)) {
$result += $significance;
return self::$_errorCodes['value'];
* Rounds a number up to a specified number of decimal places
* @param float $number Number to round
* @param int $digits Number of digits to which you want to round $number
* @return float Rounded Number
public static function ROUNDUP($number,$digits) {
$number = self::flattenSingleValue($number);
$digits = self::flattenSingleValue($digits);
$significance = pow(10,$digits);
return ceil($number * $significance) / $significance;
return self::$_errorCodes['value'];
* Rounds a number down to a specified number of decimal places
* @param float $number Number to round
* @param int $digits Number of digits to which you want to round $number
* @return float Rounded Number
public static function ROUNDDOWN($number,$digits) {
$number = self::flattenSingleValue($number);
$digits = self::flattenSingleValue($digits);
$significance = pow(10,$digits);
return floor($number * $significance) / $significance;
return self::$_errorCodes['value'];
* Rounds a number to the nearest multiple of a specified value
* @param float $number Number to round
* @param int $multiple Multiple to which you want to round $number
* @return float Rounded Number
public static function MROUND($number,$multiple) {
$number = self::flattenSingleValue($number);
$multiple = self::flattenSingleValue($multiple);
if ((self::SIGN($number)) == (self::SIGN($multiple))) {
$lowerVal = floor($number / $multiple) * $multiple;
$upperVal = ceil($number / $multiple) * $multiple;
$adjustUp = abs($number - $upperVal);
if ($adjustDown < $adjustUp) {
return self::$_errorCodes['num'];
return self::$_errorCodes['value'];
* Determines the sign of a number. Returns 1 if the number is positive, zero (0)
* if the number is 0, and -1 if the number is negative.
* @param float $number Number to round
public static function SIGN($number) {
$number = self::flattenSingleValue($number);
return $number / abs($number);
return self::$_errorCodes['value'];
* Rounds number down, toward zero, to the nearest multiple of significance.
* @param float $number Number to round
* @param float $significance Significance
* @return float Rounded Number
public static function FLOOR($number,$significance= null) {
$number = self::flattenSingleValue($number);
$significance = self::flattenSingleValue($significance);
if ((is_null($significance)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
$significance = $number/ abs($number);
if ((float) $significance == 0.0) {
return self::$_errorCodes['divisionbyzero'];
if (self::SIGN($number) == self::SIGN($significance)) {
return floor($number / $significance) * $significance;
return self::$_errorCodes['num'];
return self::$_errorCodes['value'];
* Returns the number of permutations for a given number of objects that can be
* selected from number objects. A permutation is any set or subset of objects or
* events where internal order is significant. Permutations are different from
* combinations, for which the internal order is not significant. Use this function
* for lottery-style probability calculations.
* @param int $numObjs Number of different objects
* @param int $numInSet Number of objects in each permutation
* @return int Number of permutations
public static function PERMUT($numObjs,$numInSet) {
$numObjs = self::flattenSingleValue($numObjs);
$numInSet = self::flattenSingleValue($numInSet);
if ($numObjs < $numInSet) {
return self::$_errorCodes['num'];
return self::FACT($numObjs) / self::FACT($numObjs - $numInSet);
return self::$_errorCodes['value'];
* Returns the number of combinations for a given number of items. Use COMBIN to
* determine the total possible number of groups for a given number of items.
* @param int $numObjs Number of different objects
* @param int $numInSet Number of objects in each combination
* @return int Number of combinations
public static function COMBIN($numObjs,$numInSet) {
$numObjs = self::flattenSingleValue($numObjs);
$numInSet = self::flattenSingleValue($numInSet);
if ($numObjs < $numInSet) {
return self::$_errorCodes['num'];
} elseif ($numInSet < 0) {
return self::$_errorCodes['num'];
return (self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet);
return self::$_errorCodes['value'];
* Returns the sum of a power series
* @param float $x Input value to the power series
* @param float $n Initial power to which you want to raise $x
* @param float $m Step by which to increase $n for each term in the series
* @param array of mixed Data Series
foreach($aArgs as $arg) {
// Is it a numeric value?
$returnValue += $arg * pow($x,$n + ($m * $i++ ));
return self::$_errorCodes['value'];
return self::$_errorCodes['value'];
* Returns a normalized value from a distribution characterized by mean and standard_dev.
* @param float $value Value to normalize
* @param float $mean Mean Value
* @param float $stdDev Standard Deviation
* @return float Standardized value
public static function STANDARDIZE($value,$mean,$stdDev) {
$value = self::flattenSingleValue($value);
$mean = self::flattenSingleValue($mean);
$stdDev = self::flattenSingleValue($stdDev);
return self::$_errorCodes['num'];
return ($value - $mean) / $stdDev ;
return self::$_errorCodes['value'];
// Private method to return an array of the factors of the input value
private static function factors($value) {
$startVal = floor(sqrt($value));
for ($i = $startVal; $i > 1; -- $i) {
if (($value % $i) == 0) {
$factorArray = array_merge($factorArray,self::factors($value / $i));
$factorArray = array_merge($factorArray,self::factors($i));
if ($i <= sqrt($value)) {
if (count($factorArray) > 0) {
return array((integer) $value);
* Returns the lowest common multiplier of a series of numbers
* @param $array Values to calculate the Lowest Common Multiplier
* @return int Lowest Common Multiplier
public static function LCM() {
$allPoweredFactors = array();
foreach($aArgs as $value) {
return self::$_errorCodes['value'];
return self::$_errorCodes['num'];
$myFactors = self::factors(floor($value));
$myPoweredFactors = array();
foreach($myCountedFactors as $myCountedFactor => $myCountedPower) {
$myPoweredFactors[$myCountedFactor] = pow($myCountedFactor,$myCountedPower);
foreach($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
$allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
$allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
foreach($allPoweredFactors as $allPoweredFactor) {
$returnValue *= (integer) $allPoweredFactor;
* Returns the greatest common divisor of a series of numbers
* @param $array Values to calculate the Greatest Common Divisor
* @return int Greatest Common Divisor
public static function GCD() {
$allPoweredFactors = array();
foreach($aArgs as $value) {
$myFactors = self::factors($value);
$allValuesFactors[] = $myCountedFactors;
$allValuesCount = count($allValuesFactors);
$mergedArray = $allValuesFactors[0];
for ($i= 1;$i < $allValuesCount; ++ $i) {
$mergedArrayValues = count($mergedArray);
if ($mergedArrayValues == 0) {
} elseif ($mergedArrayValues > 1) {
foreach($mergedArray as $mergedKey => $mergedValue) {
foreach($allValuesFactors as $highestPowerTest) {
foreach($highestPowerTest as $testKey => $testValue) {
if (($testKey == $mergedKey) && ($testValue < $mergedValue)) {
$mergedArray[$mergedKey] = $testValue;
$mergedValue = $testValue;
foreach($mergedArray as $key => $value) {
$returnValue *= pow($key,$value);
$value = $mergedArray[$key];
foreach($allValuesFactors as $testValue) {
foreach($testValue as $mergedKey => $mergedValue) {
if (($mergedKey == $key) && ($mergedValue < $value)) {
* Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
* a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
* when trials are independent, and when the probability of success is constant throughout the
* experiment. For example, BINOMDIST can calculate the probability that two of the next three
* @param float $value Number of successes in trials
* @param float $trials Number of trials
* @param float $probability Probability of success on each trial
* @param boolean $cumulative
* @todo Cumulative distribution function
public static function BINOMDIST($value, $trials, $probability, $cumulative) {
$value = floor(self::flattenSingleValue($value));
$trials = floor(self::flattenSingleValue($trials));
$probability = self::flattenSingleValue($probability);
if (($value < 0) || ($value > $trials)) {
return self::$_errorCodes['num'];
if (($probability < 0) || ($probability > 1)) {
return self::$_errorCodes['num'];
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
for ($i = 0; $i <= $value; ++ $i) {
$summer += self::COMBIN($trials,$i) * pow($probability,$i) * pow(1 - $probability,$trials - $i);
return self::COMBIN($trials,$value) * pow($probability,$value) * pow(1 - $probability,$trials - $value) ;
return self::$_errorCodes['value'];
* Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
* there will be number_f failures before the number_s-th success, when the constant
* probability of a success is probability_s. This function is similar to the binomial
* distribution, except that the number of successes is fixed, and the number of trials is
* variable. Like the binomial, trials are assumed to be independent.
* @param float $failures Number of Failures
* @param float $successes Threshold number of Successes
* @param float $probability Probability of success on each trial
public static function NEGBINOMDIST($failures, $successes, $probability) {
$failures = floor(self::flattenSingleValue($failures));
$successes = floor(self::flattenSingleValue($successes));
$probability = self::flattenSingleValue($probability);
if (($failures < 0) || ($successes < 1)) {
return self::$_errorCodes['num'];
if (($probability < 0) || ($probability > 1)) {
return self::$_errorCodes['num'];
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
if (($failures + $successes - 1) <= 0) {
return self::$_errorCodes['num'];
return (self::COMBIN($failures + $successes - 1,$successes - 1)) * (pow($probability,$successes)) * (pow(1 - $probability,$failures)) ;
return self::$_errorCodes['value'];
* Returns the smallest value for which the cumulative binomial distribution is greater
* than or equal to a criterion value
* See http://support.microsoft.com/kb/828117/ for details of the algorithm used
* @param float $trials number of Bernoulli trials
* @param float $probability probability of a success on each trial
* @param float $alpha criterion value
* @todo Warning. This implementation differs from the algorithm detailed on the MS
* web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess
* This eliminates a potential endless loop error, but may have an adverse affect on the
* accuracy of the function (although all my tests have so far returned correct results).
public static function CRITBINOM($trials, $probability, $alpha) {
$trials = floor(self::flattenSingleValue($trials));
$probability = self::flattenSingleValue($probability);
$alpha = self::flattenSingleValue($alpha);
return self::$_errorCodes['num'];
if (($probability < 0) || ($probability > 1)) {
return self::$_errorCodes['num'];
if (($alpha < 0) || ($alpha > 1)) {
return self::$_errorCodes['num'];
$t = sqrt(log(1 / pow($alpha,2)));
$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));
$trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t);
$Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability)));
} elseif ($Guess > $trials) {
$TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0;
$EssentiallyZero = 10e-12;
$m = floor($trials * $probability);
++ $TotalUnscaledProbability;
if ($m == $Guess) { ++ $UnscaledPGuess; }
if ($m <= $Guess) { ++ $UnscaledCumPGuess; }
while ((!$Done) && ($k <= $trials)) {
$CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability));
$TotalUnscaledProbability += $CurrentValue;
if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
if ($CurrentValue <= $EssentiallyZero) { $Done = True; }
$PreviousValue = $CurrentValue;
while ((!$Done) && ($k >= 0)) {
$CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability);
$TotalUnscaledProbability += $CurrentValue;
if ($k == $Guess) { $UnscaledPGuess += $CurrentValue; }
if ($k <= $Guess) { $UnscaledCumPGuess += $CurrentValue; }
if (CurrentValue <= EssentiallyZero) { $Done = True; }
$PreviousValue = $CurrentValue;
$PGuess = $UnscaledPGuess / $TotalUnscaledProbability;
$CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability;
// $CumPGuessMinus1 = $CumPGuess - $PGuess;
$CumPGuessMinus1 = $CumPGuess - 1;
if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) {
} elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) {
$PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability);
$CumPGuessMinus1 = $CumPGuess;
$CumPGuess = $CumPGuess + $PGuessPlus1;
} elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) {
$PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability;
$CumPGuess = $CumPGuessMinus1;
$CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess;
return self::$_errorCodes['value'];
* Returns the one-tailed probability of the chi-squared distribution.
* @param float $value Value for the function
* @param float $degrees degrees of freedom
public static function CHIDIST($value, $degrees) {
$value = self::flattenSingleValue($value);
$degrees = floor(self::flattenSingleValue($degrees));
return self::$_errorCodes['num'];
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
return self::$_errorCodes['num'];
return 1 - (self::incompleteGamma($degrees/ 2,$value/ 2) / self::gamma($degrees/ 2));
return self::$_errorCodes['value'];
* Returns the one-tailed probability of the chi-squared distribution.
* @param float $probability Probability for the function
* @param float $degrees degrees of freedom
public static function CHIINV($probability, $degrees) {
$probability = self::flattenSingleValue($probability);
$degrees = floor(self::flattenSingleValue($degrees));
// Apply Newton-Raphson step
$result = self::CHIDIST($x, $degrees);
$error = $result - $probability;
} elseif ($error < 0.0) {
// Avoid division by zero
// If the NR fails to converge (which for example may be the
// case if the initial guess is too rough) we apply a bisection
// step to determine a more narrow interval around the root.
if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
$xNew = ($xLo + $xHi) / 2;
return self::$_errorCodes['na'];
return self::$_errorCodes['value'];
* Returns the exponential distribution. Use EXPONDIST to model the time between events,
* such as how long an automated bank teller takes to deliver cash. For example, you can
* use EXPONDIST to determine the probability that the process takes at most 1 minute.
* @param float $value Value of the function
* @param float $lambda The parameter value
* @param boolean $cumulative
public static function EXPONDIST($value, $lambda, $cumulative) {
$value = self::flattenSingleValue($value);
$lambda = self::flattenSingleValue($lambda);
$cumulative = self::flattenSingleValue($cumulative);
if (($value < 0) || ($lambda < 0)) {
return self::$_errorCodes['num'];
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
return 1 - exp(0- $value* $lambda);
return $lambda * exp(0- $value* $lambda);
return self::$_errorCodes['value'];
* Returns the Fisher transformation at x. This transformation produces a function that
* is normally distributed rather than skewed. Use this function to perform hypothesis
* testing on the correlation coefficient.
public static function FISHER($value) {
$value = self::flattenSingleValue($value);
if (($value <= - 1) || ($lambda >= 1)) {
return self::$_errorCodes['num'];
return 0.5 * log((1+ $value)/ (1- $value));
return self::$_errorCodes['value'];
* Returns the inverse of the Fisher transformation. Use this transformation when
* analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
$value = self::flattenSingleValue($value);
return (exp(2 * $value) - 1) / (exp(2 * $value) + 1);
return self::$_errorCodes['value'];
// Function cache for logBeta
private static $logBetaCache_p = 0.0;
private static $logBetaCache_q = 0.0;
private static $logBetaCache_result = 0.0;
* The natural logarithm of the beta function.
* @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
* @author Jaco van Kooten
private static function logBeta($p, $q) {
if ($p != self::$logBetaCache_p || $q != self::$logBetaCache_q) {
self::$logBetaCache_p = $p;
self::$logBetaCache_q = $q;
if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > LOG_GAMMA_X_MAX_VALUE)) {
self::$logBetaCache_result = 0.0;
self::$logBetaCache_result = self::logGamma($p) + self::logGamma($q) - self::logGamma($p + $q);
return self::$logBetaCache_result;
* Evaluates of continued fraction part of incomplete beta function.
* Based on an idea from Numerical Recipes (W.H. Press et al, 1992).
* @author Jaco van Kooten
$h = 1.0 - $sum_pq * $x / $p_plus;
$d = $m * ($q - $m) * $x / ( ($p_minus + $m2) * ($p + $m2));
$d = - ($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2));
* @author Jaco van Kooten
* Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
* The natural logarithm of the gamma function. <br />
* Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
* Applied Mathematics Division <br />
* Argonne National Laboratory <br />
* Argonne, IL 60439 <br />
* <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
* Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
* <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
* <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
* From the original documentation:
* This routine calculates the LOG(GAMMA) function for a positive real argument X.
* Computation is based on an algorithm outlined in references 1 and 2.
* The program uses rational functions that theoretically approximate LOG(GAMMA)
* to at least 18 significant decimal digits. The approximation for X > 12 is from
* reference 3, while approximations for X < 12.0 are similar to those in reference
* 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
* the compiler, the intrinsic functions, and proper selection of the
* machine-dependent constants.
* The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
* The computation is believed to be free of underflow and overflow.
* @return MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
// Function cache for logGamma
private static $logGammaCache_result = 0.0;
private static $logGammaCache_x = 0.0;
// Log Gamma related constants
static $lg_d1 = - 0.5772156649015328605195174;
static $lg_d2 = 0.4227843350984671393993777;
static $lg_d4 = 1.791759469228055000094023;
static $lg_p1 = array( 4.945235359296727046734888,
201.8112620856775083915565,
2290.838373831346393026739,
11319.67205903380828685045,
28557.24635671635335736389,
38484.96228443793359990269,
26377.48787624195437963534,
7225.813979700288197698961 );
static $lg_p2 = array( 4.974607845568932035012064,
542.4138599891070494101986,
15506.93864978364947665077,
184793.2904445632425417223,
1088204.76946882876749847,
3338152.967987029735917223,
5106661.678927352456275255,
3074109.054850539556250927 );
static $lg_p4 = array( 14745.02166059939948905062,
2426813.369486704502836312,
121475557.4045093227939592,
2663432449.630976949898078,
29403789566.34553899906876,
170266573776.5398868392998,
492612579337.743088758812,
560625185622.3951465078242 );
static $lg_q1 = array( 67.48212550303777196073036,
1113.332393857199323513008,
7738.757056935398733233834,
27639.87074403340708898585,
54993.10206226157329794414,
61611.22180066002127833352,
36351.27591501940507276287,
8785.536302431013170870835 );
static $lg_q2 = array( 183.0328399370592604055942,
7765.049321445005871323047,
133190.3827966074194402448,
1136705.821321969608938755,
5267964.117437946917577538,
13467014.54311101692290052,
17827365.30353274213975932,
9533095.591844353613395747 );
static $lg_q4 = array( 2690.530175870899333379843,
639388.5654300092398984238,
41355999.30241388052042842,
1120872109.61614794137657,
14886137286.78813811542398,
101680358627.2438228077304,
341747634550.7377132798597,
446315818741.9713286462081 );
static $lg_c = array( - 0.001910444077728,
- 0.002777777777777681622553,
0.08333333333333333331554247,
// Rough estimate of the fourth root of logGamma_xBig
static $lg_frtbig = 2.25e76;
static $pnt68 = 0.6796875;
if ($x == self::$logGammaCache_x) {
return self::$logGammaCache_result;
if ($y > 0.0 && $y <= LOG_GAMMA_X_MAX_VALUE) {
if ($y <= 0.5 || $y >= $pnt68) {
for ($i = 0; $i < 8; ++ $i) {
$xnum = $xnum * $xm1 + $lg_p1[$i];
$xden = $xden * $xm1 + $lg_q1[$i];
$res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden));
for ($i = 0; $i < 8; ++ $i) {
$xnum = $xnum * $xm2 + $lg_p2[$i];
$xden = $xden * $xm2 + $lg_q2[$i];
$res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
for ($i = 0; $i < 8; ++ $i) {
$xnum = $xnum * $xm2 + $lg_p2[$i];
$xden = $xden * $xm2 + $lg_q2[$i];
$res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden));
// ----------------------
// ----------------------
for ($i = 0; $i < 8; ++ $i) {
$xnum = $xnum * $xm4 + $lg_p4[$i];
$xden = $xden * $xm4 + $lg_q4[$i];
$res = $lg_d4 + $xm4 * ($xnum / $xden);
// ---------------------------------
// Evaluate for argument .GE. 12.0
// ---------------------------------
for ($i = 0; $i < 6; ++ $i)
$res = $res / $ysq + $lg_c[$i];
$res += $y * ($corr - 1.0);
// --------------------------
// Return for bad arguments
// --------------------------
// ------------------------------
// Final adjustments and return
// ------------------------------
self::$logGammaCache_x = $x;
self::$logGammaCache_result = $res;
* @author Jaco van Kooten
* @return 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
private static function beta($p, $q) {
return exp(self::logBeta($p, $q));
* Incomplete beta function
* @author Jaco van Kooten
* The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992).
* @param x require 0<=x<=1
* @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
$beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x));
if ($x < ($p + 1.0) / ($p + $q + 2.0)) {
return $beta_gam * self::betaFraction($x, $p, $q) / $p;
return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q);
* Returns the beta distribution.
* @param float $value Value at which you want to evaluate the distribution
* @param float $alpha Parameter to the distribution
* @param float $beta Parameter to the distribution
* @param boolean $cumulative
public static function BETADIST($value,$alpha,$beta,$rMin= 0,$rMax= 1) {
$value = self::flattenSingleValue($value);
$alpha = self::flattenSingleValue($alpha);
$beta = self::flattenSingleValue($beta);
$rMin = self::flattenSingleValue($rMin);
$rMax = self::flattenSingleValue($rMax);
if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) {
return self::$_errorCodes['num'];
$value /= ($rMax - $rMin);
return self::incompleteBeta($value,$alpha,$beta);
return self::$_errorCodes['value'];
* Returns the inverse of the beta distribution.
* @param float $probability Probability at which you want to evaluate the distribution
* @param float $alpha Parameter to the distribution
* @param float $beta Parameter to the distribution
* @param boolean $cumulative
public static function BETAINV($probability,$alpha,$beta,$rMin= 0,$rMax= 1) {
$probability = self::flattenSingleValue($probability);
$alpha = self::flattenSingleValue($alpha);
$beta = self::flattenSingleValue($beta);
$rMin = self::flattenSingleValue($rMin);
$rMax = self::flattenSingleValue($rMax);
if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) {
return self::$_errorCodes['num'];
$result = self::BETADIST($guess, $alpha, $beta);
if (($result == $probability) || ($result == 0)) {
} elseif ($result > $probability) {
return self::$_errorCodes['na'];
return round($rMin + $guess * ($rMax - $rMin),12);
return self::$_errorCodes['value'];
// Private implementation of the incomplete Gamma function
private static function incompleteGamma($a,$x) {
for ($n= 0; $n<= $max; ++ $n) {
for ($i= 1; $i<= $n; ++ $i) {
$summer += (pow($x,$n) / $divisor);
return pow($x,$a) * exp(0- $x) * $summer;
// Private implementation of the Gamma function
private static function gamma($data) {
if ($data == 0.0) return 0;
static $p0 = 1.000000000190015;
static $p = array ( 1 => 76.18009172947146,
5 => 1.208650973866179e-3,
$tmp -= ($x + 0.5) * log($tmp);
$summer += ($p[$j] / ++ $y);
return exp(0 - $tmp + log(2.5066282746310005 * $summer / $x));
* Returns the gamma distribution.
* @param float $value Value at which you want to evaluate the distribution
* @param float $a Parameter to the distribution
* @param float $b Parameter to the distribution
* @param boolean $cumulative
public static function GAMMADIST($value,$a,$b,$cumulative) {
$value = self::flattenSingleValue($value);
$a = self::flattenSingleValue($a);
$b = self::flattenSingleValue($b);
if (($value < 0) || ($a <= 0) || ($b <= 0)) {
return self::$_errorCodes['num'];
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
return self::incompleteGamma($a,$value / $b) / self::gamma($a);
return (1 / (pow($b,$a) * self::gamma($a))) * pow($value,$a- 1) * exp(0- ($value / $b));
return self::$_errorCodes['value'];
* Returns the inverse of the beta distribution.
* @param float $probability Probability at which you want to evaluate the distribution
* @param float $alpha Parameter to the distribution
* @param float $beta Parameter to the distribution
* @param boolean $cumulative
public static function GAMMAINV($probability,$alpha,$beta) {
$probability = self::flattenSingleValue($probability);
$alpha = self::flattenSingleValue($alpha);
$beta = self::flattenSingleValue($beta);
$rMin = self::flattenSingleValue($rMin);
$rMax = self::flattenSingleValue($rMax);
if (($alpha <= 0) || ($beta <= 0) || ($probability <= 0) || ($probability > 1)) {
return self::$_errorCodes['num'];
// Apply Newton-Raphson step
$result = self::GAMMADIST($x, $alpha, $beta, True);
$error = $result - $probability;
} elseif ($error < 0.0) {
// Avoid division by zero
// If the NR fails to converge (which for example may be the
// case if the initial guess is too rough) we apply a bisection
// step to determine a more narrow interval around the root.
if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
$xNew = ($xLo + $xHi) / 2;
return self::$_errorCodes['na'];
return self::$_errorCodes['value'];
* Returns the natural logarithm of the gamma function.
public static function GAMMALN($value) {
$value = self::flattenSingleValue($value);
return self::$_errorCodes['num'];
return log(self::gamma($value));
return self::$_errorCodes['value'];
* Returns the normal distribution for the specified mean and standard deviation. This
* function has a very wide range of applications in statistics, including hypothesis
* @param float $mean Mean Value
* @param float $stdDev Standard Deviation
* @param boolean $cumulative
public static function NORMDIST($value, $mean, $stdDev, $cumulative) {
$value = self::flattenSingleValue($value);
$mean = self::flattenSingleValue($mean);
$stdDev = self::flattenSingleValue($stdDev);
return self::$_errorCodes['num'];
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
return 0.5 * (1 + self::erfVal(($value - $mean) / ($stdDev * sqrt(2))));
return (1 / (SQRT2PI * $stdDev)) * exp(0 - (pow($value - $mean,2) / (2 * pow($stdDev,2))));
return self::$_errorCodes['value'];
* Returns the standard normal cumulative distribution function. The distribution has
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
* table of standard normal curve areas.
public static function NORMSDIST($value) {
$value = self::flattenSingleValue($value);
return self::NORMDIST($value, 0, 1, True);
* Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
* with parameters mean and standard_dev.
public static function LOGNORMDIST($value, $mean, $stdDev) {
$value = self::flattenSingleValue($value);
$mean = self::flattenSingleValue($mean);
$stdDev = self::flattenSingleValue($stdDev);
if (($value <= 0) || ($stdDev <= 0)) {
return self::$_errorCodes['num'];
return self::NORMSDIST((log($value) - $mean) / $stdDev);
return self::$_errorCodes['value'];
/***************************************************************************
* begin : Friday, January 16, 2004
* copyright : (C) 2004 Michael Nickerson
* email : nickersonm@yahoo.com
***************************************************************************/
private static function inverse_ncdf($p) {
// Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
// PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
// a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
// I have not checked the accuracy of this implementation. Be aware that PHP
// will truncate the coeficcients to 14 digits.
// You have permission to use and distribute this function freely for
// whatever purpose you want, but please show common courtesy and give credit
// Input paramater is $p - probability - where 0 < p < 1.
// Coefficients in rational approximations
static $a = array( 1 => - 3.969683028665376e+01,
2 => 2.209460984245205e+02,
3 => - 2.759285104469687e+02,
4 => 1.383577518672690e+02,
5 => - 3.066479806614716e+01,
6 => 2.506628277459239e+00
static $b = array( 1 => - 5.447609879822406e+01,
2 => 1.615858368580409e+02,
3 => - 1.556989798598866e+02,
4 => 6.680131188771972e+01,
5 => - 1.328068155288572e+01
static $c = array( 1 => - 7.784894002430293e-03,
2 => - 3.223964580411365e-01,
3 => - 2.400758277161838e+00,
4 => - 2.549732539343734e+00,
5 => 4.374664141464968e+00,
6 => 2.938163982698783e+00
static $d = array( 1 => 7.784695709041462e-03,
2 => 3.224671290700398e-01,
3 => 2.445134137142996e+00,
4 => 3.754408661907416e+00
// Define lower and upper region break-points.
$p_low = 0.02425; //Use lower region approx. below this
$p_high = 1 - $p_low; //Use upper region approx. above this
if (0 < $p && $p < $p_low) {
// Rational approximation for lower region.
return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
(((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
} elseif ($p_low <= $p && $p <= $p_high) {
// Rational approximation for central region.
return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
} elseif ($p_high < $p && $p < 1) {
// Rational approximation for upper region.
return - ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
(((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
// If 0 < p < 1, return a null value
return self::$_errorCodes['null'];
private static function inverse_ncdf2($prob) {
// Approximation of inverse standard normal CDF developed by
// B. Moro, "The Full Monte," Risk 8(2), Feb 1995, 57-58.
$c4 = 2.76438810333863E-02;
$c5 = 3.8405729373609E-03;
$c6 = 3.951896511919E-04;
$z = $y * ((($a4 * $z + $a3) * $z + $a2) * $z + $a1) / (((($b4 * $z + $b3) * $z + $b2) * $z + $b1) * $z + 1);
$z = $c1 + $z * ($c2 + $z * ($c3 + $z * ($c4 + $z * ($c5 + $z * ($c6 + $z * ($c7 + $z * ($c8 + $z * $c9)))))));
// ALGORITHM AS241 APPL. STATIST. (1988) VOL. 37, NO. 3.
// Produces the normal deviate Z corresponding to a given lower
// tail area of P; Z is accurate to about 1 part in 10**16.
// This is a PHP version of the original FORTRAN code that can
// be found at http://lib.stat.cmu.edu/apstat/
// coefficients for p close to 0.5
$a0 = 3.3871328727963666080;
$a1 = 1.3314166789178437745E+2;
$a2 = 1.9715909503065514427E+3;
$a3 = 1.3731693765509461125E+4;
$a4 = 4.5921953931549871457E+4;
$a5 = 6.7265770927008700853E+4;
$a6 = 3.3430575583588128105E+4;
$a7 = 2.5090809287301226727E+3;
$b1 = 4.2313330701600911252E+1;
$b2 = 6.8718700749205790830E+2;
$b3 = 5.3941960214247511077E+3;
$b4 = 2.1213794301586595867E+4;
$b5 = 3.9307895800092710610E+4;
$b6 = 2.8729085735721942674E+4;
$b7 = 5.2264952788528545610E+3;
// coefficients for p not close to 0, 0.5 or 1.
$c0 = 1.42343711074968357734;
$c1 = 4.63033784615654529590;
$c2 = 5.76949722146069140550;
$c3 = 3.64784832476320460504;
$c4 = 1.27045825245236838258;
$c5 = 2.41780725177450611770E-1;
$c6 = 2.27238449892691845833E-2;
$c7 = 7.74545014278341407640E-4;
$d1 = 2.05319162663775882187;
$d2 = 1.67638483018380384940;
$d3 = 6.89767334985100004550E-1;
$d4 = 1.48103976427480074590E-1;
$d5 = 1.51986665636164571966E-2;
$d6 = 5.47593808499534494600E-4;
$d7 = 1.05075007164441684324E-9;
// coefficients for p near 0 or 1.
$e0 = 6.65790464350110377720;
$e1 = 5.46378491116411436990;
$e2 = 1.78482653991729133580;
$e3 = 2.96560571828504891230E-1;
$e4 = 2.65321895265761230930E-2;
$e5 = 1.24266094738807843860E-3;
$e6 = 2.71155556874348757815E-5;
$e7 = 2.01033439929228813265E-7;
$f1 = 5.99832206555887937690E-1;
$f2 = 1.36929880922735805310E-1;
$f3 = 1.48753612908506148525E-2;
$f4 = 7.86869131145613259100E-4;
$f5 = 1.84631831751005468180E-5;
$f6 = 1.42151175831644588870E-7;
$f7 = 2.04426310338993978564E-15;
// computation for p close to 0.5
$z = $q * ((((((($a7 * $R + $a6) * $R + $a5) * $R + $a4) * $R + $a3) * $R + $a2) * $R + $a1) * $R + $a0) /
((((((($b7 * $R + $b6) * $R + $b5) * $R + $b4) * $R + $b3) * $R + $b2) * $R + $b1) * $R + 1);
// computation for p not close to 0, 0.5 or 1.
$z = ((((((($c7 * $R + $c6) * $R + $c5) * $R + $c4) * $R + $c3) * $R + $c2) * $R + $c1) * $R + $c0) /
((((((($d7 * $R + $d6) * $R + $d5) * $R + $d4) * $R + $d3) * $R + $d2) * $R + $d1) * $R + 1);
// computation for p near 0 or 1.
$z = ((((((($e7 * $R + $e6) * $R + $e5) * $R + $e4) * $R + $e3) * $R + $e2) * $R + $e1) * $R + $e0) /
((((((($f7 * $R + $f6) * $R + $f5) * $R + $f4) * $R + $f3) * $R + $f2) * $R + $f1) * $R + 1);
* Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
* @param float $mean Mean Value
* @param float $stdDev Standard Deviation
public static function NORMINV($probability,$mean,$stdDev) {
$probability = self::flattenSingleValue($probability);
$mean = self::flattenSingleValue($mean);
$stdDev = self::flattenSingleValue($stdDev);
if (($probability < 0) || ($probability > 1)) {
return self::$_errorCodes['num'];
return self::$_errorCodes['num'];
return (self::inverse_ncdf($probability) * $stdDev) + $mean;
return self::$_errorCodes['value'];
* Returns the inverse of the standard normal cumulative distribution
public static function NORMSINV($value) {
return self::NORMINV($value, 0, 1);
* Returns the inverse of the normal cumulative distribution
* @todo Try implementing P J Acklam's refinement algorithm for greater
* accuracy if I can get my head round the mathematics
* (as described at) http://home.online.no/~pjacklam/notes/invnorm/
public static function LOGINV($probability, $mean, $stdDev) {
$probability = self::flattenSingleValue($probability);
$mean = self::flattenSingleValue($mean);
$stdDev = self::flattenSingleValue($stdDev);
if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) {
return self::$_errorCodes['num'];
return exp($mean + $stdDev * self::NORMSINV($probability));
return self::$_errorCodes['value'];
* Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of
* sample successes, given the sample size, population successes, and population size.
* @param float $sampleSuccesses Number of successes in the sample
* @param float $sampleNumber Size of the sample
* @param float $populationSuccesses Number of successes in the population
* @param float $populationNumber Population size
public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
$sampleSuccesses = floor(self::flattenSingleValue($sampleSuccesses));
$sampleNumber = floor(self::flattenSingleValue($sampleNumber));
$populationSuccesses = floor(self::flattenSingleValue($populationSuccesses));
$populationNumber = floor(self::flattenSingleValue($populationNumber));
if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
return self::$_errorCodes['num'];
if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
return self::$_errorCodes['num'];
if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
return self::$_errorCodes['num'];
return self::COMBIN($populationSuccesses,$sampleSuccesses) *
self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
self::COMBIN($populationNumber,$sampleNumber);
return self::$_errorCodes['value'];
public static function hypGeom($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) {
return self::COMBIN($populationSuccesses,$sampleSuccesses) *
self::COMBIN($populationNumber - $populationSuccesses,$sampleNumber - $sampleSuccesses) /
self::COMBIN($populationNumber,$sampleNumber);
* Returns the probability of Student's T distribution.
* @param float $value Value for the function
* @param float $degrees degrees of freedom
* @param float $tails number of tails (1 or 2)
public static function TDIST($value, $degrees, $tails) {
$value = self::flattenSingleValue($value);
$degrees = floor(self::flattenSingleValue($degrees));
$tails = floor(self::flattenSingleValue($tails));
if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
return self::$_errorCodes['num'];
// tdist, which finds the probability that corresponds to a given value
// of t with k degrees of freedom. This algorithm is translated from a
// pascal function on p81 of "Statistical Computing in Pascal" by D
// Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
// London). The above Pascal algorithm is itself a translation of the
// fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
// Laboratory as reported in (among other places) "Applied Statistics
// Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
// Horwood Ltd.; W. Sussex, England).
$ttheta = atan2($value,sqrt($tterm));
if (($degrees % 2) == 1) {
$tterm *= $tc * $tc * ($ti - 1) / $ti;
if (($degrees % 2) == 1) { $tsum = $ta * ($tsum + $ttheta); }
$tValue = 0.5 * (1 + $tsum);
return 1 - abs((1 - $tValue) - $tValue);
return self::$_errorCodes['value'];
* Returns the one-tailed probability of the chi-squared distribution.
* @param float $probability Probability for the function
* @param float $degrees degrees of freedom
public static function TINV($probability, $degrees) {
$probability = self::flattenSingleValue($probability);
$degrees = floor(self::flattenSingleValue($degrees));
// Apply Newton-Raphson step
$result = self::TDIST($x, $degrees, 2);
$error = $result - $probability;
} elseif ($error < 0.0) {
// Avoid division by zero
// If the NR fails to converge (which for example may be the
// case if the initial guess is too rough) we apply a bisection
// step to determine a more narrow interval around the root.
if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
$xNew = ($xLo + $xHi) / 2;
return self::$_errorCodes['na'];
return self::$_errorCodes['value'];
* Returns the confidence interval for a population mean
* @param float $stdDev Standard Deviation
public static function CONFIDENCE($alpha,$stdDev,$size) {
$alpha = self::flattenSingleValue($alpha);
$stdDev = self::flattenSingleValue($stdDev);
$size = floor(self::flattenSingleValue($size));
if (($alpha <= 0) || ($alpha >= 1)) {
return self::$_errorCodes['num'];
if (($stdDev <= 0) || ($size < 1)) {
return self::$_errorCodes['num'];
return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
return self::$_errorCodes['value'];
* Returns the Poisson distribution. A common application of the Poisson distribution
* is predicting the number of events over a specific time, such as the number of
* cars arriving at a toll plaza in 1 minute.
* @param float $mean Mean Value
* @param boolean $cumulative
public static function POISSON($value, $mean, $cumulative) {
$value = self::flattenSingleValue($value);
$mean = self::flattenSingleValue($mean);
if (($value <= 0) || ($mean <= 0)) {
return self::$_errorCodes['num'];
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
for ($i = 0; $i <= floor($value); ++ $i) {
$summer += pow($mean,$i) / self::FACT($i);
return exp(0- $mean) * $summer;
return (exp(0- $mean) * pow($mean,$value)) / self::FACT($value);
return self::$_errorCodes['value'];
* Returns the Weibull distribution. Use this distribution in reliability
* analysis, such as calculating a device's mean time to failure.
* @param float $alpha Alpha Parameter
* @param float $beta Beta Parameter
* @param boolean $cumulative
public static function WEIBULL($value, $alpha, $beta, $cumulative) {
$value = self::flattenSingleValue($value);
$alpha = self::flattenSingleValue($alpha);
$beta = self::flattenSingleValue($beta);
if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) {
return self::$_errorCodes['num'];
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
return 1 - exp(0 - pow($value / $beta,$alpha));
return ($alpha / pow($beta,$alpha)) * pow($value,$alpha - 1) * exp(0 - pow($value / $beta,$alpha));
return self::$_errorCodes['value'];
* Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry
* of a distribution around its mean. Positive skewness indicates a distribution with an
* asymmetric tail extending toward more positive values. Negative skewness indicates a
* distribution with an asymmetric tail extending toward more negative values.
* @param array Data Series
public static function SKEW() {
$mean = self::AVERAGE($aArgs);
$stdDev = self::STDEV($aArgs);
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$summer += pow((($arg - $mean) / $stdDev),3) ;
return $summer * ($count / (($count- 1) * ($count- 2)));
return self::$_errorCodes['divisionbyzero'];
* Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness
* or flatness of a distribution compared with the normal distribution. Positive
* kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a
* relatively flat distribution.
* @param array Data Series
public static function KURT() {
$mean = self::AVERAGE($aArgs);
$stdDev = self::STDEV($aArgs);
// Loop through arguments
foreach ($aArgs as $arg) {
// Is it a numeric value?
$summer += pow((($arg - $mean) / $stdDev),4) ;
return $summer * ($count * ($count+ 1) / (($count- 1) * ($count- 2) * ($count- 3))) - (3 * pow($count- 1,2) / (($count- 2) * ($count- 3)));
return self::$_errorCodes['divisionbyzero'];
* @param int $min Minimal value
* @param int $max Maximal value
* @return int Random number
public static function RAND($min = 0, $max = 0) {
$min = self::flattenSingleValue($min);
$max = self::flattenSingleValue($max);
if ($min == 0 && $max == 0) {
return (rand(0,10000000)) / 10000000;
public static function MOD($a = 1, $b = 1) {
$a = self::flattenSingleValue($a);
$b = self::flattenSingleValue($b);
* @param string $character Value
public static function ASCIICODE($characters) {
$characters = self::flattenSingleValue($characters);
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
$characters = (int) $characters;
if (strlen($characters) > 0) {
return self::$_errorCodes['value'];
public static function CONCATENATE() {
foreach ($aArgs as $arg) {
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
* @param string $needle The string to look for
* @param string $haystack The string in which to look
* @param int $offset Offset within $haystack
$needle = (string) self::flattenSingleValue($needle);
$haystack = (string) self::flattenSingleValue($haystack);
$offset = self::flattenSingleValue($offset);
if (($offset > 0) && (strlen($haystack) > $offset)) {
$pos = strpos($haystack, $needle, -- $offset);
return self::$_errorCodes['value'];
* @param string $needle The string to look for
* @param string $haystack The string in which to look
* @param int $offset Offset within $haystack
public static function SEARCHINSENSITIVE($needle,$haystack,$offset= 1) {
$needle = (string) self::flattenSingleValue($needle);
$haystack = (string) self::flattenSingleValue($haystack);
$offset = self::flattenSingleValue($offset);
if (($offset > 0) && (strlen($haystack) > $offset)) {
$pos = stripos($haystack, $needle, -- $offset);
return self::$_errorCodes['value'];
* @param string $value Value
* @param int $chars Number of characters
public static function LEFT($value = '', $chars = null) {
$value = self::flattenSingleValue($value);
$chars = self::flattenSingleValue($chars);
return substr($value, 0, $chars);
* @param string $value Value
* @param int $chars Number of characters
public static function RIGHT($value = '', $chars = null) {
$value = self::flattenSingleValue($value);
$chars = self::flattenSingleValue($chars);
* @param string $value Value
* @param int $start Start character
* @param int $chars Number of characters
public static function MID($value = '', $start = 1, $chars = null) {
$value = self::flattenSingleValue($value);
$start = self::flattenSingleValue($start);
$chars = self::flattenSingleValue($chars);
return substr($value, -- $start, $chars);
* @param mixed $value Value to check
$testValue = self::flattenSingleValue($testValue);
* @param mixed $value Value to check
public static function TRIMSPACES($stringValue = '') {
$stringValue = self::flattenSingleValue($stringValue);
private static $_invalidChars = Null;
* @param mixed $value Value to check
$stringValue = self::flattenSingleValue($stringValue);
if (self::$_invalidChars == Null) {
self::$_invalidChars = range(chr(0),chr(31));
return str_replace(self::$_invalidChars,'',trim($stringValue,"\x00..\x1F"));
* @param mixed $value Value to check
$value = self::flattenSingleValue($value);
foreach(self::$_errorCodes as $errorCode) {
if ($value == $errorCode) {
return self::$_errorCodes['na'];
* @param mixed $value Value to check
public static function IS_BLANK($value = '') {
$value = self::flattenSingleValue($value);
* @param mixed $value Value to check
public static function IS_ERR($value = '') {
$value = self::flattenSingleValue($value);
return self::IS_ERROR($value) && (!self::IS_NA($value));
* @param mixed $value Value to check
public static function IS_ERROR($value = '') {
$value = self::flattenSingleValue($value);
* @param mixed $value Value to check
public static function IS_NA($value = '') {
$value = self::flattenSingleValue($value);
return ($value == self::$_errorCodes['na']);
* @param mixed $value Value to check
public static function IS_EVEN($value = 0) {
$value = self::flattenSingleValue($value);
while (intval($value) != $value) {
return ($value % 2 == 0);
* @param mixed $value Value to check
public static function IS_NUMBER($value = 0) {
$value = self::flattenSingleValue($value);
* @param mixed $value Value to check
public static function IS_LOGICAL($value = true) {
$value = self::flattenSingleValue($value);
* @param mixed $value Value to check
public static function IS_TEXT($value = '') {
$value = self::flattenSingleValue($value);
* @param mixed $value Value to check
* @param mixed $truepart Value when true
* @param mixed $falsepart Value when false
public static function STATEMENT_IF($value = true, $truepart = '', $falsepart = '') {
$value = self::flattenSingleValue($value);
$truepart = self::flattenSingleValue($truepart);
$falsepart = self::flattenSingleValue($falsepart);
return ($value ? $truepart : $falsepart);
* @param mixed $value Value to check , is also value when no error
* @param mixed $errorpart Value when error
return self::STATEMENT_IF(self::IS_ERROR($value), $errorpart, $value);
* @return string Version information
return 'PHPExcel 1.6.5, 2009-01-05';
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
public static function DATE($year = 0, $month = 1, $day = 1) {
$year = (integer) self::flattenSingleValue($year);
$month = (integer) self::flattenSingleValue($month);
$day = (integer) self::flattenSingleValue($day);
if ($year < ($baseYear- 1900)) {
return self::$_errorCodes['num'];
if ((($baseYear- 1900) != 0) && ($year < $baseYear) && ($year >= 1900)) {
return self::$_errorCodes['num'];
if (($year < $baseYear) && ($year > ($baseYear- 1900))) {
// Handle year/month adjustment if month < 1
$year += ceil($month / 12) - 1;
$month = 13 - abs($month % 12);
// Handle year/month adjustment if month > 12
$year += floor($month / 12);
// Re-validate the year parameter after adjustments
if (($year < $baseYear) || ($year >= 10000)) {
return self::$_errorCodes['num'];
$excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($year, $month, $day);
switch (self::getReturnDateType()) {
case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
public static function TIME($hour = 0, $minute = 0, $second = 0) {
$hour = self::flattenSingleValue($hour);
$minute = self::flattenSingleValue($minute);
$second = self::flattenSingleValue($second);
if ($hour == '') { $hour = 0; }
if ($minute == '') { $minute = 0; }
if ($second == '') { $second = 0; }
return self::$_errorCodes['value'];
$minute = (integer) $minute;
$second = (integer) $second;
$minute += floor($second / 60);
$second = 60 - abs($second % 60);
if ($second == 60) { $second = 0; }
} elseif ($second >= 60) {
$minute += floor($second / 60);
$hour += floor($minute / 60);
$minute = 60 - abs($minute % 60);
if ($minute == 60) { $minute = 0; }
} elseif ($minute >= 60) {
$hour += floor($minute / 60);
return self::$_errorCodes['num'];
switch (self::getReturnDateType()) {
case self::RETURNDATE_EXCEL : $date = 0;
case self::RETURNDATE_PHP_OBJECT : $dayAdjust = 0;
$dayAdjust = floor($hour / 24);
$hour = 24 - abs($hour % 24);
if ($hour == 24) { $hour = 0; }
$dayAdjust = floor($hour / 24);
$phpDateObject = new DateTime('1900-01-01 '. $hour. ':'. $minute. ':'. $second);
$phpDateObject->modify($dayAdjust. ' days');
* @param string $dateValue
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
public static function DATEVALUE($dateValue = 1) {
$dateValue = self::flattenSingleValue($dateValue);
if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
$testVal1 = strtok($dateValue,'/- ');
if ($testVal1 !== False) {
if ($testVal2 !== False) {
if ($testVal3 === False) {
return self::$_errorCodes['value'];
return self::$_errorCodes['value'];
$PHPDateArray = date_parse($testVal1. '-'. $testVal2. '-'. $testVal3);
if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
$PHPDateArray = date_parse($testVal2. '-'. $testVal1. '-'. $testVal3);
if (($PHPDateArray === False) || ($PHPDateArray['error_count'] > 0)) {
return self::$_errorCodes['value'];
if (($PHPDateArray !== False) && ($PHPDateArray['error_count'] == 0)) {
if ($PHPDateArray['year'] == '') { $PHPDateArray['year'] = strftime('%Y'); }
if ($PHPDateArray['month'] == '') { $PHPDateArray['month'] = strftime('%m'); }
if ($PHPDateArray['day'] == '') { $PHPDateArray['day'] = strftime('%d'); }
switch (self::getReturnDateType()) {
case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
case self::RETURNDATE_PHP_OBJECT : return new DateTime($PHPDateArray['year']. '-'. $PHPDateArray['month']. '-'. $PHPDateArray['day']. ' 00:00:00');
return self::$_errorCodes['value'];
* @param string $dateValue
* @return mixed Excel date/time serial value, or string if error
private static function _getDateValue($dateValue) {
if ((is_string($dateValue)) && (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC)) {
return self::$_errorCodes['value'];
$dateValue = PHPExcel_Shared_Date::PHPToExcel($dateValue);
$saveReturnDateType = self::getReturnDateType();
self::setReturnDateType(self::RETURNDATE_EXCEL);
$dateValue = self::DATEVALUE($dateValue);
self::setReturnDateType($saveReturnDateType);
* @param string $timeValue
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
public static function TIMEVALUE($timeValue) {
$timeValue = self::flattenSingleValue($timeValue);
if ((($PHPDateArray = date_parse($timeValue)) !== False) && ($PHPDateArray['error_count'] == 0)) {
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
$excelDateValue = PHPExcel_Shared_Date::FormattedPHPToExcel($PHPDateArray['year'],$PHPDateArray['month'],$PHPDateArray['day'],$PHPDateArray['hour'],$PHPDateArray['minute'],$PHPDateArray['second']);
switch (self::getReturnDateType()) {
case self::RETURNDATE_EXCEL : return (float) $excelDateValue;
case self::RETURNDATE_PHP_OBJECT : return new DateTime('1900-01-01 '. $PHPDateArray['hour']. ':'. $PHPDateArray['minute']. ':'. $PHPDateArray['second']);
return self::$_errorCodes['value'];
* @param string $timeValue
* @return mixed Excel date/time serial value, or string if error
private static function _getTimeValue($timeValue) {
$saveReturnDateType = self::getReturnDateType();
self::setReturnDateType(self::RETURNDATE_EXCEL);
$timeValue = self::TIMEVALUE($timeValue);
self::setReturnDateType($saveReturnDateType);
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
switch (self::getReturnDateType()) {
case self::RETURNDATE_PHP_NUMERIC : $retValue = (integer) time();
case self::RETURNDATE_PHP_OBJECT : $retValue = new DateTime();
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
switch (self::getReturnDateType()) {
case self::RETURNDATE_EXCEL : $retValue = (float) $excelDateTime;
return ((($year % 4) == 0) && (($year % 100) != 0) || (($year % 400) == 0));
private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) {
} elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) {
if ($methodUS && $startDay != 30) {
return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
* @param long $startDate Excel date serial value or a standard date string
* @param long $endDate Excel date serial value or a standard date string
* @param boolean $method US or European Method
* @return long PHP date/time serial
public static function DAYS360($startDate = 0, $endDate = 0, $method = false) {
$startDate = self::flattenSingleValue($startDate);
$endDate = self::flattenSingleValue($endDate);
if (is_string($startDate = self::_getDateValue($startDate))) {
return self::$_errorCodes['value'];
if (is_string($endDate = self::_getDateValue($endDate))) {
return self::$_errorCodes['value'];
$PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
$startDay = $PHPStartDateObject->format('j');
$startMonth = $PHPStartDateObject->format('n');
$startYear = $PHPStartDateObject->format('Y');
$endDay = $PHPEndDateObject->format('j');
$endMonth = $PHPEndDateObject->format('n');
$endYear = $PHPEndDateObject->format('Y');
return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method);
* @param long $startDate Excel date serial value or a standard date string
* @param long $endDate Excel date serial value or a standard date string
* @return long Interval between the dates
public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') {
$startDate = self::flattenSingleValue($startDate);
$endDate = self::flattenSingleValue($endDate);
$unit = strtoupper(self::flattenSingleValue($unit));
if (is_string($startDate = self::_getDateValue($startDate))) {
return self::$_errorCodes['value'];
if (is_string($endDate = self::_getDateValue($endDate))) {
return self::$_errorCodes['value'];
if ($startDate >= $endDate) {
return self::$_errorCodes['num'];
$difference = $endDate - $startDate;
$PHPStartDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($startDate);
$startDays = $PHPStartDateObject->format('j');
$startMonths = $PHPStartDateObject->format('n');
$startYears = $PHPStartDateObject->format('Y');
$endDays = $PHPEndDateObject->format('j');
$endMonths = $PHPEndDateObject->format('n');
$endYears = $PHPEndDateObject->format('Y');
$retVal = self::$_errorCodes['num'];
$retVal = intval($difference);
$retVal = intval($endMonths - $startMonths) + (intval($endYears - $startYears) * 12);
// We're only interested in full months
if ($endDays < $startDays) {
$retVal = intval($endYears - $startYears);
// We're only interested in full months
if ($endMonths < $startMonths) {
} elseif (($endMonths == $startMonths) && ($endDays < $startDays)) {
if ($endDays < $startDays) {
$PHPEndDateObject->modify('-'. $endDays. ' days');
$adjustDays = $PHPEndDateObject->format('j');
if ($adjustDays > $startDays) {
$retVal += ($adjustDays - $startDays);
$retVal = $endDays - $startDays;
$retVal = abs(intval($endMonths - $startMonths));
// We're only interested in full months
if ($endDays < $startDays) {
$retVal = intval($difference);
if ($endYears > $startYears) {
while ($endYears > $startYears) {
$PHPEndDateObject->modify('-1 year');
$endYears = $PHPEndDateObject->format('Y');
$retVal = abs($PHPEndDateObject->format('z') - $PHPStartDateObject->format('z'));
* @param long $startDate Excel date serial value or a standard date string
* @param long $endDate Excel date serial value or a standard date string
* @param integer $method Method used for the calculation
* @return long PHP date/time serial
public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) {
$startDate = self::flattenSingleValue($startDate);
$endDate = self::flattenSingleValue($endDate);
$method = self::flattenSingleValue($method);
if (is_string($startDate = self::_getDateValue($startDate))) {
return self::$_errorCodes['value'];
if (is_string($endDate = self::_getDateValue($endDate))) {
return self::$_errorCodes['value'];
if ((is_numeric($method)) && (!is_string($method))) {
return self::DAYS360($startDate,$endDate) / 360;
$startYear = self::YEAR($startDate);
$endYear = self::YEAR($endDate);
if (self::isLeapYear($startYear) || self::isLeapYear($endYear)) {
return self::DATEDIF($startDate,$endDate) / (365 + $leapDay);
return self::DATEDIF($startDate,$endDate) / 360;
return self::DATEDIF($startDate,$endDate) / 365;
return self::DAYS360($startDate,$endDate,True) / 360;
return self::$_errorCodes['value'];
* @param mixed Start date
* @param array of mixed Optional Date Series
* @return long Interval between the dates
public static function NETWORKDAYS($startDate,$endDate) {
// Flush the mandatory start and end date that are referenced in the function definition
// Validate the start and end dates
if (is_string($startDate = $sDate = self::_getDateValue($startDate))) {
return self::$_errorCodes['value'];
if (is_string($endDate = $eDate = self::_getDateValue($endDate))) {
return self::$_errorCodes['value'];
$startDoW = 6 - self::DAYOFWEEK($startDate,2);
if ($startDoW < 0) { $startDoW = 0; }
$endDoW = self::DAYOFWEEK($endDate,2);
if ($endDoW >= 6) { $endDoW = 0; }
$wholeWeekDays = floor(($endDate - $startDate) / 7) * 5;
$partWeekDays = $endDoW + $startDoW;
// Test any extra holiday parameters
$holidayCountedArray = array();
foreach ($dateArgs as $holidayDate) {
if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
return self::$_errorCodes['value'];
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
$holidayCountedArray[] = $holidayDate;
return 0 - ($wholeWeekDays + $partWeekDays);
return $wholeWeekDays + $partWeekDays;
* @param mixed Start date
* @param mixed number of days for adjustment
* @param array of mixed Optional Date Series
* @return long Interval between the dates
public static function WORKDAY($startDate,$endDays) {
if (is_string($startDate = self::_getDateValue($startDate))) {
return self::$_errorCodes['value'];
if (!is_numeric($endDays)) {
return self::$_errorCodes['value'];
$endDate = (float) $startDate + (floor($endDays / 5) * 7) + ($endDays % 5);
$endDoW = self::DAYOFWEEK($endDate,3);
$endDate += (7 - $endDoW);
$endDate -= ($endDoW - 5);
// Test any extra holiday parameters
if (count($dateArgs) > 0) {
$holidayCountedArray = $holidayDates = array();
foreach ($dateArgs as $holidayDate) {
if (is_string($holidayDate = self::_getDateValue($holidayDate))) {
return self::$_errorCodes['value'];
$holidayDates[] = $holidayDate;
sort($holidayDates, SORT_NUMERIC);
rsort($holidayDates, SORT_NUMERIC);
foreach ($holidayDates as $holidayDate) {
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
$holidayCountedArray[] = $holidayDate;
if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
if ((self::DAYOFWEEK($holidayDate,2) < 6) && (!in_array($holidayDate,$holidayCountedArray))) {
$holidayCountedArray[] = $holidayDate;
$endDoW = self::DAYOFWEEK($endDate,3);
$endDate += (7 - $endDoW);
$endDate -= ($endDoW - 5);
switch (self::getReturnDateType()) {
case self::RETURNDATE_EXCEL : return (float) $endDate;
* @param long $dateValue Excel date serial value or a standard date string
public static function DAYOFMONTH($dateValue = 1) {
$dateValue = self::flattenSingleValue($dateValue);
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
return $PHPDateObject->format('j');
* @param long $dateValue Excel date serial value or a standard date string
public static function DAYOFWEEK($dateValue = 1, $style = 1) {
$dateValue = self::flattenSingleValue($dateValue);
$style = floor(self::flattenSingleValue($style));
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
$DoW = $PHPDateObject->format('w');
case 2: if ($DoW == 0) { $DoW = 7; }
case 3: if ($DoW == 0) { $DoW = 7; }
if (self::$compatibilityMode == self::COMPATIBILITY_EXCEL) {
// Test for Excel's 1900 leap year, and introduce the error as required
if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) {
* @param long $dateValue Excel date serial value or a standard date string
* @param boolean $method Week begins on Sunday or Monday
* @return int Week Number
public static function WEEKOFYEAR($dateValue = 1, $method = 1) {
$dateValue = self::flattenSingleValue($dateValue);
$method = floor(self::flattenSingleValue($method));
return self::$_errorCodes['value'];
} elseif (($method < 1) || ($method > 2)) {
return self::$_errorCodes['num'];
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
$dayOfYear = $PHPDateObject->format('z');
$dow = $PHPDateObject->format('w');
$PHPDateObject->modify('-'. $dayOfYear. ' days');
$dow = $PHPDateObject->format('w');
$daysInFirstWeek = 7 - (($dow + (2 - $method)) % 7);
$dayOfYear -= $daysInFirstWeek;
$weekOfYear = ceil($dayOfYear / 7) + 1;
* @param long $dateValue Excel date serial value or a standard date string
$dateValue = self::flattenSingleValue($dateValue);
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
return $PHPDateObject->format('n');
* @param long $dateValue Excel date serial value or a standard date string
public static function YEAR($dateValue = 1) {
$dateValue = self::flattenSingleValue($dateValue);
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = PHPExcel_Shared_Date::ExcelToPHPObject($dateValue);
return $PHPDateObject->format('Y');
* @param mixed $timeValue Excel time serial value or a standard time string
public static function HOUROFDAY($timeValue = 0) {
$timeValue = self::flattenSingleValue($timeValue);
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
$testVal = strtok($timeValue,'/-: ');
return self::$_errorCodes['value'];
$timeValue = self::_getTimeValue($timeValue);
return self::$_errorCodes['value'];
if (is_real($timeValue)) {
return date('G',$timeValue);
* @param long $timeValue Excel time serial value or a standard time string
$timeValue = $timeTester = self::flattenSingleValue($timeValue);
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
$testVal = strtok($timeValue,'/-: ');
return self::$_errorCodes['value'];
$timeValue = self::_getTimeValue($timeValue);
return self::$_errorCodes['value'];
if (is_real($timeValue)) {
return (int) date('i',$timeValue);
* @param long $timeValue Excel time serial value or a standard time string
$timeValue = self::flattenSingleValue($timeValue);
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
$testVal = strtok($timeValue,'/-: ');
return self::$_errorCodes['value'];
$timeValue = self::_getTimeValue($timeValue);
return self::$_errorCodes['value'];
if (is_real($timeValue)) {
return (int) date('s',$timeValue);
$oMonth = (int) $PHPDateObject->format('m');
$oYear = (int) $PHPDateObject->format('Y');
$adjustmentMonthsString = (string) $adjustmentMonths;
if ($adjustmentMonths > 0) {
$adjustmentMonthsString = '+'. $adjustmentMonths;
if ($adjustmentMonths != 0) {
$PHPDateObject->modify($adjustmentMonthsString. ' months');
$nMonth = (int) $PHPDateObject->format('m');
$nYear = (int) $PHPDateObject->format('Y');
$monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
if ($monthDiff != $adjustmentMonths) {
$adjustDays = (int) $PHPDateObject->format('d');
$adjustDaysString = '-'. $adjustDays. ' days';
$PHPDateObject->modify($adjustDaysString);
* Returns the serial number that represents the date that is the indicated number of months before or after a specified date
* (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.
* @param long $dateValue Excel date serial value or a standard date string
* @param int $adjustmentMonths Number of months to adjust by
* @return long Excel date serial value
public static function EDATE($dateValue = 1, $adjustmentMonths = 0) {
$dateValue = self::flattenSingleValue($dateValue);
$adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
return self::$_errorCodes['value'];
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = self::adjustDateByMonths($dateValue,$adjustmentMonths);
switch (self::getReturnDateType()) {
case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
* Returns the serial number for the last day of the month that is the indicated number of months before or after start_date.
* Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
* @param long $dateValue Excel date serial value or a standard date string
* @param int $adjustmentMonths Number of months to adjust by
* @return long Excel date serial value
public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) {
$dateValue = self::flattenSingleValue($dateValue);
$adjustmentMonths = floor(self::flattenSingleValue($adjustmentMonths));
return self::$_errorCodes['value'];
if (is_string($dateValue = self::_getDateValue($dateValue))) {
return self::$_errorCodes['value'];
$PHPDateObject = self::adjustDateByMonths($dateValue,$adjustmentMonths+ 1);
$adjustDays = (int) $PHPDateObject->format('d');
$adjustDaysString = '-'. $adjustDays. ' days';
$PHPDateObject->modify($adjustDaysString);
switch (self::getReturnDateType()) {
case self::RETURNDATE_PHP_OBJECT : return $PHPDateObject;
* Truncates value to the number of fractional digits by number_digits.
* @param int $number_digits
* @return float Truncated value
public static function TRUNC($value = 0, $number_digits = 0) {
$value = self::flattenSingleValue($value);
$number_digits = self::flattenSingleValue($number_digits);
if ($number_digits < 0) {
return self::$_errorCodes['value'];
if ($number_digits > 0) {
$value = $value * pow(10, $number_digits);
if ($number_digits > 0) {
$value = $value / pow(10, $number_digits);
* Computes x raised to the power y.
public static function POWER($x = 0, $y = 2) {
$x = self::flattenSingleValue($x);
$y = self::flattenSingleValue($y);
return self::$_errorCodes['num'];
if ($x == 0 && $y <= 0) {
return self::$_errorCodes['divisionbyzero'];
* Return a binary value as Decimal.
$x = self::flattenSingleValue($x);
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
return self::$_errorCodes['value'];
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
return self::$_errorCodes['num'];
return self::$_errorCodes['num'];
} elseif (strlen($x) == 10) {
* Return a binary value as Hex.
$x = floor(self::flattenSingleValue($x));
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
return self::$_errorCodes['value'];
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
return self::$_errorCodes['num'];
return self::$_errorCodes['num'];
} elseif (strlen($x) == 10) {
* Return a binary value as Octal.
$x = floor(self::flattenSingleValue($x));
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
return self::$_errorCodes['value'];
if (self::$compatibilityMode == self::COMPATIBILITY_GNUMERIC) {
return self::$_errorCodes['num'];
return self::$_errorCodes['num'];
} elseif (strlen($x) == 10) {
* Return an octal value as binary.
$x = self::flattenSingleValue($x);
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
return self::$_errorCodes['value'];
return self::$_errorCodes['value'];
return self::$_errorCodes['num'];
* Return an octal value as binary.
public static function DECTOOCT($x) {
$x = self::flattenSingleValue($x);
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
return self::$_errorCodes['value'];
return self::$_errorCodes['value'];
* Return an octal value as binary.
$x = self::flattenSingleValue($x);
if (self::$compatibilityMode == self::COMPATIBILITY_OPENOFFICE) {
return self::$_errorCodes['value'];
return self::$_errorCodes['value'];
* Return a hex value as binary.
$x = self::flattenSingleValue($x);
return self::$_errorCodes['value'];
return self::$_errorCodes['num'];
* Return a hex value as octal.
$x = self::flattenSingleValue($x);
return self::$_errorCodes['value'];
return self::$_errorCodes['num'];
* Return a hex value as octal.
$x = self::flattenSingleValue($x);
return self::$_errorCodes['value'];
return self::$_errorCodes['num'];
* Return an octal value as binary.
$x = self::flattenSingleValue($x);
return self::$_errorCodes['value'];
if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
return self::$_errorCodes['num'];
* Return an octal value as binary.
$x = self::flattenSingleValue($x);
return self::$_errorCodes['value'];
if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
return self::$_errorCodes['num'];
* Return an octal value as hex.
$x = self::flattenSingleValue($x);
return self::$_errorCodes['value'];
if (preg_match_all('/[01234567]/',$x,$out) != strlen($x)) {
return self::$_errorCodes['num'];
$workString = $complexNumber;
$realNumber = $imaginary = 0;
// Extract the suffix, if there is one
$suffix = substr($workString,- 1);
$workString = substr($workString,0,- 1);
// Split the input into its Real and Imaginary components
$leadingSign = (($workString{0} == '+') || ($workString{0} == '-')) ? 1 : 0;
$realNumber = strtok($workString, '+-');
if (($imaginary == '') && (($realNumber == '') || ($realNumber == '+') || ($realNumber == '-'))) {
$imaginary = $realNumber. '1';
} else if ($imaginary == '') {
$imaginary = $realNumber;
} elseif (($imaginary == '+') || ($imaginary == '-')) {
$complexArray = array( 'real' => $realNumber,
'imaginary' => $imaginary,
* returns a complex number of the form x + yi or x + yj.
* @param float $realNumber
* @param float $imaginary
public static function COMPLEX($realNumber= 0.0, $imaginary= 0.0, $suffix= 'i') {
$realNumber = self::flattenSingleValue($realNumber);
$imaginary = self::flattenSingleValue($imaginary);
$suffix = self::flattenSingleValue($suffix);
(($suffix == 'i') || ($suffix == 'j'))) {
if ($realNumber == 0.0) {
} elseif ($imaginary == 1.0) {
} elseif ($imaginary == - 1.0) {
return (string) '-'. $suffix;
return (string) $imaginary. $suffix;
} elseif ($imaginary == 0.0) {
return (string) $realNumber;
} elseif ($imaginary == 1.0) {
return (string) $realNumber. '+'. $suffix;
} elseif ($imaginary == - 1.0) {
return (string) $realNumber. '-'. $suffix;
if ($imaginary > 0) { $imaginary = (string) '+'. $imaginary; }
return (string) $realNumber. $imaginary. $suffix;
return self::$_errorCodes['value'];
* Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMAGINARY($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
return $parsedComplex['imaginary'];
* Returns the real coefficient of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMREAL($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
return $parsedComplex['real'];
* Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMABS($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
return sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
* Returns the argument theta of a complex number, i.e. the angle in radians from the real axis to the representation of the number in polar coordinates.
* @param string $complexNumber
public static function IMARGUMENT($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if ($parsedComplex['real'] == 0.0) {
if ($parsedComplex['imaginary'] == 0.0) {
} elseif($parsedComplex['imaginary'] < 0.0) {
} elseif ($parsedComplex['real'] > 0.0) {
return atan($parsedComplex['imaginary'] / $parsedComplex['real']);
} elseif ($parsedComplex['imaginary'] < 0.0) {
return 0 - (pi() - atan(abs($parsedComplex['imaginary']) / abs($parsedComplex['real'])));
return pi() - atan($parsedComplex['imaginary'] / abs($parsedComplex['real']));
* Returns the complex conjugate of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if ($parsedComplex['imaginary'] == 0.0) {
return $parsedComplex['real'];
return self::COMPLEX($parsedComplex['real'], 0 - $parsedComplex['imaginary'], $parsedComplex['suffix']);
* Returns the cosine of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMCOS($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if ($parsedComplex['imaginary'] == 0.0) {
return cos($parsedComplex['real']);
return self::IMCONJUGATE(self::COMPLEX(cos($parsedComplex['real']) * cosh($parsedComplex['imaginary']),sin($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']));
* Returns the sine of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMSIN($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if ($parsedComplex['imaginary'] == 0.0) {
return sin($parsedComplex['real']);
return self::COMPLEX(sin($parsedComplex['real']) * cosh($parsedComplex['imaginary']),cos($parsedComplex['real']) * sinh($parsedComplex['imaginary']),$parsedComplex['suffix']);
* Returns the square root of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMSQRT($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
$theta = self::IMARGUMENT($complexNumber);
$r = sqrt(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
if ($parsedComplex['suffix'] == '') {
return self::COMPLEX($d1 * $r,$d2 * $r);
return self::COMPLEX($d1 * $r,$d2 * $r,$parsedComplex['suffix']);
* Returns the natural logarithm of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMLN($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
return self::$_errorCodes['num'];
$logR = log(sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary'])));
$t = self::IMARGUMENT($complexNumber);
if ($parsedComplex['suffix'] == '') {
return self::COMPLEX($logR,$t);
return self::COMPLEX($logR,$t,$parsedComplex['suffix']);
* Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMLOG10($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
return self::$_errorCodes['num'];
} elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
return log10($parsedComplex['real']);
return self::IMPRODUCT(log10(EULER),self::IMLN($complexNumber));
* Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMLOG2($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
return self::$_errorCodes['num'];
} elseif (($parsedComplex['real'] > 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
return log($parsedComplex['real'],2);
return self::IMPRODUCT(log(EULER,2),self::IMLN($complexNumber));
* Returns the exponential of a complex number in x + yi or x + yj text format.
* @param string $complexNumber
public static function IMEXP($complexNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$parsedComplex = self::parseComplex($complexNumber);
if (($parsedComplex['real'] == 0.0) && ($parsedComplex['imaginary'] == 0.0)) {
$e = exp($parsedComplex['real']);
$eX = $e * cos($parsedComplex['imaginary']);
$eY = $e * sin($parsedComplex['imaginary']);
if ($parsedComplex['suffix'] == '') {
return self::COMPLEX($eX,$eY);
return self::COMPLEX($eX,$eY,$parsedComplex['suffix']);
* Returns a complex number in x + yi or x + yj text format raised to a power.
* @param string $complexNumber
public static function IMPOWER($complexNumber,$realNumber) {
$complexNumber = self::flattenSingleValue($complexNumber);
$realNumber = self::flattenSingleValue($realNumber);
return self::$_errorCodes['value'];
$parsedComplex = self::parseComplex($complexNumber);
$r = sqrt(($parsedComplex['real'] * $parsedComplex['real']) + ($parsedComplex['imaginary'] * $parsedComplex['imaginary']));
$rPower = pow($r,$realNumber);
$theta = self::IMARGUMENT($complexNumber) * $realNumber;
if ($parsedComplex['imaginary'] == 0.0) {
return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
return self::COMPLEX($rPower * cos($theta),$rPower * sin($theta),$parsedComplex['suffix']);
* Returns the quotient of two complex numbers in x + yi or x + yj text format.
* @param string $complexDividend
* @param string $complexDivisor
public static function IMDIV($complexDividend,$complexDivisor) {
$complexDividend = self::flattenSingleValue($complexDividend);
$complexDivisor = self::flattenSingleValue($complexDivisor);
$parsedComplexDividend = self::parseComplex($complexDividend);
if (!is_array($parsedComplexDividend)) {
return $parsedComplexDividend;
$parsedComplexDivisor = self::parseComplex($complexDivisor);
return $parsedComplexDividend;
if ($parsedComplexDividend['suffix'] != $parsedComplexDivisor['suffix']) {
return self::$_errorCodes['num'];
$d1 = ($parsedComplexDividend['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['imaginary']);
$d2 = ($parsedComplexDividend['imaginary'] * $parsedComplexDivisor['real']) - ($parsedComplexDividend['real'] * $parsedComplexDivisor['imaginary']);
$d3 = ($parsedComplexDivisor['real'] * $parsedComplexDivisor['real']) + ($parsedComplexDivisor['imaginary'] * $parsedComplexDivisor['imaginary']);
return $d1/ $d3. $d2/ $d3. $parsedComplexDivisor['suffix'];
* Returns the difference of two complex numbers in x + yi or x + yj text format.
* @param string $complexNumber1
* @param string $complexNumber2
public static function IMSUB($complexNumber1,$complexNumber2) {
$complexNumber1 = self::flattenSingleValue($complexNumber1);
$complexNumber2 = self::flattenSingleValue($complexNumber2);
$parsedComplex1 = self::parseComplex($complexNumber1);
$parsedComplex2 = self::parseComplex($complexNumber2);
if ($parsedComplex1['suffix'] != $parsedComplex2['suffix']) {
return self::$_errorCodes['num'];
$d1 = $parsedComplex1['real'] - $parsedComplex2['real'];
$d2 = $parsedComplex1['imaginary'] - $parsedComplex2['imaginary'];
return self::COMPLEX($d1,$d2,$parsedComplex1['suffix']);
* Returns the sum of two or more complex numbers in x + yi or x + yj text format.
* @param array of mixed Data Series
public static function IMSUM() {
$returnValue = self::parseComplex('0');
// Loop through the arguments
foreach ($aArgs as $arg) {
$parsedComplex = self::parseComplex($arg);
if ($activeSuffix == '') {
$activeSuffix = $parsedComplex['suffix'];
} elseif ($activeSuffix != $parsedComplex['suffix']) {
return self::$_errorCodes['num'];
$returnValue['real'] += $parsedComplex['real'];
$returnValue['imaginary'] += $parsedComplex['imaginary'];
if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
* Returns the product of two or more complex numbers in x + yi or x + yj text format.
* @param array of mixed Data Series
$returnValue = self::parseComplex('1');
// Loop through the arguments
foreach ($aArgs as $arg) {
$parsedComplex = self::parseComplex($arg);
$workValue = $returnValue;
if (($parsedComplex['suffix'] != '') && ($activeSuffix == '')) {
$activeSuffix = $parsedComplex['suffix'];
} elseif (($parsedComplex['suffix'] != '') && ($activeSuffix != $parsedComplex['suffix'])) {
return self::$_errorCodes['num'];
$returnValue['real'] = ($workValue['real'] * $parsedComplex['real']) - ($workValue['imaginary'] * $parsedComplex['imaginary']);
$returnValue['imaginary'] = ($workValue['real'] * $parsedComplex['imaginary']) + ($workValue['imaginary'] * $parsedComplex['real']);
if ($returnValue['imaginary'] == 0.0) { $activeSuffix = ''; }
return self::COMPLEX($returnValue['real'],$returnValue['imaginary'],$activeSuffix);
* Returns the modified Bessel function, which is equivalent to the Bessel function evaluated for purely imaginary arguments
public static function BESSELI($x, $n) {
$x = self::flattenSingleValue($x);
$n = floor(self::flattenSingleValue($n));
return self::$_errorCodes['num'];
$fTerm = pow($x / 2, $n) / self::FACT($n);
$fTerm /= ($nK * ($nK + $n));
} while ((abs($fTerm) > 1e-10) && (++ $nK < 100));
$fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs);
if (($n && 1) && ($x < 0)) {
return self::$_errorCodes['value'];
* Returns the Bessel function
public static function BESSELJ($x, $n) {
$x = self::flattenSingleValue($x);
$n = floor(self::flattenSingleValue($n));
return self::$_errorCodes['num'];
$fTerm = pow($x / 2, $n) / self::FACT($n);
$fTerm /= ($nK * ($nK + $n));
} while ((abs($fTerm) > 1e-10) && (++ $nK < 100));
$fResult = sqrt($f_2_DIV_PI / $fXAbs) * cos($fXAbs - $n * $f_PI_DIV_2 - $f_PI_DIV_4);
if (($n && 1) && ($x < 0)) {
return self::$_errorCodes['value'];
private static function Besselk0($fNum) {
$fRet = - log($fNum2) * self::BESSELI($fNum, 0) +
(- 0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y *
(0.10750e-3 + $y * 0.74e-5))))));
$fRet = exp(- $fNum) / sqrt($fNum) *
(1.25331414 + $y * (- 0.7832358e-1 + $y * (0.2189568e-1 + $y * (- 0.1062446e-1 + $y *
(0.587872e-2 + $y * (- 0.251540e-2 + $y * 0.53208e-3))))));
private static function Besselk1($fNum) {
$fRet = log($fNum2) * self::BESSELI($fNum, 1) +
(1 + $y * (0.15443144 + $y * (- 0.67278579 + $y * (- 0.18156897 + $y * (- 0.1919402e-1 + $y *
(- 0.110404e-2 + $y * (- 0.4686e-4))))))) / $fNum;
$fRet = exp(- $fNum) / sqrt($fNum) *
(1.25331414 + $y * (0.23498619 + $y * (- 0.3655620e-1 + $y * (0.1504268e-1 + $y * (- 0.780353e-2 + $y *
(0.325614e-2 + $y * (- 0.68245e-3)))))));
* Returns the modified Bessel function, which is equivalent to the Bessel functions evaluated for purely imaginary arguments.
public static function BESSELK($x, $ord) {
$x = self::flattenSingleValue($x);
$n = floor(self::flattenSingleValue($ord));
return self::$_errorCodes['num'];
case 0 : return self::Besselk0($x);
case 1 : return self::Besselk1($x);
default : $fTox = 2 / $x;
$fBkm = self::Besselk0($x);
$fBk = self::Besselk1($x);
for ($n = 1; $n < $ord; ++ $n) {
$fBkp = $fBkm + $n * $fTox * $fBk;
return self::$_errorCodes['value'];
private static function Bessely0($fNum) {
$f1 = - 2957821389.0 + $y * (7062834065.0 + $y * (- 512359803.6 + $y * (10879881.29 + $y * (- 86327.92757 + $y * 228.4622733))));
$f2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * (47447.26470 + $y * (226.1030244 + $y))));
$fRet = $f1 / $f2 + 0.636619772 * self::BESSELJ($fNum, 0) * log($fNum);
$xx = $fNum - 0.785398164;
$f1 = 1 + $y * (- 0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (- 0.2073370639e-5 + $y * 0.2093887211e-6)));
$f2 = - 0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (- 0.6911147651e-5 + $y * (0.7621095161e-6 + $y * (- 0.934945152e-7))));
$fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
private static function Bessely1($fNum) {
$f1 = $fNum * (- 0.4900604943e13 + $y * (0.1275274390e13 + $y * (- 0.5153438139e11 + $y * (0.7349264551e9 + $y *
(- 0.4237922726e7 + $y * 0.8511937935e4)))));
$f2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
(0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
$fRet = $f1 / $f2 + 0.636619772 * ( self::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum);
$xx = $fNum - 2.356194491;
$f1 = 1 + $y * (0.183105e-2 + $y * (- 0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (- 0.240337019e6))));
$f2 = 0.04687499995 + $y * (- 0.2002690873e-3 + $y * (0.8449199096e-5 + $y * (- 0.88228987e-6 + $y * 0.105787412e-6)));
$fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
#i12430# ...but this seems to work much better.
// $fRet = sqrt(0.636619772 / $fNum) * sin($fNum - 2.356194491);
* Returns the Bessel function, which is also called the Weber function or the Neumann function.
public static function BESSELY($x, $ord) {
$x = self::flattenSingleValue($x);
$n = floor(self::flattenSingleValue($ord));
return self::$_errorCodes['num'];
case 0 : return self::Bessely0($x);
case 1 : return self::Bessely1($x);
$fBym = self::Bessely0($x);
$fBy = self::Bessely1($x);
for ($n = 1; $n < $ord; ++ $n) {
$fByp = $n * $fTox * $fBy - $fBym;
return self::$_errorCodes['value'];
* Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
public static function DELTA($a, $b= 0) {
$a = self::flattenSingleValue($a);
$b = self::flattenSingleValue($b);
* Returns 1 if number = step; returns 0 (zero) otherwise
public static function GESTEP($number, $step= 0) {
$number = self::flattenSingleValue($number);
$step = self::flattenSingleValue($step);
return (int) ($number >= $step);
// Private method to calculate the erf value
private static $two_sqrtpi = 1.128379167095512574;
private static $rel_error = 1E-15;
private static function erfVal($x) {
return 1 - self::erfcVal($x);
$sum -= $term / (2 * $j + 1);
$sum += $term / (2 * $j + 1);
} while (abs($term / $sum) > self::$rel_error);
return self::$two_sqrtpi * $sum;
* Returns the error function integrated between lower_limit and upper_limit
* @param float $lower lower bound for integrating ERF
* @param float $upper upper bound for integrating ERF.
* If omitted, ERF integrates between zero and lower_limit
public static function ERF($lower, $upper = 0) {
$lower = self::flattenSingleValue($lower);
$upper = self::flattenSingleValue($upper);
if (($lower < 0) || ($upper < 0)) {
return self::$_errorCodes['num'];
return self::erfVal($upper) - self::erfVal($lower);
return self::erfVal($lower) - self::erfVal($upper);
return self::$_errorCodes['value'];
// Private method to calculate the erfc value
private static $one_sqrtpi = 0.564189583547756287;
private static function erfcVal($x) {
return 1 - self::erfVal($x);
return 2 - self::erfc(- $x);
} while ((abs($q1 - $q2) / $q2) > self::$rel_error);
return self::$one_sqrtpi * exp(- $x * $x) * $q2;
* Returns the complementary ERF function integrated between x and infinity
* @param float $x The lower bound for integrating ERF
public static function ERFC($x) {
$x = self::flattenSingleValue($x);
return self::$_errorCodes['num'];
return self::erfcVal($x);
return self::$_errorCodes['value'];
* Returns the effective interest rate given the nominal rate and the number of compounding payments per year.
* @param float $nominal_rate Nominal interest rate
* @param int $npery Number of compounding payments per year
public static function EFFECT($nominal_rate = 0, $npery = 0) {
$nominal_rate = self::flattenSingleValue($ $nominal_rate);
$npery = (int) self::flattenSingleValue($npery);
if ($ $nominal_rate <= 0 || $npery < 1) {
return self::$_errorCodes['num'];
return pow((1 + $nominal_rate / $npery), $npery) - 1;
* Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
* @param float $effect_rate Effective interest rate
* @param int $npery Number of compounding payments per year
public static function NOMINAL($effect_rate = 0, $npery = 0) {
$effect_rate = self::flattenSingleValue($effect_rate);
$npery = (int) self::flattenSingleValue($npery);
if ($effect_rate <= 0 || $npery < 1) {
return self::$_errorCodes['num'];
return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
* Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
* @param float $rate Interest rate per period
* @param int $nper Number of periods
* @param float $pmt Periodic payment (annuity)
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0) {
$rate = self::flattenSingleValue($rate);
$nper = self::flattenSingleValue($nper);
$pmt = self::flattenSingleValue($pmt);
$fv = self::flattenSingleValue($fv);
$type = self::flattenSingleValue($type);
if ($type != 0 && $type != 1) {
return self::$_errorCodes['num'];
if (!is_null($rate) && $rate != 0) {
return (- $pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
return - $fv - $pmt * $nper;
* Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
* @param float $rate Interest rate per period
* @param int $nper Number of periods
* @param float $pmt Periodic payment (annuity)
* @param float $pv Present Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0) {
$rate = self::flattenSingleValue($rate);
$nper = self::flattenSingleValue($nper);
$pmt = self::flattenSingleValue($pmt);
$pv = self::flattenSingleValue($pv);
$type = self::flattenSingleValue($type);
if ($type != 0 && $type != 1) {
return self::$_errorCodes['num'];
if (!is_null($rate) && $rate != 0) {
return - $pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
return - $pv - $pmt * $nper;
* Returns the constant payment (annuity) for a cash flow with a constant interest rate.
* @param float $rate Interest rate per period
* @param int $nper Number of periods
* @param float $pv Present Value
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) {
$rate = self::flattenSingleValue($rate);
$nper = self::flattenSingleValue($nper);
$pv = self::flattenSingleValue($pv);
$fv = self::flattenSingleValue($fv);
$type = self::flattenSingleValue($type);
if ($type != 0 && $type != 1) {
return self::$_errorCodes['num'];
if (!is_null($rate) && $rate != 0) {
return (- $fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
return (- $pv - $fv) / $nper;
* Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
* @param float $rate Interest rate per period
* @param int $pmt Periodic payment (annuity)
* @param float $pv Present Value
* @param float $fv Future Value
* @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0) {
$rate = self::flattenSingleValue($rate);
$pmt = self::flattenSingleValue($pmt);
$pv = self::flattenSingleValue($pv);
$fv = self::flattenSingleValue($fv);
$type = self::flattenSingleValue($type);
if ($type != 0 && $type != 1) {
return self::$_errorCodes['num'];
if (!is_null($rate) && $rate != 0) {
if ($pmt == 0 && $pv == 0) {
return self::$_errorCodes['num'];
return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
return self::$_errorCodes['num'];
return (- $pv - $fv) / $pmt;
* Returns the Net Present Value of a cash flow series given a discount rate.
* @param float Discount interest rate
* @param array Cash flow series
public static function NPV() {
for ($i = 1; $i <= count($aArgs); ++ $i) {
// Is it a numeric value?
$returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
* Computes the accrued interest for a security that pays periodic interest.
* @param int $firstInterest
* @return int The accrued interest for a security that pays periodic interest.
public static function ACCRINT($issue = 0, $firstInterest = 0, $settlement = 0, $rate = 0, $par = 1000, $frequency = 1, $basis = 0) {
$issue = self::flattenSingleValue($issue);
$firstInterest = self::flattenSingleValue($firstInterest);
$settlement = self::flattenSingleValue($settlement);
$rate = self::flattenSingleValue($rate);
$par = self::flattenSingleValue($par);
$frequency = self::flattenSingleValue($frequency);
$basis = self::flattenSingleValue($basis);
if ($issue >= $settlement || $rate <= 0 || $par <= 0 || !($frequency == 1 || $frequency == 2 || $frequency == 4) || $basis < 0 || $basis > 4) return self::$_errorCodes['num'];
return $par * ($rate / $frequency) *
* Returns the straight-line depreciation of an asset for one period
* @param cost Initial cost of the asset
* @param salvage Value at the end of the depreciation
* @param life Number of periods over which the asset is depreciated
public static function SLN($cost, $salvage, $life) {
$cost = self::flattenSingleValue($cost);
$salvage = self::flattenSingleValue($salvage);
$life = self::flattenSingleValue($life);
return self::$_errorCodes['num'];
return ($cost - $salvage) / $life;
return self::$_errorCodes['value'];
* Returns the straight-line depreciation of an asset for one period
* @param row Row number to use in the cell reference
* @param column Column number to use in the cell reference
* @param relativity Flag indicating the type of reference to return
* @param sheetText Name of worksheet to use
public static function CELL_ADDRESS($row, $column, $relativity= 1, $referenceStyle= True, $sheetText= '') {
$row = self::flattenSingleValue($row);
$column = self::flattenSingleValue($column);
$relativity = self::flattenSingleValue($relativity);
$sheetText = self::flattenSingleValue($sheetText);
if (strpos($sheetText,' ') !== False) { $sheetText = "'". $sheetText. "'"; }
if (($relativity == 2) || ($relativity == 4)) { $column = '['. $column. ']'; }
if (($relativity == 3) || ($relativity == 4)) { $row = '['. $row. ']'; }
return $sheetText. 'R'. $row. 'C'. $column;
$rowRelative = $columnRelative = '$';
if (($relativity == 2) || ($relativity == 4)) { $columnRelative = ''; }
if (($relativity == 3) || ($relativity == 4)) { $rowRelative = ''; }
return $sheetText. $columnRelative. $column. $rowRelative. $row;
public static function COLUMN($cellAddress= Null) {
if (is_null($cellAddress) || $cellAddress === '') {
foreach($cellAddress as $columnKey => $value) {
public static function ROW($cellAddress= Null) {
if ($cellAddress === Null) {
foreach($cellAddress as $columnKey => $rowValue) {
foreach($rowValue as $rowKey => $cellValue) {
public static function OFFSET($cellAddress= Null,$rows= 0,$columns= 0,$height= null,$width= null) {
if ($cellAddress == Null) {
foreach($cellAddress as $startColumnKey => $rowValue) {
foreach($rowValue as $startRowKey => $cellValue) {
foreach($cellAddress as $endColumnKey => $rowValue) {
foreach($rowValue as $endRowKey => $cellValue) {
$startColumnIndex += -- $columns;
$endColumnIndex += $columns - 1;
$endColumnIndex = $startColumnIndex + $width;
$endRowKey = $startRowKey + $height - 1;
if (($startColumnIndex < 0) || ($startRowKey <= 0)) {
return self::$_errorCodes['reference'];
$startColumnKey = PHPExcel_Cell::stringFromColumnIndex($startColumnIndex);
$startCell = $startColumnKey. $startRowKey;
$endCell = $endColumnKey. $endRowKey;
if ($startCell == $endCell) {
return $startColumnKey. $startRowKey;
return $startColumnKey. $startRowKey. ':'. $endColumnKey. $endRowKey;
public static function CHOOSE() {
$chosenEntry = self::flattenSingleValue(array_shift($chooseArgs));
$entryCount = count($chooseArgs) - 1;
return self::$_errorCodes['value'];
$chosenEntry = floor($chosenEntry);
if (($chosenEntry <= 0) || ($chosenEntry > $entryCount)) {
return self::$_errorCodes['value'];
if (is_array($chooseArgs[$chosenEntry])) {
return self::flattenArray($chooseArgs[$chosenEntry]);
return $chooseArgs[$chosenEntry];
* The MATCH function searches for a specified item in a range of cells
* @param lookup_value The value that you want to match in lookup_array
* @param lookup_array The range of cells being searched
* @param match_type The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. If match_type is 1 or -1, the list has to be ordered.
* @return integer the relative position of the found item
public static function MATCH($lookup_value, $lookup_array, $match_type= 1) {
// flatten the lookup_array
$lookup_array = self::flattenArray($lookup_array);
// flatten lookup_value since it may be a cell reference to a value or the value itself
$lookup_value = self::flattenSingleValue($lookup_value);
// MATCH is not case sensitive
echo "--------------------<br>looking for $lookup_value in <br>";
// lookup_value type has to be number, text, or logical values
// error: lookup_array should contain only number, text, or logical values
//echo "error: lookup_array should contain only number, text, or logical values<br>";
return self::$_errorCodes['na'];
// match_type is 0, 1 or -1
if ($match_type!== 0 && $match_type!==- 1 && $match_type!== 1){
// error: wrong value for match_type
//echo "error: wrong value for match_type<br>";
return self::$_errorCodes['na'];
// lookup_array should not be empty
if (sizeof($lookup_array)<= 0){
//echo "error: empty range ".sizeof($lookup_array)."<br>";
return self::$_errorCodes['na'];
// lookup_array should contain only number, text, or logical values
for ($i= 0;$i< sizeof($lookup_array);++ $i){
// check the type of the value
// error: lookup_array should contain only number, text, or logical values
//echo "error: lookup_array should contain only number, text, or logical values<br>";
return self::$_errorCodes['na'];
if (is_string($lookup_array[$i]))
$lookup_array[$i] = strtolower($lookup_array[$i]);
// if match_type is 1 or -1, the list has to be ordered
if($match_type== 1 || $match_type==- 1){
$iLastValue= $lookup_array[0];
for ($i= 0;$i< sizeof($lookup_array);++ $i){
if(($match_type== 1 && $lookup_array[$i]< $iLastValue)
// OR check descending order
|| ($match_type==- 1 && $lookup_array[$i]> $iLastValue)){
// error: list is not ordered correctly
//echo "error: list is not ordered correctly<br>";
return self::$_errorCodes['na'];
for ($i= 0; $i < sizeof($lookup_array); ++ $i){
// if match_type is 0 <=> find the first value that is exactly equal to lookup_value
if ($match_type== 0 && $lookup_array[$i]== $lookup_value){
// this is the exact match
// if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
if ($match_type==- 1 && $lookup_array[$i] < $lookup_value){
// 1st cell was allready smaller than the lookup_value
// the previous cell was the match
// if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
if ($match_type== 1 && $lookup_array[$i] > $lookup_value){
// 1st cell was allready bigger than the lookup_value
// the previous cell was the match
// unsuccessful in finding a match, return #N/A error value
//echo "unsuccessful in finding a match<br>";
return self::$_errorCodes['na'];
* Uses an index to choose a value from a reference or array
* implemented: Return the value of a specified cell or array of cells Array form
* not implemented: Return a reference to specified cells Reference form
* @param range_array a range of cells or an array constant
* @param row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required.
* @param column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required.
public static function INDEX($range_array,$row_num= null,$column_num= null) {
// at least one of row_num and column_num is required
if ($row_num== null && $column_num== null){
// error: row_num and column_num are both undefined
//echo "error: row_num and column_num are both undefined<br>";
return self::$_errorCodes['value'];
// default values for row_num and column_num
echo "<br>$row_num , $column_num<br>";
// row_num and column_num may not have negative values
if (($row_num!= null && $row_num < 0) || ($column_num!= null && $column_num < 0)) {
// error: row_num or column_num has negative value
//echo "error: row_num or column_num has negative value<br>";
return self::$_errorCodes['value'];
// convert column and row numbers into array indeces
$columnKeys = array_keys($range_array);
$rowKeys = array_keys($range_array[$columnKeys[0]]);
if ($column_num >= sizeof($columnKeys)){
// error: column_num is out of range
//echo "error: column_num is out of range - $column_num > ".sizeof($columnKeys)."<br>";
return self::$_errorCodes['reference'];
if ($row_num >= sizeof($rowKeys)){
// error: row_num is out of range
//echo "error: row_num is out of range - $row_num > ".sizeof($rowKeys)."<br>";
return self::$_errorCodes['reference'];
// compute and return result
return $range_array[$columnKeys[$column_num]][$rowKeys[$row_num]];
/* public static function INDEX($arrayValues,$rowNum = 0,$columnNum = 0) {
if (($rowNum < 0) || ($columnNum < 0)) {
return self::$_errorCodes['value'];
$columnKeys = array_keys($arrayValues);
$rowKeys = array_keys($arrayValues[$columnKeys[0]]);
if ($columnNum > count($columnKeys)) {
return self::$_errorCodes['value'];
} elseif ($columnNum == 0) {
$rowNum = $rowKeys[--$rowNum];
foreach($arrayValues as $arrayColumn) {
$returnArray[] = $arrayColumn[$rowNum];
$columnNum = $columnKeys[--$columnNum];
if ($rowNum > count($rowKeys)) {
return self::$_errorCodes['value'];
} elseif ($rowNum == 0) {
return $arrayValues[$columnNum];
$rowNum = $rowKeys[--$rowNum];
return $arrayValues[$columnNum][$rowNum];
* Returns the sum-of-years' digits depreciation of an asset for a specified period.
* @param cost Initial cost of the asset
* @param salvage Value at the end of the depreciation
* @param life Number of periods over which the asset is depreciated
public static function SYD($cost, $salvage, $life, $period) {
$cost = self::flattenSingleValue($cost);
$salvage = self::flattenSingleValue($salvage);
$life = self::flattenSingleValue($life);
$period = self::flattenSingleValue($period);
if (($life < 1) || ($salvage < $life)) {
return self::$_errorCodes['num'];
return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
return self::$_errorCodes['value'];
* @param mixed $value Value to check
* Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix.
public static function TRANSPOSE($matrixData) {
foreach($matrixData as $matrixRow) {
foreach($matrixRow as $matrixCell) {
$returnMatrix[$column][$row] = $matrixCell;
* @param mixed $value Value to check
public static function MMULT($matrixData1,$matrixData2) {
$matrixAData = $matrixBData = array();
foreach($matrixData1 as $matrixRow) {
foreach($matrixRow as $matrixCell) {
if ((is_string($matrixCell)) || ($matrixCell === null)) {
return self::$_errorCodes['value'];
$matrixAData[$columnA][$rowA] = $matrixCell;
$matrixA = new Matrix($matrixAData);
foreach($matrixData2 as $matrixRow) {
foreach($matrixRow as $matrixCell) {
if ((is_string($matrixCell)) || ($matrixCell === null)) {
return self::$_errorCodes['value'];
$matrixBData[$columnB][$rowB] = $matrixCell;
$matrixB = new Matrix($matrixBData);
if (($rowA != $columnB) || ($rowB != $columnA)) {
return self::$_errorCodes['value'];
return $matrixA->times($matrixB)->getArray();
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number.
* @param lookup_value The value that you want to match in lookup_array
* @param lookup_array The range of cells being searched
* @param index_number The column number in table_array from which the matching value must be returned. The first column is 1.
* @param not_exact_match Determines if you are looking for an exact match based on lookup_value.
* @return mixed The value of the found cell
public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match= true) {
// index_number must be greater than or equal to 1
return self::$_errorCodes['value'];
// index_number must be less than or equal to the number of columns in lookup_array
if ($index_number > count($lookup_array)) {
return self::$_errorCodes['reference'];
// re-index lookup_array with numeric keys starting at 1
array_unshift($lookup_array, array());
// look for an exact match
$row_number = array_search($lookup_value, $lookup_array[1]);
// if an exact match is required, we have what we need to return an appropriate response
if ($not_exact_match == false) {
if ($row_number === false) {
return self::$_errorCodes['na'];
return $lookup_array[$index_number][$row_number];
// TODO: The VLOOKUP spec in Excel states that, at this point, we should search for
// the highest value that is less than lookup_value. However, documentation on how string
// values should be treated here is sparse.
return self::$_errorCodes['na'];
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
* @param lookup_value The value that you want to match in lookup_array
* @param lookup_vector The range of cells being searched
* @param result_vector The column from which the matching value must be returned
* @return mixed The value of the found cell
public static function LOOKUP($lookup_value, $lookup_vector, $result_vector= null) {
// check for LOOKUP Syntax (view Excel documentation)
// TODO: Syntax 2 (array)
// get key (column or row) of lookup_vector
$kl = key($lookup_vector);
// check if lookup_value exists in lookup_vector
if( in_array($lookup_value, $lookup_vector[$kl]) )
// FOUND IT! Get key of lookup_vector
// Get the smallest value in lookup_vector
// The LOOKUP spec in Excel states --> IMPORTANT - The values in lookup_vector must be placed in ascending order!
$ksv = key($lookup_vector[$kl]);
$smallest_value = $lookup_vector[$kl][$ksv];
// If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP gives the #N/A error value.
if( $lookup_value < $smallest_value )
return self::$_errorCodes['na'];
// If LOOKUP can't find the lookup_value, it matches the largest value in lookup_vector that is less than or equal to lookup_value.
// IMPORTANT : In Excel Documentation is not clear what happen if lookup_value is text!
foreach( $lookup_vector[$kl] AS $kk => $value )
if( $lookup_value >= $value )
// Returns a value from the same position in result_vector
// get key (column or row) of result_vector
$kr = key($result_vector);
if( isset ($result_vector[$kr][$k_res]) )
return $result_vector[$kr][$k_res];
// TODO: In Excel Documentation is not clear what happen here...
* Flatten multidemensional array
* @param array $array Array to be flattened
* @return array Flattened array
$array = array ( $array );
foreach ($array as $value) {
$arrayValues[] = self::flattenSingleValue($value);
$arrayValues = array_merge($arrayValues, self::flattenArray($value));
* Convert an array with one element to a flat value
* @param mixed $value Array or flat value
$value = self::flattenSingleValue(array_pop($value));
// There are a few mathematical functions that aren't available on all versions of PHP for all platforms
// These functions aren't available in Windows implementations of PHP prior to version 5.3.0
// So we test if they do exist for this version of PHP/operating platform; and if not we create them
return 2 * log(sqrt(($x + 1) / 2) + sqrt(($x - 1) / 2));
return log($x + sqrt(1 + $x * $x));
return (log(1 + $x) - log(1 - $x)) / 2;
|