阅读:0       作者:严长生

阿拉伯数字转中文数字方法详解(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 中的中文数字和权位名称替换成大写数字就可以了,转换算法是一样的。如果用于人民币记账,可调整节权位的名称,加上“圆”或“圆整”等权名,有兴趣的读者可自行完成转换代码。