Skip to content
11 changes: 11 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,17 @@ public enum Feature {
*/
USE_FAST_DOUBLE_PARSER(false),

/**
* Feature that determines whether we use the built-in {@link new BigDecimal(String)} code to parse
* <code>BigDecimal</code>s or if we use {@link com.fasterxml.jackson.core.io.doubleparser}
* instead. This feature has a similar effect on <code>BigInteger</code> parsing.
*<p>
* This setting is disabled by default for backwards compatibility.
*
* @since 2.15
*/
USE_FAST_BIG_DECIMAL_PARSER(false)

;

/**
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/com/fasterxml/jackson/core/StreamReadFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,18 @@ public enum StreamReadFeature
*
* @since 2.14
*/
USE_FAST_DOUBLE_PARSER(JsonParser.Feature.USE_FAST_DOUBLE_PARSER)
USE_FAST_DOUBLE_PARSER(JsonParser.Feature.USE_FAST_DOUBLE_PARSER),

/**
* Feature that determines whether we use the built-in {@link new BigDecimal(String)} code to parse
* <code>BigDecimal</code>s or if we use {@link com.fasterxml.jackson.core.io.doubleparser}
* instead. This feature has a similar effect on <code>BigInteger</code> parsing.
*<p>
* This setting is disabled by default.
*
* @since 2.15
*/
USE_FAST_BIG_DECIMAL_PARSER(JsonParser.Feature.USE_FAST_BIG_DECIMAL_PARSER)

;

Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,9 @@ protected BigInteger _getBigInteger() {
} else if (_numberString == null) {
throw new IllegalStateException("cannot get BigInteger from current parser state");
}
_numberBigInt = NumberInput.parseBigInteger(_numberString);
_numberBigInt = NumberInput.parseBigInteger(
_numberString,
isEnabled(StreamReadFeature.USE_FAST_BIG_DECIMAL_PARSER));
_numberString = null;
return _numberBigInt;
}
Expand All @@ -1165,7 +1167,9 @@ protected BigDecimal _getBigDecimal() {
} else if (_numberString == null) {
throw new IllegalStateException("cannot get BigDecimal from current parser state");
}
_numberBigDecimal = NumberInput.parseBigDecimal(_numberString);
_numberBigDecimal = NumberInput.parseBigDecimal(
_numberString,
isEnabled(StreamReadFeature.USE_FAST_BIG_DECIMAL_PARSER));
_numberString = null;
return _numberBigDecimal;
}
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/io/BigDecimalParser.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.fasterxml.jackson.core.io;

import ch.randelshofer.fastdoubleparser.JavaBigDecimalParser;
import ch.randelshofer.fastdoubleparser.JavaBigIntegerParser;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;

// Based on a great idea of Eric Obermühlner to use a tree of smaller BigDecimals for parsing
Expand Down Expand Up @@ -60,6 +64,40 @@ public static BigDecimal parse(char[] chars) {
return parse(chars, 0, chars.length);
}

public static BigDecimal parseWithFastParser(final String valueStr) {
try {
return JavaBigDecimalParser.parseBigDecimal(valueStr);
} catch (NumberFormatException nfe) {
final String reportNum = valueStr.length() <= MAX_CHARS_TO_REPORT ?
valueStr : valueStr.substring(0, MAX_CHARS_TO_REPORT) + " [truncated]";
throw new NumberFormatException("Value \"" + reportNum
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + nfe.getMessage());
}
}

public static BigDecimal parseWithFastParser(final char[] ch, final int off, final int len) {
try {
return JavaBigDecimalParser.parseBigDecimal(ch, off, len);
} catch (NumberFormatException nfe) {
final String reportNum = len <= MAX_CHARS_TO_REPORT ?
new String(ch, off, len) : new String(ch, off, MAX_CHARS_TO_REPORT) + " [truncated]";
final int reportLen = Math.min(len, MAX_CHARS_TO_REPORT);
throw new NumberFormatException("Value \"" + new String(ch, off, reportLen)
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + nfe.getMessage());
}
}

public static BigInteger parseBigIntegerWithFastParser(final String valueStr) {
try {
return JavaBigIntegerParser.parseBigInteger(valueStr);
} catch (NumberFormatException nfe) {
final String reportNum = valueStr.length() <= MAX_CHARS_TO_REPORT ?
valueStr : valueStr.substring(0, MAX_CHARS_TO_REPORT) + " [truncated]";
throw new NumberFormatException("Value \"" + reportNum
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + nfe.getMessage());
}
}

