Skip to content

doubleToLongBits can observe non-canonical NaN bit patterns #5208

@sjrd

Description

@sjrd

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).

Metadata

Metadata

Assignees

Labels

bugConfirmed bug. Needs to be fixed.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions