42

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.

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
Cristian
  • 198,401
  • 62
  • 356
  • 264
  • 1
    Can you check if `$expression` is dividing by zero beforehand? – Anthony Forloney Jun 18 '10 at 15:44
  • @Anthony Forloney: Good question, my answer assumed you could, but if Cristian is really using eval for this, then the answer is probably "no." – Powerlord Jun 18 '10 at 15:47
  • 2
    Using `eval` can be a bad idea. You're now going to let your end-user execute PHP code on your server. I don't know an alternative, so I'm not posting an answer, but you should think about whether you want me to be able to type in any PHP code no matter how destructive into your webpage. – Umang Jun 18 '10 at 15:52
  • Can you not first use eval to test if the denominator is zero or not and then compute your original expression only if denominator is not zero? – vad Jun 18 '10 at 15:58
  • Write a parser that tokenizes those php-code-formulas and interpret them by your own ;) token_get_all() will help – Tobias P. Jun 18 '10 at 15:59
  • I don't quite understand why you can't avoid entering the code if `$expression` would divide by zero. You can evaluate parens / etc **prior** to evaluating the expression, while checking if something in a paren is division by zero prior to evaluating the paren. Are you sure you're solving the right problem? – Tim Post Jun 18 '10 at 16:00
  • Why the try catch block in the expression? WOE are you swallowing the exception? $res should be initalised to 0 before attempting the eval() – Pete Jun 18 '10 at 16:01

16 Answers16

19

On PHP7 you can use DivisionByZeroError

try {
    echo 1/0;
} catch(DivisionByZeroError $e){
    echo "got $e";
} catch(ErrorException $e) {
    echo "got $e";
}
David L
  • 703
  • 6
  • 22
  • I agree, if you use PHP >= 7 its the more clean way to catch it. – Nicolas Talichet Jun 29 '18 at 02:03
  • 1
    This is essentially repeating [@simpod's answer](https://stackoverflow.com/a/41185139/3303195). – faintsignal Jul 13 '18 at 19:32
  • @celsowm Note that on division by zero 1/0 and module by zero 1%0 an E_WARNING is triggered first (probably for backward compatibility with PHP5), then the DivisionByZeroError exception is thrown next. – David L Jun 24 '19 at 14:14
  • 1
    @celsowm Didn't work for me either. But I found a better way, see my answer below... – Gus Costa Oct 16 '19 at 12:50
  • PHP 8.0 seems to insist on crashing the program; I would be quite happy to get INF or NAN. (The rest of my code already takes care of those values.) – Rick James Feb 15 '22 at 02:05
15
if ($baz == 0.0) {
    echo 'Divisor is 0';
} else {
    ...
}

Rather than use eval, which is highly dangerous if you're using user-input within the evalled expression, why not use a proper parser such as evalmath on PHPClasses, and which raises a clean exception on divide by zero

alexia
  • 14,440
  • 8
  • 42
  • 52
Mark Baker
  • 209,507
  • 32
  • 346
  • 385
  • 3
    No... as I said the expression is built dinamically. If it were a static expression it'd be really easy, but it's not. – Cristian Jun 18 '10 at 15:46
  • 4
    How is it built dynamically? It must be "constructed" somehow by your "parser"... and at that point you should be able to identify whether the divisor is 0 or not! – Mark Baker Jun 18 '10 at 15:49
  • This should work if you replace $baz with eval("\$result = $baz;") and then test is $result is 0 or not. – vad Jun 18 '10 at 15:55
  • @Mark I have updated the post with more information that answers your question. Thanks for your help. – Cristian Jun 18 '10 at 15:55
  • Added link to an evaluation class which might save you a lot of grief in the future – Mark Baker Jun 18 '10 at 16:03
  • Thank you... that's much better that reinvent the wheel :D That class worked fine. – Cristian Jun 18 '10 at 16:32
  • How is this an answer to a question about *Catching* exception? – David Mar 28 '12 at 15:59
  • @David.... as the question is "So, how can I avoid that my application crashes when there is a division by zero?"... 1. prevent a division by zero by testing for the possibility before executing the division. 2. Suggest safer alternative than eval() for handling the situation. 3. Satisfy OP that his question has been answered. Now let's see your answer! – Mark Baker Mar 28 '12 at 16:05
  • Check my answer for a solution to be used nowadays https://stackoverflow.com/a/41185139/519333 – simPod Jul 13 '18 at 19:37
13

You just need to set an error handler to throw an exception in case of errors:

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();
tacone
  • 11,371
  • 8
  • 43
  • 60
  • Thank you, this same syntax works well for trying to load a MediaWiki extension using wfLoadExtension without causing the entire Wiki to fail if the file is missing. – hackerb9 Nov 29 '21 at 04:25
  • In PHP 8.0, the program is aborted at `$result = 4 / 0;`. There seems to be no way to "catch" div-zero. – Rick James Feb 15 '22 at 02:09
  • @RickJames there is a way to catch it, use Throwable instead of Exception – Alpha2k May 19 '22 at 08:31
5

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()

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • "It is not possible to catch a parse error in eval() using set_error_handler()" http://www.php.net/manual/en/function.eval.php – Pete Jun 18 '10 at 15:56
  • 5
    @Pete: Right, but the OP was asking about division by zero errors, not parse errors. I tested the above script and it catches the error. – Bill Karwin Jun 18 '10 at 16:04
  • @BillKarwin & I tested it and it doesn't work – ShifraSec Nov 27 '21 at 10:07
  • 1
    @ShifraSec, You are probably using PHP 7 or later. The answer I wrote in 2010 is no longer appropriate. Use the solutions that describe catching the [DivisionByZeroError](https://www.php.net/manual/en/class.divisionbyzeroerror.php) exception. – Bill Karwin Nov 27 '21 at 17:31
  • @BillKarwin what can we do now? to solve this issue? I tried all the other answers nothing works – ShifraSec Nov 28 '21 at 12:23
  • @ShifraSec, I don't know what you tried. In my test on PHP 8.0.12, the [answer from David L](https://stackoverflow.com/a/46473760/20860) works. And of course, avoiding the error by checking your denominator first works, as shown by the [answer from Mark Baker](https://stackoverflow.com/a/3071097/20860). – Bill Karwin Nov 28 '21 at 13:47
  • In 8.0, the program dies on `eval('echo 1/0;');` -- No way to get INF, which I would be happy with. – Rick James Feb 15 '22 at 02:12
4

As others have mentioned, consider trying a solution that will let you check if the denominator is 0.

Since that advice seems useless your purpose, here's a little background on PHP error handling.

Early versions of PHP didn't have exceptions. Instead, error messages of various levels were raised (Notices, Warnings, Etc). A Fatal error stops execution.

PHP5 brought exceptions to the table, and newer PHP provided libraries (PDO) will throw exceptions when bad/unexpected things happen. Hoever, the core codebase was NOT rewritten to use exception. Core functions and operations still rely on the old error system.

When you divide by 0, you get a Warning, not an exception

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

If you want to "catch" these, you'll need to set a custom error handler that will detect division by zero errors and do something about them. Unfortunately, custom error handlers are a catch all, which means you'll also need to write some code to do something appropriate with all other errors.

Alana Storm
  • 164,128
  • 91
  • 395
  • 599
2

Problem:

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

Solution simple:

if(c!=0) a=b/c;
else // error handling
Muisca
  • 69
  • 1
  • 5
2

If no additional handling is necessary you could also just do

$division = $foo / ($bar ?: 1);
Dave Tjong
  • 21
  • 1
1

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).

dinesh
  • 11
  • 1
1
if(@eval("\$result = $expresion;")===FALSE){
  $result=0;
}

Won't just catch divide by 0 errors though.

Pete
  • 1,773
  • 8
  • 11
1

I was facing that problem as well (dynamic expressions). Idid it that way which might not be the nicest way but it works. Instead of throwing an Exception you can of course return null or false or whatever you wish. Hope this helps.

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;
}
1

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.

