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

PHP 人民幣數值轉中文大寫封裝類:支持進位、零處理、繁簡體、多幣種單位的企業級實現

     編輯:PHP 綜合知識

前言

財務系統、電子合同、報表導出中,人民幣數值轉中文大寫是高頻剛需。本文提供三個層次的解決方案:功能完整的企業級封裝類(支持進位、尾零、繁簡體、自定義單位)、函數式精簡實現(利用映射表逆序處理)、教學級逐位解析法。重點解決小數進位、連續零合併、大單位(萬/億)零保留、「整」字補全等核心難點。

方案一:企業級封裝類 Num2RmbClass(推薦生產環境)

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 元)
     * @return string 中文大寫金額
     */
    public static function num2rmb(
        string $number = '0',
        string $int_unit = '元',
        bool $is_round = true,
        bool $is_extra_zero = false,
        bool $dec_to_int = false
    ): string {
        // 1. 分離整數/小數部分
        $parts = explode('.', $number, 2);
        $int = $parts[0] ?? '0';
        $dec = $parts[1] ?? '';
        
        // 2. 小數處理:超過 2 位時四捨五入/截斷/進位
        $decLen = strlen($dec);
        if ($decLen > 2) {
            if ($is_round) {
                $rounded = round((float)("0.$dec"), 2);
                if ($rounded >= 1.0 && $dec_to_int) {
                    // 小數進位到整數部分
                    $int = (string)((int)$int + 1);
                    $dec = '00';
                } elseif ($rounded >= 1.0) {
                    // 不進位,小數置為 99 分(最大)
                    $dec = '99';
                } else {
                    $dec = sprintf('%02d', (int)($rounded * 100));
                }
            } else {
                $dec = substr($dec, 0, 2);
            }
        } elseif ($decLen === 1) {
            $dec .= '0'; // 如 .5 → .50
        } elseif ($decLen === 0) {
            $dec = '00';
        }
        
        // 3. 雙零快速返回
        if ($int === '0' && $dec === '00') return '零' . $int_unit . '整';
        
        // 4. 映射表
        $chs = ['零','壹','貳','三','肆','伍','陸','柒','捌','玖'];
        $uni = ['','拾','佰','仟'];           // 段內單位
        $decUni = ['角','分'];                // 小數單位
        $exp = ['','萬','億','兆'];           // 大單位(每 4 位)
        
        // 5. 整數部分:從右向左,每 4 位一段
        $res = '';
        for ($i = strlen($int) - 1, $seg = 0; $i >= 0; $seg++) {
            $segStr = '';
            // 處理段內 4 位
            for ($j = 0; $j < 4 && $i >= 0; $j++, $i--) {
                $digit = (int)$int[$i];
                $unit = $digit > 0 ? $uni[$j] : '';
                $segStr = $chs[$digit] . $unit . $segStr;
            }
            // 段後處理
            $segStr = rtrim($segStr, '零');           // 去尾部零
            $segStr = preg_replace('/零+/', '零', $segStr); // 合併連續零
            
            if ($segStr !== '') {
                $res = $segStr . $exp[$seg] . $res;
            } elseif ($seg > 0 && $res !== '') {
                // 段全為零但非最高段,需保留大單位(如 1,0001 → 壹萬零壹)
                $res = $exp[$seg] . $res;
            }
        }
        
        // 6. 小數部分
        if ($dec !== '00') {
            $res .= $int_unit;
            // 可選:整數末尾為零且有小數,輸出「零」
            if ($is_extra_zero && substr($int, -1) === '0') $res .= '零';
            
            for ($i = 0; $i < 2; $i++) {
                $digit = (int)$dec[$i];
                $unit = $digit > 0 ? $decUni[$i] : '';
                $res .= $chs[$digit] . $unit;
            }
            $res = rtrim($res, '零');
            $res = preg_replace('/零+/', '零', $res);
        } else {
            $res .= $int_unit . '整';
        }
        
        return $res;
    }
}

// 使用示例
echo Num2RmbClass::num2rmb('1600020039.9989', '圓', false, false, false);
// 輸出:壹拾陸億零貳萬零叁拾玖圓玖角玖分

echo Num2RmbClass::num2rmb('1960.30', '元', true, true, false);
// 輸出:壹仟玖佰陸拾元零三角整

echo Num2RmbClass::num2rmb('0.005', '元', true, false, true);
// 輸出:壹元整(小數進位)

