closedquestion-8.x-3.x-dev/src/Utility/EvalMath.php

src/Utility/EvalMath.php
<?php

namespace Drupal\closedquestion\Utility;

use Drupal\Core\Messenger\MessengerInterface;

/**
 * Class EvalMath.
 *
 * EvalMath - PHP Class to safely evaluate math expressions.
 *
 * Copyright (C) 2006-2007 Zack Bloom
 * Miles Kaufmann - EvalMath Class Library
 * Hylke van der Schaaf - ClosedQeustion/Drupal integration
 *
 * Modifications for use in ClosedQuestion:
 * - Function handling rewritten to allow functions with 0 and 2 or more
 *   arguments.
 * - Removed uses of eval since php supports variable functions.
 * - Added lcg_value & random function.
 * - Added round and 2-argument number_format (nf) function.
 * - Added min, max, ceil, floor.
 * - Added setVars method to load variables.
 * - Trigger now throws drupal error messages instead of php errors.
 * - Wrapped messages in t().
 * - Converted constructor to __construct().
 * - Added docs to fields and methods.
 * - Changed use of var to private, all properties are private now.
 * - Added getters and setters for some now-private properties.
 * - Public/Private modifiers for all functions.
 * - More descriptive variable names.
 * - Added stat functions.
 * - Allowed for variable number of variables by setting the count to -1
 */
class EvalMath {

  /**
   * Flag to indicate wether errors should be shown or suppressed.
   *
   * @var bool
   */
  private $suppressErrors = FALSE;

  /**
   * The last error that was generated.
   *
   * @var string
   */
  private $lastError = NULL;

  /**
   * The list of currently available variables and constants.
   *
   * @var array
   */
  private $variables = array();

  /**
   * The list of currently available user-defined functions.
   *
   * @var array
   */
  private $funcUser = array();

  /**
   * The list of constant names.
   *
   * @var array
   */
  private $constants = array();

  /**
   * The list of build-in functions.
   *
   * Each item has the function name as key and the argument count as value.
   *
   * @var array
   */
  private $funcBuildIn = array(
    'abs' => 1,
    'acos' => 1,
    'acosh' => 1,
    'asin' => 1,
    'asinh' => 1,
    'atan' => 1,
    'atanh' => 1,
    'ceil' => 1,
    'cos' => 1,
    'cosh' => 1,
    'sqrt' => 1,
    'floor' => 1,
    'formatPlusminusSign' => -1,
    'linregFormula' => -1,
    'linreg_slope' => -1,
    'linregIntercept' => -1,
    'lcg_value' => 0,
    'log' => -1,
    'max' => 2,
    'mean' => -1,
    'median' => -1,
    'min' => 2,
    'number_format' => 2,
    'round' => 2,
    'sin' => 1,
    'sinh' => 1,
    'stdev' => -1,
    'sum' => -1,
    'tan' => 1,
    'tanh' => 1,
  );

  /**
   * A list of all functions with dynamic amounts of arguments.
   *
   * @var array
   */
  private $dynamicArgFunc = array();

  /**
   * A list of the number of arguments used for all functions in expression.
   *
   * @var array
   */
  private $usedFuncArgCount = array();

  /**
   * A list of function names that are aliases of other functions.
   *
   * Both the function and the alias needs to be in the $fb table for now.
   *
   * @var array
   */
  private $funcAliases = array(
    'arcsin' => 'asin',
    'arccos' => 'acos',
    'arctan' => 'atan',
    'arcsinh' => 'asinh',
    'arccosh' => 'acosh',
    'arctanh' => 'atanh',
    'ln' => 'log',
    'nf' => 'number_format',
    'random' => 'lcg_value',
  );

  /**
   * Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  private $messenger;

  /**
   * Constructs a new EvalMath object.
   */
  public function __construct(MessengerInterface $messenger) {
    $this->messenger = $messenger;

    // Keep track of functions with dynamic number of arguments.
    foreach ($this->funcBuildIn as $fnn => $argsCnt) {
      if ($argsCnt < 0) {
        $this->dynamicArgFunc[] = $fnn;
      }
    }
  }

  /**
   * Evaluate the given expression and return the result.
   *
   * This is an alias for evaluate().
   *
   * @param string $expr
   *   The expression to evaluate.
   *
   * @return bool
   *   The result of the evaluation.
   */
  public function e($expr) {
    return $this->evaluate($expr);
  }

