Skip to content
Closed
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
69 changes: 68 additions & 1 deletion sjsonnet/src/sjsonnet/BaseCharRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,61 @@ class BaseCharRenderer[T <: upickle.core.CharOps.Output](
case d if java.lang.Double.isNaN(d) => visitNonNullString("NaN", -1)
case d =>
val i = d.toLong
if (d == i) visitFloat64StringParts(i.toString, -1, -1, index)
if (d == i) writeLongDirect(i)
else super.visitFloat64(d, index)
flushBuffer()
}
flushCharBuilder()
out
}

/**
* Write a long integer directly into elemBuilder without intermediate String allocation. Uses
* digit-pair table for fast two-digits-at-a-time conversion.
*/
private def writeLongDirect(v: Long): Unit = {
flushBuffer()
if (v == 0L) {
elemBuilder.ensureLength(1)
elemBuilder.appendUnsafe('0')
return
}
if (v == Long.MinValue) {
// -Long.MinValue overflows; handle specially
visitFloat64StringParts("-9223372036854775808", -1, -1, -1)
return
}
val negative = v < 0
var abs = if (negative) -v else v
// Count digits
var numDigits = 0
var tmp = abs
while (tmp > 0) { numDigits += 1; tmp /= 10 }
val totalLen = numDigits + (if (negative) 1 else 0)
elemBuilder.ensureLength(totalLen)
val cbArr = elemBuilder.arr
val startPos = elemBuilder.getLength
var writePos = startPos + totalLen - 1
// Write digits from right to left, two at a time
while (abs >= 100) {
val q = abs / 100
val r = (abs - q * 100L).toInt
abs = q
cbArr(writePos) = BaseCharRenderer.DIGIT_ONES(r)
cbArr(writePos - 1) = BaseCharRenderer.DIGIT_TENS(r)
writePos -= 2
}
if (abs >= 10) {
val r = abs.toInt
cbArr(writePos) = BaseCharRenderer.DIGIT_ONES(r)
cbArr(writePos - 1) = BaseCharRenderer.DIGIT_TENS(r)
} else {
cbArr(writePos) = ('0' + abs.toInt).toChar
}
if (negative) cbArr(startPos) = '-'
elemBuilder.length = startPos + totalLen
}

def visitString(s: CharSequence, index: Int): T = {

if (s eq null) visitNull(index)
Expand Down Expand Up @@ -226,3 +273,23 @@ class BaseCharRenderer[T <: upickle.core.CharOps.Output](
}
}
}

object BaseCharRenderer {

/**
* Digit-pair lookup tables for fast two-digit-at-a-time integer rendering. DIGIT_TENS(i) gives
* the tens digit for value i (0..99). DIGIT_ONES(i) gives the ones digit for value i (0..99).
*/
private[sjsonnet] val DIGIT_TENS: Array[Char] = {
val a = new Array[Char](100)
var i = 0
while (i < 100) { a(i) = ('0' + i / 10).toChar; i += 1 }
a
}
private[sjsonnet] val DIGIT_ONES: Array[Char] = {
val a = new Array[Char](100)
var i = 0
while (i < 100) { a(i) = ('0' + i % 10).toChar; i += 1 }
a
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Test large integer-valued doubles render correctly
// Covers integers beyond Int.MaxValue (2^31-1 = 2147483647)
local x = 300000000000;
local y = 1000000000000;
local z = 9007199254740992; // 2^53, max exact integer in double
std.assertEqual(std.toString(x), "300000000000") &&
std.assertEqual(std.toString(y), "1000000000000") &&
std.assertEqual(std.toString(z), "9007199254740992") &&
std.assertEqual(std.toString(-x), "-300000000000") &&
std.assertEqual(std.toString(-y), "-1000000000000") &&
std.assertEqual(std.toString(0), "0") &&
std.assertEqual(std.toString(2147483647), "2147483647") &&
std.assertEqual(std.toString(2147483648), "2147483648") &&
true
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true