🐞fix toNumber bug

This commit is contained in:
Looly 2021-04-22 03:23:25 +08:00
parent c3eefb614c
commit 10503d632f
3 changed files with 144 additions and 80 deletions

View File

@ -3,7 +3,7 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.6.4 (2021-04-20) # 5.6.4 (2021-04-22)
### 新特性 ### 新特性
* 【core 】 DatePattern补充DateTimeFormatterpr#308@Gitee * 【core 】 DatePattern补充DateTimeFormatterpr#308@Gitee
@ -15,6 +15,7 @@
### Bug修复 ### Bug修复
* 【db 】 修复SQL分页时未使用别名导致的错误同时count时取消order by子句issue#I3IJ8X@Gitee * 【db 】 修复SQL分页时未使用别名导致的错误同时count时取消order by子句issue#I3IJ8X@Gitee
* 【extra 】 修复Sftp.reconnectIfTimeout方法判断错误issue#1524@Github * 【extra 】 修复Sftp.reconnectIfTimeout方法判断错误issue#1524@Github
* 【core 】 修复NumberChineseFormatter转数字问题issue#I3IS3S@Gitee
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -17,32 +17,25 @@ import cn.hutool.core.util.StrUtil;
public class NumberChineseFormatter { public class NumberChineseFormatter {
/** /**
* 简体中文形式 * 中文形式奇数位置是简体偶数位置是记账繁体0共用<br>
* 使用混合数组提高效率和数组复用
**/ **/
private static final char[] SIMPLE_DIGITS = {'零', '一', '二', '三', '四', '五', '六', '七', '八', '九'}; private static final char[] DIGITS = {'零', '一', '壹', '二', '贰', '三', '叁', '四', '肆', '五', '伍',
/** '六', '陆', '七', '柒', '八', '捌', '九', '玖'};
* 繁体中文形式
**/
private static final char[] TRADITIONAL_DIGITS = {'零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'};
/**
* 简体中文单位
**/
private static final String[] SIMPLE_UNITS = {"", "", "", ""};
/**
* 繁体中文单位
**/
private static final String[] TRADITIONAL_UNITS = {"", "", "", ""};
/** /**
* 汉字转阿拉伯数字的 * 汉字转阿拉伯数字的
*/ */
private static final ChineseNameValue[] CHINESE_NAME_VALUE = { private static final ChineseUnit[] CHINESE_NAME_VALUE = {
new ChineseNameValue("", 10, false), new ChineseUnit(' ', 1, false),
new ChineseNameValue("", 100, false), new ChineseUnit('十', 10, false),
new ChineseNameValue("", 1000, false), new ChineseUnit('拾', 10, false),
new ChineseNameValue("", 10000, true), new ChineseUnit('百', 100, false),
new ChineseNameValue("亿", 100000000, true), new ChineseUnit('佰', 100, false),
new ChineseUnit('千', 1000, false),
new ChineseUnit('仟', 1000, false),
new ChineseUnit('万', 10000, true),
new ChineseUnit('亿', 100000000, true),
}; };
@ -66,8 +59,6 @@ public class NumberChineseFormatter {
* @return 中文 * @return 中文
*/ */
public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) { public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) {
final char[] numArray = isUseTraditional ? TRADITIONAL_DIGITS : SIMPLE_DIGITS;
if (amount > 99999999999999.99 || amount < -99999999999999.99) { if (amount > 99999999999999.99 || amount < -99999999999999.99) {
throw new IllegalArgumentException("Number support only: (-99999999999999.99 99999999999999.99)"); throw new IllegalArgumentException("Number support only: (-99999999999999.99 99999999999999.99)");
} }
@ -98,7 +89,7 @@ public class NumberChineseFormatter {
StringBuilder chineseStr = new StringBuilder(); StringBuilder chineseStr = new StringBuilder();
for (int i = 0; i < numParts; i++) { for (int i = 0; i < numParts; i++) {
String partChinese = toChinese(parts[i], isUseTraditional); final String partChinese = toChinese(parts[i], isUseTraditional);
if (i % 2 == 0) { if (i % 2 == 0) {
beforeWanIsZero = StrUtil.isEmpty(partChinese); beforeWanIsZero = StrUtil.isEmpty(partChinese);
} }
@ -127,7 +118,7 @@ public class NumberChineseFormatter {
// 整数部分为 0, 则表达为"" // 整数部分为 0, 则表达为""
if (StrUtil.EMPTY.equals(chineseStr.toString())) { if (StrUtil.EMPTY.equals(chineseStr.toString())) {
chineseStr = new StringBuilder(String.valueOf(numArray[0])); chineseStr = new StringBuilder(String.valueOf(DIGITS[0]));
} }
//负数 //负数
if (negative) { // 整数部分不为 0 if (negative) { // 整数部分不为 0
@ -137,12 +128,12 @@ public class NumberChineseFormatter {
// 小数部分 // 小数部分
if (numFen != 0 || numJiao != 0) { if (numFen != 0 || numJiao != 0) {
if (numFen == 0) { if (numFen == 0) {
chineseStr.append(isMoneyMode ? "" : "").append(numArray[numJiao]).append(isMoneyMode ? "" : ""); chineseStr.append(isMoneyMode ? "" : "").append(numberToChinese(numJiao, isUseTraditional)).append(isMoneyMode ? "" : "");
} else { // 数不为 0 } else { // 数不为 0
if (numJiao == 0) { if (numJiao == 0) {
chineseStr.append(isMoneyMode ? "元零" : "点零").append(numArray[numFen]).append(isMoneyMode ? "" : ""); chineseStr.append(isMoneyMode ? "元零" : "点零").append(numberToChinese(numFen, isUseTraditional)).append(isMoneyMode ? "" : "");
} else { } else {
chineseStr.append(isMoneyMode ? "" : "").append(numArray[numJiao]).append(isMoneyMode ? "" : "").append(numArray[numFen]).append(isMoneyMode ? "" : ""); chineseStr.append(isMoneyMode ? "" : "").append(numberToChinese(numJiao, isUseTraditional)).append(isMoneyMode ? "" : "").append(numberToChinese(numFen, isUseTraditional)).append(isMoneyMode ? "" : "");
} }
} }
} else if (isMoneyMode) { } else if (isMoneyMode) {
@ -163,12 +154,10 @@ public class NumberChineseFormatter {
* @since 5.3.9 * @since 5.3.9
*/ */
public static String numberCharToChinese(char c, boolean isUseTraditional) { public static String numberCharToChinese(char c, boolean isUseTraditional) {
char[] numArray = isUseTraditional ? TRADITIONAL_DIGITS : SIMPLE_DIGITS; if (c < '0' || c > '9') {
int index = c - 48;
if (index < 0 || index >= numArray.length) {
return String.valueOf(c); return String.valueOf(c);
} }
return String.valueOf(numArray[index]); return String.valueOf(numberToChinese(c - '0', isUseTraditional));
} }
/** /**
@ -179,9 +168,6 @@ public class NumberChineseFormatter {
* @return 转换后的汉字 * @return 转换后的汉字
*/ */
private static String toChinese(int amountPart, boolean isUseTraditional) { private static String toChinese(int amountPart, boolean isUseTraditional) {
final char[] numArray = isUseTraditional ? TRADITIONAL_DIGITS : SIMPLE_DIGITS;
String[] units = isUseTraditional ? TRADITIONAL_UNITS : SIMPLE_UNITS;
int temp = amountPart; int temp = amountPart;
StringBuilder chineseStr = new StringBuilder(); StringBuilder chineseStr = new StringBuilder();
@ -195,7 +181,7 @@ public class NumberChineseFormatter {
} }
lastIsZero = true; lastIsZero = true;
} else { // 取到的数字不是 0 } else { // 取到的数字不是 0
chineseStr.insert(0, numArray[digit] + units[i]); chineseStr.insert(0, numberToChinese(digit, isUseTraditional) + getUnitName(i, isUseTraditional));
lastIsZero = false; lastIsZero = false;
} }
temp = temp / 10; temp = temp / 10;
@ -204,7 +190,7 @@ public class NumberChineseFormatter {
} }
/** /**
* 把中文转换为数字 二百二 220<br> * 把中文转换为数字 二百二 220<br>
* https://www.d5.nz/read/sfdlq/text-part0000_split_030.html * https://www.d5.nz/read/sfdlq/text-part0000_split_030.html
* <ul> * <ul>
* <li>一百一十二 - 112</li> * <li>一百一十二 - 112</li>
@ -216,63 +202,118 @@ public class NumberChineseFormatter {
* @since 5.6.0 * @since 5.6.0
*/ */
public static int chineseToNumber(String chinese) { public static int chineseToNumber(String chinese) {
int pos = 0; final int length = chinese.length();
int rtn = 0; int result = 0;
// 节总和
int section = 0; int section = 0;
int number = 0; int number = 0;
boolean secUnit = false; ChineseUnit unit = null;
final int length = chinese.length(); char c;
while (pos < length) { for (int i = 0; i < length; i++) {
final int num = ArrayUtil.indexOf(SIMPLE_DIGITS, chinese.charAt(pos)); c = chinese.charAt(i);
final int num = chineseToNumber(c);
if (num >= 0) { if (num >= 0) {
if (num == 0) {
// 遇到零时节结束权位失效比如两万二零一十
if (number > 0 && null != unit) {
section += number * (unit.value / 10);
}
unit = null;
} else if (number > 0) {
// 多个数字同时出现报错
throw new IllegalArgumentException(StrUtil.format("Bad number '{}{}' at: {}", chinese.charAt(i - 1), c, i));
}
// 普通数字 // 普通数字
number = num; number = num;
pos += 1;
if (pos >= length) {
// 末尾是数字
section += number;
rtn += section;
break;
}
} else { } else {
//单位 unit = chineseToUnit(c);
int unit = 1; if (null == unit) {
int tmp = chineseToUnit(chinese.substring(pos, pos + 1)); // 出现非法字符
if (tmp != -1) { throw new IllegalArgumentException(StrUtil.format("Unknown unit '{}' at: {}", c, i));
unit = CHINESE_NAME_VALUE[tmp].value;
secUnit = CHINESE_NAME_VALUE[tmp].secUnit;
} }
if (secUnit) {
section = (section + number) * unit; //单位
rtn += section; if (unit.secUnit) {
// 节单位按照节求和
section = (section + number) * unit.value;
result += section;
section = 0; section = 0;
} else { } else {
section += (number * unit); // 非节单位和单位前的单数字组合为值
section += (number * unit.value);
} }
number = 0; number = 0;
pos += 1;
if (pos >= chinese.length()) {
rtn += section;
break;
}
} }
} }
return rtn;
if (number > 0 && null != unit) {
number = number * (unit.value / 10);
}
return result + section + number;
} }
/** /**
* 查找对应的权 * 查找对应的权对象
* *
* @param chinese 中文权位名 * @param chinese 中文权位名
* @return 位置 * @return 权对象
*/ */
private static int chineseToUnit(String chinese) { private static ChineseUnit chineseToUnit(char chinese) {
for (int i = 0; i < CHINESE_NAME_VALUE.length; i++) { for (ChineseUnit chineseNameValue : CHINESE_NAME_VALUE) {
if (CHINESE_NAME_VALUE[i].name.equals(chinese)) { if (chineseNameValue.name == chinese) {
return i; return chineseNameValue;
} }
} }
return -1; return null;
}
/**
* 将汉字单个数字转换为int类型数字
*
* @param chinese 汉字数字支持简体和繁体
* @return 数字-1表示未找到
* @since 5.6.4
*/
private static int chineseToNumber(char chinese) {
if ('两' == chinese) {
// 口语纠正
chinese = '二';
}
final int i = ArrayUtil.indexOf(DIGITS, chinese);
if (i > 0) {
return (i + 1) / 2;
}
return i;
}
/**
* 单个数字转汉字
*
* @param number 数字
* @param isUseTraditional 是否使用繁体
* @return 汉字
*/
private static char numberToChinese(int number, boolean isUseTraditional) {
if (0 == number) {
return DIGITS[0];
}
return DIGITS[number * 2 - (isUseTraditional ? 0 : 1)];
}
/**
* 获取对应级别的单位
*
* @param index 级别0表示各位1表示十位2表示百位以此类推
* @param isUseTraditional 是否使用繁体
* @return 单位
*/
private static String getUnitName(int index, boolean isUseTraditional) {
if (0 == index) {
return StrUtil.EMPTY;
}
return String.valueOf(CHINESE_NAME_VALUE[index * 2 - (isUseTraditional ? 0 : 1)].name);
} }
/** /**
@ -281,21 +322,29 @@ public class NumberChineseFormatter {
* @author totalo * @author totalo
* @since 5.6.0 * @since 5.6.0
*/ */
private static class ChineseNameValue { private static class ChineseUnit {
/** /**
* 中文权名称 * 中文权名称
*/ */
private final String name; private final char name;
/** /**
* 10的倍数值 * 10的倍数值
*/ */
private final int value; private final int value;
/** /**
* 是否为节权位 * 是否为节权位它不是与之相邻的数字的倍数而是整个小节的倍数<br>
* 例如二十三万万是节权位与三无关而和二十三关联
*/ */
private final boolean secUnit; private final boolean secUnit;
public ChineseNameValue(String name, int value, boolean secUnit) { /**
* 构造
*
* @param name 名称
* @param value 即10的倍数
* @param secUnit 是否为节权位
*/
public ChineseUnit(char name, int value, boolean secUnit) {
this.name = name; this.name = name;
this.value = value; this.value = value;
this.secUnit = secUnit; this.secUnit = secUnit;

View File

@ -1,7 +1,6 @@
package cn.hutool.core.convert; package cn.hutool.core.convert;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
public class NumberChineseFormatterTest { public class NumberChineseFormatterTest {
@ -92,8 +91,23 @@ public class NumberChineseFormatterTest {
} }
@Test @Test
@Ignore
public void chineseToNumberTest2(){ public void chineseToNumberTest2(){
Assert.assertEquals(120, NumberChineseFormatter.chineseToNumber("一百二")); Assert.assertEquals(120, NumberChineseFormatter.chineseToNumber("一百二"));
Assert.assertEquals(1200, NumberChineseFormatter.chineseToNumber("一千二"));
Assert.assertEquals(22000, NumberChineseFormatter.chineseToNumber("两万二"));
Assert.assertEquals(22003, NumberChineseFormatter.chineseToNumber("两万二零三"));
Assert.assertEquals(22010, NumberChineseFormatter.chineseToNumber("两万二零一十"));
}
@Test(expected = IllegalArgumentException.class)
public void badNumberTest(){
// 连续数字检查
NumberChineseFormatter.chineseToNumber("一百一二三");
}
@Test(expected = IllegalArgumentException.class)
public void badNumberTest2(){
// 非法字符
NumberChineseFormatter.chineseToNumber("一百你三");
} }
} }