网上看过很多实现,但都有 BUG,不是多余0没有处理,就是很多生成的汉字字符串根本不符合中文阅读习惯(各位代码写完测试的时候用例多搞一点啊)
刚好公司有个项目就要实现这个小功能,顾把自己的实现分享出来
看代码前,先观察一下结果是否满足各位的预期
用例输入:
int[] nums = {1000, 1024, 10, 100024, 92030003, 90000003, 1, 2, 3, 11};
用例输出:
一千
一千零二十四
十
十万零二十四
九千二百零三万零三
九千万零三
一
二
三
十一
用例基本能覆盖所有特殊场景,支持 Integer.MIN_VALUE - Integer.MAX_VALUE
范围内的所有输入
小数场景没做,因为暂时没这个需要,不过小数场景要更好做一些,毕竟按照中文阅读习惯,小数只要把小数点后的数字一一列出来转换为中文字符就行,没有位数转换的需求
3.14159 -> 三点一四一五九
有小数转换需求的同学,只要把整数部分取出来,用我的代码转换,小数部分再写个简单的函数实现即可
可能有人习惯把"二"读成"两"的,到时候在代码里替换字符就行
急着用的同学,写个工具类,直接拷贝过去就能用了
/**
* @ClassName NumStrConvertor
* @Description 阿拉伯数字转中文
* @Author faro_z
* @Date 2022/12/5 18:07
* @Version 1.0
**/
public class NumStrConvertor {
public static void main(String[] args) {
int[] nums = {1000, 1024, 10, 100024, 92030003, 90000003, 1, 2, 3, 11};
for (int num : nums) {
System.out.println(cnNumConvertor(num));
}
}
/**
* 转换阿拉伯数字到中文字符
*
* @param num
* @return
*/
public static String cnNumConvertor(int num) {
if (num==0) {
return "零";
}
final String illegalPrefix = "一十";
final String[] units = {"", "万", "亿", "兆"};
String prefix = "";
if (num < 0) {
prefix = "负";
num = -num;
}
String numStr = String.valueOf(num);
final int metaLen = 4;
int r = numStr.length() - 1;
int l = Math.max(0, r - metaLen + 1);
int unitIndex = 0;
StringBuilder builder = new StringBuilder();
while (r >= 0 && l >= 0) {
builder.insert(0, cnNumMetaConvertor(numStr.substring(l, r + 1)) + units[unitIndex++]);
r = l - 1;
l = Math.max(0, r - metaLen + 1);
}
String res = builder.toString();
if (res.startsWith(illegalPrefix)) {
res = res.substring(1, res.length());
}
return prefix + res;
}
/**
* 按照中文阅读习惯,处理最高4位的 meta 数字集
*
* @param num
* @return
*/
private static String cnNumMetaConvertor(String num) {
final int metaLen = 4;
final String zeroStr = "0";
final String[] cnNums = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
final String[] units = {"千", "百", "十", ""};
StringBuilder builder = new StringBuilder();
int N = num.length();
boolean canNotJudge = N > metaLen || (N < metaLen && num.startsWith(zeroStr));
if (canNotJudge) {
return "";
}
boolean isPreZero = false;
int unitIndex = metaLen - num.length() - 1;
for (int i = 0; i < N; i++) {
++unitIndex;
final int currNum = num.charAt(i) - '0';
final String currNumCn = cnNums[currNum];
final String currUnit = units[unitIndex];
if (currNum == 0) {
if (isPreZero) {
continue;
}
isPreZero = true;
builder.append(currNumCn);
} else {
isPreZero = false;
builder.append(currNumCn);
builder.append(currUnit);
}
}
String res = builder.toString();
return res.endsWith("零") ? res.substring(0, res.length() - 1) : res;
}
}
按照中文阅读习惯,我们习惯将每四位分为一个单元做阅读,比如如下示例:
因为每四位的处理逻辑都是一样的,所以这部分我们可以单独拆出来处理:
private static String cnNumMetaConvertor(String num) {
final int metaLen = 4;
final String zeroStr = "0";
final String[] cnNums = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
final String[] units = {"千", "百", "十", ""};
StringBuilder builder = new StringBuilder();
int N = num.length();
// 大于四位或者 小于四位,但是开头的数字为0,都不满足处理条件
boolean canNotJudge = N > metaLen || (N < metaLen && num.startsWith(zeroStr));
if (canNotJudge) {
return "";
}
// 用来标识前一个字符是否为0,避免出现重复打印“零”的情况
boolean isPreZero = false;
int unitIndex = metaLen - num.length() - 1;
for (int i = 0; i < N; i++) {
++unitIndex;
final int currNum = num.charAt(i) - '0';
final String currNumCn = cnNums[currNum];
final String currUnit = units[unitIndex];
if (currNum == 0) {
if (isPreZero) {
continue;
}
isPreZero = true;
builder.append(currNumCn);
} else {
isPreZero = false;
builder.append(currNumCn);
builder.append(currUnit);
}
}
String res = builder.toString();
return res.endsWith("零") ? res.substring(0, res.length() - 1) : res;
}
}
接着,就是将“每个四位”进行拼接,并依次在后面加上,万、亿、兆的单位:
这里使用双指针法进行范围截取,以 4 为单位截取字符串,然后使用上面的函数处理
cnNumConvertor(num) {
// 负数和非0判断
// ...
res=""
while() {
currSplit = split(num)
currSplitCN = cnNumMetaConvertor(currSplit)
res+= currSplitCN + 单位
}
// 特殊字符处理
// ...
return res
}
同时外层函数我们还要做个负数和0的判断
负数的话,要转换成正数处理,最后返回结果时加上"负"的前缀
0 直接返回 “零”
public static String cnNumConvertor(int num) {
if (num==0) {
return "零";
}
final String illegalPrefix = "一十";
final String[] units = {"", "万", "亿", "兆"};
String prefix = "";
if (num < 0) {
prefix = "负";
num = -num;
}
String numStr = String.valueOf(num);
final int metaLen = 4;
int r = numStr.length() - 1;
int l = Math.max(0, r - metaLen + 1);
int unitIndex = 0;
StringBuilder builder = new StringBuilder();
while (r >= 0 && l >= 0) {
builder.insert(0, cnNumMetaConvertor(numStr.substring(l, r + 1)) + units[unitIndex++]);
r = l - 1;
l = Math.max(0, r - metaLen + 1);
}
String res = builder.toString();
// 对一十,一十万,一十四这类结果做处理
if (res.startsWith(illegalPrefix)) {
res = res.substring(1, res.length());
}
return prefix + res;
}
特殊结果处理:
上述代码本身能完成很好的结果生成,但是还是有部分结果不满足中文阅读习惯
比如 对一十,一十万,一十四
这类结果,只要将前缀的 “一” 去掉即可
if (res.startsWith(illegalPrefix)) {
res = res.substring(1, res.length());
}