方案二:函數式精簡實現(利用逆序映射,代碼極短)

/**
 * 精簡版:逆序遍歷 + 單位映射表
 * @param string $ns 數字字符串
 * @return string
 */
function cny(string $ns): string {
    static $cnums = ['零','壹','貳','三','肆','伍','陸','柒','捌','玖'];
    static $cnyUnits = ['圓','角','分'];
    static $grees = ['拾','佰','仟','萬','拾','佰','仟','億'];
    
    // 分離整數/小數
    [$ns1, $ns2] = explode('.', $ns . '.00', 2); // 確保有小數部分
    $ns2 = array_filter([$ns2[1] ?? '0', $ns2[0] ?? '0']); // 角、分(逆序)
    
    // 整數部分逆序分段處理
    $intRev = strrev($ns1);
    $intParts = [];
    for ($i = 0; $i < strlen($intRev); $i++) {
        $digit = $intRev[$i];
        $unit = $digit !== '0' ? $grees[$i] : '';
        $intParts[] = $digit . $unit;
    }
    // 合併:小數(逆序) + 整數(逆序) + 空佔位
    $all = array_merge($ns2, $intParts, ['']);
    // 逆序恢復正序
    $all = array_reverse($all);
    // 映射數字→漢字
    $mapped = array_map(function($v) use ($cnums) {
        return strtr($v, $cnums);
    }, $all);
    
    return implode('', $mapped);
}

// 測試
echo cny('23058.04'); // 貳萬三仟零伍拾捌圓肆分

核心技巧解析

  1. 統一逆序:整數 strrev、小數 array_filter([角,分]) 統一為低位在前
  2. 單位映射表 $grees:索引直接對應位權(0→空、1→拾、2→佰、3→仟、4→萬...)
  3. strtr 批量替換:一次性將所有數字字符替換為漢字
  4. array_filter 去除空單位:零不輸出單位,自動實現零處理

方案三:教學級逐位解析法(邏輯最清晰)

/**
 * 教學版:逐位處理,邏輯直觀
 */
function num_to_rmb($num) {
    $c1 = "零壹貳三肆伍陸柒捌玖"; // 每字 3 字節
    $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, ['億','萬','元']))) {
            $c = $p1 . $p2 . $c;
        } else {
            $c = $p1 . $c;
        }
        
        $num = (int)($num / 10);
        if ($num == 0) break;
        $i++;
    }
    
    // 後處理:合併零、去尾零
    $j = 0; $len = strlen($c);
    while ($j < $len) {
        $m = substr($c, $j, 6);
        if (in_array($m, ['零元','零萬','零億','零零'])) {
            $c = substr($c, 0, $j) . substr($c, $j + 3);
            $j -= 3; $len -= 3;
        }
        $j += 3;
    }
    if (substr($c, -3) === '零') $c = substr($c, 0, -3);
    
    return empty($c) ? "零元整" : $c . "整";
}

關鍵難點對比總結

難點方案一(類)方案二(函數式)方案三(教學)
小數進位參數 $dec_to_int 控制不支持不支持
尾零輸出參數 $is_extra_zero 控制自動處理後處理正則
大單位零保留段級邏輯顯式處理映射表自然保留條件判斷保留
連續零合併preg_replace('/零+/', '零')array_filter 隱式字符串替換
繁簡體切換可擴展映射表需修改靜態數組修改 $c1
性能O(n) 字符串操作O(n) 數組操作,極快O(n) 字符串操作
可讀性高(參數語義化)中(技巧性強)最高(邏輯直觀)

生產環境部署建議

  1. 輸入統一為字符串:避免 0.58 * 100 = 57.9999 浮點陷阱
  2. 單元測試覆蓋邊界:0、負數、純整數、純小數、進位、大數、連續零、尾零
  3. 異常處理:非數字輸入拋出 InvalidArgumentException
  4. 緩存優化:高頻調用時,映射表靜態化、編譯期常量化
  5. 國際化擴展:映射表外置配置,支持多語言貨幣格式

總結

方案一(Num2RmbClass)功能最完整、參數化靈活,適合企業級財務系統;方案二(cny 函數)代碼極簡、性能極佳,適合輕量級項目;方案三(教學版)邏輯最清晰,適合理解原理、教學演示。核心難點在於零的語義處理:中間零只保留數字、大單位位零保留單位、尾部零去除、小數進位。建議根據項目複雜度選擇方案,並建立完整測試用例庫。

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