Releases: nats-io/jparse
jparse v1.2.3
Cleaned up JavaDocs, and got rid of commented-out code. Improved test code coverage.
jparse v1.2.2
Updated JavaDocs and improved code coverage.
jparse v1.2.0
Release Notes for jparse v1.2.0
Improve floating point parsing speed and make it easier to port to other languages.
New Features and Improvements
- Added support for double precision parsing with the new
ParseDouble
class.
This class provides aparseDouble
method for parsing double-precision numbers
from character arrays, increasing the range of supported numerical values and
precision. (Also added support forParseFloat
. - Extended the lookup table for the exponent calculations in both
ParseFloat
and
ParseDouble
classes. This enhancement ensures more efficient computation of
larger exponents, improving the overall performance of the parsing methods. - Expanded test coverage by adding a JUnit test class for
ParseDouble
.
This test suite includes tests for valid and invalid
inputs that should throw exceptions. The increased test coverage helps ensure
the correctness and reliability of the newParseDouble
class. - General code cleanup, refactoring, and comments have been updated for better
readability and maintainability. The new code structure should make it easier
for developers to understand the logic and contribute to the project.
Bug Fixes
- No bug fixes in this release.
Upgrade Notes
- To start using the new
ParseDouble
class for double-precision parsing, simply
update your project dependencies to jparse v1.2.0.
Thank you for using jparse, and we hope you find the new features and improvements
helpful. As always, your feedback and contributions are highly appreciated.
General flow of ParseFloat and ParseDouble utility classes.
The ParseFloat
class in the io.nats.jparse.source.support
package is designed
to parse float numbers from a character array.
-
A static lookup table called
powersOf10
is defined, containing powers of 10
from 10^0 to 10^18 as float values. -
The
parseFloat
method is the main entry point of the class. It takes three
arguments: a character arraychars
, astartIndex
, and anendIndex
. The
method parses the float number represented by the characters between
startIndex
andendIndex
. -
Inside the
parseFloat
method, anegative
flag and an accumulator variable
result
are initialized. Thenegative
flag is set totrue
if the first
character is a minus sign. -
The main loop iterates through the characters in the given range, identifying
digits, decimal points, and exponent indicators.- For digits, the character is converted to its numerical value and accumulated
in theresult
variable. - If a decimal point is encountered, the
parseFractionPart
method is called
to handle the fractional part. - If an exponent indicator 'e' is encountered, the
parseExponent
method is
called to handle the exponent part.
- For digits, the character is converted to its numerical value and accumulated
-
The
parseFractionPart
method processes the fractional part of the float
number. It keeps track of the current decimal position using thefraction
variable and updates theresult
variable accordingly. -
The
parseExponent
method handles the exponent part of the float number. It
determines the sign of the exponent and accumulates the exponent value. Then,
it uses thepowersOf10
lookup table to multiply or divide theresult
variable by the appropriate power of 10. -
Finally, if the
negative
flag was set, theresult
variable is negated, and
the parsed float value is returned.
Throughout the code, if an unexpected character is encountered, an
UnexpectedCharacterException
is thrown with a message indicating the character's index
and value.
Benchmark improvements
After improvements
Benchmark Mode Cnt Score Error Units
BenchMark.jParseFastDoubleArray thrpt 2 1326409.042 ops/s
BenchMark.jParseFastFloatArray thrpt 2 1314308.391 ops/s
1326409.042
769132.236
1314308.391
717548.249
ParseFloat
package io.nats.jparse.source.support;
public class ParseFloat {
static final float[] powersOf10 = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f, 1e7f,
1e8f, 1e9f, 1e10f, 1e11f, 1e12f, 1e13f, 1e14f, 1e15f, 1e16f, 1e17f, 1e18f};
public static float parseFloat(char[] chars, int startIndex, int endIndex) {
boolean negative = false;
int i = startIndex;
float result = 0;
// Check for a negative sign
if (chars[i] == '-') {
negative = true;
i++;
}
loop:
while (i < endIndex) {
char ch = chars[i];
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
result = result * 10 + (ch - '0');
i++;
break;
case '.':
result = parseFractionPart(i + 1, endIndex, chars, result);
break loop;
case 'e':
result = parseExponent(i+1, endIndex, chars, result);
break loop;
default:
throw new UnexpectedCharacterException("parsing float", "Illegal character", ch, i);
}
}
if (negative) {
result = -result;
}
return result;
}
private static float parseFractionPart(int i, int endIndex, char[] chars, float result) {
float fraction = 0.1f;
while (i < endIndex) {
char ch = chars[i];
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
result += (ch - '0') * fraction;
fraction /= 10;
i++;
break;
case 'e':
return parseExponent(i + 1, endIndex, chars, result);
default:
throw new UnexpectedCharacterException("float parsing fraction part", "Illegal character", ch, i);
}
}
return result;
}
private static float parseExponent(int i, int endIndex, char[] chars, float result) {
boolean exponentNegative = false;
int exponent = 0;
char sign = chars[i];
switch (sign) {
case '-':
exponentNegative = true;
i++;
break;
case '+':
i++;
break;
}
while (i < endIndex) {
char ch = chars[i];
switch (chars[i]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
exponent = exponent * 10 + (ch - '0');
i++;
break;
default:
throw new UnexpectedCharacterException("float parsing exponent part", "Illegal character", ch, i);
}
}
if (exponentNegative) {
exponent = -exponent;
}
// Use Lookup table for powers of 10
// Calculate the power of 10
if (!exponentNegative) {
while (exponent >= powersOf10.length) {
result *= 1e18f;
exponent -= 18;
}
result *= powersOf10[exponent];
} else {
while (-exponent >= powersOf10.length) {
result /= 1e18f;
exponent += 18;
}
result /= powersOf10[-exponent];
}
return result;
}
}
ParseDouble
The parse double is nearly the same.
package io.nats.jparse.source.support;
public class ParseDouble {
static final double[] powersOf10 = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
public static double parseDouble(char[] chars, int startIndex, int endIndex) {
boolean negative = false;
int i = startIndex;
double result = 0;
// Check for a negative sign
if (chars[i] == '-') {
negative = true;
i++;
}
loop:
while (i < endIndex) {
char ch = chars[i];
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
result = result * 10 + (ch - '0');
i++;
break;
case '.':
result = parseFractionPart(i + 1, endIndex, chars, result);
break loop;
case 'E':
case 'e':
result = parseExponent(i + 1, endIndex, chars, result);
break loop;
default:
throw new UnexpectedCharacterException("parsing double", "Illegal character", ch, i);
}
}
if (negative) {
...
v1.1.3
v1.1.3
v1.1.2 bug fix
Small bug fix for adding an array or object to the list of tokens. Internal.
Added build.
v1.1.0
Added support for JDK 1.8.
v1.0.2
v1.0.2
Moved back to gradle 7.5, which Vertx Nats interface uses.
There were error messages with gradle 8 and no clear path forward.
v1.0.1
v1.0.1
v1.0.0.1
Version 1
- Added benchmarks where this parser is faster than mainstream parsers
- Added a strict mode where this JSON parser is RFC-8259 compliant
- Added an event-driven mode for streaming parsers.
- There is a fast and strict mode for both index overlay and event-driven parser.
- Added support for comments (not turned on by default) and keys without quotes (not turned on by default).