金額顯示是電商、金融系統核心需求。本文提供功能完整的 `format_money()` 函式,支援負數處理、任意小數位、貨幣符號自訂、千分位分隔符自訂,並解析浮點數精度陷阱與現代 NumberFormatter 替代方案。
<?php
/**
* 格式化金額
* @param float|int $money 金額 (分為單位建議乘 100 存整數)
* @param int $decimals 小數位數 (預設 2)
* @param string $symbol 貨幣符號 (預設 ¥)
* @param string $thousandsSep 千分位分隔符 (預設 ,)
* @param string $decimalPoint 小數點符號 (預設 .)
* @return string 格式化後字串
*/
function format_money($money, $decimals = 2, $symbol = '¥', $thousandsSep = ',', $decimalPoint = '.') {
// 1. 處理負號
$negative = $money < 0;
$money = abs($money);
// 2. 使用 sprintf 精確控制小數 (避免浮點數精度問題)
$format = '%01.' . (int)$decimals . 'f';
$moneyStr = sprintf($format, $money);
// 3. 分離整數與小數部分
if ($decimals > 0) {
[$intPart, $decPart] = explode('.', $moneyStr);
$decimal = $decimalPoint . $decPart;
} else {
$intPart = $moneyStr;
$decimal = '';
}
// 4. 整數部分加千分位分隔符 (從右向左每 3 位)
$reversed = strrev($intPart);
$chunks = str_split($reversed, 3);
$formattedInt = strrev(implode($thousandsSep, $chunks));
// 5. 組合結果
return $symbol . ($negative ? '-' : '') . $formattedInt . $decimal;
}
// 使用範例
echo format_money(1234567.89); // ¥1,234,567.89
echo format_money(-98765.5); // -¥98,765.50
echo format_money(1000, 0, '$'); // $1,000
echo format_money(1234.5, 3, '€', ' ', ','); // €1 234,500
?>
<?php
// ❌ 錯誤:直接運算浮點數
$price = 19.99 * 3; // 59.970000000000006
echo format_money($price); // ¥59.97 (sprintf 自動四捨五入掩蓋問題)
// ✅ 正確:使用整數分為單位
$priceCents = 1999 * 3; // 5997
echo format_money($priceCents / 100); // ¥59.97
// ✅ 進階:BCMath 高精度
$price = bcmul('19.99', '3', 2); // '59.97'
echo format_money($price);
<?php
// 需安裝 php-intl 擴展
$fmt = new NumberFormatter('zh_TW', NumberFormatter::CURRENCY);
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, 'NT$');
echo $fmt->formatCurrency(1234567.89, 'TWD'); // NT$1,234,568 (依地區四捨五入)
echo $fmt->formatCurrency(-98765.5, 'TWD'); // -NT$98,766
// 更多控制
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 2);
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, 2);
echo $fmt->formatCurrency(1000, 'TWD'); // NT$1,000.00
| 策略 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| DECIMAL(10,2) | 精確、SQL 可直接運算 | 固定小數位、範圍有限 | 一般金額、價格 |
| BIGINT (分為單位) | 極高精度、效能最好、無圍誤差 | 讀取需除 100、跨幣種複雜 | 高頻交易、會計核心 |
| VARCHAR (字串) | 無精度限制 | 無法 SQL 運算、排序錯誤 | 極大金額、多幣種混存 |
// 使用 NumberFormatter 自動處理地區差異
$locales = ['zh_TW', 'en_US', 'de_DE', 'fr_FR', 'ja_JP'];
foreach ($locales as $locale) {
$fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
echo "$locale: " . $fmt->formatCurrency(1234.56, 'USD') . "\n";
}
// zh_TW: NT$1,234.56
// en_US: $1,234.56
// de_DE: 1.234,56 $
// fr_FR: 1 234,56 $
// ja_JP: ¥1,235 (日圓無小數)