財務系統、合同生成、報表導出常需將阿拉伯數字金額轉為中文大寫(壹貳參肆伍陸柒捌玖)。本文提供三個版本實作:功能完整版、簡潔版、高效版,涵蓋四捨五入、進位、零處理、幣種單位自定義等細節。
class Num2RmbClass {
/**
* 人民幣數值轉中文大寫
* @param string $number 金額字串,預設 '0'
* @param string $int_unit 整數單位,預設 '元'(可改 '圓')
* @param bool $is_round 小數是否四捨五入,預設 true
* @param bool $is_extra_zero 整數尾零且有小數時是否輸出『零』,預設 false
* @param bool $dec_to_int 小數進位到個位,預設 false
* @return string 中文大寫金額
*/
public static function num2rmb($number = '0', $int_unit = '元', $is_round = true, $is_extra_zero = false, $dec_to_int = false) {
// 1. 分離整數/小數部分
$parts = explode('.', $number, 2);
$int = isset($parts[0]) ? strval($parts[0]) : '0';
$dec = isset($parts[1]) ? strval($parts[1]) : '';
// 2. 小數位處理:超過 2 位按參數決定截斷/四捨五入/進位
$dec_len = strlen($dec);
if ($dec_len > 2) {
if ($is_round) {
$rounded = round(floatval("0." . $dec), 2);
if ($rounded == 1 && $dec_to_int) { // 0.995 → 1.00 進位
$int = empty($int) ? '1' : strval(intval($parts[0]) + 1);
$dec = '00';
} elseif ($rounded == 1) { // 僅四捨五入不進位
$dec = '99';
} else {
$dec = substr(strrchr(strval($rounded), '.'), 1);
}
} else {
$dec = substr($dec, 0, 2); // 直接截斷
}
}
// 3. 零值快速返回
if (empty($int) && empty($dec)) return '零';
// 4. 定義中文數字與單位
$chs = array('零','壹','貳','參','肆','伍','陸','柒','捌','玖');
$uni = array('','拾','佰','仟');
$dec_uni = array('角','分');
$exp = array('', '萬');
$res = '';
// 5. 整數部分:從右向左,每 4 位一段(萬、億)
for ($i = strlen($int) - 1, $k = 0; $i >= 0; $k++) {
$str = '';
for ($j = 0; $j < 4 && $i >= 0; $j++, $i--) {
$u = $int{$i} > 0 ? $uni[$j] : '';
$str = $chs[$int{$i}] . $u . $str;
}
$str = rtrim($str, '0');
$str = preg_replace('/0+/', '零', $str);
if (!isset($exp[$k])) $exp[$k] = $exp[$k-2] . '億';
$u2 = $str != '' ? $exp[$k] : '';
$res = $str . $u2 . $res;
}
// 6. 小數部分:從左向右
$dec = rtrim($dec, '0');
if (!empty($dec)) {
$res .= $int_unit;
if ($is_extra_zero && substr($int, -1) === '0') $res .= '零';
for ($i = 0, $cnt = strlen($dec); $i < $cnt; $i++) {
$u = $dec{$i} > 0 ? $dec_uni[$i] : '';
$res .= $chs[$dec{$i}] . $u;
}
$res = rtrim($res, '0');
$res = preg_replace('/0+/', '零', $res);
} else {
$res .= $int_unit . '整';
}
return $number < 0 ? '(負)' . $res : $res;
}
}
// 使用範例
echo Num2RmbClass::num2rmb('1600020039.9989', '圓', false, false, false);
// 輸出:壹拾陸億零貳佰零參拾玖圓玖角玖分
function cny($ns) {
static $cnums = array('零','壹','貳','參','肆','伍','陸','柒','捌','玖');
static $cnyunits = array('圓','角','分');
static $grees = array('拾','佰','仟','萬','拾','佰','仟','億');
list($ns1, $ns2) = explode('.', $ns, 2);
$ns2 = array_filter(array($ns2[1], $ns2[0])); // 分、角
$ret = array_merge($ns2, array(implode('', _cny_map_unit(str_split($ns1), $grees)), ''));
$ret = implode('', array_reverse(_cny_map_unit($ret, $cnyunits)));
return str_replace(array_keys($cnums), $cnums, $ret);
}
function _cny_map_unit($list, $units) {
$ul = count($units);
$xs = array();
foreach (array_reverse($list) as $x) {
$l = count($xs);
if ($x != '0' || !($l % 4)) {
$n = ($x == '0' ? '' : $x) . $units[($l-1) % $ul];
} else {
$n = is_numeric($xs[0][0]) ? $x : '';
}
array_unshift($xs, $n);
}
return $xs;
}
echo cny('23058.04'); // 貳萬參仟零伍拾捌圓肆分
function num_to_rmb($num) {
$c1 = "零壹貳參肆伍陸柒捌玖";
$c2 = "分角元拾佰仟萬拾佰仟億";
$num = round($num, 2) * 100; // 轉為分,整數處理
if (strlen($num) > 10) return "金額太大,請檢查";
$i = 0; $c = "";
while (true) {
$n = ($i == 0) ? substr($num, -1, 1) : $num % 10;
$p1 = substr($c1, 3 * $n, 3); // 取中文數字
$p2 = substr($c2, 3 * $i, 3); // 取單位
// 非零或關鍵單位(億/萬/元)才加單位
if ($n != '0' || ($n == '0' && in_array($p2, array('億','萬','元')))) {
$c = $p1 . $p2 . $c;
} else {
$c = $p1 . $c;
}
$i++; $num = intval($num / 10);
if ($num == 0) break;
}
// 清理多餘『零』:零元→元、零萬→萬、零億→億、零零→零
$j = 0; $slen = strlen($c);
while ($j < $slen) {
$m = substr($c, $j, 6); // UTF-8 一漢字 3 字節
if (in_array($m, array('零元','零萬','零億','零零'))) {
$c = substr($c, 0, $j) . substr($c, $j + 3);
$j -= 3; $slen -= 3;
}
$j += 3;
}
// 尾部『零』處理
if (substr($c, -3) == '零') $c = substr($c, 0, -3);
return empty($c) ? '零元整' : $c . '整';
}
echo num_to_rmb(23000000.00); // 貳仟參佰萬元整
| 版本 | 特點 | 適用場景 | 效能 |
|---|---|---|---|
| 完整版 | 參數豐富、邊界全覆蓋、OOP 風格 | 財務核心系統、需精確控制格式 | 中等 |
| 簡潔版 | 代碼短、陣列映射巧妙、易閱讀 | 快速原型、中小專案、教學示範 | 良好 |
| 高效版 | 字串操作、無陣列開銷、單函數 | 高頻調用、批量導出、效能敏感場景 | 最佳 |
$tests = array(
'0' => '零元整',
'0.01' => '零元零壹分',
'0.10' => '零元壹角整',
'100' => '壹佰元整',
'101' => '壹佰零壹元整',
'1000' => '壹仟元整',
'1001' => '壹仟零壹元整',
'10000' => '壹萬元整',
'10001' => '壹萬零壹元整',
'100000000' => '壹億元整',
'123456789.45'=> '壹億貳仟參佰肆拾伍萬陸仟柒佰捌拾玖元肆角伍分',
'-99.99' => '(負)玖拾玖元玖角玖分',
);
foreach ($tests as $input => $expected) {
$out = Num2RmbClass::num2rmb($input);
echo "$input => $out " . ($out === $expected ? '✓' : '✗ EXPECT: '.$expected) . "\n";
}
財務級建議:生產環境務必建立自動化測試覆蓋上述邊界用例,並對接單元測試流程。涉及金額計算請優先使用
bcmath任意精度擴展避免浮點誤差,轉換前先bcadd($num, '0', 2)固定兩位小數。