- 发布于
阿拉伯数字转中文
- 作者
- 作者
- 霍浩东
- @huohaodong
初步想法
下面是一些将阿拉伯数字转为中文的例子:
- 0 = 零
- 10 = 十
- 1000 = 一千
- 9527 = 九千五百二十七
- 10051 = 一万零五十一
- 111111111 = 一亿一千一百一十一万一千一百一十一
一个最直观的想法就是按照从最低位到最高位的顺序,依次将数字映射为相应的中文字符并加上单位,比如:
- 0 = 零
- 10 = 一十零
- 1000 = 一千零百零十零
- 10051 = 一万零千零百五十一
- 110010 = 一十一万零千零百一十零
- 210010 = 二十一万零千零百一十零
但是这样的转换方法对于“零”的处理并不理想,我们中文的习惯一般是将连续出现的多个“零”合为一个,并去除末尾的“零”。因此我们需要对“零”进行单独处理。
此外,对于数字 110010 最高位的 11 而言,我们一般是读成“十一万”而不是“一十一万”,所以还应对位于开头的 10 ~ 19 之间的数字进行处理。
观察上面的例子也很容易注意到,按照这种朴素的处理方法,添加“万”、“亿”等特殊数位的时机也是一个棘手的问题。
通过上述分析可以看到,阿拉伯数字转中文并没有一个统一的规则,且因为中文的阅读习惯比较特殊,还应进行许多特殊处理。
算法设计与实现
以数字 123456789 为例,我们可以将数字从右到左按照每 4 位为一段进行单独处理,之后对每段处理后的结果进行拼接:
- 将 123456789 分为 1、2345、6789 三段,每段单独处理;
- 6789 = 六千 七百 八十 九,2345 = 二千 三百 四十 五,1 = 一;
- 对结果进行拼接,注意拼接的时候带上每段相应的数位,比如“万”和“亿”,拼接后为:一 亿 二千三百四十五 万 六千七百八十九。
按照这样的规则处理就可以解决“万”、“亿”等特殊单位的添加问题。
下面我们探讨对“零”的处理。根据上面的分析,对于“零”的处理大致有如下几种规则:
零不能加数位,比如“零百”、“零千”;
在每段处理结果中间部分出现的连续的零要去重;
每段末尾的“零”也要去除。
以 10000010 为例
- 10000010 = 一千零百零十零 万 零千零百一十零
- 使用第一个规则:一千零零零 万 零零一十零
- 使用第二个规则:一千零 万 零一十零
- 使用第三个规则:一千 万 零一十
这样就完成了对“零”的处理。
最后还需要对 110010 例子中提到的情况进行处理。处理的方法非常简单,检查结果是否以“一十”开头即可,若是这种情况则除去开头的“一”,比如“一十一”转为“十一”,“一十八”转为“十八”。
根据上述算法设计,编写相应的转换算法及其测试:
public class NumberToChinese {
public static void main(String[] args) {
test();
}
public static void test() {
int[] numbers = new int[]{0, 1, 10, 18, 114, 343, 10000, 15000, 201058, 10018, 11018, 1100118, 1902020117};
for (int number : numbers) {
System.out.println(numberToChinese(number));
}
}
public static String numberToChinese(int number) {
String[] UNITS = {null, "十", "百", "千"};
String[] TOKENS = {null, "万", "亿"};
String[] NUMBERS = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
String str = String.valueOf(number);
String res = "";
// 倒着将数字按照每 4 个为一段切片处理
for (int right = str.length(), count = 0; right >= 0; right -= 4, count++) {
int left = Math.max(0, right - 4);
String temp = "";
for (int j = right - 1, k = 0; j >= left; j--, k++) {
int index = Integer.parseInt(String.valueOf(str.charAt(j)));
// 连续的零去重
if (index == 0 && temp.length() >= 1 && String.valueOf(temp.charAt(0)).equals("零")) {
continue;
}
if (UNITS[k] != null && index != 0) {
temp = UNITS[k] + temp;
}
temp = NUMBERS[index] + temp;
}
// 去除每段结果末尾的零
if (temp.length() > 0 && String.valueOf(temp.charAt(temp.length() - 1)).equals("零")) {
temp = temp.substring(0, temp.length() - 1);
}
if (TOKENS[count] != null && temp.length() > 0) {
temp = temp + TOKENS[count];
}
res = temp + res;
}
// 处理结果开头的“一十”
if (res.startsWith("一十")) {
res = res.substring(1);
}
// 特例,如果输入的是 0,上述步骤得到的结果会为空字符串
if (res.length() == 0) {
res = "零";
}
return res;
}
}
测试输出结果如下:
零
一
十
十八
一百一十四
三百四十三
一万
一万五千
二十万一千零五十八
一万零一十八
一万一千零一十八
一百一十万零一百一十八
十九亿零二百零二万零一百一十七
进阶
我们目前所实现的转换算法仅适用于整数,对于浮点数而言,整数部分按照我们实现的转换算法进行转换即可,小数部分则只需将数字映射为对应的中文字符,不需要做任何特殊处理。
算法实现如下:
public static String numberToChinese(BigDecimal number) {
String[] NUMBERS = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
String[] str = number.toPlainString().split("\\.");
String res = "";
String cur = str[1];
for (int i = 0; i < str[1].length(); i++) {
int index = Integer.parseInt(String.valueOf(str[1].charAt(i)));
res += NUMBERS[index];
}
// 整数部分处理结果 + 点 + 小数部分处理结果
return numberToChinese(Integer.parseInt(str[0])) + "点" + res;
}
这里有几点需要注意:
- 利用
split()
拆分整数和小数部分时,小数点.
要进行转义处理; - 数位较多(较长)的 double 转为字符串后会以科学计数法的形式表示,比如:123456789.123456789 转为字符串后就变成了 1.2345678912345679E8。如果想要得到非科学计数法表示的浮点字符,需要用到
BigDecimal
类提供的toPlainString()
方法; - 浮点数的精度存在损失,比如 123456789.123456789101112 在拆分后,小数部分并不一定会是 123456789101112。想要实现完美的转换就需要将小数部分和整数部分拆开,分别以整数形式存储,使用的时候再进行单独处理。
将浮点数的小数部分和整数部分拆分存储,不仅可以避免精度损失问题,还可以扩大数字的表示范围。
整数部分和小数部分单独存储后的转换算法如下:
public static String numberToChinese(int integer, int decimal) {
String[] NUMBERS = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
String res = "";
String cur = String.valueOf(decimal);
for (int i = 0; i < cur.length(); i++) {
int index = Integer.parseInt(String.valueOf(cur.charAt(i)));
res += NUMBERS[index];
}
// 整数部分处理结果 + 点 + 小数部分处理结果
return numberToChinese(integer) + "点" + res;
}
这里又出现了一个问题,int
存储的范围是有限的,既然已经将整数部分和小数部分单独存储了,不妨再进一步,直接以字符串的形式存储数字,进一步扩大数字的表示范围:
public static String numberToChinese(String integer, String decimal) {
String[] NUMBERS = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
String res = "";
for (int i = 0; i < decimal.length(); i++) {
int index = Integer.parseInt(String.valueOf(decimal.charAt(i)));
res += NUMBERS[index];
}
// 整数部分处理结果 + 点 + 小数部分处理结果
return numberToChinese(integer) + "点" + res;
}
对于负数情况,添加相应的判断逻辑进行处理即可,这里不再赘述。此外,如果想要转换成零、壹、贰、叁、肆、伍、陆、柒、捌、玖、拾、佰、仟、万、亿
的形式,替换代码中对应的中文字符即可,元、角、分
等计数单位同理。