Skip to content

Commit

Permalink
use fast parser for large bigdecimals (500+ chars) (#1157)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjfanning authored Dec 8, 2023
1 parent f24fde7 commit 4518d17
Showing 1 changed file with 1 addition and 129 deletions.
130 changes: 1 addition & 129 deletions src/main/java/com/fasterxml/jackson/core/io/BigDecimalParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static BigDecimal parse(final char[] chars, final int off, final int len)
if (len < 500) {
return new BigDecimal(chars, off, len);
}
return parseBigDecimal(chars, off, len, len / 10);
return JavaBigDecimalParser.parseBigDecimal(chars, off, len);

// 20-Aug-2022, tatu: Although "new BigDecimal(...)" only throws NumberFormatException
// operations by "parseBigDecimal()" can throw "ArithmeticException", so handle both:
Expand Down Expand Up @@ -111,132 +111,4 @@ public static BigDecimal parseWithFastParser(final char[] ch, final int off, fin
+ "\" 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;
boolean neg = false;
int numIdx = off;
int expIdx = -1;
int dotIdx = -1;
int scale = 0;
final int endIdx = off + len;

for (int i = off; i < endIdx; i++) {
char c = chars[i];
switch (c) {
case '+':
if (expIdx >= 0) {
if (expHasSign) {
throw new NumberFormatException("Multiple signs in exponent");
}
expHasSign = true;
} else {
if (numHasSign) {
throw new NumberFormatException("Multiple signs in number");
}
numHasSign = true;
numIdx = i + 1;
}
break;
case '-':
if (expIdx >= 0) {
if (expHasSign) {
throw new NumberFormatException("Multiple signs in exponent");
}
expHasSign = true;
} else {
if (numHasSign) {
throw new NumberFormatException("Multiple signs in number");
}
numHasSign = true;
neg = true;
numIdx = i + 1;
}
break;
case 'e':
case 'E':
if (expIdx >= 0) {
throw new NumberFormatException("Multiple exponent markers");
}
expIdx = i;
break;
case '.':
if (dotIdx >= 0) {
throw new NumberFormatException("Multiple decimal points");
}
dotIdx = i;
break;
default:
if (dotIdx >= 0 && expIdx == -1) {
scale++;
}
}
}

int numEndIdx;
int exp = 0;
if (expIdx >= 0) {
numEndIdx = expIdx;
String expStr = new String(chars, expIdx + 1, endIdx - expIdx - 1);
exp = Integer.parseInt(expStr);
scale = adjustScale(scale, exp);
} else {
numEndIdx = endIdx;
}

BigDecimal res;

if (dotIdx >= 0) {
int leftLen = dotIdx - numIdx;
BigDecimal left = toBigDecimalRec(chars, numIdx, leftLen, exp, splitLen);

int rightLen = numEndIdx - dotIdx - 1;
BigDecimal right = toBigDecimalRec(chars, dotIdx + 1, rightLen, exp - rightLen, splitLen);

res = left.add(right);
} else {
res = toBigDecimalRec(chars, numIdx, numEndIdx - numIdx, exp, splitLen);
}

if (scale != 0) {
res = res.setScale(scale);
}

if (neg) {
res = res.negate();
}

return res;
}

private static int adjustScale(int scale, long exp) {
long adjScale = scale - exp;
if (adjScale > Integer.MAX_VALUE || adjScale < Integer.MIN_VALUE) {
throw new NumberFormatException(
"Scale out of range: " + adjScale + " while adjusting scale " + scale + " to exponent " + exp);
}

return (int) adjScale;
}

private static BigDecimal toBigDecimalRec(final char[] chars, final int off, final int len,
final int scale, final int splitLen) {
if (len > splitLen) {
int mid = len / 2;
BigDecimal left = toBigDecimalRec(chars, off, mid, scale + len - mid, splitLen);
BigDecimal right = toBigDecimalRec(chars, off + mid, len - mid, scale, splitLen);

return left.add(right);
}

if (len == 0) {
return BigDecimal.ZERO;
}
// 02-Apr-2023, tatu: [core#967] Looks like "scaleByPowerOfThen" avoids performance issue
// there would be with "movePointRight" (both doing about same thing), so)
return new BigDecimal(chars, off, len)
// .movePointRight(scale);
.scaleByPowerOfTen(scale);
}
}

0 comments on commit 4518d17

Please sign in to comment.