十四年前我寫過地址阿拉伯數字轉中文大寫的 .NET 函式,但它有兩個問題:一是依賴 Visual Studio International Feature Pack,二是當年只做了阿拉伯數字轉中文,沒有中文數字逆解回阿拉伯數字的能力。

總之,十四年後我把這個題目當成暖身練習,打算不靠第三方程式庫,用 C# 從頭寫一個阿拉伯數字與中文數字雙向轉換函式。

為了充分測試各種狀況,我寫了一小段程式列舉從零到「1234 兆 1234 億 1234 萬 1234」的 164 = 65536 種組合,將數字轉中文,再將中文轉回數字,並比對還原後數字是否相符。

var numSets = new[] {
    "0000", "0001", "0010", "0012",
    "0100", "0102", "0310", "0123",
    "1000", "1002", "1020", "1023",
    "1200", "1203", "1230", "1234"
};
// 列出所有可能的組合
var count = numSets.Length;
var idxs = new int[4];
var lv = idxs.Length - 1;
while (true)
{
    var sb = new StringBuilder();
    for (var i = 0; i < idxs.Length; i++)
    {
        sb.Append(numSets[idxs[i]]);
    }
    long n = long.Parse(sb.ToString());
    var cht = ChtNumConverter.ToChtNum(n);
    var restored = ChtNumConverter.ParseChtNum(cht);
    Console.WriteLine($"{n, -16:n0} {restored, -16:n0} " + 
        $"\x1b[{(n == restored ? "32mPASS" : "31mFAIL")}\x1b[0m {cht} ");
    if (n != restored)
        throw new ApplicationException($"數字轉換錯誤 {n} vs {restored}");
    if (idxs.All(o => o == count - 1)) break;
    idxs[lv]++;
    while (idxs[lv] == count)
    {
        idxs[lv] = 0;
        lv--;
        if (lv < 0) break;
        idxs[lv]++;
    }
    lv = idxs.Length - 1;
}

隨意抽樣幾個較易出狀況的情境看起來都 OK:

          100,102           100,102 十萬零一百零二
        1,021,023         1,021,023 一百零二萬一千零二十三    
       12,000,012        12,000,012 一千二百萬零一十二        
      103,100,310       103,100,310 一億零三百一十萬零三百一十  
    1,000,101,234     1,000,101,234 十億零一十萬一千二百三十四       
1,000,000,000,310 1,000,000,000,310 一兆零三百一十 
1,000,000,100,000 1,000,000,100,000 一兆零一十萬

附上完整程式碼(含測試),需要的同學請自取,如發現 Bug 歡迎留言反應,感謝。

using System.Text;
using System.Text.RegularExpressions;

var numSets = new[] {
    "0000", "0001", "0010", "0012",
    "0100", "0102", "0310", "0123",
    "1000", "1002", "1020", "1023",
    "1200", "1203", "1230", "1234"
};
// 列出所有可能的組合
var count = numSets.Length;
var idxs = new int[3];
var lv = idxs.Length - 1;
while (true)
{
    var sb = new StringBuilder();
    for (var i = 0; i < idxs.Length; i++)
    {
        sb.Append(numSets[idxs[i]]);
    }
    long n = long.Parse(sb.ToString());
    var cht = ChtNumConverter.ToChtNum(n);
    var restored = ChtNumConverter.ParseChtNum(cht);
    Console.WriteLine($"{n, 16:n0} {restored, 16:n0} " + 
        $"\x1b[{(n == restored ? "32mPASS" : "31mFAIL")}\x1b[0m {cht} ");
    if (n != restored)
        throw new ApplicationException($"數字轉換錯誤 {n} vs {restored}");
    if (idxs.All(o => o == count - 1)) break;
    idxs[lv]++;
    while (idxs[lv] == count)
    {
        idxs[lv] = 0;
        lv--;
        if (lv < 0) break;
        idxs[lv]++;
    }
    lv = idxs.Length - 1;
}

public class ChtNumConverter
{
    public static string ChtNums = "零一二三四五六七八九";
    public static Dictionary<string, long> ChtUnits = new Dictionary<string, long>{
            {"十", 10},
            {"百", 100},
            {"千", 1000},
            {"萬", 10000},
            {"億", 100000000},
            {"兆", 1000000000000}
        };
    // 解析中文數字        
    public static long ParseChtNum(string chtNumString)
    {
        var isNegative = false;
        if (chtNumString.StartsWith("負"))
        {
            chtNumString = chtNumString.Substring(1);
            isNegative = true;
        }
        long num = 0;
        // 處理千百十範圍的四位數
        Func<string, long> Parse4Digits = (s) =>
        {
            long lastDigit = 0;
            long subNum = 0;
            foreach (var rawChar in s)
            {
                var c = rawChar.ToString().Replace("〇", "零");
                if (ChtNums.Contains(c))
                {
                    lastDigit = (long)ChtNums.IndexOf(c);
                }
                else if (ChtUnits.ContainsKey(c))
                {
                    if (c == "十" && lastDigit == 0) lastDigit = 1;
                    long unit = ChtUnits[c];
                    subNum += lastDigit * unit;
                    lastDigit = 0;
                }
                else
                {
                    throw new ArgumentException($"包含無法解析的中文數字:{c}");
                }
            }
            subNum += lastDigit;
            return subNum;
        };
        // 以兆億萬分割四位值個別解析
        foreach (var splitUnit in "兆億萬".ToArray())
        {
            var pos = chtNumString.IndexOf(splitUnit);
            if (pos == -1) continue;
            var subNumString = chtNumString.Substring(0, pos);
            chtNumString = chtNumString.Substring(pos + 1);
            num += Parse4Digits(subNumString) * ChtUnits[splitUnit.ToString()];
        }
        num += Parse4Digits(chtNumString);
        return isNegative ? -num : num;
    }
    // 轉換為中文數字
    public static string ToChtNum(long n)
    {
        var negtive = n < 0;
        if (negtive) n = -n;
        if (n >= 10000 * ChtUnits["兆"])
            throw new ArgumentException("數字超出可轉換範圍");
        var unitChars = "千百十".ToArray();
        // 處理 0000 ~ 9999 範圍數字
        Func<long, string> Conv4Digits = (subNum) =>
        {
            var sb = new StringBuilder();
            foreach (var c in unitChars)
            {
                if (subNum >= ChtUnits[c.ToString()])
                {
                    var digit = subNum / ChtUnits[c.ToString()];
                    subNum = subNum % ChtUnits[c.ToString()];
                    sb.Append($"{ChtNums[(int)digit]}{c}");
                }
                else sb.Append("零");
            }
            sb.Append(ChtNums[(int)subNum]);
            return sb.ToString();
        };
        var numString = new StringBuilder();
        var forceRun = false;
        foreach (var splitUnit in "兆億萬".ToArray())
        {
            var unit = ChtUnits[splitUnit.ToString()];
            if (n < unit)
            {
                if (forceRun) numString.Append("零");
                continue;
            }
            forceRun = true;
            var subNum = n / unit;
            n = n % unit;
            if (subNum > 0)
                numString.Append(Conv4Digits(subNum).TrimEnd('零') + splitUnit);
            else numString.Append("零");
        }
        numString.Append(Conv4Digits(n));
        var t = Regex.Replace(numString.ToString(), "[零]+", "零");
        if (t.Length > 1) t = t.Trim('零');
        t = Regex.Replace(t, "^一十", "十");
        return (negtive ? "負" : string.Empty) + t;
    }
}

This article provide example of converting Arabic numerals and Chinese numerals.


Comments

Be the first to post a comment

Post a comment