1. 程式人生 > >阿拉伯數字轉中文數字方法詳解(C++實現)

阿拉伯數字轉中文數字方法詳解(C++實現)

阿拉伯數字與中文數字沒有一一對應關係,不存在直接轉換的公式化演算法,因此需要根據兩種數字體系的特點精心構造轉換演算法。

中文計數有一個特點,就是“零”的使用變化多端。阿拉伯數字中數字的權位依靠數字在整個數字長度中的偏移位置確定,因此數字中間出現的0用於標記數字的偏移位置,即便是連續出現的0也不能省略。中文計數方式中每個數字的權位都直接跟在數字後面,因此代表連續出現的若干個0。

儘管如此,也不是所有的情況都使用“零”,比如阿拉伯數字 20001234,中文數字表示為“二千萬一千二百三十四”,沒有用一個“零”;再比如阿拉伯數字 12000,中文數字表示為“一萬二千”,也沒有用“零”;但是對於阿拉伯數字 10210300,中文數字表示為“一千零二十一萬零三百”,兩次出現“零”。

針對這種情況,中文數字對“零”的使用總結起來有以下三條規則:
  1. 以 10000 為小節,小節的結尾即使是 0,也不使用“零”。
  2. 小節內兩個非 0 數字之間要使用“零”。
  3. 當小節的“千”位是 0 時,若本小節的前一小節無其他數字,則不用“零”,否則就要用“零”。

從阿拉伯數字到中文數字的轉換,第一步是以“萬”為單位分節,並確定節權位。第二步是對每小節內的數字確定權位,並按照前面的三種方法處理“零”的問題:

一個轉換示例

以阿拉伯數字 200001010200 為例,首先以“萬”為單位對其分節,可分為三節:2000 0101 0200:
  • 第一節 2000,節權位是“億”,因為這一節的 0 都在結尾,根據規則 1,此處不使用“零”,直接表示為“二千億”。
  • 第二節 0101,節權位是“萬”,因兩個 1 之間有 0,根據規則 2,101 可以描述為“一百零一”。另外,此節的千位是 0,根據規則 3,因本小節前還有數字,因此需要用“零”。也就是說,本小節需要兩個“零”。
  • 最後一個小節,結尾的兩個 0 根據規則 1,不使用“零”,但是千位的 0 根據規則 3,需要使用“零”。

根據以上分析,將三個小節的轉換結果組合在一起,阿拉伯數字 200001010200 的中文表示就是“二千億零一百零一萬零二百”。

從這個例子可以看出來,對阿拉伯數字分節,確定數字的權位很簡單,最難處理的就是 0 的轉換,需要根據三個規則靈活選擇是否需要使用“零”。

轉換演算法設計

設計阿拉伯數字轉中文數字的演算法,也可以遵循上例中的兩個步驟來處理,但是需要解決三個問題。

第一個問題是單個數字的轉換,這個並不難,因為阿拉伯數字 0〜9 與相應的中文數字是一一對應的。對這個轉換設計演算法非常簡單,可以定義中文數字表:
const char *chnNumChar[CHN_NUM_CHAR_COUNT] = {"零","一","二","三","四","五","六","七","八","九"};
待轉換的阿拉伯數字作為陣列下標,比如 chnNumChar[5] 就是阿拉伯數字 5 對應的中文數字。

第二個需要解決的問題是節與權位的識別。節的劃分很簡單,以“萬”為單位截斷即可。節權位的定義也釆用一維表,可以利用陣列下標直接定位出節權的中文名稱:
const char *chnUnitSection[] = {"萬","億","萬億"};
對於 32 位正數能表達的最大數來說,最大節權是“萬億”己經足夠了,如果要轉換更大的數,可以延伸這個節權表的定義,比如增加“億億”。數字中最低的節沒有節權,使用空字串作為佔位符也是一個演算法設計常用的一致性處理的技巧:對最低的節不做特殊處理,

和其他節一樣指定節權位,只不過節權位是空字串,對轉換出的中文數字最終結果沒有影響。每個節內的數字對應的權位也採用這種方式定義:
const char *chnUnitChar[] = {"十","百","千"};
最低位的權位是空字串,處理方式和節權位的處理方式一樣。數字權位的確定並不困難,通過移位就可以確定每個數字對應的權位。阿拉伯數字的權位是隱含在數字的位數中的,使用 0 作為佔位符。比如數字 1000,要使1處在千位,一定會補 3 個 0 作為佔位符,否則1就不代表“一千”。 

既然每一位的權都在固定的位置上,只要記錄移位的次數就可以確定阿拉伯數字的權位,以移位次數做下標,直接查 chnUnitSection 和 chnUnitChar 表就可以得到正確的中文數字的權位。