private static BigDecimal parseBigDecimal(final char[] chars, final int off, final int len, final int splitLen) {
boolean numHasSign = false;
boolean expHasSign = false;
Expand Down
83 changes: 79 additions & 4 deletions src/main/java/com/fasterxml/jackson/core/io/NumberInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -380,28 +380,103 @@ public static float parseFloat(final String s, final boolean useFastParser) thro
return useFastParser ? JavaFloatParser.parseFloat(s) : Float.parseFloat(s);
}

public static BigDecimal parseBigDecimal(String s) throws NumberFormatException {
/**
* @param s a string representing a number to parse
* @return a BigDecimal
* @throws NumberFormatException if the char array cannot be represented by a BigDecimal
*/
public static BigDecimal parseBigDecimal(final String s) throws NumberFormatException {
return BigDecimalParser.parse(s);
}

public static BigDecimal parseBigDecimal(char[] ch, int off, int len) throws NumberFormatException {
/**
* @param s a string representing a number to parse
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
* @return a BigDecimal
* @throws NumberFormatException if the char array cannot be represented by a BigDecimal
* @since v2.15
*/
public static BigDecimal parseBigDecimal(final String s, final boolean useFastParser) throws NumberFormatException {
return useFastParser ?
BigDecimalParser.parseWithFastParser(s) :
BigDecimalParser.parse(s);
}

/**
* @param ch a char array with text that makes up a number
* @param off the offset to apply when parsing the number in the char array
* @param len the length of the number in the char array
* @return a BigDecimal
* @throws NumberFormatException if the char array cannot be represented by a BigDecimal
*/
public static BigDecimal parseBigDecimal(final char[] ch, final int off, final int len) throws NumberFormatException {
return BigDecimalParser.parse(ch, off, len);
}

public static BigDecimal parseBigDecimal(char[] ch) throws NumberFormatException {
/**
* @param ch a char array with text that makes up a number
* @param off the offset to apply when parsing the number in the char array
* @param len the length of the number in the char array
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
* @return a BigDecimal
* @throws NumberFormatException if the char array cannot be represented by a BigDecimal
* @since v2.15
*/
public static BigDecimal parseBigDecimal(final char[] ch, final int off, final int len,
final boolean useFastParser)
throws NumberFormatException {
return useFastParser ?
BigDecimalParser.parseWithFastParser(ch, off, len) :
BigDecimalParser.parse(ch, off, len);
}

/**
* @param ch a char array with text that makes up a number
* @return a BigDecimal
* @throws NumberFormatException if the char array cannot be represented by a BigDecimal
*/
public static BigDecimal parseBigDecimal(final char[] ch) throws NumberFormatException {
return BigDecimalParser.parse(ch);
}

/**
* @param ch a char array with text that makes up a number
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
* @return a BigDecimal
* @throws NumberFormatException if the char array cannot be represented by a BigDecimal
* @since v2.15
*/
public static BigDecimal parseBigDecimal(final char[] ch, final boolean useFastParser) throws NumberFormatException {
return useFastParser ?
BigDecimalParser.parseWithFastParser(ch, 0, ch.length) :
BigDecimalParser.parse(ch);
}

/**
* @param s a string representing a number to parse
* @return a BigInteger
* @throws NumberFormatException if string cannot be represented by a BigInteger
* @since v2.14
*/
public static BigInteger parseBigInteger(String s) throws NumberFormatException {
public static BigInteger parseBigInteger(final String s) throws NumberFormatException {
if (s.length() > LARGE_INT_SIZE) {
return BigDecimalParser.parse(s).toBigInteger();
}
return new BigInteger(s);
}

/**
* @param s a string representing a number to parse
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
* @return a BigInteger
* @throws NumberFormatException if string cannot be represented by a BigInteger
* @since v2.15
*/
public static BigInteger parseBigInteger(final String s, final boolean useFastParser) throws NumberFormatException {
if (useFastParser) {
return BigDecimalParser.parseBigIntegerWithFastParser(s);
} else {
return parseBigInteger(s);
}
}
}
15 changes: 9 additions & 6 deletions src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -484,38 +484,41 @@ public char[] contentsAsArray() {
* Convenience method for converting contents of the buffer
* into a {@link BigDecimal}.
*
* @param constraints constraints for stream reading
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
* @return Buffered text value parsed as a {@link BigDecimal}, if possible
*
* @throws NumberFormatException if contents are not a valid Java number
*
* @since 2.15
*/
public BigDecimal contentsAsDecimal(StreamReadConstraints constraints) throws NumberFormatException
public BigDecimal contentsAsDecimal(final StreamReadConstraints constraints,
final boolean useFastParser) throws NumberFormatException
{
// Already got a pre-cut array?
if (_resultArray != null) {
constraints.validateFPLength(_resultArray.length);
return NumberInput.parseBigDecimal(_resultArray);
return NumberInput.parseBigDecimal(_resultArray, useFastParser);
}
// Or a shared buffer?
if ((_inputStart >= 0) && (_inputBuffer != null)) {
constraints.validateFPLength(_inputLen);
return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen);
return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen, useFastParser);
}
// Or if not, just a single buffer (the usual case)
if ((_segmentSize == 0) && (_currentSegment != null)) {
constraints.validateFPLength(_currentSize);
return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize);
return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize, useFastParser);
}
// If not, let's just get it aggregated...
final char[] numArray = contentsAsArray();
constraints.validateFPLength(numArray.length);
return NumberInput.parseBigDecimal(numArray);
return NumberInput.parseBigDecimal(numArray, useFastParser);
}

