Skip to content

Commit 944be5a

Browse files
committed
Branchless algorithm for RuntimeLong.toDouble.
It turns out the computation we did in the non-negative case also works for the negative case. The proof relies on elementary properties of the two's complement representation. I don't know how I never saw this before. To make things worse, it seems that Kotlin and J2CL knew all along, and I never realized when skimming through their implementations either.
1 parent c614c82 commit 944be5a

File tree

3 files changed

+40
-22
lines changed

3 files changed

+40
-22
lines changed

linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -695,17 +695,8 @@ object RuntimeLong {
695695
a.lo
696696

697697
@inline
698-
def toDouble(a: RuntimeLong): Double = {
699-
val lo = a.lo
700-
val hi = a.hi
701-
if (hi < 0) {
702-
// We need unsignedToDoubleApprox specifically for MinValue
703-
val neg = inline_negate(lo, hi)
704-
-unsignedToDoubleApprox(neg.lo, neg.hi)
705-
} else {
706-
nonNegativeToDoubleApprox(lo, hi)
707-
}
708-
}
698+
def toDouble(a: RuntimeLong): Double =
699+
signedToDoubleApprox(a.lo, a.hi)
709700

710701
@inline
711702
def toFloat(a: RuntimeLong): Float =
@@ -1197,7 +1188,7 @@ object RuntimeLong {
11971188

11981189
/** Converts an unsigned safe double into its Double representation. */
11991190
@inline def asUnsignedSafeDouble(lo: Int, hi: Int): Double =
1200-
nonNegativeToDoubleApprox(lo, hi)
1191+
signedToDoubleApprox(lo, hi) // can use either signed or unsigned here; signed folds better
12011192

12021193
/** Converts an unsigned safe double into its RuntimeLong representation. */
12031194
@inline def fromUnsignedSafeDouble(x: Double): RuntimeLong =
@@ -1215,14 +1206,41 @@ object RuntimeLong {
12151206
@inline def unsignedToDoubleApprox(lo: Int, hi: Int): Double =
12161207
uintToDouble(hi) * TwoPow32 + uintToDouble(lo)
12171208

1218-
/** Approximates a non-negative (lo, hi) with a Double.
1209+
/** Approximates a signed (lo, hi) with a Double.
12191210
*
12201211
* If `hi` is known to be non-negative, this method is equivalent to
12211212
* `unsignedToDoubleApprox`, but it can fold away part of the computation if
12221213
* `hi` is in fact constant.
12231214
*/
1224-
@inline def nonNegativeToDoubleApprox(lo: Int, hi: Int): Double =
1215+
@inline def signedToDoubleApprox(lo: Int, hi: Int): Double = {
1216+
/* We note a_u the mathematical value of a when interpreted as an unsigned
1217+
* quantity, and a_s when interpreted as a signed quantity.
1218+
*
1219+
* For x = (lo, hi), the result must be the correctly rounded value of x_s.
1220+
*
1221+
* If x_s >= 0, then hi_s >= 0. The obvious mathematical value of x_s is
1222+
* x_s = hi_s * 2^32 + lo_u
1223+
*
1224+
* If x_s < 0, then hi_s < 0. The fundamental definition of two's
1225+
* completement means that
1226+
* x_s = -2^64 + hi_u * 2^32 + lo_u
1227+
* Likewise,
1228+
* hi_s = -2^32 + hi_u
1229+
*
1230+
* Now take the computation for the x_s >= 0 case, but substituting values
1231+
* for the negative case:
1232+
* hi_s * 2^32 + lo_u
1233+
* = (-2^32 + hi_u) * 2^32 + lo_u
1234+
* = (-2^64 + hi_u * 2^32) + lo_u
1235+
* which is the correct mathematical result for x_s in the negative case.
1236+
*
1237+
* Therefore, we can always compute
1238+
* x_s = hi_s * 2^32 + lo_u
1239+
* When computed with `Double` values, only the last `+` can be inexact,
1240+
* hence the result is correctly round.
1241+
*/
12251242
hi.toDouble * TwoPow32 + uintToDouble(lo)
1243+
}
12261244

12271245
/** Interprets an `Int` as an unsigned integer and returns its value as a
12281246
* `Double`.

linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ class LibrarySizeTest {
7070
)
7171

7272
testLinkedSizes(
73-
expectedFastLinkSize = 148481,
74-
expectedFullLinkSizeWithoutClosure = 87816,
75-
expectedFullLinkSizeWithClosure = 20704,
73+
expectedFastLinkSize = 147779,
74+
expectedFullLinkSizeWithoutClosure = 87411,
75+
expectedFullLinkSizeWithClosure = 20696,
7676
classDefs,
7777
moduleInitializers = MainTestModuleInitializers
7878
)

project/Build.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,16 +2053,16 @@ object Build {
20532053
case `default212Version` =>
20542054
if (!useMinifySizes) {
20552055
Some(ExpectedSizes(
2056-
fastLink = 625000 to 626000,
2056+
fastLink = 624000 to 625000,
20572057
fullLink = 94000 to 95000,
20582058
fastLinkGz = 75000 to 79000,
20592059
fullLinkGz = 24000 to 25000,
20602060
))
20612061
} else {
20622062
Some(ExpectedSizes(
2063-
fastLink = 426000 to 427000,
2064-
fullLink = 283000 to 284000,
2065-
fastLinkGz = 61000 to 62000,
2063+
fastLink = 425000 to 426000,
2064+
fullLink = 282000 to 283000,
2065+
fastLinkGz = 60000 to 61000,
20662066
fullLinkGz = 43000 to 44000,
20672067
))
20682068
}
@@ -2078,7 +2078,7 @@ object Build {
20782078
} else {
20792079
Some(ExpectedSizes(
20802080
fastLink = 301000 to 302000,
2081-
fullLink = 259000 to 260000,
2081+
fullLink = 258000 to 259000,
20822082
fastLinkGz = 47000 to 48000,
20832083
fullLinkGz = 42000 to 43000,
20842084
))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy