NaN的两个案例研究
IEEE-754 NaN是奇怪的。因此,它经常被意外忽视。我发现有两个实例涉及编程语言设计,这导致NaN泄漏。也就是说,这些语言的语义持有隐含的假设,这在NaN的情况下会破裂。案例研究1:python >>> from math import nan >>> nan == nan False >>> [nan] == [nan] True作为一种优化,当比较列表的相等性时,元素首先按身份进行比较。只有在身份不相等的情况下,才会检查相等性。一般来说,假设一个对象总是与自身相等。这在Python 3参考手册中明确提到:自定义比较行为的用户定义类应尽可能遵循某些一致性规则:相等比较应是自反的。换句话说,相同的对象应相等比较:x is y 暗示 x == y 但这对于NaN并不成立。这没问题,但由于这个假设对几乎所有其他东西都成立,因此做出的设计选择使得NaN未被考虑在内(或者至少,让NaN的行为变得非常奇怪)。明确来说,我并不是在说Python在这里的行为是错误的,或这种优化是坏的,因为NaN行为奇怪,但我认为至少值得指出。案例研究2:Lua 中的数值for循环看起来像这样:for i = 1, 10 do stuff() end 这种工作的方式是,i以初始值开始(这里是1),每次迭代将其增加步长(可选的第三个操作数;默认为1),直到新值大于限制(10),此时循环终止。因此该循环迭代了10次,从1到10(包含)。那么当NaN(0/0)被引入时会发生什么?这是参考实现(puc-rio lua)中的行为:-- 对于 i = 0/0, 10 执行一次 do print(i) -- nan end -- 对于 i = 0/0, 0/0 执行一次 do print(i) -- nan end -- 对于 i = 1, 10, 0/0 从不执行 do print(i) end -- 对于 i = 10, 1, 0/0 执行一次 do print(i) -- 10.0 end 通读这段代码,试图理解发生了什么。每一个结果都是极其反直觉的。这就是发生的事情:前两个只执行一次,因为for循环检查是实现为limit < init,但后续迭代检查idx <= limit。因此NaN通过了第一个测试,但没有通过第二个测试。后两个示例显示无论你是否使用math.abs(0/0),NaN作为步长始终被视为负值。这是因为检查步长是否为正的条件是0 < step,而这对于NaN来说当然总是错误的。顺便说一句,这些都没有被记录,因此这很可能是一个真正的疏漏。但这真的很有趣:使用NaN导致实现所使用的特定比较运算符泄漏到解释器的行为中。这里的简单解决方案是仅仅检查并禁止在数值for循环中使用NaN。Lua已经在当步长为0时引发错误,因此有趣的是并没有针对NaN进行这样的检查。结论我想Chris Siebenmann也之前写过关于NaN在Go中作为映射键的奇怪行为,所以我想这使得NaN奇怪性的案例研究增加到三个。NaN是奇怪的。这意味着其他事物在与它一起使用时会表现得奇怪。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