getMessage(); } if (($value < 0) || ($value > $trials)) { return ExcelError::NAN(); } if ($cumulative) { return self::calculateCumulativeBinomial($value, $trials, $probability); } /** @var float */ $comb = Combinations::withoutRepetition($trials, $value); return $comb * $probability ** $value * (1 - $probability) ** ($trials - $value); } /** * BINOM.DIST.RANGE. * * Returns returns the Binomial Distribution probability for the number of successes from a specified number * of trials falling into a specified range. * * @param mixed $trials Integer number of trials * Or can be an array of values * @param mixed $probability Probability of success on each trial as a float * Or can be an array of values * @param mixed $successes The integer number of successes in trials * Or can be an array of values * @param mixed $limit Upper limit for successes in trials as null, or an integer * If null, then this will indicate the same as the number of Successes * Or can be an array of values * * @return array|float|string * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function range($trials, $probability, $successes, $limit = null) { if (is_array($trials) || is_array($probability) || is_array($successes) || is_array($limit)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $successes, $limit); } $limit = $limit ?? $successes; try { $trials = DistributionValidations::validateInt($trials); $probability = DistributionValidations::validateProbability($probability); $successes = DistributionValidations::validateInt($successes); $limit = DistributionValidations::validateInt($limit); } catch (Exception $e) { return $e->getMessage(); } if (($successes < 0) || ($successes > $trials)) { return ExcelError::NAN(); } if (($limit < 0) || ($limit > $trials) || $limit < $successes) { return ExcelError::NAN(); } $summer = 0; for ($i = $successes; $i <= $limit; ++$i) { /** @var float */ $comb = Combinations::withoutRepetition($trials, $i); $summer += $comb * $probability ** $i * (1 - $probability) ** ($trials - $i); } return $summer; } /** * NEGBINOMDIST. * * 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 mixed $failures Number of Failures as an integer * Or can be an array of values * @param mixed $successes Threshold number of Successes as an integer * Or can be an array of values * @param mixed $probability Probability of success on each trial as a float * Or can be an array of values * * @return array|float|string The result, or a string containing an error * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions * * TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST * The cumulative default should be false to reflect the behaviour of NEGBINOMDIST */ public static function negative($failures, $successes, $probability) { if (is_array($failures) || is_array($successes) || is_array($probability)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $failures, $successes, $probability); } try { $failures = DistributionValidations::validateInt($failures); $successes = DistributionValidations::validateInt($successes); $probability = DistributionValidations::validateProbability($probability); } catch (Exception $e) { return $e->getMessage(); } if (($failures < 0) || ($successes < 1)) { return ExcelError::NAN(); } if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { if (($failures + $successes - 1) <= 0) { return ExcelError::NAN(); } } /** @var float */ $comb = Combinations::withoutRepetition($failures + $successes - 1, $successes - 1); return $comb * ($probability ** $successes) * ((1 - $probability) ** $failures); } /** * BINOM.INV. * * Returns the smallest value for which the cumulative binomial distribution is greater * than or equal to a criterion value * * @param mixed $trials number of Bernoulli trials as an integer * Or can be an array of values * @param mixed $probability probability of a success on each trial as a float * Or can be an array of values * @param mixed $alpha criterion value as a float * Or can be an array of values * * @return array|int|string * If an array of numbers is passed as an argument, then the returned result will also be an array * with the same dimensions */ public static function inverse($trials, $probability, $alpha) { if (is_array($trials) || is_array($probability) || is_array($alpha)) { return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $alpha); } try { $trials = DistributionValidations::validateInt($trials); $probability = DistributionValidations::validateProbability($probability); $alpha = DistributionValidations::validateFloat($alpha); } catch (Exception $e) { return $e->getMessage(); } if ($trials < 0) { return ExcelError::NAN(); } elseif (($alpha < 0.0) || ($alpha > 1.0)) { return ExcelError::NAN(); } $successes = 0; while ($successes <= $trials) { $result = self::calculateCumulativeBinomial($successes, $trials, $probability); if ($result >= $alpha) { break; } ++$successes; } return $successes; } /** * @return float|int */ private static function calculateCumulativeBinomial(int $value, int $trials, float $probability) { $summer = 0; for ($i = 0; $i <= $value; ++$i) { /** @var float */ $comb = Combinations::withoutRepetition($trials, $i); $summer += $comb * $probability ** $i * (1 - $probability) ** ($trials - $i); } return $summer; } }