From 85a1076c9c1f3bf94a0ec6a40c91ec50c8e78c42 Mon Sep 17 00:00:00 2001 From: Werner Randelshofer Date: Sun, 26 Feb 2023 18:17:56 +0100 Subject: [PATCH] Implement lookup of hex digits with branchless code. Simplify isDigit() methods. --- README.md | 76 +++++------ .../AbstractFloatValueParser.java | 33 ----- ...actJavaFloatingPointBitsFromByteArray.java | 22 ++-- ...actJavaFloatingPointBitsFromCharArray.java | 23 ++-- ...JavaFloatingPointBitsFromCharSequence.java | 21 +-- ...actJsonFloatingPointBitsFromByteArray.java | 11 +- ...actJsonFloatingPointBitsFromCharArray.java | 11 +- ...JsonFloatingPointBitsFromCharSequence.java | 11 +- .../AbstractNumberParser.java | 98 ++++++++++++++ .../fastdoubleparser/FastDoubleSwar.java | 6 +- .../JavaBigDecimalFromByteArray.java | 16 +-- .../JavaBigDecimalFromCharArray.java | 16 +-- .../JavaBigDecimalFromCharSequence.java | 16 +-- .../JavaBigIntegerFromByteArray.java | 2 +- .../JavaBigIntegerFromCharArray.java | 2 +- .../JavaBigIntegerFromCharSequence.java | 2 +- .../AbstractFloatValueParserTest.java | 79 +++++++++++ .../AbstractJavaDoubleParserTest.java | 31 ++--- .../AbstractJavaFloatParserTest.java | 22 +++- .../AbstractJsonDoubleParserTest.java | 11 +- .../JavaDoubleParserTest.java | 44 +++++-- .../fastdoubleparser/JavaFloatParserTest.java | 45 +++++-- ...avaBigDecimalFromByteArrayScalability.java | 124 ++---------------- .../JsonDoubleParserTest.java | 43 ++++-- .../fastdoubleparser/FastDoubleSwar.java | 4 +- .../fastdoubleparser/FastDoubleSwar.java | 4 +- .../fastdoubleparser/FastDoubleSwar.java | 6 +- .../fastdoubleparser/FastDoubleSwar.java | 4 +- pom.xml | 6 +- 29 files changed, 455 insertions(+), 334 deletions(-) diff --git a/README.md b/README.md index 406438a5..46c919a6 100644 --- a/README.md +++ b/README.md @@ -107,21 +107,21 @@ Most input lines look like this: `0.4011441469603171`. |Method | MB/s |stdev|Mfloats/s| ns/f | speedup | JDK | |---------------------------|------:|-----:|------:|--------:|--------:|--------| -|java.lang.Double | 91.07| 3.8 %| 5.23| 191.31| 1.00|20-ea | -|java.lang.Float | 96.15| 7.3 %| 5.52| 181.20| 1.00|20-ea | -|java.math.BigDecimal | 192.26| 8.0 %| 11.03| 90.62| 1.00|20-ea | -|JavaDoubleParser String | 400.80|14.9 %| 23.00| 43.47| 4.40|20-ea | -|JavaDoubleParser char[] | 520.21|14.2 %| 29.86| 33.49| 5.71|20-ea | -|JavaDoubleParser byte[] | 593.21|21.2 %| 34.05| 29.37| 6.51|20-ea | -|JsonDoubleParser String | 411.79|13.6 %| 23.64| 42.31| 4.52|20-ea | -|JsonDoubleParser char[] | 562.95|14.9 %| 32.31| 30.95| 6.18|20-ea | -|JsonDoubleParser byte[] | 613.03|14.0 %| 35.19| 28.42| 6.73|20-ea | -|JavaFloatParser String | 367.07|12.2 %| 21.07| 47.46| 3.82|20-ea | -|JavaFloatParser char[] | 518.64|11.4 %| 29.77| 33.59| 5.39|20-ea | -|JavaFloatParser byte[] | 613.75| 9.3 %| 35.23| 28.39| 6.38|20-ea | -|JavaBigDecimalParser String| 398.83|14.1 %| 22.89| 43.68| 2.07|20-ea | -|JavaBigDecimalParser char[]| 557.90|14.3 %| 32.02| 31.23| 2.90|20-ea | -|JavaBigDecimalParser byte[]| 652.20|16.6 %| 37.43| 26.71| 3.39|20-ea | +|java.lang.Double | 91.73|10.1 %| 5.26| 189.95| 1.00|20 | +|java.lang.Float | 92.93| 6.1 %| 5.33| 187.50| 1.00|20 | +|java.math.BigDecimal | 172.14| 7.2 %| 9.88| 101.22| 1.00|20 | +|JavaDoubleParser String | 554.66| 4.0 %| 31.83| 31.41| 6.05|20 | +|JavaDoubleParser char[] | 592.95|13.4 %| 34.03| 29.39| 6.46|20 | +|JavaDoubleParser byte[] | 642.69| 3.6 %| 36.88| 27.11| 7.01|20 | +|JsonDoubleParser String | 551.93|17.4 %| 31.68| 31.57| 6.02|20 | +|JsonDoubleParser char[] | 612.85|14.2 %| 35.17| 28.43| 6.68|20 | +|JsonDoubleParser byte[] | 642.12| 4.6 %| 36.85| 27.14| 7.00|20 | +|JavaFloatParser String | 519.72|16.3 %| 29.83| 33.53| 5.59|20 | +|JavaFloatParser char[] | 599.44| 5.6 %| 34.40| 29.07| 6.45|20 | +|JavaFloatParser byte[] | 621.22|11.2 %| 35.65| 28.05| 6.68|20 | +|JavaBigDecimalParser String| 513.70|17.4 %| 29.48| 33.92| 2.98|20 | +|JavaBigDecimalParser char[]| 617.66|25.9 %| 35.45| 28.21| 3.59|20 | +|JavaBigDecimalParser byte[]| 670.74| 5.8 %| 38.49| 25.98| 3.90|20 | ### The data file `canada.txt` @@ -130,21 +130,21 @@ Most input lines look like this: `52.038048000000117`. |Method | MB/s |stdev|Mfloats/s| ns/f | speedup | JDK | |---------------------------|------:|-----:|------:|--------:|--------:|--------| -|java.lang.Double | 71.28| 9.7 %| 4.10| 244.12| 1.00|20-ea | -|java.lang.Float | 87.45| 7.1 %| 5.03| 198.98| 1.00|20-ea | -|java.math.BigDecimal | 244.03|10.9 %| 14.02| 71.31| 1.00|20-ea | -|JavaDoubleParser String | 294.45|13.1 %| 16.92| 59.10| 4.13|20-ea | -|JavaDoubleParser char[] | 419.26|13.0 %| 24.09| 41.51| 5.88|20-ea | -|JavaDoubleParser byte[] | 461.00|20.0 %| 26.49| 37.75| 6.47|20-ea | -|JsonDoubleParser String | 312.11|16.1 %| 17.94| 55.75| 4.38|20-ea | -|JsonDoubleParser char[] | 381.20|22.6 %| 21.91| 45.65| 5.35|20-ea | -|JsonDoubleParser byte[] | 463.28|20.2 %| 26.62| 37.56| 6.50|20-ea | -|JavaFloatParser String | 285.14|13.0 %| 16.39| 61.03| 3.26|20-ea | -|JavaFloatParser char[] | 411.34|20.3 %| 23.64| 42.30| 4.70|20-ea | -|JavaFloatParser byte[] | 526.98|16.9 %| 30.28| 33.02| 6.03|20-ea | -|JavaBigDecimalParser String| 288.74|23.5 %| 16.59| 60.27| 1.18|20-ea | -|JavaBigDecimalParser char[]| 442.03|14.5 %| 25.40| 39.37| 1.81|20-ea | -|JavaBigDecimalParser byte[]| 457.09|21.1 %| 26.27| 38.07| 1.87|20-ea | +|java.lang.Double | 80.33| 5.2 %| 4.62| 216.62| 1.00|20 | +|java.lang.Float | 94.04| 4.7 %| 5.40| 185.04| 1.00|20 | +|java.math.BigDecimal | 298.46|11.9 %| 17.15| 58.30| 1.00|20 | +|JavaDoubleParser String | 366.14|13.9 %| 21.04| 47.53| 4.56|20 | +|JavaDoubleParser char[] | 590.31| 4.8 %| 33.92| 29.48| 7.35|20 | +|JavaDoubleParser byte[] | 548.01|12.2 %| 31.49| 31.75| 6.82|20 | +|JsonDoubleParser String | 404.25|14.8 %| 23.23| 43.05| 5.03|20 | +|JsonDoubleParser char[] | 575.44| 3.5 %| 33.07| 30.24| 7.16|20 | +|JsonDoubleParser byte[] | 576.21| 3.2 %| 33.11| 30.20| 7.17|20 | +|JavaFloatParser String | 342.41|14.5 %| 19.68| 50.82| 3.64|20 | +|JavaFloatParser char[] | 576.07|16.1 %| 33.10| 30.21| 6.13|20 | +|JavaFloatParser byte[] | 592.73|12.2 %| 34.06| 29.36| 6.30|20 | +|JavaBigDecimalParser String| 420.41|15.4 %| 24.16| 41.39| 1.41|20 | +|JavaBigDecimalParser char[]| 643.92|18.4 %| 37.00| 27.02| 2.16|20 | +|JavaBigDecimalParser byte[]| 686.99| 5.8 %| 39.48| 25.33| 2.30|20 | ### The data file `mesh.txt` @@ -175,14 +175,14 @@ Most input lines look like this: `-0x1.09219008205fcp6`. |Method | MB/s |stdev|Mfloats/s| ns/f | speedup | JDK | |---------------------------|------:|-----:|------:|--------:|--------:|--------| -|java.lang.Double | 35.74| 3.4 %| 1.96| 510.34| 1.00|20 | -|java.lang.Float | 35.76| 3.3 %| 1.96| 510.04| 1.00|20 | -|JavaDoubleParser String | 312.41|11.2 %| 17.13| 58.38| 8.74|20 | -|JavaDoubleParser char[] | 443.68|16.9 %| 24.33| 41.11| 12.41|20 | -|JavaDoubleParser byte[] | 497.49|14.5 %| 27.28| 36.66| 13.92|20 | -|JavaFloatParser String | 313.17|11.5 %| 17.17| 58.24| 8.76|20 | -|JavaFloatParser char[] | 434.04|16.2 %| 23.80| 42.02| 12.14|20 | -|JavaFloatParser byte[] | 494.68|14.7 %| 27.12| 36.87| 13.83|20 | +|java.lang.Double | 36.60| 7.1 %| 2.01| 498.35| 1.00|20 | +|java.lang.Float | 37.47| 5.0 %| 2.05| 486.78| 1.00|20 | +|JavaDoubleParser String | 387.82|11.9 %| 21.26| 47.03| 10.60|20 | +|JavaDoubleParser char[] | 535.71|10.9 %| 29.37| 34.04| 14.64|20 | +|JavaDoubleParser byte[] | 523.70| 9.4 %| 28.71| 34.83| 14.31|20 | +|JavaFloatParser String | 404.32|12.4 %| 22.17| 45.11| 10.79|20 | +|JavaFloatParser char[] | 531.39| 3.3 %| 29.14| 34.32| 14.18|20 | +|JavaFloatParser byte[] | 519.60| 9.6 %| 28.49| 35.10| 13.87|20 | ### Comparison with C version diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParser.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParser.java index 8b9ebb3d..ad5835a8 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParser.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParser.java @@ -34,38 +34,5 @@ abstract class AbstractFloatValueParser extends AbstractNumberParser { * The hexadecimal exponent of a double has a range of -1022 to +1023. */ final static int MAX_EXPONENT_NUMBER = 1024; - /** - * Special value in {@link #CHAR_TO_HEX_MAP} for - * the decimal point character. - */ - static final byte DECIMAL_POINT_CLASS = -4; - /** - * Special value in {@link #CHAR_TO_HEX_MAP} for - * characters that are neither a hex digit nor - * a decimal point character.. - */ - static final byte OTHER_CLASS = -1; - /** - * Includes all non-negative values of a {@code byte}, so that we only have - * to check for byte values {@literal <} 0 before accessing this array. - */ - static final byte[] CHAR_TO_HEX_MAP = new byte[128]; - static { - for (char ch = 0; ch < AbstractFloatValueParser.CHAR_TO_HEX_MAP.length; ch++) { - AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch] = AbstractFloatValueParser.OTHER_CLASS; - } - for (char ch = '0'; ch <= '9'; ch++) { - AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch] = (byte) (ch - '0'); - } - for (char ch = 'A'; ch <= 'F'; ch++) { - AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch] = (byte) (ch - 'A' + 10); - } - for (char ch = 'a'; ch <= 'f'; ch++) { - AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch] = (byte) (ch - 'a' + 10); - } - for (char ch = '.'; ch <= '.'; ch++) { - AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch] = AbstractFloatValueParser.DECIMAL_POINT_CLASS; - } - } } diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromByteArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromByteArray.java index 093c544d..45b2b192 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromByteArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromByteArray.java @@ -111,10 +111,10 @@ private long parseDecFloatLiteral(byte[] str, int index, int startIndex, int end // --------------------- int expNumber = 0; if (ch == 'e' || ch == 'E') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -122,7 +122,7 @@ private long parseDecFloatLiteral(byte[] str, int index, int startIndex, int end if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * expNumber + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -174,6 +174,7 @@ private long parseDecFloatLiteral(byte[] str, int index, int startIndex, int end exponentOfTruncatedSignificand); } + /** * Parses a {@code FloatingPointLiteral} production with optional leading and trailing * white space. @@ -210,7 +211,7 @@ public long parseFloatingPointLiteral(byte[] str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -226,7 +227,7 @@ public long parseFloatingPointLiteral(byte[] str, int offset, int length) { // --------------------------- final boolean hasLeadingZero = ch == '0'; if (hasLeadingZero) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 'x' || ch == 'X') { return parseHexFloatingPointLiteral(str, index + 1, offset, endIndex, isNegative); } @@ -273,7 +274,7 @@ private long parseHexFloatingPointLiteral( for (; index < endIndex; index++) { ch = str[index]; // Table look up is faster than a sequence of if-else-branches. - int hexValue = ch < 0 ? AbstractFloatValueParser.OTHER_CLASS : AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch]; + int hexValue = lookupHex(ch); if (hexValue >= 0) { significand = (significand << 4) | hexValue;// This might overflow, we deal with it later. } else if (hexValue == AbstractFloatValueParser.DECIMAL_POINT_CLASS) { @@ -308,10 +309,10 @@ private long parseHexFloatingPointLiteral( int expNumber = 0; final boolean hasExponent = (ch == 'p') || (ch == 'P'); if (hasExponent) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -319,7 +320,7 @@ private long parseHexFloatingPointLiteral( if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -352,7 +353,7 @@ private long parseHexFloatingPointLiteral( for (index = significandStartIndex; index < significandEndIndex; index++) { ch = str[index]; // Table look up is faster than a sequence of if-else-branches. - int hexValue = ch < 0 ? AbstractFloatValueParser.OTHER_CLASS : AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch]; + int hexValue = lookupHex(ch); if (hexValue >= 0) { if (Long.compareUnsigned(significand, AbstractFloatValueParser.MINIMAL_NINETEEN_DIGIT_INTEGER) < 0) { significand = (significand << 4) | hexValue; @@ -372,6 +373,7 @@ private long parseHexFloatingPointLiteral( (virtualIndexOfPoint - index + skipCountInTruncatedDigits) * 4 + expNumber); } + private long parseNaNOrInfinity(byte[] str, int index, int endIndex, boolean isNegative) { if (str[index] == 'N') { if (index + 2 < endIndex diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharArray.java index 38391db0..05496437 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharArray.java @@ -115,10 +115,10 @@ private long parseDecFloatLiteral(char[] str, int index, int startIndex, int end // --------------------- int expNumber = 0; if (ch == 'e' || ch == 'E') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -126,7 +126,7 @@ private long parseDecFloatLiteral(char[] str, int index, int startIndex, int end if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -214,7 +214,7 @@ public long parseFloatingPointLiteral(char[] str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -230,7 +230,7 @@ public long parseFloatingPointLiteral(char[] str, int offset, int length) { // --------------------------- final boolean hasLeadingZero = ch == '0'; if (hasLeadingZero) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 'x' || ch == 'X') { return parseHexFloatLiteral(str, index + 1, offset, endIndex, isNegative); } @@ -277,10 +277,10 @@ private long parseHexFloatLiteral( for (; index < endIndex; index++) { ch = str[index]; // Table look up is faster than a sequence of if-else-branches. - int hexValue = ch > 127 ? AbstractFloatValueParser.OTHER_CLASS : AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch]; + int hexValue = lookupHex(ch); if (hexValue >= 0) { significand = (significand << 4) | hexValue;// This might overflow, we deal with it later. - } else if (hexValue == AbstractFloatValueParser.DECIMAL_POINT_CLASS) { + } else if (hexValue == DECIMAL_POINT_CLASS) { illegal |= virtualIndexOfPoint >= 0; virtualIndexOfPoint = index; if (CONDITIONAL_COMPILATION_PARSE_EIGHT_HEX_DIGITS) { @@ -312,10 +312,10 @@ private long parseHexFloatLiteral( int expNumber = 0; final boolean hasExponent = (ch == 'p') || (ch == 'P'); if (hasExponent) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -323,7 +323,7 @@ private long parseHexFloatLiteral( if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -356,7 +356,7 @@ private long parseHexFloatLiteral( for (index = significandStartIndex; index < significandEndIndex; index++) { ch = str[index]; // Table look up is faster than a sequence of if-else-branches. - int hexValue = ch > 127 ? AbstractFloatValueParser.OTHER_CLASS : AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch]; + int hexValue = lookupHex(ch); if (hexValue >= 0) { if (Long.compareUnsigned(significand, AbstractFloatValueParser.MINIMAL_NINETEEN_DIGIT_INTEGER) < 0) { significand = (significand << 4) | hexValue; @@ -418,6 +418,7 @@ private long tryToParseEightHexDigits(char[] str, int offset) { return FastDoubleSwar.tryToParseEightHexDigits(str, offset); } + /** * Computes a float value from the given components of a decimal float * literal. diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharSequence.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharSequence.java index a7978a4d..5468046e 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharSequence.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatingPointBitsFromCharSequence.java @@ -111,10 +111,10 @@ private long parseDecFloatLiteral(CharSequence str, int index, int startIndex, i // --------------------- int expNumber = 0; if (ch == 'e' || ch == 'E') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -122,7 +122,7 @@ private long parseDecFloatLiteral(CharSequence str, int index, int startIndex, i if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * expNumber + ch - '0'; } - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -210,7 +210,7 @@ public final long parseFloatingPointLiteral(CharSequence str, int offset, int le // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -226,7 +226,7 @@ public final long parseFloatingPointLiteral(CharSequence str, int offset, int le // --------------------------- final boolean hasLeadingZero = ch == '0'; if (hasLeadingZero) { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == 'x' || ch == 'X') { return parseHexFloatLiteral(str, index + 1, offset, endIndex, isNegative); } @@ -273,7 +273,7 @@ private long parseHexFloatLiteral( for (; index < endIndex; index++) { ch = str.charAt(index); // Table look up is faster than a sequence of if-else-branches. - int hexValue = ch > 127 ? AbstractFloatValueParser.OTHER_CLASS : AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch]; + int hexValue = lookupHex(ch); if (hexValue >= 0) { significand = significand << 4 | hexValue;// This might overflow, we deal with it later. } else if (hexValue == AbstractFloatValueParser.DECIMAL_POINT_CLASS) { @@ -306,10 +306,10 @@ private long parseHexFloatLiteral( int expNumber = 0; final boolean hasExponent = ch == 'p' || ch == 'P'; if (hasExponent) { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -317,7 +317,7 @@ private long parseHexFloatLiteral( if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * expNumber + ch - '0'; } - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -350,7 +350,7 @@ private long parseHexFloatLiteral( for (index = significandStartIndex; index < significandEndIndex; index++) { ch = str.charAt(index); // Table look up is faster than a sequence of if-else-branches. - int hexValue = ch > 127 ? AbstractFloatValueParser.OTHER_CLASS : AbstractFloatValueParser.CHAR_TO_HEX_MAP[ch]; + int hexValue = lookupHex(ch); if (hexValue >= 0) { if (Long.compareUnsigned(significand, AbstractFloatValueParser.MINIMAL_NINETEEN_DIGIT_INTEGER) < 0) { significand = significand << 4 | hexValue; @@ -370,6 +370,7 @@ private long parseHexFloatLiteral( (virtualIndexOfPoint - index + skipCountInTruncatedDigits) * 4 + expNumber); } + private long parseNaNOrInfinity(CharSequence str, int index, int endIndex, boolean isNegative) { if (str.charAt(index) == 'N') { if (index + 2 < endIndex diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromByteArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromByteArray.java index 15c87d5f..8f3975fc 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromByteArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromByteArray.java @@ -39,7 +39,7 @@ public final long parseNumber(byte[] str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -49,7 +49,7 @@ public final long parseNumber(byte[] str, int offset, int length) { // --------------------------- final boolean hasLeadingZero = ch == '0'; if (hasLeadingZero) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == '0') { throw new NumberFormatException(SYNTAX_ERROR); } @@ -108,10 +108,10 @@ public final long parseNumber(byte[] str, int offset, int length) { // --------------------- int expNumber = 0; if (ch == 'e' || ch == 'E') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -119,7 +119,7 @@ public final long parseNumber(byte[] str, int offset, int length) { if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * expNumber + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -163,6 +163,7 @@ public final long parseNumber(byte[] str, int offset, int length) { exponentOfTruncatedSignificand); } + /** * Computes a float value from the given components of a decimal float * literal. diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharArray.java index 95811f70..3505b72f 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharArray.java @@ -39,7 +39,7 @@ public final long parseNumber(char[] str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -49,7 +49,7 @@ public final long parseNumber(char[] str, int offset, int length) { // --------------------------- final boolean hasLeadingZero = ch == '0'; if (hasLeadingZero) { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == '0') { throw new NumberFormatException(SYNTAX_ERROR); } @@ -99,10 +99,10 @@ public final long parseNumber(char[] str, int offset, int length) { // --------------------- int expNumber = 0; if (ch == 'e' || ch == 'E') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -110,7 +110,7 @@ public final long parseNumber(char[] str, int offset, int length) { if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * expNumber + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -154,6 +154,7 @@ public final long parseNumber(char[] str, int offset, int length) { exponentOfTruncatedSignificand); } + private int tryToParseEightDigits(char[] str, int offset) { return FastDoubleSwar.tryToParseEightDigits(str, offset); } diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharSequence.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharSequence.java index 7e3572ad..a99ed335 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharSequence.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonFloatingPointBitsFromCharSequence.java @@ -39,7 +39,7 @@ public final long parseNumber(CharSequence str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative) { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -49,7 +49,7 @@ public final long parseNumber(CharSequence str, int offset, int length) { // --------------------------- final boolean hasLeadingZero = ch == '0'; if (hasLeadingZero) { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == '0') { throw new NumberFormatException(SYNTAX_ERROR); } @@ -99,10 +99,10 @@ public final long parseNumber(CharSequence str, int offset, int length) { // --------------------- int expNumber = 0; if (ch == 'e' || ch == 'E') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -110,7 +110,7 @@ public final long parseNumber(CharSequence str, int offset, int length) { if (expNumber < AbstractFloatValueParser.MAX_EXPONENT_NUMBER) { expNumber = 10 * expNumber + ch - '0'; } - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -154,6 +154,7 @@ public final long parseNumber(CharSequence str, int offset, int length) { exponentOfTruncatedSignificand); } + /** * Computes a float value from the given components of a {@code number} * literal. diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java index 3956c702..5641f7c6 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java @@ -20,6 +20,104 @@ abstract class AbstractNumberParser { * when there are too many input digits. */ public static final String VALUE_EXCEEDS_LIMITS = "value exceeds limits"; + /** + * Special value in {@link #CHAR_TO_HEX_MAP} for + * the decimal point character. + */ + static final byte DECIMAL_POINT_CLASS = -4; + /** + * Special value in {@link #CHAR_TO_HEX_MAP} for + * characters that are neither a hex digit nor + * a decimal point character.. + */ + static final byte OTHER_CLASS = -1; + /** + * Includes all non-negative values of a {@code byte}, so that we only have + * to check for byte values {@literal <} 0 before accessing this array. + */ + static final byte[] CHAR_TO_HEX_MAP = new byte[128]; + static { + for (char ch = 0; ch < CHAR_TO_HEX_MAP.length; ch++) { + CHAR_TO_HEX_MAP[ch] = OTHER_CLASS; + } + for (char ch = '0'; ch <= '9'; ch++) { + CHAR_TO_HEX_MAP[ch] = (byte) (ch - '0'); + } + for (char ch = 'A'; ch <= 'F'; ch++) { + CHAR_TO_HEX_MAP[ch] = (byte) (ch - 'A' + 10); + } + for (char ch = 'a'; ch <= 'f'; ch++) { + CHAR_TO_HEX_MAP[ch] = (byte) (ch - 'a' + 10); + } + for (char ch = '.'; ch <= '.'; ch++) { + CHAR_TO_HEX_MAP[ch] = DECIMAL_POINT_CLASS; + } + } + + /** + * Returns the character at the specified index if index is < endIndex; + * otherwise returns 0. + * + * @param str the string + * @param i the index + * @param endIndex the end index + * @return the character or 0 + */ + protected static byte charAt(byte[] str, int i, int endIndex) { + return i < endIndex ? str[i] : 0; + } + + /** + * Returns the character at the specified index if index is < endIndex; + * otherwise returns 0. + * + * @param str the string + * @param i the index + * @param endIndex the end index + * @return the character or 0 + */ + protected static char charAt(char[] str, int i, int endIndex) { + return i < endIndex ? str[i] : 0; + } + + /** + * Returns the character at the specified index if index is < endIndex; + * otherwise returns 0. + * + * @param str the string + * @param i the index + * @param endIndex the end index + * @return the character or 0 + */ + protected static char charAt(CharSequence str, int i, int endIndex) { + return i < endIndex ? str.charAt(i) : 0; + } + + /** + * Looks the character up in the {@link #CHAR_TO_HEX_MAP} returns + * a value < 0 if the character is not in the map. + *

