The Secret Life of NaN (2018)

· coding · Source ↗

TLDR

  • IEEE 754 NaN carries up to 51 spare payload bits; dynamically typed runtimes exploit this to pack type tags and non-float values into a single 64-bit word.

Key Takeaways

  • Every double-precision NaN has 51 bits of usable payload; the standard explicitly encourages preserving it for “diagnostic information” propagation.
  • Quiet NaN propagates silently; signaling NaN fires an exception flag, making it a natural sentinel for uninitialized variables.
  • NaN-boxing encodes all JS values in 64 bits: pointers in bits 0-47, integers in FFFF:0000:IIII:IIII, doubles shifted by 2^48 to avoid the 0x0000/0xFFFF ranges.
  • JavaScriptCore (Safari/WebKit), SpiderMonkey (nun-boxing/pun-boxing), and LuaJIT (NaN-tagging) all ship production NaN-boxing; the JSC source comment is the clearest public documentation.
  • The scheme depends on x86-64 using only 48-bit virtual addresses; any future 64-bit full-address CPUs would break every NaN-boxing VM.

Hacker News Comment Review

  • Commenters agree NaN-boxing’s main runtime benefit is avoiding heap allocation for floats, which directly reduces GC pressure – a concrete engineering win, not just a curiosity.
  • The 48-bit pointer assumption surfaces as the key fragility: if hardware ever uses all 64 address bits, NaN-boxing VMs break silently without architectural mitigation.
  • There is light discussion on using signaling NaN to detect uninitialized variables at the language level, with the D language cited as a shipped example of float-defaults-to-NaN semantics.

Notable Comments

  • @WalterBright: D initializes floats to NaN by default, not 0.0 – catches uninitialized-variable bugs that 0.0 silently masks.
  • @GMoromisato: uses NaN-boxing in GridWhale; frames it as “Infinite Hotel” – you can always add a type tag, and float allocation savings at GC time are the practical payoff.

Original | Discuss on HN