整数转中文金额大写

起因

前几天用网银给朋友转账,在金额一栏中输入阿拉伯数字,右边会立即显示出相应的汉语数字大写。感觉挺有意思,就到网上搜索一下现成代码,找到一段 Java 的和一段 C# 的,但它们的实现都太繁琐,并且功能上有缺陷,例如 100 被翻译成“壹佰零拾零”,在汉语中应该是“壹佰元整”。因此决定自己研究汉语中数字的读写方法,实现正确并且优雅的算法。

算法设计

一提起处理文本,我现在脑海中就会思维定势般得闪过正则表达式;一想到正则表达式,就会联想到 Perl;然后再想到正则方面能和 Perl 能相媲美的 JavaScript。因此本文的代码用JavaScript来实现。

数字分组

西方国家习惯把数字按三分组,如1,234,567,890;而中国文化中数字按四分组,如12,3456,7890。即,数字的单位(“个”、“十”、“百”、“千”)在不同的组(“万”、“亿”等)里会反复出现。因此定义两个数组分别记录单位名和组名:

var unit = ['', '拾', '佰', '仟'];
var group = ['元', '万', '亿'];

从数学的角度,“万”以下没有组级别的单位,但金额里会在带上“元”这个单位,因此把group[0]定义成“元”。然后,使用双重循环往数字中间填入单位:

var s = '';
for (var i = 0; i < group.length && n > 0; i++) {
  var p = '';
  for (var j = 0; j < unit.length && n > 0; j++) {
    p = (n % 10) + unit[j] + p;
    n = Math.floor(n / 10);
  }
  s = p + group[i] + s;
}

此时“1002300405”会被翻译成“1拾0亿0仟2佰3拾0万0仟4佰0拾5元”。

翻译数字

其次,阿拉伯数字要翻译成相应的中文大写:零壹贰叁肆伍陆柒捌玖。在JS中,将这些汉字保存成一个数组,然后用数字做索引即可。

var digit = [
    '零', '壹', '贰', '叁', '肆',
    '伍', '陆', '柒', '捌', '玖'
];

p = digit[n % 10] + unit[j] + p;

此时“1002300405”会被翻译成“壹拾零亿零仟贰佰叁拾零万零仟肆佰零拾伍元”。

去除多余的零

到目前为止,翻译的结果还些不符合汉语的规则。

分组末尾的零

例如“壹拾零亿”会读成“壹拾亿”,“贰佰零拾零万”读成“贰佰万”。即末尾的零不读:

p.replace(/(零.)*零$/, '')

此时“1002300405”会被翻译成“壹拾亿零仟贰佰叁拾万零仟肆佰零拾伍元”。

当某个分组全是零

“0”这种特殊情况会被上面的逻辑处理成“空”,因此需要额外处理:

p.replace(/(零.)*零$/, '')
 .replace(/^$/, '零')

即把空替换成零。

合并之后

此时“1000000000”会被翻译成“壹拾亿零万零元”,即分组合并之后依然有多余的零,因此也要对合并的结果作相同的处理。

s.replace(/(零.)*零元/, '元')
 .replace(/^$/, '零元')

中间连续的零

虽然我们有处理末尾的零,但中间的连续零需要被整合成一个零。例如“1000000002”被翻译成“壹拾亿零万零仟零佰零拾贰元”,但按照中文的习惯应该读成“壹拾亿零贰元”。

s.replace(/(零.)*零元/, '元')
 .replace(/(零.)+/g, '零')
 .replace(/^$/, '零元')

代码

下面是完整的代码,目前只处理正整数。处理小数部分的算法请参见《浮点数转中文金额大写》。

var digit_uppercase = function(n) {
    var digit = [
        '零', '壹', '贰', '叁', '肆',
        '伍', '陆', '柒', '捌', '玖'
    ];
    var unit = ['', '拾', '佰', '仟'];
    var group = ['元', '万', '亿'];

    var s = '';
    for (var i = 0; i < group.length && n > 0; i++) {
        var p = '';
        for (var j = 0; j < unit.length && n > 0; j++) {
            p = digit[n % 10] + unit[j] + p;
            n = Math.floor(n / 10);
        }
        s = p.replace(/(零.)*零$/, '')
             .replace(/^$/, '零')
          + group[i] + s;
    }
    return s.replace(/(零.)*零元/, '元')
            .replace(/(零.)+/g, '零')
            .replace(/^$/, '零元') + '整';
};

功能测试

alert(digit_uppercase(0));          // 零元整
alert(digit_uppercase(123));        // 壹佰贰拾叁元整
alert(digit_uppercase(1000000));    // 壹佰万元整
alert(digit_uppercase(100000001));  // 壹亿零壹元整
alert(digit_uppercase(1000000000)); // 壹拾亿元整
alert(digit_uppercase(1234567890)); // 壹拾贰亿叁仟肆佰伍拾陆万柒仟捌佰玖拾元整
alert(digit_uppercase(1001100101)); // 壹拾亿零壹佰壹拾万零壹佰零壹元整
alert(digit_uppercase(110101010));  // 壹亿壹仟零壹拾万壹仟零壹拾元整
zzp-me添加小数处理算法的链接
zzp-me重命名文章名字