+ * Returns -4 if the character is a decimal point. + * + * @param ch a character + * @return the hex value or a value < 0. + */ + protected static int lookupHex(byte ch) { + return CHAR_TO_HEX_MAP[ch & 127] | (ch >> 31); + } + + /** + * Looks the character up in the {@link #CHAR_TO_HEX_MAP} returns + * a value < 0 if the character is not in the map. + *

+ * Returns -4 if the character is a decimal point. + * + * @param ch a character + * @return the hex value or a value < 0. + */ + protected static int lookupHex(char ch) { + return CHAR_TO_HEX_MAP[ch & 127] | (127 - ch) >> 31; + } } diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java index a29305b6..10fabdce 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java @@ -66,7 +66,7 @@ public static int countUpToEightDigitsUtf8(long chunk) { protected static boolean isDigit(char c) { // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } /** @@ -79,7 +79,7 @@ protected static boolean isDigit(byte c) { // We check if '0' <= c && c <= '9'. // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } public static boolean isEightDigits(byte[] a, int offset) { @@ -451,7 +451,7 @@ public static long tryToParseEightHexDigits(byte[] a, int offset) { * returns a negative value if the two longs do not contain 8 hex digits */ public static long tryToParseEightHexDigitsUtf16(long first, long second) { - long highBytes = (first | second) & 0xff00ff00_ff00ff00L; + long highBytes = (first | second) & 0xff80ff80_ff80ff80L; if (highBytes != 0L) { return -1L; } diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromByteArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromByteArray.java index 8d5b91b3..75bf0022 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromByteArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromByteArray.java @@ -91,7 +91,7 @@ public BigDecimal parseBigDecimalString(byte[] str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -137,10 +137,10 @@ public BigDecimal parseBigDecimalString(byte[] str, int offset, int length) { long expNumber = 0; if (ch == 'e' || ch == 'E') { exponentIndicatorIndex = index; - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -148,7 +148,7 @@ public BigDecimal parseBigDecimalString(byte[] str, int offset, int length) { if (expNumber < MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -200,7 +200,7 @@ BigDecimal parseBigDecimalStringWithManyDigits(byte[] str, int offset, int lengt // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -263,10 +263,10 @@ BigDecimal parseBigDecimalStringWithManyDigits(byte[] str, int offset, int lengt long expNumber = 0; if (ch == 'e' || ch == 'E') { exponentIndicatorIndex = index; - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal = !FastDoubleSwar.isDigit(ch); do { @@ -274,7 +274,7 @@ BigDecimal parseBigDecimalStringWithManyDigits(byte[] str, int offset, int lengt if (expNumber < MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharArray.java index 8b3f932d..bc68850f 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharArray.java @@ -90,7 +90,7 @@ public BigDecimal parseBigDecimalString(char[] str, int offset, int length) { // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -136,10 +136,10 @@ public BigDecimal parseBigDecimalString(char[] str, int offset, int length) { long expNumber = 0; if (ch == 'e' || ch == 'E') { exponentIndicatorIndex = index; - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -147,7 +147,7 @@ public BigDecimal parseBigDecimalString(char[] str, int offset, int length) { if (expNumber < MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -199,7 +199,7 @@ BigDecimal parseBigDecimalStringWithManyDigits(char[] str, int offset, int lengt // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -262,10 +262,10 @@ BigDecimal parseBigDecimalStringWithManyDigits(char[] str, int offset, int lengt long expNumber = 0; if (ch == 'e' || ch == 'E') { exponentIndicatorIndex = index; - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } illegal = !FastDoubleSwar.isDigit(ch); do { @@ -273,7 +273,7 @@ BigDecimal parseBigDecimalStringWithManyDigits(char[] str, int offset, int lengt if (expNumber < MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharSequence.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharSequence.java index b5cc3dfb..8a10e314 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharSequence.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigDecimalFromCharSequence.java @@ -91,7 +91,7 @@ public BigDecimal parseBigDecimalString(CharSequence str, int offset, int length // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -137,10 +137,10 @@ public BigDecimal parseBigDecimalString(CharSequence str, int offset, int length long expNumber = 0; if (ch == 'e' || ch == 'E') { exponentIndicatorIndex = index; - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } illegal |= !FastDoubleSwar.isDigit(ch); do { @@ -148,7 +148,7 @@ public BigDecimal parseBigDecimalString(CharSequence str, int offset, int length if (expNumber < MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; @@ -201,7 +201,7 @@ BigDecimal parseBigDecimalStringWithManyDigits(CharSequence str, int offset, int // ------------------- final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } @@ -264,10 +264,10 @@ BigDecimal parseBigDecimalStringWithManyDigits(CharSequence str, int offset, int long expNumber = 0; if (ch == 'e' || ch == 'E') { exponentIndicatorIndex = index; - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); boolean isExponentNegative = ch == '-'; if (isExponentNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } illegal = !FastDoubleSwar.isDigit(ch); do { @@ -275,7 +275,7 @@ BigDecimal parseBigDecimalStringWithManyDigits(CharSequence str, int offset, int if (expNumber < MAX_EXPONENT_NUMBER) { expNumber = 10 * (expNumber) + ch - '0'; } - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); } while (FastDoubleSwar.isDigit(ch)); if (isExponentNegative) { expNumber = -expNumber; diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java index 56e8e9df..3a068351 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java @@ -44,7 +44,7 @@ public BigInteger parseBigIntegerLiteral(byte[] str, int offset, int length, int byte ch = str[index]; final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java index ff5e8466..44f933ba 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java @@ -39,7 +39,7 @@ public BigInteger parseBigIntegerLiteral(char[] str, int offset, int length, int char ch = str[index]; final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str[index] : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java index 908a4a83..8fac4063 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java @@ -38,7 +38,7 @@ public BigInteger parseBigIntegerLiteral(CharSequence str, int offset, int lengt char ch = str.charAt(index); final boolean isNegative = ch == '-'; if (isNegative || ch == '+') { - ch = ++index < endIndex ? str.charAt(index) : 0; + ch = charAt(str, ++index, endIndex); if (ch == 0) { throw new NumberFormatException(SYNTAX_ERROR); } diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParserTest.java index 2d05e779..dfe0bdbe 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractFloatValueParserTest.java @@ -4,10 +4,16 @@ */ package ch.randelshofer.fastdoubleparser; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; public abstract class AbstractFloatValueParserTest { + protected boolean longRunningTests = !"false".equals(System.getProperty("enableLongRunningTests")); protected List createDataForDoubleDecimalLimits() { return Arrays.asList( @@ -115,6 +121,79 @@ protected List createDataForSignificandDigitsInputClasses() { ); } + enum NumberType { + FLOAT16, FLOAT32, FLOAT64 + } + + protected Stream createSupplementalTestData(NumberType type) { + try { + return + Files.walk(Path.of("../supplemental_test_files/data")) + .filter(path -> path.getFileName().toString().endsWith(".txt")) + .map(path1 -> createSupplementalTestData(path1, type)) + .reduce(Stream.empty(), Stream::concat); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + protected Stream getSupplementalTestDataFiles() { + if (longRunningTests) { + try { + return Files.walk(Path.of("../supplemental_test_files/data")) + .filter(path -> path.getFileName().toString().endsWith(".txt")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + return Stream.empty(); + } + + + /** + * Reads a text file. Each line has the following format: + *

+     * float16 bits (4 hex digits)
+     * │    float32 bits (8 hex digits)
+     * │    │        float64 bits (16 hex digits)
+     * │    │        │                input string
+     * ↓    ↓        ↓                ↓
+     * 0000 00000000 0000000000000000 .0
+     * 
+ * + * @param path path to text file + * @return A stream of {@link NumberTestData}. + */ + protected Stream createSupplementalTestData(Path path, NumberType type) { + try { + @SuppressWarnings("resource") + Stream lines = Files.lines(path); + String fileName = path.getFileName().toString(); + int[] lineNumber = {1}; + return lines.map(line -> { + String[] fields = line.split(" "); + Number number; + switch (type) { + case FLOAT16: + number = Short.parseShort(fields[0], 16); + break; + case FLOAT32: + number = Float.intBitsToFloat(Integer.parseInt(fields[1], 16)); + break; + case FLOAT64: + number = Double.longBitsToDouble(Long.parseLong(fields[2], 16)); + break; + default: + throw new IllegalArgumentException(); + } + return new NumberTestData(fileName + " " + lineNumber[0]++ + " " + fields[3], fields[3], number); + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + protected List createDataWithVeryLongInputStrings() { return Arrays.asList( new NumberTestData("too many input characters", new VirtualCharSequence('1', Integer.MAX_VALUE - 3), AbstractNumberParser.ILLEGAL_OFFSET_OR_ILLEGAL_LENGTH, IllegalArgumentException.class), diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaDoubleParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaDoubleParserTest.java index a579546b..12b7a34e 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaDoubleParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaDoubleParserTest.java @@ -7,9 +7,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; public abstract class AbstractJavaDoubleParserTest extends AbstractFloatValueParserTest { - protected boolean longRunningTests = !"false".equals(System.getProperty("enableLongRunningTests")); protected final static int EXPECTED_MAX_INPUT_LENGTH = Integer.MAX_VALUE - 4; protected List createTestDataForNaN() { @@ -142,7 +142,7 @@ protected List createDataForLegalDecStrings() { ); } - List createDataForLegalHexStrings() { + protected List createDataForLegalHexStrings() { return Arrays.asList( new NumberTestData("0xap2", 0xap2), @@ -167,6 +167,13 @@ List createDataForLegalHexStrings() { ); } + protected List createDataForIllegalHexStrings() { + return Arrays.asList( + new NumberTestData("0xäp2"), + new NumberTestData("0x0.1234567äp0") + ); + } + protected List createFloatTestDataForInputClassesInMethodParseFloatValue() { return Arrays.asList( new NumberTestData("parseFloatValue(): charOffset too small", "3.14", -1, 4, -1, 4, AbstractNumberParser.ILLEGAL_OFFSET_OR_ILLEGAL_LENGTH, IllegalArgumentException.class), @@ -249,7 +256,6 @@ protected List createDataForLegalCroppedStrings() { ); } - List createRegularDoubleTestData() { List list = new ArrayList<>(); list.addAll(createTestDataForInfinity()); @@ -259,6 +265,7 @@ List createRegularDoubleTestData() { list.addAll(createDataForBadStrings()); list.addAll(createDataForLegalDecStrings()); list.addAll(createDataForLegalHexStrings()); + list.addAll(createDataForIllegalHexStrings()); list.addAll(createDataForDoubleDecimalClingerInputClasses()); list.addAll(createDataForDoubleHexadecimalClingerInputClasses()); list.addAll(createDataForLegalCroppedStrings()); @@ -267,21 +274,11 @@ List createRegularDoubleTestData() { return list; } - List createLongRunningDoubleTestData() { - List list = new ArrayList<>(); + Stream createLongRunningDoubleTestData() { + Stream s = Stream.empty(); if (longRunningTests) { - list.addAll(createDataWithVeryLongInputStrings()); + s = Stream.concat(s, createDataWithVeryLongInputStrings().stream()); } - return list; + return s; } - - - List createLongRunningFloatTestData() { - List list = new ArrayList<>(); - if (longRunningTests) { - list.addAll(createDataWithVeryLongInputStrings()); - } - return list; - } - } \ No newline at end of file diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatParserTest.java index cf1f3665..550aa44b 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJavaFloatParserTest.java @@ -7,9 +7,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; public abstract class AbstractJavaFloatParserTest extends AbstractFloatValueParserTest { - protected boolean longRunningTests = !"false".equals(System.getProperty("enableLongRunningTests")); protected final static int EXPECTED_MAX_INPUT_LENGTH = Integer.MAX_VALUE - 4; protected List createTestDataForNaN() { @@ -142,9 +142,9 @@ protected List createDataForLegalDecStrings() { ); } - List createDataForLegalHexStrings() { + protected List createDataForLegalHexStrings() { return Arrays.asList( - new NumberTestData("0xap2", 0xap2), + new NumberTestData("0xap2", 0xap2f), new NumberTestData("FloatTypeSuffix", "0xap2d", 0xap2f), new NumberTestData("FloatTypeSuffix", "0xap2D", 0xap2f), @@ -167,6 +167,13 @@ List createDataForLegalHexStrings() { ); } + protected List createDataForIllegalHexStrings() { + return Arrays.asList( + new NumberTestData("0xäp2"), + new NumberTestData("0x0.1234567äp0") + ); + } + protected List createFloatTestDataForInputClassesInMethodParseFloatValue() { return Arrays.asList( new NumberTestData("parseFloatValue(): charOffset too small", "3.14", -1, 4, -1, 4, AbstractNumberParser.ILLEGAL_OFFSET_OR_ILLEGAL_LENGTH, IllegalArgumentException.class), @@ -259,6 +266,7 @@ List createRegularFloatTestData() { list.addAll(createDataForBadStrings()); list.addAll(createDataForLegalDecStrings()); list.addAll(createDataForLegalHexStrings()); + list.addAll(createDataForIllegalHexStrings()); list.addAll(createDataForFloatDecimalClingerInputClasses()); list.addAll(createDataForFloatHexadecimalClingerInputClasses()); list.addAll(createDataForLegalCroppedStrings()); @@ -267,12 +275,12 @@ List createRegularFloatTestData() { return list; } - List createLongRunningFloatTestData() { - List list = new ArrayList<>(); + Stream createLongRunningFloatTestData() { + Stream s = Stream.empty(); if (longRunningTests) { - list.addAll(createDataWithVeryLongInputStrings()); + s = Stream.concat(s, createDataWithVeryLongInputStrings().stream()); } - return list; + return s; } } \ No newline at end of file diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonDoubleParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonDoubleParserTest.java index 8df4dc15..b4ada8d2 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonDoubleParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractJsonDoubleParserTest.java @@ -7,10 +7,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; public abstract class AbstractJsonDoubleParserTest extends AbstractFloatValueParserTest { public final static int EXPECTED_MAX_INPUT_LENGTH = Integer.MAX_VALUE - 4; - private boolean longRunningTests = !"false".equals(System.getProperty("enableLongRunningTests")); protected List createDataForBadStrings() { return Arrays.asList( @@ -183,11 +183,12 @@ protected List createDataWithVeryLongInputStrings() { ); } - List createLongRunningTestData() { - List list = new ArrayList<>(); + Stream createLongRunningTestData() { + Stream s = Stream.empty(); if (longRunningTests) { - list.addAll(createDataWithVeryLongInputStrings()); + s = Stream.concat(s, createDataWithVeryLongInputStrings().stream()); } - return list; + return s; } + } \ No newline at end of file diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaDoubleParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaDoubleParserTest.java index ecf62435..697fe75e 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaDoubleParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaDoubleParserTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.TestFactory; +import java.nio.file.Path; import java.util.Objects; import java.util.function.ToDoubleFunction; import java.util.stream.Stream; @@ -40,9 +41,15 @@ public Stream dynamicTests_parseDouble_CharSequence_int_int() { @TestFactory public Stream dynamicTests_parseDouble_CharSequence_int_int_longRunningTests() { - return createLongRunningFloatTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JavaDoubleParser.parseDouble(u.input(), u.charOffset(), u.charLength())))); + ToDoubleFunction lambda = u -> JavaDoubleParser.parseDouble(u.input(), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningDoubleTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } @TestFactory @@ -64,9 +71,15 @@ public Stream dynamicTests_parseDouble_byteArray_int_int() { @TestFactory public Stream dynamicTests_parseDouble_byteArray_int_int_longRunningTests() { - return createLongRunningDoubleTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JavaDoubleParser.parseDouble(toByteArray(u.input()), u.byteOffset(), u.byteLength())))); + ToDoubleFunction lambda = u -> JavaDoubleParser.parseDouble(toByteArray(u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningDoubleTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } @TestFactory @@ -88,9 +101,20 @@ public Stream dynamicTests_parseDouble_charArray_int_int() { @TestFactory public Stream dynamicTests_parseDouble_charArray_int_int_longRunningTests() { - return createLongRunningDoubleTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JavaDoubleParser.parseDouble(toCharArray(u.input()), u.charOffset(), u.charLength())))); + ToDoubleFunction lambda = u -> JavaDoubleParser.parseDouble(toCharArray(u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningDoubleTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); + } + + protected void testFile(Path path, ToDoubleFunction f) { + createSupplementalTestData(path, NumberType.FLOAT64) + .forEach(d -> test(d, f)); } private void test(NumberTestData d, ToDoubleFunction f) { @@ -110,6 +134,4 @@ private void test(NumberTestData d, ToDoubleFunction f) { assertEquals(d.expectedValue().doubleValue(), actual); } } - - } diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaFloatParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaFloatParserTest.java index 6f609beb..7cffe937 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaFloatParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaFloatParserTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.TestFactory; +import java.nio.file.Path; import java.util.Objects; import java.util.stream.Stream; @@ -41,9 +42,15 @@ public Stream dynamicTests_parseFloat_CharSequence_Int_Int() { @TestFactory public Stream dynamicTests_parseFloat_CharSequence_int_int_longRunningTests() { - return createLongRunningFloatTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JavaFloatParser.parseFloat(u.input(), u.charOffset(), u.charLength())))); + ToFloatFunction lambda = u -> JavaFloatParser.parseFloat((u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningFloatTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } @TestFactory @@ -68,10 +75,15 @@ public Stream dynamicTests_parseFloat_byteArray_int_int() { @TestFactory public Stream dynamicTests_parseFloat_byteArray_int_int_longRunningTests() { - return createLongRunningFloatTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JavaFloatParser.parseFloat(toByteArray(u.input()), - u.byteOffset(), u.byteLength())))); + ToFloatFunction lambda = u -> JavaFloatParser.parseFloat(toByteArray(u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningFloatTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } @TestFactory @@ -94,12 +106,21 @@ public Stream dynamicTests_parseFloat_charArray_int_int() { @TestFactory public Stream dynamicTests_parseFloat_charArray_int_int_longRunningTests() { - return createLongRunningFloatTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JavaFloatParser.parseFloat(toCharArray(u.input()), - u.charOffset(), u.charLength())))); + ToFloatFunction lambda = u -> JavaFloatParser.parseFloat(toCharArray(u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningFloatTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } + protected void testFile(Path path, ToFloatFunction f) { + createSupplementalTestData(path, NumberType.FLOAT32) + .forEach(d -> test(d, f)); + } protected void test(NumberTestData d, ToFloatFunction f) { if (d.expectedErrorMessage() != null) { @@ -115,7 +136,7 @@ protected void test(NumberTestData d, ToFloatFunction f) { } } else { float actual = f.applyAsFloat(d); - assertEquals(d.expectedValue().floatValue(), actual); + assertEquals(d.expectedValue().floatValue(), actual, () -> d.input().toString()); } } diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhJavaBigDecimalFromByteArrayScalability.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhJavaBigDecimalFromByteArrayScalability.java index 6c5363eb..5c69314c 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhJavaBigDecimalFromByteArrayScalability.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhJavaBigDecimalFromByteArrayScalability.java @@ -26,119 +26,15 @@ /** * Benchmarks for selected floating point strings. *
- * # JMH version: 1.35
- * # VM version: JDK 20-ea, OpenJDK 64-Bit Server VM, 20-ea+29-2280
+ * # JMH version: 1.36
+ * # VM version: JDK 20-ea, OpenJDK 64-Bit Server VM, 20+36-2344
  * # Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz
  *
- * After we integrated FftMultiplier:
- *
- *      (digits)  Mode  Cnt                Score   Error  Units
- * seq   10000000  avgt          2_858113121.500          ns/op
- * seq  646391315  avgt        284_707376315.000          ns/op
- *
- *        (digits)  Mode  Cnt     _        Score   Error  Units
- * par           1  avgt          _       28.920          ns/op
- * par          10  avgt          _       33.386          ns/op
- * par         100  avgt          _      239.757          ns/op
- * par        1000  avgt          _     5340.531          ns/op
- * par       10000  avgt          _   148642.089          ns/op
- * par      100000  avgt          _  4191720.295          ns/op
- * par     1000000  avgt          _ 55534506.779          ns/op
- * par    10000000  avgt         1_265775257.500          ns/op
- * par   100000000  avgt        11_097618757.000          ns/op
- * par   646456993  avgt       124_624773576.000          ns/op
- * par  1292782621  avgt       113_081690529.000          ns/op
- * seq           1  avgt          _       26.439          ns/op
- * seq          10  avgt          _       31.468          ns/op
- * seq         100  avgt          _      227.576          ns/op
- * seq        1000  avgt          _     4871.361          ns/op
- * seq       10000  avgt          _   164414.233          ns/op
- * seq      100000  avgt          _  5626324.087          ns/op
- * seq     1000000  avgt          _ 89353576.741          ns/op
- * seq    10000000  avgt         1_621865401.429          ns/op
- * seq   100000000  avgt        23_823624891.000          ns/op
- * seq   646456993  avgt       232_468403178.000          ns/op
- * seq  1292782621  avgt       222_404103626.000          ns/op
- *
- * Before we integrated FftMultiplier:
- *
- *      (digits)  Mode  Cnt            Score   Error  Units
- * par        24  avgt                77.542          ns/op
- * par         1  avgt                10.387          ns/op
- * par        10  avgt                16.126          ns/op
- * par       100  avgt               218.620          ns/op
- * par      1000  avgt              4804.543          ns/op
- * par     10000  avgt            117301.625          ns/op
- * par    100000  avgt           3805758.892          ns/op
- * par   1000000  avgt          55026280.527          ns/op
- * par  10000000  avgt       1_455180955.000          ns/op
- * par 100000000  avgt      33_793710446.000          ns/op
- * par 646391315  avgt     393_551821905.000          ns/op
- * seq        24  avgt                68.342          ns/op
- * seq         1  avgt                 7.347          ns/op
- * seq        10  avgt                14.433          ns/op
- * seq       100  avgt               217.995          ns/op
- * seq      1000  avgt              4784.030          ns/op
- * seq     10000  avgt            166472.648          ns/op
- * seq    100000  avgt           6472800.381          ns/op
- * seq   1000000  avgt         111609901.267          ns/op
- * seq  10000000  avgt       2_418388865.000          ns/op
- * seq 100000000  avgt      41_679193822.000          ns/op
- * seq 646391315  avgt     400_608301899.000          ns/op
- *
- * recursive only (recursion threshold=0)
- *      (digits)  Mode  Cnt      _        Score   Error  Units
- * rec        24  avgt           _      122.948          ns/op
- * rec         1  avgt           _        8.775          ns/op
- * rec        10  avgt           _       17.646          ns/op
- * rec       100  avgt           _      703.225          ns/op
- * rec      1000  avgt           _     8707.115          ns/op
- * rec     10000  avgt           _   229812.381          ns/op
- * rec    100000  avgt           _  7127677.071          ns/op
- * rec   1000000  avgt           _229351230.159          ns/op
- * rec  10000000  avgt          4_322373197.000          ns/op
- *
- * iterative only (recursion threshold=Integer.MAX_VALUE)
- *      (digits)  Mode  Cnt     _        Score   Error  Units
- * itr        24  avgt          _       85.368          ns/op
- * itr         1  avgt          _       11.053          ns/op
- * itr        10  avgt          _       18.350          ns/op
- * itr       100  avgt          _      233.284          ns/op
- * itr      1000  avgt          _     6581.352          ns/op
- * itr     10000  avgt          _   539732.484          ns/op
- * itr    100000  avgt          _ 51324255.979          ns/op
- * itr   1000000  avgt         5_049179432.000          ns/op
- * itr  10000000  avgt       537_316705670.000          ns/op
- * 
- * - *
- * # JMH version: 1.35
- * # VM version: OpenJDK 64-Bit Server VM, Oracle Corporation, 20-ea+22-1594
- * # Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz
- * parse only:
- *         (digits)  Mode  Cnt          Score   Error  Units
- * fprs           1  avgt    2         10.046          ns/op
- * fprs          10  avgt    2         14.924          ns/op
- * fprs         100  avgt    2         21.201          ns/op
- * fprs        1000  avgt    2         75.387          ns/op
- * fprs       10000  avgt    2        629.977          ns/op
- * fprs      100000  avgt    2       7197.921          ns/op
- * fprs     1000000  avgt    2      93134.505          ns/op
- * fprs    10000000  avgt    2    1324385.898          ns/op
- * fprs   100000000  avgt    2   13841659.306          ns/op
- * fprs  1000000000  avgt    2  139549257.979          ns/op
- * fprs  1292782621  avgt    2  183389538.331          ns/op
- * iprs           1  avgt    2          7.095          ns/op
- * iprs          10  avgt    2         12.980          ns/op
- * iprs         100  avgt    2         18.822          ns/op
- * iprs        1000  avgt    2         81.936          ns/op
- * iprs       10000  avgt    2        712.674          ns/op
- * iprs      100000  avgt    2       6844.904          ns/op
- * iprs     1000000  avgt    2      86105.059          ns/op
- * iprs    10000000  avgt    2    1241240.895          ns/op
- * iprs   100000000  avgt    2   12981130.860          ns/op
- * iprs  1000000000  avgt    2  133794721.567          ns/op
- * iprs  1292782621  avgt    2  170464284.271          ns/op
+ *      (digits)  Mode  Cnt              Score   Error  Units
+ * m    10000000  avgt         1_837172513.667          ns/op
+ * m   100000000  avgt        22_068128637.000          ns/op
+ * m   646456993  avgt       202_281998613.000          ns/op
+ * m  1292782621  avgt       212_085007186.000          ns/op
  * 
*/ @@ -165,9 +61,9 @@ public class JmhJavaBigDecimalFromByteArrayScalability { // , "100000" // , "1000000" "10000000" - // , "100000000" - // , "646456993" - // , "1292782621" + , "100000000" + , "646456993" + , "1292782621" }) public int digits; diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JsonDoubleParserTest.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JsonDoubleParserTest.java index 019168c2..ce23e8a5 100644 --- a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JsonDoubleParserTest.java +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JsonDoubleParserTest.java @@ -8,10 +8,13 @@ import org.junit.jupiter.api.TestFactory; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.Objects; import java.util.function.ToDoubleFunction; import java.util.stream.Stream; +import static ch.randelshofer.fastdoubleparser.VirtualCharSequence.toByteArray; +import static ch.randelshofer.fastdoubleparser.VirtualCharSequence.toCharArray; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.DynamicTest.dynamicTest; @@ -40,9 +43,15 @@ public Stream dynamicTests_parseDouble_CharSequence_int_int() { @TestFactory public Stream dynamicTests_parseDouble_CharSequence_int_int_longRunningTest() { - return createLongRunningTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JsonDoubleParser.parseDouble(u.input(), u.charOffset(), u.charLength())))); + ToDoubleFunction lambda = u -> JsonDoubleParser.parseDouble((u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } @TestFactory @@ -64,9 +73,15 @@ public Stream dynamicTests_parseDouble_ByteArray_int_int() { @TestFactory public Stream dynamicTests_parseDouble_ByteArray_int_int_longRunningTests() { - return createLongRunningTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JsonDoubleParser.parseDouble(u.input().toString().getBytes(StandardCharsets.UTF_8), u.byteOffset(), u.byteLength())))); + ToDoubleFunction lambda = u -> JsonDoubleParser.parseDouble(toByteArray(u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } @TestFactory @@ -88,11 +103,21 @@ public Stream dynamicTests_parseDouble_charArray_int_int() { @TestFactory public Stream dynamicTests_parseDouble_charArray_int_int_longRunningTests() { - return createLongRunningTestData().stream() - .map(t -> dynamicTest(t.title(), - () -> test(t, u -> JsonDoubleParser.parseDouble(u.input().toString().toCharArray(), u.charOffset(), u.charLength())))); + ToDoubleFunction lambda = u -> JsonDoubleParser.parseDouble(toCharArray(u.input()), u.charOffset(), u.charLength()); + return Stream.concat( + getSupplementalTestDataFiles() + .map(t -> dynamicTest(t.getFileName().toString(), + () -> testFile(t, lambda))), + createLongRunningTestData() + .map(t -> dynamicTest(t.title(), + () -> test(t, lambda))) + ); } + protected void testFile(Path path, ToDoubleFunction f) { + createSupplementalTestData(path, NumberType.FLOAT64) + .forEach(d -> test(d, f)); + } private void test(NumberTestData d, ToDoubleFunction f) { if (d.expectedErrorMessage() != null) { diff --git a/fastdoubleparser-java11/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java b/fastdoubleparser-java11/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java index 8d759cdf..cc6228cc 100644 --- a/fastdoubleparser-java11/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java +++ b/fastdoubleparser-java11/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java @@ -52,7 +52,7 @@ class FastDoubleSwar { protected static boolean isDigit(char c) { // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } /** @@ -65,7 +65,7 @@ protected static boolean isDigit(byte c) { // We check if '0' <= c && c <= '9'. // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } public static boolean isEightDigits(byte[] a, int offset) { diff --git a/fastdoubleparser-java17/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java b/fastdoubleparser-java17/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java index b76b10b0..bbfdcfef 100644 --- a/fastdoubleparser-java17/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java +++ b/fastdoubleparser-java17/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java @@ -52,7 +52,7 @@ class FastDoubleSwar { protected static boolean isDigit(char c) { // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } /** @@ -65,7 +65,7 @@ protected static boolean isDigit(byte c) { // We check if '0' <= c && c <= '9'. // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } public static boolean isEightDigits(byte[] a, int offset) { diff --git a/fastdoubleparser-java19/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java b/fastdoubleparser-java19/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java index 1fd7cf90..87ac2f77 100644 --- a/fastdoubleparser-java19/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java +++ b/fastdoubleparser-java19/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java @@ -52,7 +52,7 @@ class FastDoubleSwar { protected static boolean isDigit(char c) { // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } /** @@ -65,7 +65,7 @@ protected static boolean isDigit(byte c) { // We check if '0' <= c && c <= '9'. // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } public static boolean isEightDigits(byte[] a, int offset) { @@ -455,7 +455,7 @@ public static long tryToParseEightHexDigits(byte[] a, int offset) { * returns a negative value if the two longs do not contain 8 hex digits */ public static long tryToParseEightHexDigitsUtf16(long first, long second) { - long highBytes = (first | second) & 0xff00ff00_ff00ff00L; + long highBytes = (first | second) & 0xff80ff80_ff80ff80L; if (highBytes != 0L) { return -1L; } diff --git a/fastdoubleparser-java8/src/main/java/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java b/fastdoubleparser-java8/src/main/java/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java index 354fa02f..fb4ce628 100644 --- a/fastdoubleparser-java8/src/main/java/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java +++ b/fastdoubleparser-java8/src/main/java/ch/randelshofer/fastdoubleparser/FastDoubleSwar.java @@ -40,7 +40,7 @@ class FastDoubleSwar { protected static boolean isDigit(char c) { // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } /** @@ -53,7 +53,7 @@ protected static boolean isDigit(byte c) { // We check if '0' <= c && c <= '9'. // We take advantage of the fact that char is an unsigned numeric type: // subtracted values wrap around. - return (char) (c - '0') <= (char) ('9' - '0'); + return (char) (c - '0') < 10; } public static boolean isEightDigits(byte[] a, int offset) { diff --git a/pom.xml b/pom.xml index 5548f9af..c203718a 100644 --- a/pom.xml +++ b/pom.xml @@ -322,19 +322,19 @@ org.junit.jupiter junit-jupiter - 5.9.0 + 5.9.2 test org.openjdk.jmh jmh-core - 1.35 + 1.36 test org.openjdk.jmh jmh-generator-annprocess - 1.35 + 1.36 test