web-dev-qa-db-fra.com

PHP, Comment attraper une division par zéro?

J'ai une grande expression mathématique qui doit être créée de manière dynamique. Par exemple, une fois que j'ai analysé "quelque chose", le résultat sera une chaîne telle que: "$foo+$bar/$baz";.

Donc, pour calculer le résultat de cette expression, j'utilise la fonction eval ... quelque chose comme ceci:

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

Le problème ici est que parfois, des erreurs me disent qu'il y a eu une division par zéro et que je ne sais pas comment détecter cette exception. J'ai essayé des choses comme:

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

Ou:

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

Mais ça ne marche pas. Alors, comment puis-je éviter que mon application se bloque lorsqu'il existe une division par zéro?

Modifier:

Premièrement, je veux clarifier quelque chose: l'expression est construite de manière dynamique, je ne peux donc pas simplement évaluer si le dénominateur est zéro. Alors ... en ce qui concerne le commentaire de Mark Baker, laissez-moi vous donner un exemple. Mon analyseur pourrait construire quelque chose comme ceci:

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

L'analyseur construit la chaîne étape par étape sans se soucier de la valeur des vars ... donc dans ce cas, si $foz == $bak il y a en fait une division par zéro: $baz / ( 0 ).

D'autre part, comme Pete l'a suggéré, j'ai essayé:

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

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

Mais cela n'imprime rien.

25
Cristian
if ($baz == 0.0) {
    echo 'Divisor is 0';
} else {
    ...
}

Plutôt que d'utiliser eval, ce qui est très dangereux si vous utilisez une entrée utilisateur dans l'expression evalled, pourquoi ne pas utiliser un analyseur syntaxique approprié tel que evalmath sur PHPClasses , et qui lève une exception pure avec division par zéro

16
Mark Baker

Voici une autre solution:

<?php

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

set_error_handler('e');

eval('echo 1/0;');

Voir set_error_handler()

6
Bill Karwin

Il vous suffit de définir un gestionnaire d’erreur pour générer une exception en cas d’erreur:

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();
4
tacone

Vous pouvez simplement attraper DivisionByZeroError depuis PHP 7.0

Voir http://php.net/manual/en/class.divisionbyzeroerror.php

try {
    $fooNum / 0;
} catch (DivisionByZeroError $exception) {
    // Handle division by 0 here
}
3
simPod

Comme d'autres l'ont mentionné, envisagez une solution qui vous permettra de vérifier si le dénominateur est 0.

Puisque ce conseil vous semble inutile, voici un bref aperçu de la gestion des erreurs PHP.

Les premières versions de PHP ne comportaient pas d'exceptions. Au lieu de cela, des messages d'erreur de différents niveaux ont été générés (avis, avertissements, etc.). Une erreur fatale arrête l'exécution.

PHP5 a apporté des exceptions à la table, et les bibliothèques plus récentes fournies par PHP (PDO) lèveront des exceptions lorsque des événements inattendus/mauvais se produisent. Cependant, la base de code principale n'a PAS été réécrite pour utiliser l'exception. Les fonctions et opérations principales reposent toujours sur l'ancien système d'erreur.

Lorsque vous divisez par 0, vous obtenez un avertissement, pas une 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

Si vous voulez "attraper" ceux-ci, vous devrez définir un gestionnaire d'erreurs personnalisé qui détectera les erreurs de division par zéro et fera quelque chose à leur sujet. Malheureusement, les gestionnaires d’erreurs personnalisés sont un piège à tous, ce qui signifie que vous devrez également écrire du code pour faire quelque chose d’approprié avec toutes les autres erreurs.

3
Alan Storm

Sur PHP7, vous pouvez utiliser DivisionByZeroError

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

Je faisais également face à ce problème (expressions dynamiques). Idid it de cette façon qui pourrait ne pas être la meilleure façon mais ça marche. Au lieu de lancer une exception, vous pouvez bien sûr renvoyer null ou false ou ce que vous voulez. J'espère que cela t'aides.

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;
}
2
Christopher Fox
if(@eval("\$result = $expresion;")===FALSE){
  $result=0;
}

Ne vous contentez pas d'attraper la division par 0 erreurs cependant.

2
Pete

Je me rends compte que c’est une vieille question, mais elle est pertinente aujourd’hui et je n’aime pas beaucoup les réponses ici.

La bonne façon de résoudre ce problème est d’évaluer vous-même l’expression - c’est-à-dire d’analyser l’expression puis de l’évaluer étape par étape au lieu de la transpiler en PHP. Cela peut être fait en utilisant le https://en.wikipedia.org/wiki/Shunting-yard_algorithm

J'ai écrit l'implémentation suivante, mais je ne l'ai pas testée. Il est basé sur l'article de Wikipedia ci-dessus. Il n'y a pas de support pour les opérateurs à droite associative, donc c'est légèrement simplifié.

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

Dans votre cas particulier

Même si je préfèrerais la solution Shunting Yard - si je décidais quand même d’opter pour une version de eval () -, je créerais une méthode custom_division ($ leftHandSide, $ rightHandSide) qui lève une exception. Ce code:

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

devient

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

J'ai également eu du mal à cela, les solutions set_error_handler ne fonctionnaient pas pour moi, probablement à cause des différences de version de PHP.

La solution pour moi était de tenter de détecter une erreur à l’arrêt:

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

Je ne suis pas sûr de savoir pourquoi une division par 0 est en train de disparaître plutôt que d'être traitée par le set_error_handler mais cela m'a aidé à aller au-delà de cette mort silencieuse.

0
justin.m.chase

Une chaîne contenant des nombres et les opérateurs mathématiques + - */est passée en entrée . Le programme doit évaluer la valeur de l'expression (selon BODMAS) et imprimer le résultat.

Exemple d'entrée/sortie: Si l'argument est "7 + 4 * 5", la sortie doit être 27 . Si l'argument est "55 + 21 * 11 - 6/0", la sortie doit être "erreur". (Comme la division par zéro n'est pas définie).

0
dinesh

Problème:

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

Solution simple:

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

en utilisant intdiv et DivisionByZeroError:

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

Utilisez un @ (Un opérateur de contrôle error .) Ceci indique à php de ne pas émettre d'avertissements en cas d'erreur.

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