第三個需要解決的問題是如何處理中文“零”。這個問題稍微有點困難,需要根據文章開頭給出的三個規則靈活判斷,此外,對於連續出現的阿拉伯數字 0,也只能用一箇中文“零”。

演算法實現

轉換演算法首先要對阿拉伯數字分節,並確定節權位名稱。num 對 10000 取模可得到一個 section,將這個 section 轉成中文數字,然後根據節的位置補上節權位,即可完成一個節的中文數字轉換。重複這個過程,直到 num 等於 0 為止,整個轉換就算完成。

unitPos 變數記錄節的位置,0 對應空字串,1 對應“萬”,2 對應“億”,隨著 unitPos 的增加,節權位也越來越大。全 0 的節不需要節權位,這個在程式碼中也有處理。根據規則 3 的定義,如果一節內數字的千位是 0,需要根據前面是否還有數字決定是否需要加“零”,NumberToChinese() 函式中利用變數 needZero 和 while(num > 0) 迴圈語句,巧妙地做了這個加“零”處理,省去了一個if判斷。
//num == 0需要特殊處理,直接返回"零"
void NumberToChinese(unsigned int num, std::string& chnStr)
{
    int unitPos = 0;
    std::string strIns;
    bool needZero = false;
    while(num > 0)
    {
        unsigned int section = num % 10000;
        if(needZero)
        {
            chnStr.insert(0, chnNumChar[0]);
        }
        SectionToChinese(section, strIns);
        /*是否需要節權位? */
        strIns += (section != 0) ? chnUnitSection[unitPos] : chnUnitSection[0];
        chnStr.insert(0, strIns);
        /*千位是0需要在下一個section補零*/
        needZero = (section < 1000) && (section > 0);
        num = num / 10000;
        unitPos++;
    }
}
SectionToChinese() 函式將一個節的數字轉換成中文數字,利用中文數字表 chnNumChar 轉換中文數字,利用表 chnUnitChar 得到數字權位,unitPos 變數用作權位索引。SectionToChinese() 函式的關鍵部分是對 0 的處理,根據規則 1 和規則 2,小節結尾的 0 不需要轉換成“零”,但是兩個數字之間的 0 需要轉換成“零”。如果兩個數字之間有 多個 0,也只轉換一個“零”,變數 zero 用於控制“零”的轉換,避免出現多個“零”連在一起的情況。
void SectionToChinese(unsigned int section, std::string& chnStr)
{
    std::string strIns;
    int unitPos = 0;
    bool zero = true;
    while(section > 0)
    {
        int v = section % 10;
        if(v == 0)
        {
            if( (section ==0) || Izero )
            {
                zero = true; /*需要補,zero的作用是確保對連續的多個,只補一箇中文零*/
                chnStr.insert(0, chnNumChar[v]);
            }
        }
        else
        {
            zero = false; //至少有一個數字不是
            strIns = chnNumChar[v]; //此位對應的中文數字
            strIns += chnUnitChar [unitPos]; //此位對應的中文權位
            chnStr.insert(0, strIns);
        }
        unitPos++; //移位
        section = section / 10;
    }
}

中文大寫數字

中文數字還有一個很有意思的現象,就是中文數字大寫。所謂的大寫其實就是用一些筆畫複雜的漢字代替簡單的數字漢字,其目的就是為了保證其不容易被篡改。中文大寫用“壹貳叄肆伍陸柒捌玫”代替“一二三四五六七八九”,用“拾佰仟”代替“十百千”。這些數字的繁寫其實在唐代就己經出現,但正式作為記載錢糧、稅收等專案用的官方數字,是在明朝初年著名的“郭桓案”之後。

郭桓案:與空印案、胡惟庸案和藍玉案一起並稱為明初四大案。郭桓案發生在明朝洪武十八年(1385年),屬於官吏貪汙案件。戶部侍郎郭桓等人,串通地方官吏作弊,篡改賬冊,私吞太平、鎮江等府的賦稅,還盜賣官糧。後被揭發,以其涉案金額巨大,對經濟領域影響深遠而為世人矚目,對此,明太祖將六部左、右侍郎以下官員全部處死,地方官吏死於獄中者達數萬人以上。為了追贓,牽連到全國各地的小富百姓,遭到抄家破產的不計其數。由於牽扯麵 廣,全國百姓對此案非常不滿意,明太祖為了平息民怨,將審刑官吳席等人也一併處死。

實現中文大寫數字的轉換,只需要將 chnNumChar、chnUnitSection 中的中文數字和權位名稱替換成大寫數字就可以了,轉換演算法是一樣的。如果用於人民幣記賬,可調整節權位的名稱,加上“圓”或“圓整”等權名,有興趣的讀者可自行完成轉換程式碼。