Как математически оценить строку типа «2-1» для получения «1»? - PullRequest
27 голосов
/ 20 февраля 2011

Мне просто интересно, есть ли в PHP функция, которая может взять строку типа 2-1 и вывести ее арифметический результат?

Или мне придется делать это вручную с помощью explode(), чтобы получить значения слева и справа от арифметического оператора?

Ответы [ 8 ]

52 голосов
/ 18 апреля 2013

Я знаю, что этот вопрос старый, но я натолкнулся на него прошлой ночью, когда искал что-то, что было не совсем связано, и каждый ответ здесь плохой. Не просто плохо, очень плохо. Примеры, которые я привожу здесь, будут из класса, который я создал в 2005 году и провел последние несколько часов, обновляя PHP5 из-за этого вопроса. Другие системы существуют и существовали до того, как этот вопрос был опубликован, поэтому меня сбивает с толку, почему каждый ответ здесь говорит о том, что вы должны использовать eval, когда PHP предупреждает:

Конструкция языка eval () очень опасна, поскольку она позволяет выполнять произвольный код PHP. Таким образом, его использование не рекомендуется. Если вы тщательно проверили, что нет другого выбора, кроме как использовать эту конструкцию, обратите особое внимание на то, чтобы не передавать какие-либо данные, предоставленные пользователем, без надлежащей проверки их заранее.

Прежде чем перейти к примеру, места, где я буду использовать класс, находятся в PHPClasses или GitHub . Обе eos.class.php и stack.class.php являются обязательными, но могут быть объединены в один файл.

Причина использования такого класса заключается в том, что он включает в себя анализатор и постфиксный (RPN) инфикс и затем RPN Solver. При этом вам никогда не придется использовать функцию eval и открывать свою систему для уязвимостей. Когда у вас есть классы, следующий код - это все, что нужно для решения простого (более сложного) уравнения, такого как ваш 2-1 пример.

require_once "eos.class.php";
$equation = "2-1";
$eq = new eqEOS();
$result = $eq->solveIF($equation);

Вот и все! Вы можете использовать такой синтаксический анализатор для большинства уравнений, какими бы сложными и ни были вложенными, даже не прибегая к «злу eval».

Поскольку я действительно не хочу, чтобы это было только для моего класса, вот некоторые другие варианты. Я просто знаком с моим, так как я использую его в течение 8 лет. ^^

Wolfram | Alpha API
Шалфей
Довольно плохой парсер
phpdicecalc

Не совсем уверен, что случилось с другими, которые я обнаружил ранее - раньше встречался и с другим на GitHub, к сожалению, я не добавил его в закладки, но это было связано с большими операциями с плавающей запятой, которые также включали парсер.

В любом случае, я хотел убедиться, что ответ на решение уравнений в PHP здесь не будет указывать всем будущим поисковикам на eval, так как это было в верхней части поиска Google. ^^

14 голосов
/ 20 февраля 2011
$operation='2-1';
eval("\$value = \"$operation\";");

или

$value=eval("return ($op);");
8 голосов
/ 20 февраля 2011

Это один из случаев, когда eval пригодится:

$expression = '2 - 1';
eval( '$result = (' . $expression . ');' );
echo $result;
7 голосов
/ 20 февраля 2011

Вы можете использовать BC Math произвольной точности

echo bcsub(5, 4); // 1
echo bcsub(1.234, 5); // 3
echo bcsub(1.234, 5, 4); // -3.7660

http://www.php.net/manual/en/function.bcsub.php

3 голосов
/ 20 июня 2015

На этом форуме кто-то сделал это без eval. Может быть, вы можете попробовать это? Кредиты им я просто нашел.

function calculate_string( $mathString )    {
    $mathString = trim($mathString);     // trim white spaces
    $mathString = ereg_replace ('[^0-9\+-\*\/\(\) ]', '', $mathString);    // remove any non-numbers chars; exception for math operators

    $compute = create_function("", "return (" . $mathString . ");" );
    return 0 + $compute();
}

$string = " (1 + 1) * (2 + 2)";
echo calculate_string($string);  // outputs 8  
2 голосов
/ 27 января 2016

Также см. Этот ответ здесь: Оценка строки простых математических выражений

Обратите внимание, что это решение НЕ соответствует BODMAS, но вы можете использовать скобки в строке оценки, чтобы преодолеть это.

function callback1($m) {
    return string_to_math($m[1]);
}
function callback2($n,$m) {
    $o=$m[0];
    $m[0]=' ';
    return $o=='+' ? $n+$m : ($o=='-' ? $n-$m : ($o=='*' ? $n*$m : $n/$m));
}
function string_to_math($s){ 
    while ($s != ($t = preg_replace_callback('/\(([^()]*)\)/','callback1',$s))) $s=$t;
    preg_match_all('![-+/*].*?[\d.]+!', "+$s", $m);
    return array_reduce($m[0], 'callback2');
}
echo string_to_match('2-1'); //returns 1
1 голос
/ 08 июля 2017

