🧮

「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