web-dev-qa-db-ja.com

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

しかし、それは機能しません。それでは、ゼロによる除算があるときにアプリケーションがクラッシュするのをどのように回避できますか?

編集:

まず、私は何かを明確にしたい:式は動的に構築されるので、分母がゼロである場合、単に評価することはできません。それで... Mark Ba​​kerのコメントに関して、例を挙げましょう。私のパーサーは次のようなものを構築できます:

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

しかし、何も印刷しません。

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

評価した式でユーザー入力を使用している場合は非常に危険なevalを使用するのではなく、 PHPClassesでのevalmath などの適切なパーサーを使用してください。ゼロ

17
Mark Baker

エラーが発生した場合に例外をスローするようにエラーハンドラを設定する必要があります。

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

別のソリューションを次に示します。

_<?php

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

set_error_handler('e');

eval('echo 1/0;');
_

set_error_handler() を参照してください

6
Bill Karwin

PHP 7.0なので、単にDivisionByZeroErrorをキャッチできます

http://php.net/manual/en/class.divisionbyzeroerror.php を参照してください

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

他の人が述べたように、分母が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

これらを「キャッチ」したい場合は、 カスタムエラーハンドラーを設定 を実行する必要があります。これは、ゼロエラーによる除算を検出し、それらについて何かを行います。残念ながら、カスタムエラーハンドラーはキャッチオールです。つまり、他のすべてのエラーに対して適切な処理を行うためのコードを記述する必要もあります。

3
Alan Storm

PHP7では、 DivisionByZeroError を使用できます

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

私もその問題に直面していました(動的表現)。一番いい方法ではないかもしれませんが、それは機能します。例外をスローする代わりに、もちろんnullまたはfalseを返すことができます。お役に立てれば。

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

ただし、0による除算エラーをキャッチするだけではありません。

2
Pete

これは古い質問であることに気づきましたが、今日それは関連性があり、ここでの答えはあまり好きではありません。

これを修正する適切な方法は、実際に式を評価することです。つまり、式を解析し、PHPにトランスコンパイルするのではなく、段階的に評価します。これは https://en.wikipedia.org/wiki/Shunting-yard_algorithm を使用して実行できます。

次の実装を作成しましたが、テストしていません。上記のウィキペディアの記事に基づいています。右結合演算子はサポートされていないため、少し簡略化されています。

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

特定の場合

シャンティングヤードソリューションを好む場合でも、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 ) )");
0
frodeborli

私もこれに苦労していますが、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によって処理されるのではなくシャットダウンする理由はわかりませんが、これは静かに死ぬだけでなく、それを超えるのに役立ちました。

0
justin.m.chase

数値と数学演算子+-* /を含む文字列が入力として渡されます。プログラムは(BODMASに従って)式の値を評価し、出力を印刷する必要があります。

入力/出力の例:引数が「7 + 4 * 5」の場合、出力は27でなければなりません。引数が「55 + 21 * 11-6/0」の場合、出力は「エラー」でなければなりません(ゼロ除算は定義されていません)。

0
dinesh

問題:

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

シンプルなソリューション:

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

intdivおよびDivisionByZeroErrorを使用:

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

使う @(An エラー制御演算子 。)これは、エラーの場合に警告を出力しないようにphpに指示します。

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