🧮
「PHPの計算が合わない!?」浮動小数点誤差の原因と正しい対処法
「えっ、計算がズレる…?」
PHPで掛け算をしただけなのに、微妙に合わないという経験はありませんか?
😱 現象
たとえば、次の計算。
0.0768 * 5000
本来の正しい答えは:
0.0768 × 5000 = 384
ところが、PHPで実際に計算すると…
echo 0.0768 * 5000;
// => 383.99999999999994
となります。
🔍 原因:浮動小数点の「丸め誤差」
PHP(や多くのプログラミング言語)では、
**浮動小数点数(float / double)**を 2進数で近似的に表現しています。
✴️ イメージ図
10進数の 0.0768
↓
2進数では「有限小数」にならない(繰り返し小数)
↓
コンピュータは「近い値」で保存
↓
計算時にわずかな誤差が発生
つまり、0.0768 は内部的に「ほぼ 0.0768」な値として保存されており、
その結果、0.0768 * 5000 は正確に 384 にはならず、
383.99999999999994
のようにごくわずかに小さい結果となります。
📘 技術的背景
この現象は IEEE 754 という浮動小数点演算の国際規格に基づく仕様です。
PHPだけでなく、JavaScript・Python・C言語などでも同様に発生します。
つまり、これは「PHPのバグ」ではなく、計算方式そのものの制約です。
✅ 正しい解決方法
✅ ① bcmul()(任意精度計算関数)を使う
PHPには「文字列ベースで正確な計算」を行うための拡張関数 BCMath が標準で用意されています。
$this->suuryou = 0.0768; // 数量
$this->tanka = 5000; // 単価
// 高精度な掛け算
$value = bcmul((string)$this->suuryou, (string)$this->tanka, 10);
Log::debug('計算値', ['value' => $value]);
// => 384
ポイント:
- 引数は文字列として渡す(floatで渡すと意味がない)
- 第3引数は小数点以下の精度(桁数)を指定
✅ ② 桁数を丸める(誤差を無視できる場合)
もし表示目的などで「誤差が1円未満ならOK」なケースでは、round()を使って丸めるのも一つの手です。
$value = round(0.0768 * 5000, 2); // => 384.00
ただし、この方法は根本解決ではありません。
金額計算など正確さが要求される処理ではNGです。
⚠️ Laravelプロジェクトでの実務上の注意
- 金額・数量・税計算は 必ず文字列で計算(
bcmul,bcaddなど) - モデル内で数値演算する場合も、floatではなくstringを扱う
- データベースに保存する際は DECIMAL型 を使う
💡 補足:BCMathの主な関数一覧
| 関数 | 説明 |
|---|---|
bcadd(a, b, scale) |
加算 |
bcsub(a, b, scale) |
減算 |
bcmul(a, b, scale) |
乗算 |
bcdiv(a, b, scale) |
除算 |
bccomp(a, b, scale) |
比較 |
🧮 まとめ
| ポイント | 内容 |
|---|---|
| PHPのfloatは近似値 | 0.0768は2進数で正確に表せない |
| 誤差は仕様 | IEEE 754の制限によるもの |
| 解決策 |
bcmul()などの任意精度関数を使う |
| Laravelでも有効 | 金額計算はstring型&BCMathで扱う |
💬 まとめると
PHPの「0.0768 * 5000 = 383.99999999999994」はバグではなく仕様。
正確に扱いたいなら、bcmath系関数を使おう!
🧑💻 Written with ❤️ by @fuga-setoguchi
Discussion