PHP, How to catch a division by zero?

2019-01-01 16:32发布

I have a large mathematical expression that has to be created dynamically. For example, once I have parsed "something" the result will be a string like: "$foo+$bar/$baz";.

So, for calculating the result of that expression I'm using the eval function... something like this:

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

The problem here is that sometimes I get errors that says there was a division by zero, and I don't know how to catch that Exception. I have tried things like:

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

Or:

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

But it does not work. So, how can I avoid that my application crashes when there is a division by zero?

Edit:

First, I want to clarify something: the expression is built dynamically, so I can't just eval if the denominator is zero. So... with regards to the Mark Baker's comment, let me give you an example. My parser could build something like this:

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

The parser build the string step by step without worrying about the value of the vars... so in this case if $foz == $bak there's in fact a division by zero: $baz / ( 0 ).

On the other hand as Pete suggested, I tried:

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

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

But it does not print anything.

13条回答
泪湿衣
2楼-- · 2019-01-01 16:44

A string containing numbers and the mathematical operators + - * / is passed as input. The program must evaluate the value of the expression (as per BODMAS) and print the output.

Example Input/Output: If the argument is "7 + 4*5" the output must be 27. If the argument is "55 + 21 * 11 - 6/0" the output must be "error" (As division by zero is not defined).

查看更多
墨雨无痕
3楼-- · 2019-01-01 16:45

I realize this is an old question, but it is relevant today and I don't really like the answers here.

The proper way to fix this, is by actually evaluating the expression yourself - that is, by parsing the expression, then evaluating it step by step, instead of by transpiling it to PHP. This can be done using the https://en.wikipedia.org/wiki/Shunting-yard_algorithm.

I wrote the following implementation, but I haven't tested it. It's based on the above Wikipedia article. There is no support for right-associative operators, so it's slightly simplified.

// 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";

In your particular case

Even though I would prefer the Shunting Yard solution - if I still decided to go for an eval()-version, I would create a custom_division($leftHandSide, $rightHandSide) method, that throws an exception. This code:

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

becomes

function custom_division($a, $b) { if($b == 0) throw Exception("Div by 0"); }
eval("$foo + $bar * ( custom_division( $baz, ( $foz - $bak ) )");
查看更多
梦该遗忘
4楼-- · 2019-01-01 16:48
if(@eval("\$result = $expresion;")===FALSE){
  $result=0;
}

Won't just catch divide by 0 errors though.

查看更多
千与千寻千般痛.
5楼-- · 2019-01-01 16:57

Problem:

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

Solution simple:

if(c!=0) a=b/c;
else // error handling
查看更多
冷夜・残月
6楼-- · 2019-01-01 17:01

I've been struggling with this too, the set_error_handler solutions were not working for me, probably based on PHP version differences.

The solution for me was to attempt to detect an error on shutdown:

// 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');

I'm not sure why a divide by 0 is shutting down rather than being handled by the set_error_handler but this helped me get beyond it just silently dying.

查看更多
像晚风撩人
7楼-- · 2019-01-01 17:02

Here's another solution:

<?php

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

set_error_handler('e');

eval('echo 1/0;');

See set_error_handler()

查看更多
登录 后发表回答