動的に作成する必要がある大きな数式があります。たとえば、「何か」を解析すると、結果は"$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 Bakerのコメントに関して、例を挙げましょう。私のパーサーは次のようなものを構築できます:
"$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";
?>
しかし、何も印刷しません。
if ($baz == 0.0) {
echo 'Divisor is 0';
} else {
...
}
評価した式でユーザー入力を使用している場合は非常に危険なevalを使用するのではなく、 PHPClassesでのevalmath などの適切なパーサーを使用してください。ゼロ
エラーが発生した場合に例外をスローするようにエラーハンドラを設定する必要があります。
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()
を参照してください
PHP 7.0なので、単にDivisionByZeroError
をキャッチできます
http://php.net/manual/en/class.divisionbyzeroerror.php を参照してください
try {
$fooNum / 0;
} catch (DivisionByZeroError $exception) {
// Handle division by 0 here
}
他の人が述べたように、分母が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
これらを「キャッチ」したい場合は、 カスタムエラーハンドラーを設定 を実行する必要があります。これは、ゼロエラーによる除算を検出し、それらについて何かを行います。残念ながら、カスタムエラーハンドラーはキャッチオールです。つまり、他のすべてのエラーに対して適切な処理を行うためのコードを記述する必要もあります。
PHP7では、 DivisionByZeroError を使用できます
try {
echo 1/0;
} catch(DivisionByZeroError $e){
echo "got $e";
} catch(ErrorException $e) {
echo "got $e";
}
私もその問題に直面していました(動的表現)。一番いい方法ではないかもしれませんが、それは機能します。例外をスローする代わりに、もちろん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;
}
if(@eval("\$result = $expresion;")===FALSE){
$result=0;
}
ただし、0による除算エラーをキャッチするだけではありません。
これは古い質問であることに気づきましたが、今日それは関連性があり、ここでの答えはあまり好きではありません。
これを修正する適切な方法は、実際に式を評価することです。つまり、式を解析し、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 ) )");
私もこれに苦労していますが、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
によって処理されるのではなくシャットダウンする理由はわかりませんが、これは静かに死ぬだけでなく、それを超えるのに役立ちました。
数値と数学演算子+-* /を含む文字列が入力として渡されます。プログラムは(BODMASに従って)式の値を評価し、出力を印刷する必要があります。
入力/出力の例:引数が「7 + 4 * 5」の場合、出力は27でなければなりません。引数が「55 + 21 * 11-6/0」の場合、出力は「エラー」でなければなりません(ゼロ除算は定義されていません)。
問題:
b=1; c=0; a=b/c; // Error Divide by zero
シンプルなソリューション:
if(c!=0) a=b/c;
else // error handling
intdiv
およびDivisionByZeroError
を使用:
try {
$a = 5;
$b = 0;
intdiv($a,$b);
}
catch(DivisionByZeroError $e){
echo "got {$e->getMessage()}";
}
使う @
(An エラー制御演算子 。)これは、エラーの場合に警告を出力しないようにphpに指示します。
eval("\$result = @($expresion);");
if ($result == 0) {
// do division by zero handling
} else {
// it's all good
}