PHP, как отловить деление на ноль?

У меня есть большое математическое выражение, которое должно быть создано динамически. Например, после того, как я проанализировал "что-то", результатом будет строка вроде: "$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 ) )"

Парсер строит строку шаг за шагом, не беспокоясь о значении vars... так что в этом случае, если $foz == $bak на самом деле деление на ноль: $baz / ( 0 ),

С другой стороны, как предположил Пит, я попытался:

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

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

Но это ничего не печатает.

17 ответов

Решение
if ($baz == 0.0) {
    echo 'Divisor is 0';
} else {
    ...
}

Вместо использования eval, что крайне опасно, если вы используете пользовательский ввод в выражении evalled, почему бы не использовать правильный синтаксический анализатор, такой как evalmath для PHPClasses, и который вызывает чистое исключение при делении на ноль

На PHP7 вы можете использовать DivisionByZeroError

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

Вам просто нужно установить обработчик ошибок для выдачи исключения в случае ошибок:

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

Вот еще одно решение:

<?php

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

set_error_handler('e');

eval('echo 1/0;');

Увидеть set_error_handler()

Как уже упоминалось, попробуйте найти решение, которое позволит вам проверить, равен ли знаменатель 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

Если вы хотите "поймать" их, вам нужно установить собственный обработчик ошибок, который будет обнаруживать ошибки деления на ноль и что-то с ними делать. К сожалению, пользовательские обработчики ошибок - это главное, что означает, что вам также нужно написать некоторый код, чтобы сделать что-то соответствующее со всеми другими ошибками.

Если дополнительная обработка не требуется, вы также можете просто сделать

      $division = $foo / ($bar ?: 1);
if(@eval("\$result = $expresion;")===FALSE){
  $result=0;
}

Не поймать просто делить на 0 ошибок.

Проблема:

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

Решение простое:

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

Я тоже боролся с этим, 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 но это помогло мне выйти за рамки просто молча умирая.

Использовать @ ( Оператор контроля ошибок.) Это говорит php не выводить предупреждения в случае ошибок.

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

Я тоже столкнулся с этой проблемой (динамические выражения). Это было так, что, возможно, не самый хороший способ, но он работает. Вместо того, чтобы генерировать исключение, вы, конечно, можете вернуть нуль, ложь или все, что пожелаете. Надеюсь это поможет.

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

Я понимаю, что это старый вопрос, но он актуален сегодня, и мне не очень нравятся ответы здесь.

Надлежащий способ исправить это - фактически оценить выражение самостоятельно, то есть проанализировать выражение, а затем оценить его шаг за шагом, а не перенести его в PHP. Это можно сделать с помощью https://en.wikipedia.org/wiki/Shunting-yard_algorithm.

Я написал следующую реализацию, но я не проверял ее. Это основано на вышеупомянутой статье Wikipedia. Правоассоциативные операторы не поддерживаются, поэтому немного упрощены.

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

Это лучший способ, который я нашел для этого:

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
}

В качестве входных данных передается строка, содержащая числа и математические операторы + - * /. Программа должна оценить значение выражения (согласно BODMAS) и распечатать вывод.

Пример ввода / вывода: если аргумент "7 + 4*5", выход должен быть 27. Если аргумент "55 + 21 * 11 - 6/0", выход должен быть "ошибка" (поскольку деление на ноль не определен).

С помощью intdiv а также DivisionByZeroError:

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

Вы можете просто поймать DivisionByZeroError в PHP >= 7

Смотрите http://php.net/manual/en/class.divisionbyzeroerror.php

Я изучаю PHP 8 и у меня есть новая информация, если кто-то просматривает этот старый вопрос, например, из поисковой системы или чего-то подобного. Если вы выполняете деление по формуле (например, $a = $b/$c; где $b и $c — оба числа, но $c может быть нулем, вы не можете использовать Exception в качестве критерия в блоке try/catch. , поскольку соответствующее условие — это тип ошибки в PHP, а не исключение. Три критерия «ловушки», которые будут работать:

  • Деление по нулевой ошибке
  • Ошибка
  • Бросок

Как вы понимаете, Error намного шире, чем DivisionByZeroError, а Throwable, в свою очередь, намного шире, чем Error (также охватывая все возможные исключения). По этой причине DivisionByZeroError обычно является наиболее целесообразным, но все три будут работать.

Другие вопросы по тегам