From c6d50fccdae9809a31dffcbc103fcd5f8355d8c6 Mon Sep 17 00:00:00 2001 From: Philippe Marschall Date: Sun, 24 Jul 2022 14:18:21 +0200 Subject: [PATCH 1/2] Optimize parsing 19 digit longs Avoid slow path and allocation when parsing 19 digit longs. --- .../jackson/core/base/ParserBase.java | 6 ++--- .../jackson/core/io/NumberInput.java | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java index 89ae73504a..45da44107d 100644 --- a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java +++ b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java @@ -939,7 +939,6 @@ private void _parseSlowFloat(int expType) throws IOException private void _parseSlowInt(int expType) throws IOException { - final String numStr = _textBuffer.contentsAsString(); try { int len = _intLength; char[] buf = _textBuffer.getTextBuffer(); @@ -949,10 +948,10 @@ private void _parseSlowInt(int expType) throws IOException } // Some long cases still... if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) { - // Probably faster to construct a String, call parse, than to use BigInteger - _numberLong = NumberInput.parseLong(numStr); + _numberLong = NumberInput.parseLong19(buf, offset, _numberNegative); _numTypesValid = NR_LONG; } else { + String numStr = _textBuffer.contentsAsString(); // 16-Oct-2018, tatu: Need to catch "too big" early due to [jackson-core#488] if ((expType == NR_INT) || (expType == NR_LONG)) { _reportTooLongIntegral(expType, numStr); @@ -970,6 +969,7 @@ private void _parseSlowInt(int expType) throws IOException } } } catch (NumberFormatException nex) { + String numStr = _textBuffer.contentsAsString(); // Can this ever occur? Due to overflow, maybe? _wrapError("Malformed numeric value ("+_longNumberDesc(numStr)+")", nex); } diff --git a/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java index 9ea4405dfc..03189bbc66 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java +++ b/src/main/java/com/fasterxml/jackson/core/io/NumberInput.java @@ -152,6 +152,32 @@ public static long parseLong(char[] ch, int off, int len) long val = parseInt(ch, off, len1) * L_BILLION; return val + (long) parseInt(ch, off+len1, 9); } + + /** + * Parses an unsigned long made up of exactly 19 digits. + *

+ * It is the callers responsibility to make sure the input is exactly 19 digits. + * and fits into a 64bit long by calling {@link #inLongRange(char[], int, int, boolean)} + * first. + *

+ * Note that input String must NOT contain leading minus sign (even + * if {@code negative} is set to true). + * + * @param ch Buffer that contains integer value to decode + * @param off Offset of the first digit character in buffer + * @param negative Whether original number had a minus sign + * @return Decoded {@code long} value + */ + public static long parseLong19(char[] ch, int off, boolean negative) + { + // Note: caller must ensure length is [10, 18] + long num = 0L; + for (int i = 0; i < 19; i++) { + char c = ch[off + i]; + num = (num * 10) + (c - '0'); + } + return negative ? -num : num; + } /** * Similar to {@link #parseInt(String)} but for {@code long} values. From d7501ee5c893dcbaff99627132e5f0aa5d6eb85a Mon Sep 17 00:00:00 2001 From: Philippe Marschall Date: Tue, 20 Dec 2022 15:50:40 +0100 Subject: [PATCH 2/2] Implement more number tests --- .../jackson/core/read/NumberParsingTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java index bc46b03922..29263bf1e1 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java @@ -278,7 +278,9 @@ public void testLongRange() throws Exception for (int mode : ALL_MODES) { long belowMinInt = -1L + Integer.MIN_VALUE; long aboveMaxInt = 1L + Integer.MAX_VALUE; - String input = "[ "+Long.MAX_VALUE+","+Long.MIN_VALUE+","+aboveMaxInt+", "+belowMinInt+" ]"; + long belowMaxLong = -1L + Long.MAX_VALUE; + long aboveMinLong = 1L + Long.MIN_VALUE; + String input = "[ "+Long.MAX_VALUE+","+Long.MIN_VALUE+","+aboveMaxInt+", "+belowMinInt+","+belowMaxLong+", "+aboveMinLong+" ]"; JsonParser p = createParser(jsonFactory(), mode, input); assertToken(JsonToken.START_ARRAY, p.nextToken()); assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); @@ -297,6 +299,14 @@ public void testLongRange() throws Exception assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); assertEquals(belowMinInt, p.getLongValue()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); + assertEquals(belowMaxLong, p.getLongValue()); + + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(JsonParser.NumberType.LONG, p.getNumberType()); + assertEquals(aboveMinLong, p.getLongValue()); + assertToken(JsonToken.END_ARRAY, p.nextToken()); p.close();