起因
前几天用网银给朋友转账,在金额一栏中输入阿拉伯数字,右边会立即显示出相应的汉语数字大写。感觉挺有意思,就到网上搜索一下现成代码,找到一段 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)); // 壹亿壹仟零壹拾万壹仟零壹拾元整