  /**
   * Evaluate the given expression and return the result.
   *
   * @param string $expr
   *   The expression to evaluate.
   *
   * @return bool
   *   The result of the evaluation.
   */
  public function evaluate($expr) {
    $this->lastError = NULL;
    $expr = mb_strtolower(trim($expr));
    if (mb_substr($expr, -1, 1) === ';') {
      $expr = mb_substr($expr, 0, -1);
    }

    if (preg_match('/^\s*([a-zA-Z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
      if (in_array($matches[1], $this->constants)) {
        return $this->trigger(t('cannot assign to constant "%m"', array('%m' => $matches[1])));
      }

      if (($tmp = $this->postfixEvaluate($this->infixToPostfix($matches[2]))) === FALSE) {
        return FALSE;
      }

      $this->variables[$matches[1]] = $tmp;

      return $this->variables[$matches[1]];
    }
    elseif (preg_match('/^\s*([a-zA-Z]\w*)\s*\(\s*([a-zA-Z]\w*(?:\s*,\s*[a-zA-Z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
      $fnn = $matches[1];

      if (array_key_exists($fnn, $this->funcAliases)) {
        // Check if the function is an alias.
        $fnn = $this->funcAliases[$fnn];
      }
      if (array_key_exists($fnn, $this->funcBuildIn)) {
        return $this->trigger(t('cannot redefine built-in function "%m()"', array('%m' => $matches[1])));
      }

      $args = explode(",", preg_replace("/\s+/", "", $matches[2]));

      $stack = $this->infixToPostfix($matches[3]);
      if ($stack === FALSE) {
        return FALSE;
      }

      for ($i = 0; $i < count($stack); $i++) {
        $token = $stack[$i];

        if (preg_match('/^[a-zA-Z]\w*$/', $token) && !in_array($token, $args)) {
          if (array_key_exists($token, $this->variables)) {
            $stack[$i] = $this->variables[$token];
          }
          else {
            return $this->trigger(t('undefined variable "%t" in function definition', array('%t' => $token)));
          }
        }
      }

      $this->funcUser[$fnn] = array(
        'args' => $args,
        'func' => $stack,
        'def' => $matches[3],
      );

      return TRUE;
    }
    else {
      return $this->postfixEvaluate($this->infixToPostfix($expr));
    }
  }

  /**
   * Returns an array of all user-defined variables, with their values.
   *
   * @return array
   *   A list of all user-defined variables as name=>value pairs.
   */
  public function getVars() {
    $output = $this->variables;

    return $output;
  }

  /**
   * Adds the given variables to the interal set of user-defined variables.
   *
   * @param array $vars
   *   An array of name/value pairs, as given by getVars()
   */
  public function setVars(array $vars) {
    $this->variables = array_merge($this->variables, $vars);
  }

  /**
   * Returns an array of all user-defined functions.
   *
   * @return array
   *   A list of all user-defined functions.
   */
  public function getFuncs() {
    $output = array();
    foreach ($this->funcUser as $fnn => $dat) {
      $output[$fnn . '(' . implode(',', $dat['args']) . ')'] = $dat['def'];
    }
    return $output;
  }

  /**
   * Convert infix to postfix notation.
   *
   * @param string $expr
   *   The expression to convert.
   *
   * @return array
   *   An array with the elements of the expression
   */
  private function infixToPostfix($expr) {
    $this->usedFuncArgCount = array();
    $index = 0;
    $stack = new EvalMathStack();
    $output = array();
    $ops = array('+', '-', '*', '/', '^', '_');
    $opsRight = array('+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1);
    $opsPrecedence = array(
      '+' => 0,
      '-' => 0,
      '*' => 1,
      '/' => 1,
      '_' => 1,
      '^' => 2,
    );

    $expectingOp = FALSE;
    $inFunction = 0;
    $funcHasArg = array();

    if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) {
      return $this->trigger(t('illegal character "%c"', array('%c' => $matches[0])));
    }

    while (1) {
      $op = mb_substr($expr, $index, 1);
      $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?(?:[Ee][+-]?\d*)?|\.\d+|\()/', mb_substr($expr, $index), $match);

      if ($op == '-' and !$expectingOp) {
        $stack->push('_');
        $index++;
      }
      elseif ($op == '_') {
        return $this->trigger(t('illegal character "_"'));
      }
      elseif ((in_array($op, $ops) || $ex) && $expectingOp) {
        if ($ex) {
          $op = '*';
          $index--;
        }

        while ($stack->getCount() > 0 && ($o2 = $stack->last()) && in_array($o2, $ops) && ($opsRight[$op] ? $opsPrecedence[$op] < $opsPrecedence[$o2] : $opsPrecedence[$op] <= $opsPrecedence[$o2])) {
          $output[] = $stack->pop();
        }

        $stack->push($op);
        $index++;
        $expectingOp = FALSE;
      }
      elseif ($op == ')' && ($expectingOp || $inFunction)) {
        while (($o2 = $stack->pop()) != '(') {
          if (is_null($o2)) {
            return $this->trigger(t('unexpected ) found'));
          }
          else {
            $output[] = $o2;
          }
        }

        if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) {
          $fnn = $matches[1];

          $argCount = $stack->pop();
          if ($funcHasArg[$inFunction]) {
            $argCount++;
          }

          if (array_search($fnn, $this->dynamicArgFunc) !== FALSE) {
            // Dynamic number of arguments: Adjust argument count.
            $this->funcBuildIn[$fnn] = $argCount;
          }
          // Store the argument count for this function.
          $this->usedFuncArgCount[] = $argCount;

          // Pop the function and push onto the output.
          $output[] = $stack->pop();
          if (array_key_exists($fnn, $this->funcAliases)) {
            // Check if the function is an alias.
            $fnn = $this->funcAliases[$fnn];
          }
          if (array_key_exists($fnn, $this->funcBuildIn)) {

          }
          elseif (array_key_exists($fnn, $this->funcUser)) {
            if ($argCount != count($this->funcUser[$fnn]['args'])) {
              return $this->trigger(t('wrong number of arguments (@gc given, @ec expected)', array('@gc' => $argCount, '@ec' => count($this->funcUser[$fnn]['args']))));
            }
          }
          else {
            return $this->trigger(t('internal error, not a function'));
          }
          $inFunction--;
        }

        $index++;
      }
      elseif ($op == ',' and $expectingOp) {
        while (($o2 = $stack->pop()) != '(') {
          if (is_null($o2)) {
            return $this->trigger(t('unexpected , found'));
          }
          else {
            $output[] = $o2;
          }
        }

        if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) {
          return $this->trigger(t('unexpected , found'));
        }

        $stack->push($stack->pop() + 1);
        $stack->push('(');
        $index++;
        $expectingOp = FALSE;
      }
      elseif ($op == '(' and !$expectingOp) {
        $stack->push('(');
        $index++;
        $allow_neg = TRUE;
      }
      elseif ($ex and !$expectingOp) {
        $expectingOp = TRUE;
        $val = $match[1];

        if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) {
          if (array_key_exists($matches[1], $this->funcAliases) ||
              array_key_exists($matches[1], $this->funcBuildIn) ||
              array_key_exists($matches[1], $this->funcUser)) {
            $stack->push($val);
            $stack->push(0);
            $stack->push('(');
            $expectingOp = FALSE;
            // If we are in a function, it'll have at least one argument,
            // this one.
            if ($inFunction) {
              $funcHasArg[$inFunction] = TRUE;
            }
            $inFunction++;
            $funcHasArg[$inFunction] = FALSE;
          }
          else {
            if (!array_key_exists($matches[1], $this->variables)) {
              return $this->trigger(t(
                'unknown variable or function "%f"',
                array('%f' => $matches[1])
              ));
            }
            $val = $matches[1];
            $output[] = $val;
            // If we are in a function, it'll have at least one argument,
            // this one.
            if ($inFunction) {
              $funcHasArg[$inFunction] = TRUE;
            }
          }
        }
        else {
          $output[] = $val;
          // If we are in a function, it'll have at least one argument,
          // this one.
          if ($inFunction) {
            $funcHasArg[$inFunction] = TRUE;
          }
        }

        $index += mb_strlen($val);
      }
      elseif ($op == ')') {
        return $this->trigger(t('unexpected ) found'));
      }
      elseif (in_array($op, $ops) and !$expectingOp) {
        return $this->trigger(t('unexpected operator "%op"', array('%op' => $op)));
      }
      else {
        return $this->trigger(t('an unexpected error occured'));
      }

      if ($index == mb_strlen($expr)) {
        if (in_array($op, $ops)) {
          return $this->trigger(t('operator "%op" lacks operand', array('%op' => $op)));
        }
        else {
          break;
        }
      }

      while (mb_substr($expr, $index, 1) == ' ') {
        $index++;
      }
    }

