mirror of
https://gitee.com/dromara/hutool.git
synced 2025-05-05 21:28:00 +08:00
🐞fix toNumber bug
This commit is contained in:
parent
c3eefb614c
commit
10503d632f
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# 5.6.4 (2021-04-20)
|
# 5.6.4 (2021-04-22)
|
||||||
|
|
||||||
### 新特性
|
### 新特性
|
||||||
* 【core 】 DatePattern补充DateTimeFormatter(pr#308@Gitee)
|
* 【core 】 DatePattern补充DateTimeFormatter(pr#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)
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (number > 0 && null != unit) {
|
||||||
|
number = number * (unit.value / 10);
|
||||||
}
|
}
|
||||||
return rtn;
|
|
||||||
|
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 chineseNameValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个数字转汉字
|
||||||
|
*
|
||||||
|
* @param number 数字
|
||||||
|
* @param isUseTraditional 是否使用繁体
|
||||||
|
* @return 汉字
|
||||||
|
*/
|
||||||
|
private static char numberToChinese(int number, boolean isUseTraditional) {
|
||||||
|
if (0 == number) {
|
||||||
|
return DIGITS[0];
|
||||||
}
|
}
|
||||||
return -1;
|
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;
|
||||||
|
@ -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("一百你三");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user