Вот несколько подробный код, который я откатил для еще один вопрос .Он соответствует BO MDAS без eval(), но не оборудован для выполнения сложных / более высокого порядка / круглых выражений.Этот безбиблиотечный подход разделяет выражение и систематически уменьшает массив компонентов, пока не будут удалены все операторы.Это, безусловно, работает для вашего примера выражения: 2-1;)

  1. preg_match() проверяет, что у каждого оператора есть числовая подстрока на каждой стороне.
  2. preg_split() делит строку намассив чередующихся чисел и операторов.
  3. array_search() находит индекс целевого оператора, пока он существует в массиве.
  4. array_splice() заменяет элемент оператора и элементы либосторона с новым элементом, который содержит математический результат удаления трех элементов.

** обновлен, чтобы разрешить отрицательные числа **

Код: ( Demo )

$expression="-11+3*1*4/-6-12";
if(!preg_match('~^-?\d*\.?\d+([*/+-]-?\d*\.?\d+)*$~',$expression)){
    echo "invalid expression";
}else{
    $components=preg_split('~(?<=\d)([*/+-])~',$expression,NULL,PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    var_export($components);  // ['-11','+','3','*','1','*','4','/','-6','-','12']
    while(($index=array_search('*',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]*$components[$index+1]);
        var_export($components);
        // ['-11','+','3','*','4','/','-6','-','12']
        // ['-11','+','12','/','-6','-','12']
    }
    while(($index=array_search('/',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]/$components[$index+1]);
        var_export($components);  // [-'11','+','-2','-','12']
    }
    while(($index=array_search('+',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]+$components[$index+1]);
        var_export($components);  // ['-13','-','12']
    }
    while(($index=array_search('-',$components))!==false){
        array_splice($components,$index-1,3,$components[$index-1]-$components[$index+1]);
        var_export($components); // [-25]
    }
    echo current($components);  // -25
}

Вот демонстрационная версия B OMDAS , которая использует php's pow(), когда между 10 числами встречается ^или отрицательно).

Не думаю, что когда-нибудь буду писать версию, которая обрабатывает выражения в скобках ... но мы увидим, как мне скучно.

0 голосов
/ 14 февраля 2019

Поскольку функция create_function устарела, и мне совершенно необходимо было найти альтернативное облегченное решение для вычисления строки как математики Проведя пару часов, я придумал следующее. Кстати, я не заботился о скобках, так как мне не нужно в моем случае. Мне просто нужно что-то, что правильно соответствует приоритету оператора.

Обновление: я также добавил поддержку скобок. Пожалуйста, проверьте этот проект Оценить математическую строку

function evalAsMath($str) {

   $error = false;
   $div_mul = false;
   $add_sub = false;
   $result = 0;

   $str = preg_replace('/[^\d\.\+\-\*\/]/i','',$str);
   $str = rtrim(trim($str, '/*+'),'-');

   if ((strpos($str, '/') !== false ||  strpos($str, '*') !== false)) {
      $div_mul = true;
      $operators = array('*','/');
      while(!$error && $operators) {
         $operator = array_pop($operators);
         while($operator && strpos($str, $operator) !== false) {
           if ($error) {
              break;
            }
           $regex = '/([\d\.]+)\\'.$operator.'(\-?[\d\.]+)/';
           preg_match($regex, $str, $matches);
           if (isset($matches[1]) && isset($matches[2])) {
                if ($operator=='+') $result = (float)$matches[1] + (float)$matches[2];
                if ($operator=='-') $result = (float)$matches[1] - (float)$matches[2]; 
                if ($operator=='*') $result = (float)$matches[1] * (float)$matches[2]; 
                if ($operator=='/') {
                   if ((float)$matches[2]) {
                      $result = (float)$matches[1] / (float)$matches[2];
                   } else {
                      $error = true;
                   }
                }
                $str = preg_replace($regex, $result, $str, 1);
                $str = str_replace(array('++','--','-+','+-'), array('+','+','-','-'), $str);
         } else {
            $error = true;
         }
      }
    }
}

  if (!$error && (strpos($str, '+') !== false ||  strpos($str, '-') !== false)) {
     $add_sub = true;
     preg_match_all('/([\d\.]+|[\+\-])/', $str, $matches);
     if (isset($matches[0])) {
         $result = 0;
         $operator = '+';
         $tokens = $matches[0];
         $count = count($tokens);
         for ($i=0; $i < $count; $i++) { 
             if ($tokens[$i] == '+' || $tokens[$i] == '-') {
                $operator = $tokens[$i];
             } else {
                $result = ($operator == '+') ? ($result + (float)$tokens[$i]) : ($result - (float)$tokens[$i]);
             }
         }
      }
    }

    if (!$error && !$div_mul && !$add_sub) {
       $result = (float)$str;
    }
    return $error ? 0 : $result;
}

Демо: http://sandbox.onlinephpfunctions.com/code/fdffa9652b748ac8c6887d91f9b10fe62366c650

...