Stuffed-Na(a)N: stuff your NaNs

47 dgroshev 25 4/26/2025, 2:04:01 PM github.com ↗

Comments (25)

axblount · 2h ago
This is usually called NaN-boxing and is often used to implement dynamic languages.

https://piotrduperas.com/posts/nan-boxing

jasonthorsness · 54m ago
I wonder if IEEE-754 designers anticipated this use case during development. Great article - this kind of "outside-the-normal-use-case" requires a very careful reading of the specifications/guarantees of the language.
addoo · 1h ago
I appreciate this article a lot more because it contains an iota of benchmarking with an explanation about why this might be more performant. Especially since my first thought was 'wouldn't this require more instructions to be executed?'

The original post seems really weird to me. I would have dismissed it as someone's hobby project, but... that doesn't seem like what it's trying to be.

vitaut · 1h ago
dgroshev · 1h ago
I love it, how did I not think about this!
scorchingjello · 1h ago
Since the first time I had Indian food sometime in 1987 I have always called naan “not a number bread” and no one has ever laughed. I feel like i may have found my people.
dzaima · 1h ago
This doesn't work on Firefox, as it normalizes NaNs as they're extracted from ArrayBuffers. Presumably because SpiderMonkey uses NaN-boxing itself, and thus just doesn't have any way to represent actual non-canonical NaN floats.
moffkalast · 1h ago
Dammit Mozilla, first no WebGPU, now this?! /s
carterschonwald · 32m ago
The nan bits were intended to help provide more informative float error diagnostics at the language implementation level for non crashing code. I wonder if I’ll ever get to exploring it myself.
dunham · 2h ago
I learned about this when trying to decode data from Firefox IndexedDB. (I was extracting Tana data.) Their structured clone data format uses nan-boxing for serialization.
amelius · 1h ago
I'm curious why we have not-a-number, but not not-a-string, not-a-boolean, not-an-enum, and not-a-mycustomtype.
dzaima · 1h ago
Because NaNs come from a standardized hardware-supported type, whereas the rest of those are largely language-specific (and you could consider null/nil as a "not-a-*" type for those in applicable languages; and there are languages which disallow NaN floats too, which completes all combinations).

Itanium had a bit for "not a thing" for integers (and perhaps some older hardware from around the time floats started being a thing had similar things), so the idea of hardware support for not-a-* isn't exclusive to floats, but evidently this hasn't caught on; generally it's messy because it needs a bit pattern to yoink, but many types already use all possible ones (whereas floats already needed to chop out some for infinities).

Sharlin · 1h ago
You can encode not-a-bool, not-a-(utf-8)-string and not-an-enum using one of the invalid bit patterns – that's exactly what the Rust compiler can do with its "niche optimization": https://www.0xatticus.com/posts/understanding_rust_niche/
Sharlin · 1h ago
Because IEEE 754 creators wanted to signal non-trapping error conditions for mathematically undefined operations, and they had a plenty of bit patterns to spare. Apparently back in the 70s and 80s in many cases it was preferable for a computation to go through and produce NaNs rather than trapping instantly when executing an undefined operation. I'm not quite sure what the reasoning was exactly.
mathgradthrow · 52m ago
Nana basically means that floating point arithmetic is predicting that your mathematical expression is an "indeterminate form", as in the thing you learn in calculus.
usefulcat · 1h ago
C++ has std::optional for exactly that purpose.
ok_computer · 1h ago
Because representing infinity is not possible outside of symbolic logic and isn’t encodable in floats. I think it is a simple numerical reason and not a deeper computer reason.
Sharlin · 1h ago
Well, infinity is totally representable with IEEE 754 floats. For example 1.0/0.0 == +inf, -1.0/0.0 == -inf, but 0.0/0.0 == NaN.
amelius · 1h ago
A smart compiler should be able to figure out a better value for 0/0, depending on context.

For example:

    for i in range(0, 10):
        print(i/0.0)
In this case it should probably print +inf when i == 0.

But:

    for i in range(-10, 10):
        print(i/0.0)
Now it is not clear, but at least we know it's an infinity so perhaps we need a special value +-inf.

And:

    for i in range(-10, 10):
        print(i/i)
In this case, the value for 0/0 can be 1.
Sharlin · 1h ago
Well, it could, but that would be against the spec. The hardware implements IEEE 754, most languages guarantee IEEE 754, and transforming code so that 0.0/0.0 doesn't result in NaN would be invalid.
IncreasePosts · 1h ago
Some common numeric operations can result in non-numbers(eg division by zero - Nan or infinity).

Are there any common string operations with similar behavior?

AaronAPU · 1h ago
Reminds me of using the highest bit(s) of 64-bit ints to stuff auxiliary data into lockfree algorithms. So long as you’re aware of your OS environment you can enable some efficiencies you couldn’t otherwise.
eru · 1h ago
That's why OCaml has 63 bit integers by default: the language itself uses the last bit to help with GC.
haxiomic · 1h ago
That's curious! Does anyone know why the spec was designed to allow so many possible NaN bit patterns? Seems like just one would do!
dzaima · 1h ago
As long as you want all bit patterns to be Some float, there's not really one bit pattern you can chop out of somewhere (or, three, rather - both infinities, and NaN).

Taking, say, the 3 smallest subnormal numbers, or the three largest numbers, or whatever, would be extremely bad for allowing optimizations/algorithms/correctness checkers to reason about operations in the abstract.