【问题标题】:How to mathematically evaluate a string like "2-1" to produce "1"?如何在数学上评估像“2-1”这样的字符串以产生“1”?
【发布时间】:2016-05-22 16:33:32
【问题描述】:

我只是想知道 PHP 是否有一个函数可以接受像 2-1 这样的字符串并产生它的算术结果?

或者我必须手动使用explode() 来获取算术运算符的左右值吗?

【问题讨论】:

  • 你在哪里得到这个表达式?
  • /me 用eval投百万个答案
  • @Femaref:又是一个漏洞! ;-)
  • 我指的是演员阵容,而不是功能。当然,eval 应该少用。
  • @Femaref:哎呀,语言翻译问题 ;-)

标签: php math numbers operators eval


【解决方案1】:
$operation='2-1';
eval("\$value = \"$operation\";");

$value=eval("return ($operation);");

【讨论】:

  • eval() 语言结构非常危险,因为它允许执行任意 PHP 代码。因此不鼓励使用它。如果您已仔细验证除了使用此构造别无其他选择,请特别注意不要将任何用户提供的数据传入其中,而无需事先正确验证。
【解决方案2】:

这是eval 派上用场的情况之一:

$expression = '2 - 1';
eval( '$result = (' . $expression . ');' );
echo $result;

【讨论】:

    【解决方案3】:

    您可以使用 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

    【讨论】:

      【解决方案4】:

      我知道这个问题很老,但我昨晚在搜索一些不太相关的东西时遇到了这个问题,这里的每个答案都很糟糕。不仅糟糕,非常糟糕。我在这里给出的示例将来自我在 2005 年创建的一个类,并且因为这个问题而在过去的几个小时内更新了 PHP5。确实存在其他系统,并且在发布此问题之前就已经存在,所以这让我感到困惑,为什么这里的每个答案都告诉您使用 eval,而 PHP 的警告是:

      eval() 语言结构非常危险,因为它允许执行任意 PHP 代码。因此不鼓励使用它。如果您已仔细验证除了使用此构造之外别无选择,请特别注意不要将任何用户提供的数据传入其中,而无需事先正确验证。

      在我进入示例之前,获取我将使用的课程的位置是PHPClassesGitHubeos.class.phpstack.class.php 都是必需的,但可以合并到同一个文件中。

      使用这样的类的原因是它包含和后缀(RPN)解析器的中缀,然后是 RPN 求解器。有了这些,您就不必使用eval 函数并将您的系统打开到漏洞中。一旦你有了这些类,下面的代码就可以解决一个简单(更复杂)的方程,比如你的 2-1 例子。

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

      就是这样!您可以对大多数方程使用这样的解析器,无论多么复杂和嵌套,都无需求助于“邪恶的eval”。

      因为我真的不希望这只是让我的班级参与其中,所以这里有一些其他选项。我只熟悉我自己的,因为我已经使用了 8 年。 ^^

      Wolfram|Alpha API
      Sage
      A fairly bad parser
      phpdicecalc

      不太清楚我之前发现的其他人发生了什么 - 之前在 GitHub 上也遇到过另一个,不幸的是我没有为它添加书签,但它与包含解析器的大型浮动操作有关。

      无论如何,我想确保在这里用 PHP 求解方程的答案不会将所有未来的搜索者指向eval,因为这是在谷歌搜索的顶部。 ^^

      【讨论】:

      • 感谢您提供详细的答案,我现在将您的答案标记为正确,因为您是对的,不应该使用 eval,但当时我只是在寻找一个快速的解决方案。
      • 哈哈,可以理解。 =] 谢谢。我不知道你是否仍然访问过这个问题或其他任何东西,只是想要一个non-eval 随包一起提供的答案^^ 欢迎你!希望它有用。 =]
      • 如果有人对我发布的问题发表答案或评论,我会收到通知,因此我立即查看。是的,这很好——谢谢!
      • 如果数学字符串包含$variable,是否可以让这些变量对解析器可用?也许作为第二个参数(索引值数组)?我将查看您的代码,看看是否找不到执行此操作的方法。
      • @MichaelJMulligan 存储在您的代码中的变量,是的,如果您将值传递给解析器。查看 (GitHub([github.com/jlawrence11/Classes]README.md 了解更多信息。^^
      【解决方案5】:

      this 论坛中,有人在没有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  
      

      【讨论】:

      • 注意 Create_function:This function internally performs an eval() and as such has the same security issues as eval(). Additionally it has bad performance and memory usage characteristics. If you are using PHP 5.3.0 or newer a native anonymous function should be used instead.
      • 如果您要删除除0-9+, -, /, * 的精选过滤器之外的所有字符,那么在使用eval 时是否真的存在很多安全问题?
      【解决方案6】:

      在这里也可以看到这个答案:Evaluating a string of simple mathematical expressions

      请注意,此解决方案不符合 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
      

      【讨论】:

      • 那么加法呢?我得到了 5+5 = 5 的结果,解析时省略了 +((
      【解决方案7】:

      这是我为another SO question 编写的一段有点冗长的代码。它确实符合没有eval()BOMDAS,但不具备执行复杂/高阶/括号表达式的能力。这种无库方法将表达式分开并系统地减少组件数组,直到删除所有运算符。它当然适用于您的示例表达式:2-1 ;)

      1. preg_match() 检查每个运算符的每一侧是否都有一个数字子字符串。
      2. preg_split() 将字符串划分为一个由交替的数字和运算符组成的数组。
      3. array_search() 查找目标运算符的索引,而它存在于数组中。
      4. array_splice() 将 operator 元素及其两侧的元素替换为一个新元素,该元素包含删除的三个元素的数学结果。

      ** 更新为允许负数 **

      代码:(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, 0, 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
      }
      

      这是a demo of the BOMDAS version,当在两个数字(正数或负数)之间遇到^ 时,它使用php 的pow()

      我不认为我会费心写一个处理括号表达式的版本......但我们会看到我有多无聊。

      【讨论】:

      • if(!preg_match('~^\d*\.?\d+([*/+-]\d​​\.?\d+)*$~',$expression)) 抛出new \Exception("无效的表达式:$表达式"); 142/44:无效的表达式
      • @vivoconunxino 我要离开我的电脑过夜,但如果你为我创建一个sandbox.onlinephpfunctions.com 链接,我会看看并尝试帮助你找出问题。你用的是什么php版本?
      • 你好 mickmackusa,php 7.1。顺便说一句,我这样做了: if(!preg_match('~^\d*([*/+-]\d​​\.?\d+)*$~',$expression)) throw new \Exception("Invalid表达式:$表达式");
      • 它似乎不喜欢变量$expression。宣布了吗?这是什么?
      【解决方案8】:

      随着 create_function 被弃用,我完全需要一个替代的轻量级解决方案,将字符串评估为数学。经过几个小时的花费,我想出了以下内容。顺便说一句,我并不关心括号,因为在我的情况下我不需要。我只需要正确符合运算符优先级的东西。

      更新:我也添加了括号支持。请查看此项目Evaluate Math String

      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

      【讨论】:

      • 5-(-2) 应该是 7,但结果是 3,所以似乎是个大问题 :-)
      • @PoeHaH 实际上不是一个错误 :) 处理双重否定非常容易。我已经添加了你的测试用例,它现在产生了正确的结果github.com/samirkumardas/evaluate_math_string 顺便说一句,不要告诉我它不会在语法错误的情况下引发错误。我没有考虑过。
      • 酷,谢谢!如果我发现任何奇怪的东西,我会再进行一些测试并回来:-)
      猜你喜欢
      • 1970-01-01
      • 2016-07-26
      • 2020-12-11
      • 2023-03-28
      • 1970-01-01
      • 2010-09-24
      • 2017-10-16
      • 2012-09-16
      相关资源
      最近更新 更多