1.

PHP 算術演算子の落とし穴(オーバーフロー / ゼロ除算 / 浮動小数誤差 / BCMath)

編集
この記事の要点
  • 基本: + - * / % **。除算 /整数÷整数でも結果は float になる可能性
  • 整数オーバーフロー: PHP_INT_MAX 超えで自動的に float に昇格
  • ゼロ除算: PHP 8 から DivisionByZeroError をスロー(PHP 7 は警告 + INF/NaN)
  • 整数除算は intdiv()剰余% または fmod()(浮動小数用)
  • 高精度計算BCMath / GMP。金額計算で 0.1 + 0.20.3 にならない問題を回避

演算子一覧

演算子名前結果
+加算2 + 35
-減算5 - 23
*乗算4 * 312
/除算10 / 33.333... (float)
%剰余 (整数)10 % 31
**べき乗 (PHP 5.6+)2 ** 101024
-$a符号反転-5-5
+$a数値変換+"42"42 (int)

整数除算と剰余

<?php
// PHP の / は「結果が整数になっても float」を返すことがある
var_dump(10 / 2);            // int(5)    (PHP 7+ では整数返り)
var_dump(10 / 3);            // float(3.3333333333333)
var_dump(10 / 4);            // float(2.5)

// 整数除算が必要なら intdiv()
var_dump(intdiv(10, 3));     // int(3)
var_dump(intdiv(-10, 3));    // int(-3)  ← ゼロ方向への切り捨て

// (int) キャストでも整数化できるが、負の値で挙動が違う
var_dump((int)(10 / 3));     // int(3)
var_dump((int)(-10 / 3));    // int(-3)  ← ゼロ方向

// 剰余 (整数同士)
var_dump(10 % 3);            // int(1)
var_dump(-10 % 3);           // int(-1)   ← 結果の符号は左オペランド

// 浮動小数の剰余
var_dump(fmod(10.5, 3));     // float(1.5)

整数オーバーフローと float 昇格

PHP の int は環境依存(64bit OS では PHP_INT_MAX = 9223372036854775807)。それを超えると自動的に float になります:

<?php
echo PHP_INT_MAX;            // 9223372036854775807
echo PHP_INT_SIZE;           // 8 (64bit) または 4 (32bit)

$n = PHP_INT_MAX;
var_dump($n + 1);            // float(9.2233720368548E+18)  ← int の精度を超える
var_dump(is_int($n + 1));    // bool(false)

// float に変わると精度が落ちる (整数の連続性を保てない)
var_dump(PHP_INT_MAX + 1 == PHP_INT_MAX + 2);  // true ←危険

// 大きな整数を厳密に扱いたい場合は GMP
$big = gmp_add(PHP_INT_MAX, 1);
echo gmp_strval($big);       // 9223372036854775808
echo gmp_strval(gmp_mul($big, $big));  // 2 倍精度でも正確

ゼロ除算: DivisionByZeroError (PHP 8)

操作PHP 7PHP 8
1 / 0Warning + INFDivisionByZeroError
0 / 0Warning + NANDivisionByZeroError
1 % 0Warning + falseDivisionByZeroError
intdiv(1, 0)DivisionByZeroErrorDivisionByZeroError
fmod(1, 0)Warning + NANWarning + NAN (例外にならない)
<?php
// PHP 8 は安全に try/catch できる
try {
    $r = 10 / 0;
} catch (DivisionByZeroError $e) {
    echo '0 除算: ' . $e->getMessage();
}

// 事前チェックが王道
if ($denominator !== 0) {
    $r = $numerator / $denominator;
}

べき乗とルート

<?php
// べき乗演算子 (PHP 5.6+)
var_dump(2 ** 10);           // int(1024)
var_dump(2 ** 0.5);          // float(1.4142...)   sqrt(2)

// pow() 関数 (古くから存在)
var_dump(pow(2, 10));        // 1024
var_dump(pow(8, 1/3));       // 2.0  (立方根)

// ルート
var_dump(sqrt(16));          // 4.0
var_dump(sqrt(2));           // 1.4142135623731

// 結合性: ** は右結合
var_dump(2 ** 3 ** 2);       // 512 ← 2 ** (3 ** 2) = 2 ** 9

浮動小数の罠

2 進浮動小数は 10 進小数を正確に表現できません。金額計算には絶対に float を使わないのが鉄則です:

<?php
// 古典的な罠
var_dump(0.1 + 0.2);         // float(0.3)  (表示は丸められる)
var_dump(0.1 + 0.2 === 0.3); // bool(false) !!
var_dump(0.1 + 0.2 == 0.3);  // bool(false) !!

// 比較は許容誤差で
function feq(float $a, float $b, float $eps = 1e-9): bool {
    return abs($a - $b) < $eps;
}
var_dump(feq(0.1 + 0.2, 0.3));  // true

// 金額計算の事故例
$tax = 100 * 0.10;            // 10.0 (大丈夫)
$total = 158.30 + 0.05;       // 158.35... 表示は OK
$cents = (int) ($total * 100); // 15834... 1 円ずれる場合あり

// ✅ 整数 (銭単位) で扱う
$priceCents = 15830;
$taxCents = 5;
$totalCents = $priceCents + $taxCents;
$total = $totalCents / 100;

BCMath / GMP で高精度計算

<?php
// BCMath: 任意精度 10 進
bcscale(20);                 // 計算桁数

echo bcadd('0.1', '0.2');             // 0.3 (厳密)
echo bcmul('123.456', '0.10');        // 12.3456
echo bcdiv('1', '3', 10);             // 0.3333333333

// 金額計算
$price = '1980';
$tax = bcmul($price, '0.10');
$total = bcadd($price, $tax);          // '2178'

// GMP: 任意精度整数 (暗号系で多用)
$a = gmp_init('123456789012345678901234567890');
$b = gmp_init('987654321098765432109876543210');
echo gmp_strval(gmp_add($a, $b));
echo gmp_strval(gmp_mul($a, $b));
echo gmp_strval(gmp_pow(2, 256));      // 2^256

インクリメント / デクリメント

<?php
$a = 5;
echo $a++;        // 5 (出力後にインクリメント)
echo "\n";
echo ++$a;        // 7 (インクリメント後に出力)
echo --$a;        // 6

// 文字列インクリメント (Perl 由来の珍機能)
$s = 'a';
$s++;             // 'b'
$s = 'z';
$s++;             // 'aa'
$s = 'Az';
$s++;             // 'Ba'

// null をインクリメント
$x = null;
$x++;             // 1

FAQ

Q: 10 / 0 を 0 として扱いたい
A: 直接書かず、$x = $b == 0 ? 0 : $a / $b; のように事前チェック。PHP 8 では try/catch も可。

Q: 整数 ÷ 整数 で整数だけ欲しい
A: intdiv($a, $b)(int)($a / $b) は float 経由で精度を失う可能性あり。

Q: 大量データの集計で float の累積誤差が気になる
A: 金額系なら BCMath。科学計算なら Kahan の補正和。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 算術演算子
  2. 文字列演算子
  3. 代入演算子
  4. 比較演算子
  5. 論理演算子
  6. ビット演算子

最近更新/作成されたページ