首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 浮點數精度陷阱深度解析:為何 0.58 * 100 = 57 及 BCMath 高精度解決方案

     編輯:PHP 相關知識

經典現場:intval(0.58 * 100) === 57

執行以下程式碼,輸出竟然是 57 而非預期的 58:

<?php
$f = 0.58;
var_dump(intval($f * 100)); // int(57)
?>

根本原因:IEEE 754 雙精度浮點數表示限制

電腦以二進位儲存浮點數(64 位元:1 符號位 + 11 指數位 + 52 尾數位)。十進位有限小數在二進位往往是無限循環小數,如 0.58、0.1、0.7 皆無法精確表示,只能截斷近似值。

  • 0.58 實際儲存值 ≈ 0.57999999999999996
  • 0.58 * 100 ≈ 57.99999999999999
  • intval() 直接截斷小數部份 → 57

這不是 PHP Bug,而是跨語言的硬體層面特性(Python、Java、JavaScript 皆同)。

另一經典案例:0.1 + 0.7 != 0.8

<?php
$a = 0.1; $b = 0.7;
var_dump(($a + $b) == 0.8); // bool(false)
var_dump(($a + $b) * 10);   // float(7.9999999999999)
echo floor((0.1+0.7)*10);   // 7 (非預期 8)
?>

PHP 官方手冊明確警告:永遠不要信任浮點數最後一位精度,永遠不要直接比較兩個浮點數是否相等。

解決方案:BCMath 高精度數學函數庫

PHP 內建 BCMath(Binary Calculator)擴展,以字串形式運算任意精度數字,完美解決金融、電商價格計算等精度敏感場景。

常用 BCMath 函數速查

函數功能範例
bcadd($left, $right, $scale)高精度加法bcadd('1.0321456', '0.0243456', 2) → '1.04'
bcsub($left, $right, $scale)高精度減法bcsub('1.0321456', '3.0123456', 2) → '-1.98'
bcmul($left, $right, $scale)高精度乘法bcmul('3.1415926', '2.4569874566', 2) → '7.71'
bcdiv($left, $right, $scale)高精度除法bcdiv('6', '5', 2) → '1.20'
bccomp($left, $right, $scale)比較兩數 (-1/0/1)bccomp('4.45', '5.54', 2) → -1
bcmod($left, $right)求餘數bcmod('10', '3') → '1'
bcpow($num, $exp, $scale)次方bcpow('2', '10', 0) → '1024'
bcsqrt($num, $scale)平方根bcsqrt('2', 10) → '1.4142135623'
bcscale($scale)設定預設小數位bcscale(3); bcdiv('105', '6.55957') → '16.007'

關鍵參數 $scale:指定結果保留小數位數

  • 所有運算函數最後一個參數為 $scale。
  • 或使用 bcscale($scale) 全域設定。
  • 注意:超出位數直接捨棄,不會四捨五入。

實戰修正範例

<?php
// 原本錯誤
$a = 0.1; $b = 0.7;
var_dump(($a + $b) == 0.8); // false

// BCMath 修正
var_dump(bcadd($a, $b, 2) == 0.8); // true

// 金額計算範例
$price = '199.99';
$quantity = '3';
$taxRate = '0.05';
$subtotal = bcmul($price, $quantity, 2);       // 599.97
$tax = bcmul($subtotal, $taxRate, 2);          // 30.00
$total = bcadd($subtotal, $tax, 2);            // 629.97
echo "總計:$total 元";
?>

最佳實踐建議

  1. 涉及金錢、價格、利率、匯率等精度敏感業務,全程使用 BCMath 字串運算,資料庫欄位建議用 DECIMAL 型別。
  2. 避免浮點數直接參與 ==、<、> 比較,改用 bccomp()。
  3. 輸入/輸出介面統一以字串傳遞數值,防止 JSON 序列化精度損失。
  4. 若專案極度依賴高效能數學運算,可考慮 GMP 擴展(支援整數任意精度),但 BCMath 通常已足夠。

總結

浮點數精度問題源於二進位表示機制,是跨語言通病。PHP 提供 BCMath 這把「瑞士刀」,以字串模擬手算邏輯,完美解決金融級精度需求。養成「見錢用 BCMath」習慣,能避開無數坑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved