- PHPのバージョンを7.4から8.0に上げたら、カスタムプラグインがエラーになるようになりました。
- エラーをみると「ゼロ除算(DivisionByZeroError)」。
- これまでは除算後に
NaN
のチェックをしていたのですが、除算前に0
のチェックが必要になったみたいです。 - どうも、除算演算子で
fdiv
でなくintdiv
が使われるようになっていたのが原因ようです。
1. カスタムプラグインのエラー
PHPのバージョンを7.4から8.0に上げたら、以前に自分で作ったWordPressのカスタムプラグイン1がエラーになりました。
エラーの詳細がメールで届いていました。
こんにちは。
WordPress には、サイトでプラグインやテーマが致命的なエラーを発生させた場合にそれを検知してこの自動メールでお知らせする機能があります。
今回の場合、WordPress がプラグイン ちいラボ統計 でエラーを捉えました。
エラー詳細
エラータイプ E_ERROR が /〜/chiilabo-stats-main.php ファイルの 498 行目で発生しました。
エラーメッセージ: Uncaught DivisionByZeroError:
Division by zero in /〜/chiilabo-stats-main.php:498
「DivisionByZeroError」が発生しているようです。
1-1. ゼロ除算のエラーが発生している
該当するスクリプトを見てみます。
$log = $post_id2log[$post->ID];
$pv_ave = ($log->pv / ceil($days[$post->ID]));
if (is_nan($pv_ave)) {
$pv_ave = 0;
}
この $log->pv / ceil($days[$post->ID])
でゼロ除算のエラーになっているようです。
1-2. 除算の前に除数をチェック
ということで、事前に除数が 0 かどうかチェックするように修正しました。
$log = $post_id2log[$post->ID];
$days_value = ceil($days[$post->ID]);
if ($days_value == 0) {
$days_value = 0;
}
$pv_ave = ($log->pv / $days_value);
これでちゃんと動くようになりました。
2. NaNじゃなかったの?
逆になんで今まで動いていたんだろう……・
これまでも一応、ゼロ除算は考慮していたつもりでした。
ゼロで割った結果は NaNになっていたように思い、次の is_nan
関数でチェックしていました。
$log = $post_id2log[$post->ID];
$pv_ave = ($log->pv / ceil($days[$post->ID]));
if (is_nan($pv_ave)) {
$pv_ave = 0;
}
つまり、未定義の値になったら 0になるように修正していました。
しかし、PHPには、ゼロ除算の例外 DivisionByZeroError があります。
例外処理をしないと、処理がエラーになってしまいます。
「NaN
」とは、「Not a Number」の略で、「数値ではない」という意味です。
「計算できない」や「数値にならない結果」を示す特別な値です。
たとえば、0で割ったり、負の数の平方根を計算しようとした場合などにNaN
が返されます。
2-1. 2種類の除算(fdiv, intdiv)
特別な値が返ってくるか、それとも例外になるかって、大違いじゃない。
これはややこしくて、PHPには2種類の除算用の関数があります。
- fdiv(num1, num2):浮動小数点数の割り算
- intdiv(num1, num2):整数の割り算
浮動小数点数の割り算では、0.0での除算は NaN と定義されています。
var_dump(fdiv(0.0, 0.0));
// float(NAN)
一方、整数の割り算では、0での除算は DivisionByZeroError の例外処理になっています。
var_dump(intdiv(1, 0));
// Fatal error: Uncaught DivisionByZeroError: Division by zero in %s
3. 演算子では fdivとintdivが自動で切り替わる
除算の演算子( num1/num2
)を使った割り算では、
基本は小数の結果になります。
つまり、 fdiv が適用されています。
しかし、たまに intdiv が適用されることがあります。
intdiv が適用される条件は、
- 割る数・割られる数が整数
- 結果が割り切れる場合
除算演算子 (“/”) の返す値は浮動小数点数となります。
PHP: 算術演算子 – Manual
ただし、ふたつのオペランドがともに整数 (あるいは整数に変換できる文字列) であり、かつ結果が割り切れる場合には整数値を返します。
整数の除算については intdiv() を参照ください。
これは、割り切れる結果を「整数値」で返すためです。
3-1. ゼロ除算は割り切れるのか?
では、「ゼロ除算はどうなるの?」という疑問が生まれます。
「結果が割り切れる」の解釈が問題になってくるのです。
どうも、これまではゼロ除算は「結果が割り切れない」という扱いで、fdivが適用されNaNになっていたように思います。
ところが、PHP8になってふたつのオペランドがともに整数なら、ゼロ除算でも intdivの方が適用されているようなのです2。
それでこれまではエラーになっていなかったんだね。