在財務系統、電子合同、報表導出等場景中,將阿拉伯數字金額轉換為中文大寫(如「壹仟貳佰元整」)是剛性需求。本文提供兩種風格迥異的 PHP 實現方案:一種是邏輯清晰、逐位處理的教學級代碼;另一種是利用數組映射、逆序處理的工程級精簡代碼。並深入解析核心難點:零的處理、單位進位、小數角分轉換。
/**
* 數字金額轉中文大寫
* @param float|string $num 數字或數字字符串
* @return string 中文大寫金額
*/
function num_to_rmb($num) {
$c1 = "零壹貳三肆伍陸柒捌玖"; // 數字映射:每字 3 字節(UTF-8)
$c2 = "分角元拾佰仟萬拾佰仟億"; // 單位映射:同樣每字 3 字節
// 1. 精確到分,轉為整數處理
$num = round($num, 2) * 100;
if (strlen($num) > 10) return "金額太大,請檢查"; // 超過億級
$i = 0; $c = "";
// 2. 從低位向高位逐位處理
while (true) {
$n = ($i == 0) ? substr($num, -1, 1) : ($num % 10);
$p1 = substr($c1, 3 * $n, 3); // 取對應漢字
$p2 = substr($c2, 3 * $i, 3); // 取對應單位
// 3. 非零數字、或零但處於大單位位(億/萬/元)時輸出單位
if ($n != '0' || ($n == '0' && in_array($p2, ['億', '萬', '元']))) {
$c = $p1 . $p2 . $c;
} else {
$c = $p1 . $c; // 零不輸出單位
}
$num = (int)($num / 10);
if ($num == 0) break;
$i++;
}
// 4. 合併連續零、處理尾部零
$j = 0; $slen = strlen($c);
while ($j < $slen) {
$m = substr($c, $j, 6); // UTF-8 漢字佔 3 字節,檢查雙字
if (in_array($m, ['零元', '零萬', '零億', '零零'])) {
$c = substr($c, 0, $j) . substr($c, $j + 3);
$j -= 3; $slen -= 3;
}
$j += 3;
}
// 去除末尾零(如 23.0 產生的「零」)
if (substr($c, -3) == '零') $c = substr($c, 0, -3);
return empty($c) ? "零元整" : $c . "整";
}
echo num_to_rmb(23000000.00); // 貳仟三佰萬元整
/**
* 阿拉伯數字轉中文大寫金額(增強版)
* @param string|float $num 金額
* @param bool $mode true=貨幣模式(元/角/分) false=純數字模式
* @param bool $sim true=簡體 false=繁體(財務大寫)
* @return string
*/
function NumToCNMoney($num, $mode = true, $sim = true) {
if (!is_numeric($num)) return '含有非數字字符!';
$char = $sim ?
['零','一','二','三','四','五','六','七','八','九'] :
['零','壹','貳','三','肆','伍','陸','柒','捌','玖'];
$unit = $sim ?
['','十','百','千','','萬','億','兆'] :
['','拾','佰','仟','','萬','億','兆'];
$retval = $mode ? '元' : '點';
$dec = '';
// 1. 分離小數部分
if (strpos($num, '.') !== false) {
list($num, $dec) = explode('.', $num);
$dec = strval(round($dec, 2)); // 保留兩位
if ($mode) {
$retval = $char[$dec[0]] . '角' . $char[$dec[1]] . '分' . $retval;
} else {
for ($i = 0; $i < strlen($dec); $i++) $retval .= $char[$dec[$i]];
}
}
// 2. 整數部分逆序處理
$str = $mode ? strrev((string)intval($num)) : strrev($num);
$out = [];
for ($i = 0, $c = strlen($str); $i < $c; $i++) {
$digit = $str[$i];
$out[$i] = $char[$digit];
if ($mode) {
// 非零才加單位
$out[$i] .= ($digit != '0') ? $unit[$i % 4] : '';
// 連續兩零則當前位不輸出(由高位單位覆蓋)
if ($i > 1 && $str[$i] + $str[$i-1] == 0) $out[$i] = '';
// 每 4 位加大單位(萬/億/兆)
if ($i % 4 == 0) $out[$i] .= $unit[4 + floor($i / 4)];
}
}
return join('', array_reverse($out)) . $retval;
}
// 測試用例
echo NumToCNMoney(2.55) . "\n"; // 貳元伍角伍分
echo NumToCNMoney(2.55, 1, 0) . "\n"; // 二元五角五分
echo NumToCNMoney(7965) . "\n"; // 七仟九佰陸拾伍元
echo NumToCNMoney(155555555.68) . "\n"; // 壹億伍仟伍佰伍拾伍萬伍仟伍佰伍拾伍元陸角捌分
echo NumToCNMoney(0.8888888) . "\n"; // 玖角玖分
echo NumToCNMoney(99999999999) . "\n"; // 玖仟玖佰玖拾玖億玖仟玖佰玖拾玖萬玖仟玖佰玖拾玖元
$i % 4 循環對應「拾/佰/仟」,floor($i/4) 對應「萬/億/兆」$str[$i] + $str[$i-1] == 0 巧妙判斷連續兩零class Num2RmbClass {
/**
* @param string $number 金額字符串
* @param string $int_unit 單位名(元/圓)
* @param bool $is_round 是否四捨五入
* @param bool $is_extra_zero 整數末尾為零且有小數時,是否輸出「零」(如 1960.30 → 壹仟玖佰陸拾元零三角)
* @param bool $dec_to_int 小數進位到個位(0.995 → 1 元)
*/
public static function num2rmb($number = 0, $int_unit = '元', $is_round = true, $is_extra_zero = false, $dec_to_int = false) {
// ... 完整代碼見原文,核心邏輯同方案一但更健壯:
// 1. explode 分離整數/小數
// 2. 小數超長處理(四捨五入/截斷/進位)
// 3. 整數部分按 4 位分段(萬/億),段內按拾/佰/仟
// 4. 正則合併連續零:preg_replace("/0+/", "零", $str)
// 5. 可選的「尾部零」處理與「整」字補全
}
}
// 使用示例
echo Num2RmbClass::num2rmb('1600020039.9989', '圓', false, false, false);
// 壹拾陸億零貳萬零叁拾玖圓玖角玖分
| 場景 | 問題 | 解決方案 |
|---|---|---|
| 浮點數精度 | 0.58 * 100 = 57.999999 | 統一用字符串處理,或 round($num * 100) |
| 大數字溢出 | 超過 PHP 整數範圍 | 全程字符串操作,或使用 BCMath 擴展 |
| 負數處理 | 業務是否允許負數 | 前置「負」字,取絕對值處理 |
| 「零元整」輸出 | 0.00 應輸出「零元整」而非空 | 結果為空時兜底返回 |
| 小數位不足 | 23.5 → 23.50 | sprintf("%.2f", $num) 或 str_pad |
數字轉中文大寫的核心難點在於零的語義處理與單位進位邏輯。方案一適合教學與邏輯驗證,方案二適合生產環境並支持多種業務變體。實際項目中建議封裝為工具類,配合單元測試覆蓋邊界情況(0、負數、大數、小數進位、連續零等),確保財務級準確性。