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

Но это не сработает. Итак, как я могу избежать того, что мое приложение выйдет из строя, когда есть деление на ноль?

Edit:

Во-первых, я хочу кое-что прояснить: выражение построено динамически, поэтому я не могу просто оценить, если знаменатель равен нулю. Итак... что касается комментария Марка Бейкера, позвольте мне привести вам пример. Мой парсер мог бы построить что-то вроде этого:

"$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";
?> 

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

Ответ 1

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

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

Ответ 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();

Ответ 3

Здесь другое решение:

<?php

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

set_error_handler('e');

eval('echo 1/0;');

См. set_error_handler()

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

Проблема:

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

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

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

Ответ 11

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

Ответ 13

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

Правильный способ исправить это - это выполнить собственное вычисление выражения, то есть проанализировать выражение, а затем оценить его шаг за шагом, вместо того, чтобы переносить его в 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() -version, я бы создал метод 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 ) )");

Ответ 14

используя intdiv и DivisionByZeroError:

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

Ответ 15

Это лучший способ для достижения этой цели:

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
}