@Deprecated // @since 2.15
public BigDecimal contentsAsDecimal() throws NumberFormatException {
return contentsAsDecimal(StreamReadConstraints.defaults());
return contentsAsDecimal(StreamReadConstraints.defaults(), false);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,43 @@

public class BigDecimalParserTest extends com.fasterxml.jackson.core.BaseTest {
public void testLongStringParse() {
final int len = 1500;
final StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
sb.append("A");
try {
BigDecimalParser.parse(genLongString());
fail("expected NumberFormatException");
} catch (NumberFormatException nfe) {
assertTrue("exception message starts as expected?", nfe.getMessage().startsWith("Value \"AAAAA"));
assertTrue("exception message value contains truncated", nfe.getMessage().contains("truncated"));
}
}

public void testLongStringFastParse() {
try {
BigDecimalParser.parseWithFastParser(genLongString());
fail("expected NumberFormatException");
} catch (NumberFormatException nfe) {
assertTrue("exception message starts as expected?", nfe.getMessage().startsWith("Value \"AAAAA"));
assertTrue("exception message value contains truncated", nfe.getMessage().contains("truncated"));
}
}

/* there is an open issue at https://github.com/wrandelshofer/FastDoubleParser/issues/24 about this
public void testLongStringFastParseBigInteger() {
try {
BigDecimalParser.parse(sb.toString());
BigDecimalParser.parseBigIntegerWithFastParser(genLongString());
fail("expected NumberFormatException");
} catch (NumberFormatException nfe) {
assertTrue("exception message starts as expected?", nfe.getMessage().startsWith("Value \"AAAAA"));
assertTrue("exception message value contains truncated", nfe.getMessage().contains("truncated"));
}
}
*/

private String genLongString() {
final int len = 1500;
final StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
sb.append("A");
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ public void testParseLongBigInteger()
}
String test1000 = stringBuilder.toString();
assertEquals(new BigInteger(test1000), NumberInput.parseBigInteger(test1000));
assertEquals(new BigInteger(test1000), NumberInput.parseBigInteger(test1000, true));
for (int i = 0; i < 1000; i++) {
stringBuilder.append(7);
}
String test2000 = stringBuilder.toString();
assertEquals(new BigInteger(test2000), NumberInput.parseBigInteger(test2000));
assertEquals(new BigInteger(test2000), NumberInput.parseBigInteger(test2000, true));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class FastParserNonStandardNumberParsingTest
.enable(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS)
.enable(JsonReadFeature.ALLOW_TRAILING_DECIMAL_POINT_FOR_NUMBERS)
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
.enable(StreamReadFeature.USE_FAST_BIG_DECIMAL_PARSER)
.build();

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class FastParserNumberParsingTest extends NumberParsingTest
{
private final JsonFactory fastFactory = JsonFactory.builder()
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
.enable(StreamReadFeature.USE_FAST_BIG_DECIMAL_PARSER)
.build();

@Override
Expand Down