問題現象
MySQL FLOAT/DECIMAL 欄位讀出後,PHP echo 可能輸出 100.33000000000001 或 100.0000,需格式化為乾淨字串。
內建函數方案
| 需求 | 函數 | 範例 | 結果 |
| 移除科學記數法、尾部零 | floatval() | floatval('100.33000') | 100.33 |
| 保留 N 位小數 (四捨五入) | number_format() | number_format(100.336, 2) | 100.34 |
| 保留 N 位、補零 | sprintf() | sprintf('%.2f', 100.3) | 100.30 |
| 不四捨五入、直接截斷 | 自定義函數 | truncateFloat(100.339, 2) | 100.33 |
自定義:不四捨五入截斷/補零
/**
* 格式化浮點數:不四捨五入,可指定小數位、補零
* @param float|string $num
* @param int $decimals 保留小數位 (0 表示不限制)
* @return string
*/
function formatFloat($num, $decimals = 0) {
$num = (float)$num; // 移除科學記數法、前導零
if ($decimals <= 0) {
// 不限制小數位:去除尾部零
$str = rtrim(rtrim(number_format($num, 16, '.', ''), '0'), '.');
return $str === '' ? '0' : $str;
}
// 固定小數位:截斷不四捨五入
$factor = 10 ** $decimals;
$truncated = (int)($num * $factor) / $factor;
// 輸出並補零
return sprintf('%.' . $decimals . 'f', $truncated);
}
// 測試
echo formatFloat('1001.334534', 2) . "\n"; // 1001.33
echo formatFloat('-1001.000', 2) . "\n"; // -1001.00
echo formatFloat('1001.3', 5) . "\n"; // 1001.30000
echo formatFloat('1001.33') . "\n"; // 1001.33 (不限制)
echo formatFloat('1001.000') . "\n"; // 1001
高精度場景:BCMath
// 任意精度,不四捨五入截斷
function bcFormatFloat($num, $decimals = 2) {
$neg = bccomp($num, '0') < 0;
$num = $neg ? substr($num, 1) : $num;
$parts = explode('.', $num);
$intPart = $parts[0] ?? '0';
$decPart = $parts[1] ?? '';
$decPart = substr($decPart . str_repeat('0', $decimals), 0, $decimals);
$result = $intPart . ($decimals > 0 ? '.' . $decPart : '');
return $neg ? '-' . $result : $result;
}
echo bcFormatFloat('1001.339999', 2); // 1001.33
echo bcFormatFloat('0.0000000000000001', 10); // 0.0000000000
資料庫層面最佳實踐
- 金額/精確計算:使用
DECIMAL(19,4) 或 BIGINT 存分 (乘 100/10000),避免浮點誤差。
- 查詢時格式化:
SELECT FORMAT(price, 2) AS price_fmt FROM products (MySQL 返回字串,已四捨五入)。
- PHP 端統一處理:封裝
Money / Price Value Object,內部用整數分,輸出時格式化。
總結選型
| 場景 | 推薦 |
| 一般顯示、四捨五入可接受 | number_format($val, 2) |
| 金融/計費、嚴禁四捨五入 | 自定義 formatFloat() 或 BCMath |
| 高精度科學計算 | bcmath / gmp |
| 資料庫存儲 | DECIMAL 或整數分 |