【问题标题】:PHP, How to catch a division by zero?PHP,如何捕捉除以零?
【发布时间】:2011-03-05 12:07:30
【问题描述】:

我有一个必须动态创建的大型数学表达式。例如,一旦我解析了“某事”,结果将是一个字符串,如:"$foo+$bar/$baz";

因此,为了计算该表达式的结果,我使用了eval 函数...类似这样:

eval("\$result = $expresion;");
echo "The result is: $result";

这里的问题是,有时我会得到一个错误,说有一个被零除,我不知道如何捕获那个异常。我试过这样的事情:

eval("try{\$result = $expresion;}catch(Exception \$e){\$result = 0;}");
echo "The result is: $result";

或者:

try{
    eval("\$result = $expresion;");
}
catch(Exception $e){
    $result = 0;
}
echo "The result is: $result";

但它不起作用。那么,当被零除时,如何避免我的应用程序崩溃?

编辑:

首先,我想澄清一点:表达式是动态构建的,所以如果分母为零,我不能只求值。所以...关于马克贝克的评论,让我举个例子。我的解析器可以构建这样的东西:

"$foo + $bar * ( $baz / ( $foz - $bak ) )"

解析器一步一步构建字符串,而不用担心变量的值......所以在这种情况下,如果$foz == $bak 实际上被零除:$baz / ( 0 )

另一方面,正如皮特建议的那样,我尝试了:

<?php
$a = 5;
$b = 0;

if(@eval(" try{ \$res = $a/$b; } catch(Exception \$e){}") === FALSE)
        $res = 0;
echo "$res\n";
?> 

但它不打印任何东西。

【问题讨论】:

  • 你能检查$expression是否事先被零除吗?
  • @Anthony Forloney:很好的问题,我的回答假设你可以,但如果 Cristian 真的为此使用 eval,那么答案可能是“不”。
  • 使用eval 可能是个坏主意。您现在要让最终用户在您的服务器上执行 PHP 代码。我不知道替代方案,所以我没有发布答案,但是您应该考虑是否希望我能够输入任何 PHP 代码,无论对您的网页有多大的破坏性。
  • 你能不能先用 eval 来测试分母是否为零,然后只有在分母不为零时才计算你的原始表达式?
  • 编写一个解析器,将这些 php-code-formulas 标记化并由您自己解释它们;) token_get_all() 会有所帮助

标签: php exception divide-by-zero


【解决方案1】:
if ($baz == 0.0) {
    echo 'Divisor is 0';
} else {
    ...
}

不要使用 eval,如果您在 evalled 表达式中使用用户输入,这会非常危险,为什么不使用适当的解析器,例如 evalmath on PHPClasses,它会在除以零时引发干净的异常

【讨论】:

  • 不...正如我所说,表达式是动态构建的。如果它是一个静态表达式,那真的很容易,但事实并非如此。
  • 它是如何动态构建的?它必须由您的“解析器”以某种方式“构造”......此时您应该能够识别除数是否为 0!
  • 如果您将 $baz 替换为 eval("\$result = $baz;") 然后测试 $result 是否为 0,这应该可以工作。
  • @Mark 我已经更新了帖子,提供了可以回答您问题的更多信息。感谢您的帮助。
  • 添加了评估课程的链接,将来可能会为您节省很多痛苦
【解决方案2】:

您只需要设置一个错误处理程序以在发生错误时抛出异常:

set_error_handler(function () {
    throw new Exception('Ach!');
});

try {
    $result = 4 / 0;
} catch( Exception $e ){
    echo "Divide by zero, I don't fear you!".PHP_EOL;
    $result = 0;
}

restore_error_handler();

【讨论】:

  • 谢谢,同样的语法适用于尝试使用 wfLoadExtension 加载 MediaWiki 扩展,而不会在文件丢失时导致整个 Wiki 失败。
  • 在 PHP 8.0 中,程序在 $result = 4 / 0; 中止。似乎没有办法“抓住” div-zero。
【解决方案3】:

在 PHP7 上你可以使用DivisionByZeroError

try {
    echo 1/0;
} catch(DivisionByZeroError $e){
    echo "got $e";
} catch(ErrorException $e) {
    echo "got $e";
}

【讨论】:

  • 我同意,如果您使用 PHP >= 7,它是捕获它的更干净的方法。
  • 这实际上是在重复@simpod's answer
  • @celsowm 请注意,在除以零 1/0 和模块除以零 1%0 时,首先会触发 E_WARNING(可能是为了向后兼容 PHP5),然后会引发 DivisionByZeroError 异常。跨度>
  • @celsowm 也不适合我。但是我找到了更好的方法,请参阅下面的答案...
  • PHP 8.0 似乎坚持要让程序崩溃;我会很高兴获得 INF 或 NAN。 (我的其余代码已经处理了这些值。)
