This commit is contained in:
Looly 2024-08-03 19:59:52 +08:00
parent ec204f8fc2
commit 5a4fbd4386
5 changed files with 147 additions and 28 deletions

View File

@ -12,6 +12,8 @@
package org.dromara.hutool.core.math;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
@ -123,6 +125,73 @@ public class NumberParser {
}
}
/**
* 转换char数组为一个int值此方法拷贝自{@link Integer#parseInt(String, int)}<br>
* 拷贝的原因是直接转换char[]避免创建String对象造成的多余拷贝<br>
* 此方法自动跳过首尾空白符
*
* @param chars char数组
* @param radix 进制数
* @return int值
* @see Integer#parseInt(String, int)
*/
public int parseInt(final char[] chars, final int radix) {
if (ArrayUtil.isEmpty(chars)) {
throw new IllegalArgumentException("Empty chars!");
}
int result = 0;
boolean negative = false;
int i = 0;
int limit = -Integer.MAX_VALUE;
int digit;
// 跳过空白符
while (CharUtil.isBlankChar(chars[i])) {
i++;
}
final char firstChar = chars[i];
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+') {
throw new NumberFormatException("Invalid first char: " + firstChar);
}
if (chars.length == 1) {
// Cannot have lone "+" or "-"
throw new NumberFormatException("Invalid chars has lone: " + firstChar);
}
i++;
}
final int multmin = limit / radix;
while (i < chars.length) {
// 跳过空白符
if (CharUtil.isBlankChar(chars[i])) {
i++;
continue;
}
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(chars[i++], radix);
if (digit < 0) {
throw new NumberFormatException(StrUtil.format("Invalid chars: {} at {}", chars, i - 1));
}
if (result < multmin) {
throw new NumberFormatException(StrUtil.format("Invalid chars: {}", new Object[]{chars}));
}
result *= radix;
if (result < limit + digit) {
throw new NumberFormatException(StrUtil.format("Invalid chars: {}", new Object[]{chars}));
}
result -= digit;
}
return negative ? result : -result;
}
/**
* 解析转换数字字符串为long型数字规则如下
*
@ -293,7 +362,7 @@ public class NumberParser {
if (null == locale) {
locale = Locale.getDefault(Locale.Category.FORMAT);
}
if(StrUtil.startWith(numberStr, CharUtil.PLUS)){
if (StrUtil.startWith(numberStr, CharUtil.PLUS)) {
// issue#I79VS7
numberStr = StrUtil.subSuf(numberStr, 1);
}

View File

@ -1230,6 +1230,22 @@ public class NumberUtil extends NumberValidator {
return NumberParser.INSTANCE.parseInt(numberStr);
}
/**
* 转换char数组为一个int值此方法拷贝自{@link Integer#parseInt(String, int)}<br>
* 拷贝的原因是直接转换char[]避免创建String对象造成的多余拷贝<br>
* 此方法自动跳过首尾空白符
*
* @param chars char数组
* @param radix 进制数
* @return int值
* @throws NumberFormatException 数字格式异常
* @see Integer#parseInt(String, int)
* @since 6.0.0
*/
public static int parseInt(final char[] chars, final int radix) throws NumberFormatException {
return NumberParser.INSTANCE.parseInt(chars, radix);
}
/**
* 解析转换数字字符串为 {@link java.lang.Long } 规则如下
*

View File

@ -15,10 +15,51 @@ package org.dromara.hutool.core.math;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class NumberParserTest {
@Test
void parseLongTest() {
final long value = NumberParser.INSTANCE.parseLong("0.a");
Assertions.assertEquals(0L, value);
}
@Test
void testParseIntWithValidInputs() {
// Test with various valid inputs
assertEquals(123, NumberParser.INSTANCE.parseInt("123".toCharArray(), 10));
assertEquals(-123, NumberParser.INSTANCE.parseInt("-123".toCharArray(), 10));
assertEquals(123, NumberParser.INSTANCE.parseInt("+123".toCharArray(), 10));
assertEquals(0, NumberParser.INSTANCE.parseInt("0".toCharArray(), 10));
assertEquals(255, NumberParser.INSTANCE.parseInt("ff".toCharArray(), 16));
assertEquals(-255, NumberParser.INSTANCE.parseInt("-ff".toCharArray(), 16));
}
@Test
void testParseIntWithInvalidInputs() {
// Test with various invalid inputs
assertThrows(IllegalArgumentException.class, () -> NumberParser.INSTANCE.parseInt("".toCharArray(), 10));
assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("1234".toCharArray(), 2));
assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("abc".toCharArray(), 10));
assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("--123".toCharArray(), 10));
assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("++123".toCharArray(), 10));
assertThrows(NumberFormatException.class, () -> NumberParser.INSTANCE.parseInt("123".toCharArray(), 1));
}
@Test
void testParseIntWithLeadingAndTrailingWhitespace() {
// Test with leading and trailing whitespace
assertEquals(42, NumberParser.INSTANCE.parseInt(" 42 ".toCharArray(), 10));
}
@Test
void testParseIntWithMaxAndMinInt() {
// Test with values at the edge of int range
final char[] maxIntStr = Integer.toString(Integer.MAX_VALUE).toCharArray();
final char[] minIntStr = Integer.toString(Integer.MIN_VALUE).toCharArray();
assertEquals(Integer.MAX_VALUE, NumberParser.INSTANCE.parseInt(maxIntStr, 10));
assertEquals(Integer.MIN_VALUE, NumberParser.INSTANCE.parseInt(minIntStr, 10));
}
}

View File

@ -26,6 +26,7 @@ import java.text.NumberFormat;
import java.text.ParseException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* {@link NumberUtil} 单元测试类
@ -320,7 +321,7 @@ public class NumberUtilTest {
@Test
public void decimalFormatNaNTest() {
Assertions.assertThrows(IllegalArgumentException.class, ()->{
assertThrows(IllegalArgumentException.class, ()->{
final Double a = 0D;
final Double b = 0D;
@ -331,7 +332,7 @@ public class NumberUtilTest {
@Test
public void decimalFormatNaNTest2() {
Assertions.assertThrows(IllegalArgumentException.class, ()->{
assertThrows(IllegalArgumentException.class, ()->{
final Double a = 0D;
final Double b = 0D;
@ -383,7 +384,7 @@ public class NumberUtilTest {
@Test
void emptyToBigDecimalTest(){
Assertions.assertThrows(IllegalArgumentException.class,()->{
assertThrows(IllegalArgumentException.class,()->{
NumberUtil.toBigDecimal("");
});
}
@ -445,7 +446,7 @@ public class NumberUtilTest {
@Test
public void parseIntTest3() {
Assertions.assertThrows(NumberFormatException.class, ()->{
assertThrows(NumberFormatException.class, ()->{
final int v1 = NumberUtil.parseInt("d");
assertEquals(0, v1);
});
@ -453,7 +454,7 @@ public class NumberUtilTest {
@Test
public void parseIntTest4() {
Assertions.assertThrows(NumberFormatException.class, ()->{
assertThrows(NumberFormatException.class, ()->{
// issue#I5M55F
// 科学计数法忽略支持科学计数法一般用于表示非常小和非常大的数字这类数字转换为int后精度丢失没有意义
final String numberStr = "429900013E20220812163344551";
@ -692,7 +693,7 @@ public class NumberUtilTest {
@Test
public void rangeMinTest() {
Assertions.assertThrows(NegativeArraySizeException.class, ()->{
assertThrows(NegativeArraySizeException.class, ()->{
NumberUtil.range(0, Integer.MIN_VALUE);
});
}
@ -757,7 +758,7 @@ public class NumberUtilTest {
@EnabledForJreRange(max = JRE.JAVA_8)
void numberFormatTest() {
// JDK8下NaN解析报错JDK9+中返回0
Assertions.assertThrows(ParseException.class, ()->{
assertThrows(ParseException.class, ()->{
NumberFormat.getInstance().parse("NaN");
});
}

View File

@ -15,6 +15,7 @@ package org.dromara.hutool.json;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.io.ReaderWrapper;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.math.NumberUtil;
import java.io.IOException;
import java.io.InputStream;
@ -171,27 +172,21 @@ public class JSONTokener extends ReaderWrapper {
}
/**
* Get the last character read from the input or '\0' if nothing has been read yet.
* 获取上一个读取的字符如果没有读取过则返回'\0'
*
* @return the last character read from the input.
* @return 上一个读取的字符
*/
protected char getPrevious() {
return this.previous;
}
/**
* 读取下一个字符并比对是否和指定字符匹配
*
* @param c 被匹配的字符
* @return The character 匹配到的字符
* @throws JSONException 如果不匹配抛出此异常
* 获取16进制unicode转义符对应的字符值
* <pre>{@code '4f60' -> '你'}</pre>
* @return 字符
*/
public char next(final char c) throws JSONException {
final char n = this.next();
if (n != c) {
throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + "'");
}
return n;
public char nextUnicode(){
return (char) NumberUtil.parseInt(next(4), 16);
}
/**
@ -201,12 +196,9 @@ public class JSONTokener extends ReaderWrapper {
* @return 获得的n个字符组成的字符串
* @throws JSONException 如果源中余下的字符数不足以提供所需的字符数抛出此异常
*/
public String next(final int n) throws JSONException {
if (n == 0) {
return "";
}
public char[] next(final int n) throws JSONException {
final char[] chars = new char[n];
int pos = 0;
while (pos < n) {
chars[pos] = this.next();
@ -215,7 +207,7 @@ public class JSONTokener extends ReaderWrapper {
}
pos += 1;
}
return new String(chars);
return chars;
}
/**
@ -276,7 +268,7 @@ public class JSONTokener extends ReaderWrapper {
sb.append('\r');
break;
case 'u':// Unicode符
sb.append((char) Integer.parseInt(this.next(4), 16));
sb.append(nextUnicode());
break;
case '"':
case '\'':