首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 實現阿拉伯數字轉中文人民幣大寫:兩種完整方案與核心邏輯解析

     編輯:PHP 綜合知識

前言

在財務系統、電子合同、報表導出等場景中,將阿拉伯數字金額轉換為中文大寫(如「壹仟貳佰元整」)是剛性需求。本文提供兩種風格迥異的 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); // 貳仟三佰萬元整

核心邏輯解析

  1. 統一放大 100 倍:將「元、角、分」轉為整數位操作,避免浮點數精度陷阱
  2. 逆序處理:從個位(分)向高位循環,天然匹配單位遞增規律
  3. 零的智能處理:
    • 中間零只保留數字「零」,不輸出單位
    • 大單位位(億/萬/元)即使為零也必須輸出單位(如「壹億零壹萬」)
    • 事後用字符串替換合併「零零」→「零」、「零元」→「元」

方案二:映射表 + 逆序遍歷法(代碼精簡,工程實用)

/**
 * 阿拉伯數字轉中文大寫金額(增強版)
 * @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.50sprintf("%.2f", $num) 或 str_pad

總結

數字轉中文大寫的核心難點在於零的語義處理與單位進位邏輯。方案一適合教學與邏輯驗證,方案二適合生產環境並支持多種業務變體。實際項目中建議封裝為工具類,配合單元測試覆蓋邊界情況(0、負數、大數、小數進位、連續零等),確保財務級準確性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved