package com.fasterxml.jackson.core.io;
import java.math.BigDecimal;
import java.util.Arrays;
// Based on a great idea of Eric Obermühlner to use a tree of smaller BigDecimals for parsing
// really big numbers with O(n^1.5) complexity instead of O(n^2) when using the constructor
// for a decimal representation from JDK 8/11:
//
// https://github.com/eobermuhlner/big-math/commit/7a5419aac8b2adba2aa700ccf00197f97b2ad89f
Helper class used to implement more optimized parsing of BigDecimal
for REALLY big values (over 500 characters)
Based on ideas from this
this
git commit.
Since: 2.13
/**
* Helper class used to implement more optimized parsing of {@link BigDecimal} for REALLY
* big values (over 500 characters)
*<p>
* Based on ideas from this
* <a href="https://github.com/eobermuhlner/big-math/commit/7a5419aac8b2adba2aa700ccf00197f97b2ad89f">this
* git commit</a>.
*
* @since 2.13
*/
public final class BigDecimalParser
{
private final char[] chars;
BigDecimalParser(char[] chars) {
this.chars = chars;
}
public static BigDecimal parse(String valueStr) {
return parse(valueStr.toCharArray());
}
public static BigDecimal parse(char[] chars, int off, int len) {
if (off > 0 || len != chars.length) {
chars = Arrays.copyOfRange(chars, off, off+len);
}
return parse(chars);
}
public static BigDecimal parse(char[] chars) {
final int len = chars.length;
try {
if (len < 500) {
return new BigDecimal(chars);
}
return new BigDecimalParser(chars).parseBigDecimal(len / 10);
} catch (NumberFormatException e) {
String desc = e.getMessage();
// 05-Feb-2021, tatu: Alas, JDK mostly has null message so:
if (desc == null) {
desc = "Not a valid number representation";
}
throw new NumberFormatException("Value \"" + new String(chars)
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + desc);
}
}
private BigDecimal parseBigDecimal(final int splitLen) {
boolean numHasSign = false;
boolean expHasSign = false;
boolean neg = false;
int numIdx = 0;
int expIdx = -1;
int dotIdx = -1;
int scale = 0;
final int len = chars.length;
for (int i = 0; i < len; 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, len - expIdx - 1);
exp = Integer.parseInt(expStr);
scale = adjustScale(scale, exp);
} else {
numEndIdx = len;
}
BigDecimal res;
if (dotIdx >= 0) {
int leftLen = dotIdx - numIdx;
BigDecimal left = toBigDecimalRec(numIdx, leftLen, exp, splitLen);
int rightLen = numEndIdx - dotIdx - 1;
BigDecimal right = toBigDecimalRec(dotIdx + 1, rightLen, exp - rightLen, splitLen);
res = left.add(right);
} else {
res = toBigDecimalRec(numIdx, numEndIdx - numIdx, exp, splitLen);
}
if (scale != 0) {
res = res.setScale(scale);
}
if (neg) {
res = res.negate();
}
return res;
}
private 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 BigDecimal toBigDecimalRec(int off, int len, int scale, int splitLen) {
if (len > splitLen) {
int mid = len / 2;
BigDecimal left = toBigDecimalRec(off, mid, scale + len - mid, splitLen);
BigDecimal right = toBigDecimalRec(off + mid, len - mid, scale, splitLen);
return left.add(right);
}
return len == 0 ? BigDecimal.ZERO : new BigDecimal(chars, off, len).movePointRight(scale);
}
}