【解决方案4】:

这是另一个解决方案:

<?php

function e($errno, $errstr, $errfile, $errline) {
    print "caught!\n";
}

set_error_handler('e');

eval('echo 1/0;');

set_error_handler()

【讨论】:

  • "使用 set_error_handler() 无法在 eval() 中捕获解析错误" php.net/manual/en/function.eval.php
  • @Pete:是的,但是 OP 询问的是除以零错误,而不是解析错误。我测试了上面的脚本,它发现了错误。
  • @BillKarwin & 我测试了它,但它不起作用
  • @ShifraSec,您可能正在使用 PHP 7 或更高版本。我在 2010 年写的答案不再合适。使用描述捕获DivisionByZeroError 异常的解决方案。
  • @BillKarwin 我们现在能做什么?解决这个问题?我尝试了所有其他答案,没有任何效果
【解决方案5】:

正如其他人所提到的,请考虑尝试一种可以让您检查分母是否为 0 的解决方案。

由于该建议似乎对您的目的毫无用处,因此这里有一点关于 PHP 错误处理的背景知识。

早期版本的 PHP 没有例外。相反,引发了各种级别的错误消息(通知、警告等)。致命错误停止执行。

PHP5 带来了异常,而较新的 PHP 提供的库 (PDO) 将在发生不良/意外情况时抛出异常。然而,核心代码库没有被重写以使用异常。核心功能和操作仍然依赖于旧的错误系统。

当你除以 0 时,你会得到一个警告,而不是异常

PHP Warning:  Division by zero in /foo/baz/bar/test.php(2) : eval()'d code on line 1
PHP Stack trace:
PHP   1. {main}() /foo/baz/bar/test.php:0
PHP   2. eval() /foo/baz/bar/test.php:2

如果你想“捕捉”这些,你需要set a custom error handler,它会检测除以零错误并对它们做一些事情。不幸的是,自定义错误处理程序包罗万象,这意味着您还需要编写一些代码来处理所有其他错误。

【讨论】:

  • PHP 5.6 出现致命错误。不是警告。
  • 没关系,我的框架用 set_error_handler() 做了一些真正迟钝的事情
  • 他们对 PHP 8 中的异常处理做了一些事情——现在这似乎是一个无法捕获的致命错误。
【解决方案6】:
if(@eval("\$result = $expresion;")===FALSE){
  $result=0;
}

但不会只捕获除以 0 错误。

【讨论】:

  • +1,但不是 $result = 0; 而是使用 $result = "#error"; 或抛出异常。
  • eval 是邪恶的!请不要使用
【解决方案7】:

使用@error control operator。)这告诉 php 在出现错误时不输出警告。

eval("\$result = @($expresion);");
if ($result == 0) {
    // do division by zero handling 
} else {
    // it's all good
}

【讨论】:

  • 不好。如果$expression = "0 / 5" 怎么办?那么你有一个误报。
  • 除以零实际上返回false,而不是0。请尝试$result === false
  • 在 PHP 8.0 中,你无法通过 eval。
【解决方案8】:

我也遇到过这个问题(动态表达式)。这样做可能不是最好的方式,但它确实有效。您当然可以返回 null 或 false 或任何您想要的,而不是抛出异常。希望这会有所帮助。

function eval_expression($expression)
{
    ob_start();
    eval('echo (' .  $expression . ');');
    $result = ob_get_contents();
    ob_end_clean();
    if (strpos($result, 'Warning: Division by zero')!==false)
    {
        throw new Exception('Division by zero');
    }
    else return (float)$result;
}

