diff --git a/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala b/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala index 9e76042e..761310ed 100644 --- a/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala +++ b/zio-json/js/src/main/scala/zio/json/internal/SafeNumbers.scala @@ -78,14 +78,20 @@ object SafeNumbers { try Some(UnsafeNumbers.bigDecimal(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } + def toString(x: java.math.BigInteger): String = { + val out = writes.get + write(x, out) + out.buffer.toString + } + def toString(x: Double): String = { - val out = new FastStringWrite(24) + val out = writes.get write(x, out) out.buffer.toString } def toString(x: Float): String = { - val out = new FastStringWrite(16) + val out = writes.get write(x, out) out.buffer.toString } @@ -96,6 +102,59 @@ object SafeNumbers { out.buffer.toString } + def write(x: java.math.BigInteger, out: Write): Unit = writeBigInteger(x, null, out) + + private[this] def writeBigInteger(x: java.math.BigInteger, ss: Array[java.math.BigInteger], out: Write): Unit = { + val bitLen = x.bitLength + if (bitLen < 64) write(x.longValue, out) + else { + val n = calculateTenPow18SquareNumber(bitLen) + val ss1 = + if (ss eq null) getTenPow18Squares(n) + else ss + val qr = x.divideAndRemainder(ss1(n)) + writeBigInteger(qr(0), ss1, out) + writeBigIntegerRemainder(qr(1), n - 1, ss1, out) + } + } + + private[this] def writeBigIntegerRemainder( + x: java.math.BigInteger, + n: Int, + ss: Array[java.math.BigInteger], + out: Write + ): Unit = + if (n < 0) write18Digits(Math.abs(x.longValue), out) + else { + val qr = x.divideAndRemainder(ss(n)) + writeBigIntegerRemainder(qr(0), n - 1, ss, out) + writeBigIntegerRemainder(qr(1), n - 1, ss, out) + } + + private[this] def calculateTenPow18SquareNumber(bitLen: Int): Int = { + val m = Math.max( + (bitLen * 0.016723888647998956).toInt - 1, + 1 + ) // Math.max((x.bitLength * Math.log(2) / Math.log(1e18)).toInt - 1, 1) + 31 - java.lang.Integer.numberOfLeadingZeros(m) + } + + private[this] def getTenPow18Squares(n: Int): Array[java.math.BigInteger] = { + var ss = tenPow18Squares + var i = ss.length + if (n >= i) { + var s = ss(i - 1) + ss = java.util.Arrays.copyOf(ss, n + 1) + while (i <= n) { + s = s.multiply(s) + ss(i) = s + i += 1 + } + tenPow18Squares = ss + } + ss + } + // Based on the amazing work of Raffaello Giulietti // "The Schubfach way to render doubles": https://drive.google.com/file/d/1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN/view // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/DoubleToDecimal.java @@ -343,16 +402,6 @@ object SafeNumbers { write(stripTrailingZeros(x), out) } - private[this] val writes = new ThreadLocal[FastStringWrite] { - override def initialValue(): FastStringWrite = new FastStringWrite(24) - - override def get: FastStringWrite = { - val w = super.get - w.reset() - w - } - } - @inline private[this] def rop(g1: Long, g0: Long, cp: Long): Long = { val x = multiplyHigh(g0, cp) + (g1 * cp >>> 1) var y = multiplyHigh(g1, cp) @@ -578,6 +627,14 @@ object SafeNumbers { } } + @inline private[this] def write18Digits(x: Long, out: Write): Unit = { + val q1 = ((x >>> 8) * 2.56e-6).toLong // divide a medium positive long by 100000000 + val q2 = (q1 >>> 8) * 1441151881L >>> 49 // divide a small positive long by 100000000 + out.write(digits(q2.toInt)) + write8Digits((q1 - q2 * 100000000L).toInt, out) + write8Digits((x - q1 * 100000000L).toInt, out) + } + private[this] def write8Digits(x: Int, out: Write): Unit = { val ds = digits val q1 = x / 10000 @@ -603,24 +660,6 @@ object SafeNumbers { @inline private[json] def write2Digits(x: Int, out: Write): Unit = out.write(digits(x)) - private[this] final val pow10ints: Array[Int] = - Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) - - private[this] final val pow10longs: Array[Long] = - Array(1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, - 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, - 100000000000000000L) - - private[this] final val digits: Array[Short] = Array( - 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 12337, 12593, 12849, 13105, 13361, 13617, - 13873, 14129, 14385, 14641, 12338, 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 12339, 12595, - 12851, 13107, 13363, 13619, 13875, 14131, 14387, 14643, 12340, 12596, 12852, 13108, 13364, 13620, 13876, 14132, - 14388, 14644, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, 14389, 14645, 12342, 12598, 12854, 13110, - 13366, 13622, 13878, 14134, 14390, 14646, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, 14647, - 12344, 12600, 12856, 13112, 13368, 13624, 13880, 14136, 14392, 14648, 12345, 12601, 12857, 13113, 13369, 13625, - 13881, 14137, 14393, 14649 - ) - @inline private[this] def digitCount(x: Long): Int = if (x >= 1000000000000000L) { @@ -655,6 +694,16 @@ object SafeNumbers { else 10 } + private[this] final val digits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 12337, 12593, 12849, 13105, 13361, 13617, + 13873, 14129, 14385, 14641, 12338, 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 12339, 12595, + 12851, 13107, 13363, 13619, 13875, 14131, 14387, 14643, 12340, 12596, 12852, 13108, 13364, 13620, 13876, 14132, + 14388, 14644, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, 14389, 14645, 12342, 12598, 12854, 13110, + 13366, 13622, 13878, 14134, 14390, 14646, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, 14647, + 12344, 12600, 12856, 13112, 13368, 13624, 13880, 14136, 14392, 14648, 12345, 12601, 12857, 13113, 13369, 13625, + 13881, 14137, 14393, 14649 + ) + private[this] final val lowerCaseHexDigits: Array[Short] = Array( 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 24880, 25136, 25392, 25648, 25904, 26160, 12337, 12593, 12849, 13105, 13361, 13617, 13873, 14129, 14385, 14641, 24881, 25137, 25393, 25649, 25905, 26161, @@ -919,4 +968,25 @@ object SafeNumbers { 8988465674311579538L, 5963149404718312264L, 7190772539449263630L, 8459868338516560134L, 5752618031559410904L, 6767894670813248108L, 9204188850495057447L, 5294608251188331487L ) + + private[this] final val pow10ints: Array[Int] = + Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) + + private[this] final val pow10longs: Array[Long] = + Array(1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, + 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, + 100000000000000000L) + + @volatile private[this] var tenPow18Squares: Array[java.math.BigInteger] = + Array(java.math.BigInteger.valueOf(1000000000000000000L)) + + private[this] val writes = new ThreadLocal[FastStringWrite] { + override def initialValue(): FastStringWrite = new FastStringWrite(64) + + override def get: FastStringWrite = { + val w = super.get + w.reset() + w + } + } } diff --git a/zio-json/jvm-native/src/main/scala/zio/json/internal/SafeNumbers.scala b/zio-json/jvm-native/src/main/scala/zio/json/internal/SafeNumbers.scala index a1a3dad4..9dc3b3ea 100644 --- a/zio-json/jvm-native/src/main/scala/zio/json/internal/SafeNumbers.scala +++ b/zio-json/jvm-native/src/main/scala/zio/json/internal/SafeNumbers.scala @@ -78,14 +78,20 @@ object SafeNumbers { try Some(UnsafeNumbers.bigDecimal(num, max_bits)) catch { case _: UnexpectedEnd | UnsafeNumber => None } + def toString(x: java.math.BigInteger): String = { + val out = writes.get + write(x, out) + out.buffer.toString + } + def toString(x: Double): String = { - val out = new FastStringWrite(24) + val out = writes.get write(x, out) out.buffer.toString } def toString(x: Float): String = { - val out = new FastStringWrite(16) + val out = writes.get write(x, out) out.buffer.toString } @@ -96,6 +102,59 @@ object SafeNumbers { out.buffer.toString } + def write(x: java.math.BigInteger, out: Write): Unit = writeBigInteger(x, null, out) + + private[this] def writeBigInteger(x: java.math.BigInteger, ss: Array[java.math.BigInteger], out: Write): Unit = { + val bitLen = x.bitLength + if (bitLen < 64) write(x.longValue, out) + else { + val n = calculateTenPow18SquareNumber(bitLen) + val ss1 = + if (ss eq null) getTenPow18Squares(n) + else ss + val qr = x.divideAndRemainder(ss1(n)) + writeBigInteger(qr(0), ss1, out) + writeBigIntegerRemainder(qr(1), n - 1, ss1, out) + } + } + + private[this] def writeBigIntegerRemainder( + x: java.math.BigInteger, + n: Int, + ss: Array[java.math.BigInteger], + out: Write + ): Unit = + if (n < 0) write18Digits(Math.abs(x.longValue), out) + else { + val qr = x.divideAndRemainder(ss(n)) + writeBigIntegerRemainder(qr(0), n - 1, ss, out) + writeBigIntegerRemainder(qr(1), n - 1, ss, out) + } + + private[this] def calculateTenPow18SquareNumber(bitLen: Int): Int = { + val m = Math.max( + (bitLen * 71828554L >> 32).toInt - 1, + 1 + ) // Math.max((x.bitLength * Math.log(2) / Math.log(1e18)).toInt - 1, 1) + 31 - java.lang.Integer.numberOfLeadingZeros(m) + } + + private[this] def getTenPow18Squares(n: Int): Array[java.math.BigInteger] = { + var ss = tenPow18Squares + var i = ss.length + if (n >= i) { + var s = ss(i - 1) + ss = java.util.Arrays.copyOf(ss, n + 1) + while (i <= n) { + s = s.multiply(s) + ss(i) = s + i += 1 + } + tenPow18Squares = ss + } + ss + } + // Based on the amazing work of Raffaello Giulietti // "The Schubfach way to render doubles": https://drive.google.com/file/d/1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN/view // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/DoubleToDecimal.java @@ -334,16 +393,6 @@ object SafeNumbers { write(stripTrailingZeros(x), out) } - private[this] val writes = new ThreadLocal[FastStringWrite] { - override def initialValue(): FastStringWrite = new FastStringWrite(24) - - override def get: FastStringWrite = { - val w = super.get - w.reset() - w - } - } - private[this] def rop(g1: Long, g0: Long, cp: Long): Long = { val x = Math.multiplyHigh(g0, cp) + (g1 * cp >>> 1) Math.multiplyHigh(g1, cp) + (x >>> 63) | (-x ^ x) >>> 63 @@ -400,9 +449,9 @@ object SafeNumbers { else { val q2 = Math.multiplyHigh(q1, m2) >>> 25 // divide a small positive long by 100000000 writeMantissa(q2.toInt, out) - write8Digits((q1 - q2 * m1).toInt, out) + write8Digits(q1 - q2 * m1, out) } - write8Digits((q0 - q1 * m1).toInt, out) + write8Digits(q0 - q1 * m1, out) } } @@ -411,7 +460,7 @@ object SafeNumbers { else { val q1 = Math.multiplyHigh(q0, 6189700196426901375L) >>> 25 // divide a positive long by 100000000 writeMantissa(q1.toInt, out) - write8Digits((q0 - q1 * 100000000L).toInt, out) + write8Digits(q0 - q1 * 100000000L, out) } private[this] def writeMantissaWithDot(q0: Long, out: Write): Unit = @@ -419,7 +468,7 @@ object SafeNumbers { else { val q1 = Math.multiplyHigh(q0, 6189700196426901375L) >>> 25 // divide a positive long by 100000000 writeMantissaWithDot(q1.toInt, out) - write8Digits((q0 - q1 * 100000000L).toInt, out) + write8Digits(q0 - q1 * 100000000L, out) } def write(a: Int, out: Write): Unit = { @@ -526,7 +575,16 @@ object SafeNumbers { } } - private[this] def write8Digits(x: Int, out: Write): Unit = { + private[this] def write18Digits(x: Long, out: Write): Unit = { + val m1 = 6189700196426901375L + val q1 = Math.multiplyHigh(x, m1) >>> 25 // divide a positive long by 100000000 + val q2 = Math.multiplyHigh(q1, m1) >>> 25 // divide a positive long by 100000000 + out.write(digits(q2.toInt)) + write8Digits(q1 - q2 * 100000000L, out) + write8Digits(x - q1 * 100000000L, out) + } + + private[this] def write8Digits(x: Long, out: Write): Unit = { val ds = digits // Based on James Anhalt's algorithm: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ val y1 = x * 140737489L val m1 = 0x7fffffffffffL @@ -552,13 +610,9 @@ object SafeNumbers { @inline private[json] def write2Digits(x: Int, out: Write): Unit = out.write(digits(x)) - private[this] final val pow10ints: Array[Int] = - Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) - - private[this] final val pow10longs: Array[Long] = - Array(1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, - 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, - 100000000000000000L) + // Adoption of a nice trick form Daniel Lemire's blog that works for numbers up to 10^18: + // https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/ + private[this] def digitCount(x: Long): Int = (offsets(java.lang.Long.numberOfLeadingZeros(x)) + x >> 58).toInt private[this] final val digits: Array[Short] = Array( 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 12337, 12593, 12849, 13105, 13361, 13617, @@ -570,10 +624,6 @@ object SafeNumbers { 13881, 14137, 14393, 14649 ) - // Adoption of a nice trick form Daniel Lemire's blog that works for numbers up to 10^18: - // https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/ - private[this] def digitCount(x: Long): Int = (offsets(java.lang.Long.numberOfLeadingZeros(x)) + x >> 58).toInt - private[this] val offsets = Array( 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, 4889916394579099648L, 4889916394579099648L, @@ -854,4 +904,25 @@ object SafeNumbers { 8988465674311579538L, 5963149404718312264L, 7190772539449263630L, 8459868338516560134L, 5752618031559410904L, 6767894670813248108L, 9204188850495057447L, 5294608251188331487L ) + + private[this] final val pow10ints: Array[Int] = + Array(1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000) + + private[this] final val pow10longs: Array[Long] = + Array(1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L, 10000000000L, + 100000000000L, 1000000000000L, 10000000000000L, 100000000000000L, 1000000000000000L, 10000000000000000L, + 100000000000000000L) + + @volatile private[this] var tenPow18Squares: Array[java.math.BigInteger] = + Array(java.math.BigInteger.valueOf(1000000000000000000L)) + + private[this] val writes = new ThreadLocal[FastStringWrite] { + override def initialValue(): FastStringWrite = new FastStringWrite(64) + + override def get: FastStringWrite = { + val w = super.get + w.reset() + w + } + } } diff --git a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala index 6b5045a1..d41c38d0 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala @@ -232,8 +232,18 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with override def toJsonAST(a: Long): Either[String, Json] = new Right(Json.Num(a)) } - implicit val bigInteger: JsonEncoder[java.math.BigInteger] = explicit(_.toString, Json.Num.apply) - implicit val scalaBigInt: JsonEncoder[BigInt] = explicit(_.toString, Json.Num.apply) + implicit val bigInteger: JsonEncoder[java.math.BigInteger] = new JsonEncoder[java.math.BigInteger] { + def unsafeEncode(a: java.math.BigInteger, indent: Option[Int], out: Write): Unit = SafeNumbers.write(a, out) + + override def toJsonAST(a: java.math.BigInteger): Either[String, Json] = new Right(Json.Num(a)) + } + implicit val scalaBigInt: JsonEncoder[BigInt] = new JsonEncoder[BigInt] { + def unsafeEncode(a: BigInt, indent: Option[Int], out: Write): Unit = + if (a.isValidLong) SafeNumbers.write(a.longValue, out) + else SafeNumbers.write(a.bigInteger, out) + + override def toJsonAST(a: BigInt): Either[String, Json] = new Right(Json.Num(a)) + } implicit val double: JsonEncoder[Double] = new JsonEncoder[Double] { def unsafeEncode(a: Double, indent: Option[Int], out: Write): Unit = SafeNumbers.write(a, out)