Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More efficient decoding of BigInt and java.math.BigInteger values #1275

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ object SafeNumbers {
try LongSome(UnsafeNumbers.long(num))
catch { case _: UnexpectedEnd | UnsafeNumber => LongNone }

def bigInteger(
num: String,
max_bits: Int = 128
): Option[java.math.BigInteger] =
def bigInteger(num: String, max_bits: Int = 128): Option[java.math.BigInteger] =
try Some(UnsafeNumbers.bigInteger(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => None }

Expand All @@ -71,10 +68,7 @@ object SafeNumbers {
try DoubleSome(UnsafeNumbers.double(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => DoubleNone }

def bigDecimal(
num: String,
max_bits: Int = 128
): Option[java.math.BigDecimal] =
def bigDecimal(num: String, max_bits: Int = 128): Option[java.math.BigDecimal] =
try Some(UnsafeNumbers.bigDecimal(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => None }

Expand Down
38 changes: 24 additions & 14 deletions zio-json/js/src/main/scala/zio/json/internal/UnsafeNumbers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,38 @@ object UnsafeNumbers {
val negate = current == '-'
if (negate) current = in.readChar().toInt
if (current < '0' || current > '9') throw UnsafeNumber
var bigM10: java.math.BigInteger = null
var m10 = (current - '0').toLong
var loM10 = (current - '0').toLong
var loDigits = 1
var hiM10: java.math.BigDecimal = null
while ({
current = in.read()
'0' <= current && current <= '9'
}) {
if (m10 < 922337203685477580L) {
if (m10 <= 0) m10 = (current - '0').toLong
else m10 = (m10 << 3) + (m10 << 1) + (current - '0')
} else {
if (bigM10 eq null) bigM10 = java.math.BigInteger.valueOf(m10)
bigM10 = bigM10.multiply(java.math.BigInteger.TEN).add(bigIntegers(current - '0'))
if (bigM10.bitLength >= max_bits) throw UnsafeNumber
loM10 = (loM10 << 3) + (loM10 << 1) + (current - '0')
loDigits += 1
if (loM10 >= 100000000000000000L) {
if (negate) loM10 = -loM10
val bd = java.math.BigDecimal.valueOf(loM10)
if (hiM10 eq null) hiM10 = bd
else {
hiM10 = hiM10.scaleByPowerOfTen(loDigits).add(bd)
if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber
}
loM10 = 0
loDigits = 0
}
}
if (consume && current != -1) throw UnsafeNumber
if (bigM10 eq null) {
if (negate) m10 = -m10
return java.math.BigInteger.valueOf(m10)
if (hiM10 eq null) {
if (negate) loM10 = -loM10
return java.math.BigInteger.valueOf(loM10)
}
if (negate) bigM10 = bigM10.negate
bigM10
if (loDigits != 0) {
if (negate) loM10 = -loM10
hiM10 = hiM10.scaleByPowerOfTen(loDigits).add(java.math.BigDecimal.valueOf(loM10))
if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber
}
hiM10.unscaledValue
}

def bigDecimal(num: String, max_bits: Int): java.math.BigDecimal =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ object SafeNumbers {
try LongSome(UnsafeNumbers.long(num))
catch { case _: UnexpectedEnd | UnsafeNumber => LongNone }

def bigInteger(
num: String,
max_bits: Int = 128
): Option[java.math.BigInteger] =
def bigInteger(num: String, max_bits: Int = 128): Option[java.math.BigInteger] =
try Some(UnsafeNumbers.bigInteger(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => None }

Expand All @@ -71,10 +68,7 @@ object SafeNumbers {
try DoubleSome(UnsafeNumbers.double(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => DoubleNone }

def bigDecimal(
num: String,
max_bits: Int = 128
): Option[java.math.BigDecimal] =
def bigDecimal(num: String, max_bits: Int = 128): Option[java.math.BigDecimal] =
try Some(UnsafeNumbers.bigDecimal(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => None }

Expand Down
54 changes: 29 additions & 25 deletions zio-json/jvm/src/main/scala/zio/json/internal/UnsafeNumbers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,38 @@ object UnsafeNumbers {
val negate = current == '-'
if (negate) current = in.readChar().toInt
if (current < '0' || current > '9') throw UnsafeNumber
var bigM10: java.math.BigInteger = null
var m10 = (current - '0').toLong
var loM10 = (current - '0').toLong
var loDigits = 1
var hiM10: java.math.BigDecimal = null
while ({
current = in.read()
'0' <= current && current <= '9'
}) {
if (m10 < 922337203685477580L) {
if (m10 <= 0) m10 = (current - '0').toLong
else m10 = m10 * 10 + (current - '0')
} else {
if (bigM10 eq null) bigM10 = java.math.BigInteger.valueOf(m10)
bigM10 = bigM10.multiply(java.math.BigInteger.TEN).add(bigIntegers(current - '0'))
if (bigM10.bitLength >= max_bits) throw UnsafeNumber
loM10 = loM10 * 10 + (current - '0')
loDigits += 1
if (loM10 >= 100000000000000000L) {
if (negate) loM10 = -loM10
val bd = java.math.BigDecimal.valueOf(loM10)
if (hiM10 eq null) hiM10 = bd
else {
hiM10 = hiM10.scaleByPowerOfTen(loDigits).add(bd)
if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber
}
loM10 = 0
loDigits = 0
}
}
if (consume && current != -1) throw UnsafeNumber
if (bigM10 eq null) {
if (negate) m10 = -m10
return java.math.BigInteger.valueOf(m10)
if (hiM10 eq null) {
if (negate) loM10 = -loM10
return java.math.BigInteger.valueOf(loM10)
}
if (negate) bigM10 = bigM10.negate
bigM10
if (loDigits != 0) {
if (negate) loM10 = -loM10
hiM10 = hiM10.scaleByPowerOfTen(loDigits).add(java.math.BigDecimal.valueOf(loM10))
if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber
}
hiM10.unscaledValue
}

def bigDecimal(num: String, max_bits: Int): java.math.BigDecimal =
Expand Down Expand Up @@ -326,11 +336,8 @@ object UnsafeNumbers {
else if (e10 >= 39) Float.PositiveInfinity
else {
var shift = java.lang.Long.numberOfLeadingZeros(m10)
var m2 = unsignedMultiplyHigh(
pow10Mantissas(e10 + 343),
m10 << shift
) // FIXME: Use Math.unsignedMultiplyHigh after dropping of JDK 17 support
var e2 = (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1
var m2 = unsignedMultiplyHigh(pow10Mantissas(e10 + 343), m10 << shift)
var e2 = (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1
shift = java.lang.Long.numberOfLeadingZeros(m2)
m2 <<= shift
e2 -= shift
Expand Down Expand Up @@ -469,11 +476,8 @@ object UnsafeNumbers {
else if (e10 >= 310) Double.PositiveInfinity
else {
var shift = java.lang.Long.numberOfLeadingZeros(m10)
var m2 = unsignedMultiplyHigh(
pow10Mantissas(e10 + 343),
m10 << shift
) // FIXME: Use Math.unsignedMultiplyHigh after dropping of JDK 17 support
var e2 = (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1
var m2 = unsignedMultiplyHigh(pow10Mantissas(e10 + 343), m10 << shift)
var e2 = (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1
shift = java.lang.Long.numberOfLeadingZeros(m2)
m2 <<= shift
e2 -= shift
Expand Down Expand Up @@ -510,7 +514,7 @@ object UnsafeNumbers {
}

@inline private[this] def unsignedMultiplyHigh(x: Long, y: Long): Long =
Math.multiplyHigh(x, y) + x + y // Use implementation that works only when both params are negative
Math.multiplyHigh(x, y) + x + y // FIXME: Use Math.unsignedMultiplyHigh after dropping of JDK 17 support

private[this] final val bigIntegers: Array[java.math.BigInteger] =
(0L to 9L).map(java.math.BigInteger.valueOf).toArray
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ object SafeNumbers {
try LongSome(UnsafeNumbers.long(num))
catch { case _: UnexpectedEnd | UnsafeNumber => LongNone }

def bigInteger(
num: String,
max_bits: Int = 128
): Option[java.math.BigInteger] =
def bigInteger(num: String, max_bits: Int = 128): Option[java.math.BigInteger] =
try Some(UnsafeNumbers.bigInteger(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => None }

Expand All @@ -71,10 +68,7 @@ object SafeNumbers {
try DoubleSome(UnsafeNumbers.double(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => DoubleNone }

def bigDecimal(
num: String,
max_bits: Int = 128
): Option[java.math.BigDecimal] =
def bigDecimal(num: String, max_bits: Int = 128): Option[java.math.BigDecimal] =
try Some(UnsafeNumbers.bigDecimal(num, max_bits))
catch { case _: UnexpectedEnd | UnsafeNumber => None }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,38 @@ object UnsafeNumbers {
val negate = current == '-'
if (negate) current = in.readChar().toInt
if (current < '0' || current > '9') throw UnsafeNumber
var bigM10: java.math.BigInteger = null
var m10 = (current - '0').toLong
var loM10 = (current - '0').toLong
var loDigits = 1
var hiM10: java.math.BigDecimal = null
while ({
current = in.read()
'0' <= current && current <= '9'
}) {
if (m10 < 922337203685477580L) {
if (m10 <= 0) m10 = (current - '0').toLong
else m10 = m10 * 10 + (current - '0')
} else {
if (bigM10 eq null) bigM10 = java.math.BigInteger.valueOf(m10)
bigM10 = bigM10.multiply(java.math.BigInteger.TEN).add(bigIntegers(current - '0'))
if (bigM10.bitLength >= max_bits) throw UnsafeNumber
loM10 = loM10 * 10 + (current - '0')
loDigits += 1
if (loM10 >= 100000000000000000L) {
if (negate) loM10 = -loM10
val bd = java.math.BigDecimal.valueOf(loM10)
if (hiM10 eq null) hiM10 = bd
else {
hiM10 = hiM10.scaleByPowerOfTen(loDigits).add(bd)
if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber
}
loM10 = 0
loDigits = 0
}
}
if (consume && current != -1) throw UnsafeNumber
if (bigM10 eq null) {
if (negate) m10 = -m10
return java.math.BigInteger.valueOf(m10)
if (hiM10 eq null) {
if (negate) loM10 = -loM10
return java.math.BigInteger.valueOf(loM10)
}
if (negate) bigM10 = bigM10.negate
bigM10
if (loDigits != 0) {
if (negate) loM10 = -loM10
hiM10 = hiM10.scaleByPowerOfTen(loDigits).add(java.math.BigDecimal.valueOf(loM10))
if (hiM10.unscaledValue.bitLength >= max_bits) throw UnsafeNumber
}
hiM10.unscaledValue
}

def bigDecimal(num: String, max_bits: Int): java.math.BigDecimal =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ object JsonDecoder extends GeneratedTupleDecoders with DecoderLowPriority1 with
implicit val int: JsonDecoder[Int] = number(Lexer.int, _.intValueExact())
implicit val long: JsonDecoder[Long] = number(Lexer.long, _.longValueExact())
implicit val bigInteger: JsonDecoder[java.math.BigInteger] = number(Lexer.bigInteger, _.toBigIntegerExact)
implicit val scalaBigInt: JsonDecoder[BigInt] = bigInteger.map(x => x)
implicit val scalaBigInt: JsonDecoder[BigInt] = number(Lexer.bigInteger, _.toBigIntegerExact)
implicit val float: JsonDecoder[Float] = number(Lexer.float, _.floatValue())
implicit val double: JsonDecoder[Double] = number(Lexer.double, _.doubleValue())
implicit val bigDecimal: JsonDecoder[java.math.BigDecimal] = number(Lexer.bigDecimal, identity)
Expand Down
27 changes: 5 additions & 22 deletions zio-json/shared/src/main/scala/zio/json/internal/lexer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,13 @@ object Lexer {
// messages) by only checking for what we expect to see (Jon Pretty's idea).
//
// returns the index of the matched field, or -1
def field(
trace: List[JsonError],
in: OneCharReader,
matrix: StringMatrix
): Int = {
def field(trace: List[JsonError], in: OneCharReader, matrix: StringMatrix): Int = {
val f = enumeration(trace, in, matrix)
char(trace, in, ':')
f
}

def enumeration(
trace: List[JsonError],
in: OneCharReader,
matrix: StringMatrix
): Int = {
def enumeration(trace: List[JsonError], in: OneCharReader, matrix: StringMatrix): Int = {
var c = in.nextNonWhitespace()
if (c != '"') error("'\"'", c, trace)
var bs = matrix.initial
Expand Down Expand Up @@ -181,10 +173,7 @@ object Lexer {
}

// useful for embedded documents, e.g. CSV contained inside JSON
def streamingString(
trace: List[JsonError],
in: OneCharReader
): java.io.Reader = {
def streamingString(trace: List[JsonError], in: OneCharReader): java.io.Reader = {
char(trace, in, '"')
new OneCharReader {
def close(): Unit = in.close()
Expand Down Expand Up @@ -346,10 +335,7 @@ object Lexer {
case UnsafeNumbers.UnsafeNumber => error("expected a Long", trace)
}

def bigInteger(
trace: List[JsonError],
in: RetractReader
): java.math.BigInteger =
def bigInteger(trace: List[JsonError], in: RetractReader): java.math.BigInteger =
try {
val i = UnsafeNumbers.bigInteger_(in, false, NumberMaxBits)
in.retract()
Expand All @@ -376,10 +362,7 @@ object Lexer {
case UnsafeNumbers.UnsafeNumber => error("expected a Double", trace)
}

def bigDecimal(
trace: List[JsonError],
in: RetractReader
): java.math.BigDecimal =
def bigDecimal(trace: List[JsonError], in: RetractReader): java.math.BigDecimal =
try {
val i = UnsafeNumbers.bigDecimal_(in, false, NumberMaxBits)
in.retract()
Expand Down