intval(0.58 * 100) === 57執行以下程式碼,輸出竟然是 57 而非預期的 58:
<?php
$f = 0.58;
var_dump(intval($f * 100)); // int(57)
?>
電腦以二進位儲存浮點數(64 位元:1 符號位 + 11 指數位 + 52 尾數位)。十進位有限小數在二進位往往是無限循環小數,如 0.58、0.1、0.7 皆無法精確表示,只能截斷近似值。
0.5799999999999999657.99999999999999intval() 直接截斷小數部份 → 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 官方手冊明確警告:永遠不要信任浮點數最後一位精度,永遠不要直接比較兩個浮點數是否相等。
PHP 內建 BCMath(Binary Calculator)擴展,以字串形式運算任意精度數字,完美解決金融、電商價格計算等精度敏感場景。
| 函數 | 功能 | 範例 |
|---|---|---|
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 元";
?>
DECIMAL 型別。==、<、> 比較,改用 bccomp()。浮點數精度問題源於二進位表示機制,是跨語言通病。PHP 提供 BCMath 這把「瑞士刀」,以字串模擬手算邏輯,完美解決金融級精度需求。養成「見錢用 BCMath」習慣,能避開無數坑。