← 返回日报
略读 预计 2 分钟

two case studies of NaN

摘要

正文开头指出 IEEE-759 NaN 奇异导致编程语言设计隐含假设破裂。案例一 Python:列表相等优化优先 identity 比较,假定对象 x == x 成立(引用 Python 3 手册:x is y implies x == y),NaN 因未实现反射性但被忽略导致优化行为。案例二 Lua:数值 for 循环 init + step<=limit 实现不一致(首次用 <,后续 <=),NaN 步长被当作负数(0> step 恒假)且仅执行一次,参考实现显示 0 / 0 结果全 unintuitive,未文档化。结尾提及 Chris Siebenmann Go map keys 案例共三例,总结 NaN 奇异性使其他事物行为异常。

荐读理由

lua for 循环在 init/step/limit 遇到 NaN 时,puc-rio 实现会按 init < limit 仅跑一次,后续 idx <= limit 失效,导致结果只有1次或4次;可借此在 AI 工程项目里加 NaN 显式检查,避免死循环或错误结果

原文

two case studies of NaN

2026-07-09

IEEE-759 NaN is weird. and because of that, it's often accidentally left unaccounted for. i found two instances of this leaking into programming language design. that is, the semantics of these languages hold implicit assumptions which break with NaN.

case study 1: python

>>> from math import nan
>>> nan == nan
False
>>> [nan] == [nan]
True

as an optimization, when comparing lists for equality, elements are first compared by identity. they're only checked for equality if their identities are unequal.

in general, it's assumed that an object will always compare equal to itself. this is explicitly noted in the python 3 reference manual:

User-defined classes that customize their comparison behavior should follow some consistency rules, if possible:

  • Equality comparison should be reflexive. In other words, identical objects should compare equal:
  • x is y implies x == y

this isn't true for NaN. and that's fine, but because the assumption does hold for pretty much everything else, design choices are made which leave NaN unaccounted for (or at least, give NaN very weird behavior).

to be clear, i'm not necessarily saying that python's behavior here is wrong, or that this optimization is bad because NaN behaves weirdly. but i think it's at least worth pointing out.

case study 2: lua

numerical for-loops in lua look like this:

for i = 1, 10 do
    stuff()
end

the way this is supposed to work is that i starts with the initial value (here that's 1), and each iteration it increments by the step (optional third operand; defaults to 1), until the new value is greater than the limit (10), at which point the loop terminates. so this loop iterates 10 times, from 1 to 10 (inclusive).

so what happens when NaN (0/0) is thrown into the mix? here's the behavior in the reference implementation (puc-rio lua):

-- executes once
for i = 0/0, 10 do
    print(i) -- nan
end

-- executes once
for i = 0/0, 0/0 do
    print(i) -- nan
end

-- never executes
for i = 1, 10, 0/0 do
    print(i)
end

-- executes once
for i = 10, 1, 0/0 do
    print(i) -- 10.0
end

read through that code and try to figure out what's going on. every single one of those results is extremely unintuitive.

here's what's happening: the first two only execute once because the for-loop check is implemented as limit < init, but subsequent iterations check idx <= limit. so NaN passes the first test, but not the second test.

the latter two examples show that NaN is always treated as negative when used as the step, even if you do math.abs(0/0). this is because the check for whether the step is positive is 0 > step, which is of course always false for NaN.

none of this is documented btw, so this is likely a genuine oversight. but that's really interesting: using NaN causes the specific comparison operator used by the implementation to leak into the interpreter's behavior.

the simple solution here is to just check for and disallow NaN in numerical for-loops. lua already raises an error when 0 is used as the step, so it's interesting that there's no such check for NaN.

conclusion i guess

Chris Siebenmmann also previously wrote about the weird behavior of NaNs as map keys in go, so i guess that makes 3 case studies of NaN's weirdness.

NaN is weird. and that means other things behave weirdly with it.

Lobsters · 1 赞 · 0 评 讨论 → 阅读原文 →

这条对你有帮助吗?