    while (!is_null($op = $stack->pop())) {
      if ($op == '(') {
        return $this->trigger(t('expecting ) but none found'));
      }

      $output[] = $op;
    }

    return $output;
  }

  /**
   * Evaluate postfix notation.
   *
   * @param array|bool $tokens
   *   The list of tokens that make up the expression.
   * @param array $vars
   *   The list of variables set previously.
   *
   * @return int
   *   The final result of the evaluation.
   */
  private function postfixEvaluate($tokens, array $vars = array()) {
    if ($tokens == FALSE) {
      return FALSE;
    }

    $stack = new EvalMathStack();

    foreach ($tokens as $token) {
      if (in_array($token, array('+', '-', '*', '/', '^'))) {
        if (is_null($op2 = $stack->pop())) {
          return $this->trigger(t('internal error'));
        }

        if (is_null($op1 = $stack->pop())) {
          return $this->trigger(t('internal error'));
        }

        switch ($token) {
          case '+':
            $stack->push($op1 + $op2);
            break;

          case '-':
            $stack->push($op1 - $op2);
            break;

          case '*':
            $stack->push($op1 * $op2);
            break;

          case '/':
            if ($op2 == 0) {
              return $this->trigger(t('division by zero'));
            }
            $stack->push($op1 / $op2);
            break;

          case '^':
            $stack->push(pow($op1, $op2));
            break;
        }
      }
      elseif ($token == "_") {
        $stack->push(-1 * $stack->pop());
      }
      elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) {
        $fnn = $matches[1];

        if (array_key_exists($fnn, $this->funcAliases)) {
          $fnn = $this->funcAliases[$fnn];
        }

        if (array_key_exists($fnn, $this->funcBuildIn)) {
          // Get the stored argument count.
          $argCount = array_shift($this->usedFuncArgCount);
          $args = array();
          for ($i = $argCount; $i > 0; $i--) {
            $arg = $stack->pop();
            if (is_null($arg)) {
              return $this->trigger(t('internal error: argument is null'));
            }
            $args[] = $arg;
          }
          $args = array_reverse($args);
          $stack->push(call_user_func_array($fnn, $args));
        }
        elseif (array_key_exists($fnn, $this->funcUser)) {
          $args = array();

          for ($i = count($this->funcUser[$fnn]['args']) - 1; $i >= 0; $i--) {
            if (is_null($args[$this->funcUser[$fnn]['args'][$i]] = $stack->pop())) {
              return $this->trigger(t('internal error: argument is null'));
            }
          }

          $stack->push($this->postfixEvaluate($this->funcUser[$fnn]['func'], $args));
        }
      }
      else {
        if (is_numeric($token)) {
          $stack->push($token);
        }
        elseif (array_key_exists($token, $this->variables)) {
          $stack->push($this->variables[$token]);
        }
        elseif (array_key_exists($token, $vars)) {
          $stack->push($vars[$token]);
        }
        else {
          return $this->trigger(t('undefined variable "%token"', array('%token' => $token)));
        }
      }
    }

    if ($stack->getCount() != 1) {
      return $this->trigger(t('internal error, stack not empty'));
    }

    return $stack->pop();
  }

  /**
   * Trigger an error, but nicely, if need be.
   *
   * @param string $msg
   *   The message that describes the error.
   *
   * @return bool
   *   Always returns FALSE
   */
  private function trigger($msg) {
    $this->lastError = $msg;

    if (!$this->suppressErrors) {
      $this->messenger->addMessage(t('Math evaluation error: %msg', array('%msg' => $msg)), 'warning');
    }

    return FALSE;
  }

  /**
   * Getter for the suppressErrors property.
   *
   * Indicates whether errors are suppressed or returned in Drupal messages.
   *
   * @return bool
   *   The value of suppressErrors
   */
  public function getSuppressErrors() {
    return $this->suppressErrors;
  }

  /**
   * Setter for the suppressErrors property.
   *
   * Indicates whether errors are suppressed or returned in Drupal messages.
   *
   * @param bool $suppressErrors
   *   The new value for suppressErrors.
   */
  public function setSuppressErrors($suppressErrors) {
    $this->suppressErrors = $suppressErrors;
  }

  /**
   * Returns the last error message that was generated.
   *
   * @return string
   *   The last error message
   */
  public function getLastError() {
    return $this->lastError;
  }

  /**
   * Function to calculate the standard deviation.
   *
   * @param int
   *   This function accepts a variable number of numbers.
   *
   * @return float
   *   The result.
   */
  public function stdev() {
    $array = func_get_args();
    // Square root of sum of squares devided by N-1.
    return sqrt(
      array_sum(
        array_map(
          'stdev_square',
          $array,
          array_fill(0, count($array), (array_sum($array) / count($array)))
        )
      ) / (count($array) - 1)
    );
  }

  /**
   * Function to calculate the mean.
   *
   * @return float
   *   The result.
   */
  public function mean() {
    $array = func_get_args();
    $numberOfArgs = count($array);
    return array_sum($array) / $numberOfArgs;
  }

  /**
   * Function to calculate the median.
   *
   * @return float
   *   The result.
   */
  public function median() {
    $arr = func_get_args();
    if (0 === count($arr)) {
      return NULL;
    }

    // Sort the data.
    $count = count($arr);
    asort($arr);

    // Get the mid-point keys (1 or 2 of them).
    $mid = floor(($count - 1) / 2);
    $keys = array_slice(array_keys($arr), $mid, (1 === $count % 2 ? 1 : 2));
    $sum = 0;
    foreach ($keys as $key) {
      $sum += $arr[$key];
    }
    return $sum / count($keys);
  }

  /**
   * Helper function returning number with flat distribution from 0 to 1.
   *
   * @return float
   *   Random number.
   */
  protected function normalRandom_rnd01() {
    return (float) rand() / (float) getrandmax();
  }

  /**
   * Helper function returning number between 0 and 1 with normal distribution.
   *
   * @return float
   *   Random number.
   */
  protected function normalRandom_gauss() {
    $x = $this->normalRandom_rnd01();
    $y = $this->normalRandom_rnd01();
    return sqrt(-2 * log($x)) * cos(2 * pi() * $y);
  }

  /**
   * Returns a random number with a Normal distribution.

   * @param float $m
   *   The mean.
   * @param float $s
   *   The standard deviation.
   *
   * @return float
   *   Random number.
   */
  public function normalRandom($m = 0.0, $s = 1.0) {
    $returnVal = $this->normalRandom_gauss() * $s + $m;
    return $returnVal;
  }

  /**
   * Sum function.
   *
   * @return int
   *   Sum value.
   */
  public function sum() {
    return array_sum(func_get_args());
  }

  /**
   * Linear regression function.
   *
   * @param array $args
   *   The x-coords and y-coords.
   *
   * @return array
   *   The a and b value of y=ax+b.
   */
  public function linreg(array $args) {
    $x = $args['x'];
    $y = $args['y'];
    // Calculate number points.
    $n = count($x);

    // Calculate sums.
    $x_sum = array_sum($x);
    $y_sum = array_sum($y);

    $xx_sum = 0;
    $xy_sum = 0;

    for ($i = 0; $i < $n; $i++) {
      $xy_sum += ($x[$i] * $y[$i]);
      $xx_sum += ($x[$i] * $x[$i]);
    }

    // Calculate slope.
    $m = (($n * $xy_sum) - ($x_sum * $y_sum)) / (($n * $xx_sum) - ($x_sum * $x_sum));

    // Calculate intercept.
    $b = ($y_sum - ($m * $x_sum)) / $n;

    // Return result.
    return array("a" => $m, "b" => $b);
  }

  /**
   * Helper function: prepares arrays for linear regression function.
   *
   * @param array $args
   *   Array to prepare.
   *
   * @return array
   *   Prepared array.
   */
  protected function linregPrepareArrays(array $args) {
    if (count($args) % 2 !== 0) {
      $this->messenger->addMessage('ClosedQuestion: odd number of arguments provided for linear regression. Correct syntax: linreg_a(x1,y1,x2,y2,...)', 'error');
    }

    // Get two arrays for x and y.
    $xVars = array();
    $yVars = array();
    $numberOfArgs = count($args);
    for ($i = 0; $i < $numberOfArgs; $i = $i + 2) {
      $xVars[] = $args[$i];
      $yVars[] = $args[$i + 1];
    }

    return array('x' => $xVars, 'y' => $yVars);
  }

  /**
   * Returns the linear regression slope.
   *
   * @return float
   *   The slope.
   */
  public function linregSlope() {
    $regVars = $this->linreg($this->linregPrepareArrays(func_get_args()));
    return $regVars['a'];
  }

  /**
   * Returns the linear regression intercept.
   *
   * @return float
   *   The slope.
   */
  public function linregIntercept() {
    $regVars = $this->linreg($this->linregPrepareArrays(func_get_args()));
    return $regVars['b'];
  }

  /**
   * Returns a formula for the a number of data points.
   *
   * @return string
   *   A formula in the format y = ax + b.
   */
  public function linregFormula() {
    $template = "y = axb";
    $points = func_get_args();
    $numberOfPoints = count($points);

    $sigFig = 999999999999;
    for ($i = 0; $i < $numberOfPoints; $i++) {
      $newSigFig = $this->linregSignificantFigures($points[$i]);
      $sigFig = $newSigFig < $sigFig ? $newSigFig : $sigFig;
    }

    $linRegsArrays = $this->linregPrepareArrays($points);
    $linRegVars = $this->linreg($linRegsArrays);

    $a = $linRegVars['a'];
    if ($this->linregSignificantFigures($a) > $sigFig) {
      $a = sprintf('%01.' . ($sigFig - 1) . 'f', $a);
    }

    $b = $linRegVars['b'];
    if ($this->linregSignificantFigures($b) > $sigFig) {
      $b = $this->formatScientific($b, $sigFig - 1);
    }

    return str_replace(array('a', 'b'), array($a, $b), $template);
  }

  /**
   * Formats value in scientific format.
   *
   * @param float $val
   *   Value.
   * @param int $precision
   *   Precision.
   *
   * @return string
   *   Formatted value.
   */
  protected function formatScientific($val, $precision = 2) {
    $isPositive = $val >= 0 ? TRUE : FALSE;
    $absVal = abs($val);
    $exp = floor(log($absVal, 10));
    $returnValue = sprintf('%.' . $precision . 'fE%+03d', ($isPositive ? $absVal : -$absVal) / pow(10, $exp), $exp);
    return $returnValue;
  }

  /**
   * Returns whether a number has a decimal point.
   *
   * @param string $number
   *   A number to check.
   *
   * @return bool
   *   TRUE if number has a decimal point.
   */
  protected function linregHasDecimalpoint($number) {
    if (strpos($number, '.') === FALSE) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Returns position of 'e' or FALSE.
   *
   * @param string $number
   *   A number.
   *
   * @return int|bool
   *   The position of 'e' or FALSE.
   */
  protected function linregGetExponentPos($number) {
    return (strpos($number, "e") !== FALSE ? strpos($number, "e") : (strpos($number, "E") !== FALSE ? strpos($number, "E") : FALSE));
  }

  /**
   * Returns the number of significant figures.
   *
   * @param string $number
   *   A number to parse.
   *
   * @return int
   *   The number of significant figures.
   */
  public function linregSignificantFigures($number) {
    $number = (string) $number;
    $expPos = $this->linregGetExponentPos($number);

    $numberLength = strlen($number);

    // Do not include the exponent.
    $figures = $expPos !== FALSE ? $expPos : $numberLength;

    for ($i = 0; $i < $numberLength; $i++) {
      if ($number[$i] == "0" || $number[$i] == "+" || $number[$i] == "-" || $number[$i] == ".") {
        if ($number[$i] != ".") {
          $figures--;
        }
        continue;
      }
      break;
    }

    if ($this->linregHasDecimalpoint($number)) {
      $figures--;
    }

    return $figures;
  }

  /**
   * Adds a + or a - to the text if it holds a positive or negative number.
   *
   * @param string $text
   *   A text to check.
   * @param int $precision
   *   A precision required.
   *
   * @return string
   *   A text with + or - sign.
   */
  public function formatPlusminusSign($text, $precision = 3) {
    return sprintf('%+01.' . ((int) $precision) . 'f', $text);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc