-
Notifications
You must be signed in to change notification settings - Fork 397
Description
So ... it turns out JS engines don't actually canonicalize NaN's. I manage to observe a non-canonical NaN bit pattern on V8 with
object HelloWorld {
def main(args: Array[String]): Unit = {
val nan = bitsToDouble(0x7ff000fabcd12345L)
println(nan)
val bits = java.lang.Double.doubleToLongBits(nan)
println(bits.toHexString)
}
@noinline
def bitsToDouble(bits: Long): Double =
java.lang.Double.longBitsToDouble(bits)
}
which printed back
NaN
7ff000fabcd12345
This particular program happens to return a canonical NaN bit pattern on Firefox. However, a Firefox issue shows another scenario for which bit patterns are preserved.
They say that reading a NaN from a buffer is supposed to canonicalize, but that they "don't think anyone is doing this right now". The spec isn't super clear about it either.
For all intents and purposes, I believe we cannot assume normalization.
For us, I think it means we should, after all, expose doubleToRawLongBits
and floatToRawIntBits
on JS (and therefore also on Wasm). Given that we haven't shipped the new opcodes for bit manipulation of floating point numbers, I suggest we reassign the opcodes to mean the raw variant, and build the canonicalizing variant in user-space on top of it. In Wasm, the canonicalization anyway has to be done in user space.
Note that the Javadoc of doubleToRawLongBits
warns that not all NaN
bit patterns are preserved, and that it is machine-dependent. It's probably fine, therefore, to sometimes normalize (like Firefox does in the above scenario).