5

.NET 工具函式 - 阿拉伯數字與中文數字雙向轉換

 7 months ago
source link: https://blog.darkthread.net/blog/cht-num-converter/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

.NET 工具函式 - 阿拉伯數字與中文數字雙向轉換-黑暗執行緒

十四年前我寫過地址阿拉伯數字轉中文大寫的 .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;
}

Fig1_638411846253089805.png

隨意抽樣幾個較易出狀況的情境看起來都 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;
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK