タイトル: 算術演算子
SEOタイトル: PHP 算術演算子の落とし穴(オーバーフロー / ゼロ除算 / 浮動小数誤差 / BCMath)
| この記事の要点 |
|
演算子一覧
| 演算子 | 名前 | 例 | 結果 |
|---|---|---|---|
+ | 加算 | 2 + 3 | 5 |
- | 減算 | 5 - 2 | 3 |
* | 乗算 | 4 * 3 | 12 |
/ | 除算 | 10 / 3 | 3.333... (float) |
% | 剰余 (整数) | 10 % 3 | 1 |
** | べき乗 (PHP 5.6+) | 2 ** 10 | 1024 |
-$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 7 | PHP 8 |
|---|---|---|
1 / 0 | Warning + INF | DivisionByZeroError |
0 / 0 | Warning + NAN | DivisionByZeroError |
1 % 0 | Warning + false | DivisionByZeroError |
intdiv(1, 0) | DivisionByZeroError | DivisionByZeroError |
fmod(1, 0) | Warning + NAN | Warning + 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 の補正和。