【讨论】:

    【解决方案9】:

    我也一直在努力解决这个问题,set_error_handler 解决方案对我不起作用,可能是基于 PHP 版本的差异。

    我的解决方案是尝试在关机时检测错误:

    // Since set_error_handler doesn't catch Fatal errors, we do this
    function shutdown()
    {
        $lastError = error_get_last();
        if (!empty($lastError)) {
            $GLOBALS['logger']->debug(null, $lastError);
        }
    }
    register_shutdown_function('shutdown');
    

    我不确定为什么除以 0 会关闭而不是由 set_error_handler 处理,但这帮助我摆脱了它只是默默地死去。

    【讨论】:

      【解决方案10】:

      包含数字和数学运算符 + - * / 的字符串作为输入传递。 程序必须评估表达式的值(根据 BODMAS)并打印输出。

      示例输入/输出: 如果参数为“7 + 4*5”,则输出必须为 27。 如果参数是“55 + 21 * 11 - 6/0”,则输出必须是“错误”(因为没有定义除以零)。

      【讨论】:

        【解决方案11】:

        问题:

        b=1; c=0; a=b/c; // Error Divide by zero

        解决方案简单:

        if(c!=0) a=b/c;
        else // error handling
        

        【讨论】:

          【解决方案12】:

          我意识到这是一个老问题,但它在今天很重要,我不太喜欢这里的答案。

          解决此问题的正确方法是自己实际评估表达式 - 也就是说,通过解析表达式,然后逐步评估它,而不是将其转换为 PHP。这可以使用https://en.wikipedia.org/wiki/Shunting-yard_algorithm 来完成。

          我写了下面的实现,但我还没有测试过。它基于上面的维基百科文章。不支持右关联运算符,因此稍微简化了一些。

          // You may need to do a better parsing than this to tokenize your expression.
          // In PHP, you could for example use token_get_all()
          $formula = explode(' ', 'foo + bar * ( baz / ( foz - bak ) )');;
          $queue = array();
          $operators = array();
          $precedence = array('-' => 2, '+' => 2, '/' => 3, '*' => 3, '^' => 4);
          $rightAssoc = array('^');
          $variables = array('foo' => $foo, 'bar' => $bar, 'baz' => $baz, 'foz' => $foz, 'bak' => $bak);
          
          foreach($formula as $token) {
              if(isset($variables[$token])) {
                  $queue[] = $variables[$token];
              } else if(isset($precedence[$token])) {
                  // This is an operator
                  while(
                      sizeof($operators) > 0 && 
                      $operators[sizeof($operators)-1] !=  '(' && (
                          $precedence[$operators[sizeof($operators)-1]] > $precedence[$token] ||
                          (
                              $precedence[$operators[sizeof($operators)-1]] == $precedence[$token] &&
                              !in_array($operators[sizeof($operators)-1], $rightAssoc)
                          )
                      )
                  ) $queue[] = array_pop($operators);
                  $operators[] = $token;
              } else if($token == '(') {
                  $operators[] = '(';
              } else if($token == ')') {
                  while($operators[sizeof($operators)-1] != '(') {
                      $queue[] = array_pop($operators);
                  }
                  array_pop($operators);
              } else if($token == ')') {
                  while($operators[sizeof($operators)-1] != ')') {
                      $queue[] = array_pop($operators);
                  }
                  if(null === array_pop($operators))
                      throw new \Exception("Mismatched parentheses");
          }
          $queue = array_merge($queue, array_reverse($operators));
          $stack = array();
          foreach($queue as $token) {
              if(is_numeric($token)) $stack[] = $token;
              else switch($token) {
                  case '+' : 
                      $stack[] = array_pop($stack) + array_pop($stack);
                      break;
                  case '-' :
                      // Popped variables come in reverse, so...
                      $stack[] = -array_pop($stack) + array_pop($stack);
                      break;
                  case '*' :
                      $stack[] = array_pop($stack) * array_pop($stack);
                      break;
                  case '/' :
                      $b = array_pop($stack);
                      $a = array_pop($stack);
                      if($b == 0)
                          throw new \Exception("Division by zero");
                      $stack[] = $a / $b;
                      break;                
              }
          }
          echo "The result from the calculation is ".array_pop($stack)."\n";
          

          在您的特殊情况下

          即使我更喜欢 Shunting Yard 解决方案 - 如果我仍然决定使用 eval() 版本,我会创建一个 custom_division($leftHandSide, $rightHandSide) 方法,该方法会引发异常。这段代码:

          eval("$foo + $bar * ( $baz / ( $foz - $bak ) )");
          

          变成

          function custom_division($a, $b) { if($b == 0) throw Exception("Div by 0"); }
          eval("$foo + $bar * ( custom_division( $baz, ( $foz - $bak ) )");
          

          【讨论】:

            【解决方案13】:

            使用intdivDivisionByZeroError

            try {
                $a = 5;
                $b = 0;
                intdiv($a,$b);
            }
            catch(DivisionByZeroError $e){
                echo "got {$e->getMessage()}";
            }
            

            【讨论】:

              【解决方案14】:

              这是我发现的最好的方法:

              error_clear_last(); // Clear any previous error
              $result = @(1/0); // Executes the division, suppressing the errors
              $e = error_get_last(); // Catches the last error
              if ($e !== null && $e['message'] == 'Division by zero') {
                  // Division by zero occurred, do something here
              }
              

              【讨论】:

                猜你喜欢
                • 2011-06-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-09-01
                • 1970-01-01
                相关资源
                最近更新 更多