justin.m.chase
  • 13,061
  • 8
  • 52
  • 100
  • Your solution doesn't work in PHP7 nor PHP8. `set_error_handler` works in PHP7 but not in PHP8. Please check out the following test of your solution: ```function test($test){ try{ $x = 1/0; }catch(Error $w){ echo($w); } } register_shutdown_function('shutdown'); function shutdown(){ echo 'eee'; } test(1);``` – brett Jun 23 '22 at 20:30
0

Use a @ (An error control operator.) This tells php to not output warnings in case of errors.

eval("\$result = @($expresion);");
if ($result == 0) {
    // do division by zero handling 
} else {
    // it's all good
}
ghoppe
  • 21,452
  • 3
  • 30
  • 21
0

using intdiv and DivisionByZeroError:

try {
    $a = 5;
    $b = 0;
    intdiv($a,$b);
}
catch(DivisionByZeroError $e){
    echo "got {$e->getMessage()}";
}
celsowm
  • 846
  • 9
  • 34
  • 59
0

I'm learning PHP 8 and have some new information if anyone is looking at this old question e.g. from a search engine or similar. If you're doing a division as a formula (e.g. $a = $b / $c; where $b and $c are both numbers but $c might be zero, you cannot use Exception as the criteria in your try/catch block, because the relevant condition is a type of Error in PHP, not an Exception. The three "catch" criteria that will work are:

  • DivisionByZeroError
  • Error
  • Throwable

As you can imagine, Error is much broader than DivisionByZeroError and in turn Throwable is much broader than Error (also encompassing all possible Exceptions). For that reason, DivisionByZeroError is generally the most advisable, but all three will work.

-1

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 ) )");
frodeborli
  • 1,537
  • 1
  • 21
  • 30
-1

This is the best way I found to accomplish this:

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
}
Gus Costa
  • 609
  • 5
  • 13