用于浮点相等比较的SIMD指令(NaN == NaN)
哪些指令可用于比较由4 * 32位浮点值组成的两个128位向量?
是否有一条指令将双方的NaN值视为相等? 如果不是,提供反身性(即NaN等于NaN)的解决方法的性能影响有多大?
我听说,与IEEE语义相比,确保自反性会产生显着的性能影响,NaN并不等于自己,我想知道这种影响是否会大。
我知道您在处理浮点值时通常要使用epsilon比较而不是确切的质量。 但是这个问题是关于精确的相等比较,你可以用它来消除哈希集中的重复值。
要求
+0和-0必须相等。 NaN必须与自身相等。 true如果所有四个浮动元件在两种载体相同,并且如果至少一个元件不同假。 其中true由标量整数1 , false由0 。 测试用例
(NaN, 0, 0, 0) == (NaN, 0, 0, 0) // for all representations of NaN
(-0, 0, 0, 0) == (+0, 0, 0, 0) // equal despite different bitwise representations
(1, 0, 0, 0) == (1, 0, 0, 0)
(0, 0, 0, 0) != (1, 0, 0, 0) // at least one different element => not equal
(1, 0, 0, 0) != (0, 0, 0, 0)
我的想法实现这一点
我认为可以将两个NotLessThan比较( CMPNLTPS ?)结合使用and实现所需的结果。 AllTrue(!(x < y) and !(y < x))或AllFalse((x < y) or (y > x)的汇编程序等价物。
背景
这个问题的背景是微软计划向.NET添加一个Vector类型。 我在哪里争论反射式.Equals方法,并且需要更清楚地了解这种反射的性能影响等于IEEE等于多大。 请参阅Vector<float>.Equals是否自反或应遵循IEEE 754语义? 对程序员而言,这是长篇故事。
即使在AVX VCMPPS可用时(由于它的谓词选择大大增强),它的效率也不如IEEE比较。 您必须至少进行两次比较并合并结果。 尽管如此,这还不算太坏。
不同的NaN编码是不相等的:有效地增加2个insns(增加2个uops)。 没有AVX:除此之外还有一个额外的movaps 。
不同的NaN编码是相等的:有效地增加4个insns(增加4个uops)。 没有AVX:两个额外的movaps insn
IEEE比较分支是3 cmpeqps : cmpeqps / movmskps / test-and-branch。 英特尔和AMD将测试和分支宏观融合到单个uop / m-op中。
随着AVX512:逐位男可能只是一个额外的指令,因为法向量比较和部门可能使用vcmpEQ_OQps / ktest same,same / jcc ,所以组合两种不同面具的REG是免费的(只是改变参数传递给ktest )。 唯一的代价是额外的vpcmpeqd k2, xmm0,xmm1 。
AVX512 any-NaN只是两个额外的指令(2x VFPCLASSPS ,第二个使用第一个作为零掩码的结果,见下文)。 再次,然后ktest用两个不同的参数设置标志。
我迄今为止最好的想法是: ieee_equal || bitwise_equal ieee_equal || bitwise_equal
如果我们放弃考虑彼此相等的不同NaN编码:
+0 == -0情况。 有没有情况下,无论是比较给人一种假阳性(因为ieee_equal是假的,当一个操作数为NaN:我们只想要平等,不等于有或无序的AVX vcmpps 。提供了两个选项,而SSE只是提供了一个简单的等于操作)
我们想知道什么时候所有的元素都是平等的,所以我们应该从倒数比较开始。 检查至少一个非零元素比检查所有非零元素更容易。 (即水平的AND很难,水平的OR很容易( pmovmskb / test或ptest )。相反的比较是免费的( jnz而不是jz )。)这与Paul R使用的技巧是一样的。
; inputs in xmm0, xmm1
movaps xmm2, xmm0 ; unneeded with 3-operand AVX instructions
cmpneqps xmm2, xmm1 ; 0:A and B are ordered and equal. -1:not ieee_equal. predicate=NEQ_UQ in VEX encoding expanded notation
pcmpeqd xmm0, xmm1 ; -1:bitwise equal 0:otherwise
; xmm0 xmm2
; 0 0 -> equal (ieee_equal only)
; 0 -1 -> unequal (neither)
; -1 0 -> equal (bitwise equal and ieee_equal)
; -1 -1 -> equal (bitwise equal only: only happens when both are NaN)
andnps xmm0, xmm2 ; NOT(xmm0) AND xmm2
; xmm0 elements are -1 where (not bitwise equal) AND (not IEEE equal).
; xmm0 all-zero iff every element was bitwise or IEEE equal, or both
movmskps eax, xmm0
test eax, eax ; it's too bad movmsk doesn't set EFLAGS according to the result
jz no_differences
对于双精度, ...PS和pcmpeqQ工作原理是一样的。
如果不相等的代码继续查找哪个元素不相等,则对movmskps结果进行位扫描会得出第一个差异的位置。
使用SSE4.1 PTEST您可以使用andnps movmskps替换andnps / movmskps / test-and-branch:
ptest xmm0, xmm2 ; CF = 0 == (NOT(xmm0) AND xmm2).
jc no_differences
我预计这是大多数人第一次看到PTEST的CF结果对任何事物PTEST用。 :)
在Intel和AMD CPU上((2ptest + 1jcc)vs(pandn + movmsk + fused-test&branch))仍然是三个微指令,但指令较少。 如果你打算使用setcc或者cmovcc而不是jcc ,那么效率会setcc ,因为它们不能与test宏观融合。
这样总共有6个uops(5个AVX)用于自反比较分支,而3个uops用于IEEE比较分支 。 ( cmpeqps / movmskps / test-and-branch。)
PTEST在AMD Bulldozer家族CPU(Steamroller上的14c)上有非常高的延迟。 它们具有由两个整数核心共享的一组矢量执行单元。 (这是他们超线程的替代方案。)这增加了可以检测到分支错误预测或数据依赖链延迟( cmovcc / setcc )的时间。
当0==(xmm0 AND xmm2)时,PTEST设置ZF :如果没有元素都是bitwise_equal和IEEE(neq或无序),则设置ZF 。 即如果任何元素是bitwise_equal而ZF是未设置的,同时也是!ieee_equal 。 这只有在一对元素包含按位相等的NaN时才会发生(但在其他元素不相等时可能会发生)。
movaps xmm2, xmm0
cmpneqps xmm2, xmm1 ; 0:A and B are ordered and equal.
pcmpeqd xmm0, xmm1 ; -1:bitwise equal
ptest xmm0, xmm2
jc equal_reflexive ; other cases
...
equal_reflexive:
setnz dl ; set if at least one both-nan element
没有条件测试CF=1和任何关于ZF 。 ja测试CF=0 and ZF=1 。 你不可能只想测试它,所以把jnz放在jc分支目标中就可以正常工作。 (如果你只想测试equal_reflexive和at_least_one_nan ,不同的设置可能会适当地设置标志)。
考虑到所有NaNs相等,即使不是按位相等:
这与Paul R的答案是一样的,但有一个错误修正(使用AND而不是OR将NaN检查与IEEE检查相结合)。
; inputs in xmm0, xmm1
movaps xmm2, xmm0
cmpordps xmm2, xmm2 ; find NaNs in A. (0: NaN. -1: anything else). Same as cmpeqps since src and dest are the same.
movaps xmm3, xmm1
cmpordps xmm3, xmm3 ; find NaNs in B
orps xmm2, xmm3 ; 0:A and B are both NaN. -1:anything else
cmpneqps xmm0, xmm1 ; 0:IEEE equal (and ordered). -1:unequal or unordered
; xmm0 AND xmm2 is zero where elements are IEEE equal, or both NaN
; xmm0 xmm2
; 0 0 -> equal (ieee_equal and both NaN (impossible))
; 0 -1 -> equal (ieee_equal)
; -1 0 -> equal (both NaN)
; -1 -1 -> unequal (neither equality condition)
ptest xmm0, xmm2 ; ZF= 0 == (xmm0 AND xmm2). Set if no differences in any element
jz equal_reflexive
; else at least one element was unequal
; alternative to PTEST: andps xmm0, xmm2 / movmskps / test / jz
所以在这种情况下, PTEST我们不需要PTEST的CF结果。 我们在使用PCMPEQD时会PCMPEQD ,因为它没有反转( cmpunordps的方式有cmpordps )。
用于Intel SnB系列CPU的9个融合域uops。 (7 AVX:使用非破坏性3操作数指令,以避免movaps )。但是,预SKYLAKE微架构SNB-系列CPU只能运行cmpps在P1,所以在这个瓶颈如果吞吐量是一个问题FP-添加单元。 Skylake在p0 / p1上运行cmpps 。
andps比更短的编码pand ,和英特尔CPU从Nehalem处理器到Broadwell微架构只能在PORT5运行它。 为防止它从周围的FP代码盗取p0或p1周期,这可能是需要的。 否则, pandn可能是更好的选择。 在AMD BD系列中, andnps无论如何都在ivec域中运行,所以您不要避免int和FP向量之间的旁路延迟(如果您使用movmskps而不是ptest ,那么在此版本中只使用cmpps ,而不是pcmpeqd )。 另外请注意,此处选择指令排序是为了便于人员阅读。 之前在ANDPS之前将FP比较(A,B)可能会帮助CPU更快地开始该周期。
如果一个操作数被重用,应该可以重用自己的NaN查找结果。 新的操作数仍然需要自我NaN检查,并与重用的操作数进行比较,所以我们只保存一个movaps / cmpps 。
如果向量在内存中,至少其中的一个需要使用单独的加载insn加载。 另一个只能从内存中引用两次。 这很糟糕,如果它没有对齐或寻址模式不能微型融合,但可能有用。 如果vcmpps的一个操作数是一个已知没有NaN的向量(例如一个归零寄存器), vcmpunord_qps xmm2, xmm15, [rsi]将在[rsi]找到NaN。
如果我们不想使用PTEST ,可以通过使用相反的比较来获得相同的结果,但将它们与相反的逻辑运算符(AND与OR)结合使用。
; inputs in xmm0, xmm1
movaps xmm2, xmm0
cmpunordps xmm2, xmm2 ; find NaNs in A (-1:NaN 0:anything else)
movaps xmm3, xmm1
cmpunordps xmm3, xmm3 ; find NaNs in B
andps xmm2, xmm3 ; xmm2 = (-1:both NaN 0:anything else)
; now in the same boat as before: xmm2 is set for elements we want to consider equal, even though they're not IEEE equal
cmpeqps xmm0, xmm1 ; -1:ieee_equal 0:unordered or unequal
; xmm0 xmm2
; -1 0 -> equal (ieee_equal)
; -1 -1 -> equal (ieee_equal and both NaN (impossible))
; 0 0 -> unequal (neither)
; 0 -1 -> equal (both NaN)
orps xmm0, xmm2 ; 0: unequal. -1:reflexive_equal
movmskps eax, xmm0
test eax, eax
jnz equal_reflexive
其他想法:未完成的,不可行的,破碎的或者比上述更差的
真正比较的全部结果是NaN的编码。 (试一下,也许我们可以避免使用POR或PAND来cmpps在每个操作数上合并来自cmpps结果?
; inputs in A:xmm0 B:xmm1
movaps xmm2, xmm0
cmpordps xmm2, xmm2 ; find NaNs in A. (0: NaN. -1: anything else). Same as cmpeqps since src and dest are the same.
; cmpunordps wouldn't be useful: NaN stays NaN, while other values are zeroed. (This could be useful if ORPS didn't exist)
; integer -1 (all-ones) is a NaN encoding, but all-zeros is 0.0
cmpunordps xmm2, xmm1
; A:NaN B:0 -> 0 unord 0 -> false
; A:0 B:NaN -> NaN unord NaN -> true
; A:0 B:0 -> NaN unord 0 -> true
; A:NaN B:NaN -> 0 unord NaN -> true
; Desired: 0 where A and B are both NaN.
cmpordps xmm2, xmm1只是翻转每个案例的最终结果,“奇数人出”仍然在第一行。
如果两个输入都被反转(NaN - >非NaN,反之亦然),我们只能得到我们想要的结果(如果A和B都是NaN,则为true)。 这意味着我们可以使用这个想法cmpordps作为替代pand做后cmpordps self,self在A和B,这是没有用的:即使我们有AVX但不AVX2,我们可以使用vandps和vandnps (和vmovmskps因为vptest只是AVX2)。 按位布尔值仅为单周期延迟,并且不会捆绑已成为此代码瓶颈的vector-FP-add执行端口。
VFIXUPIMMPS
我花了一段时间与手动grokking其操作。
如果源元素是NaN,它可以修改目标元素,但是不能以任何关于dest元素为条件。
我希望能想到一种方法到vcmpneqps ,然后修正这个结果,每个源操作数一次( vcmpps组合3 vcmpps指令结果的vcmpps指令)。 我现在确信这是不可能的,因为知道一个操作数是NaN本身不足以改变IEEE_equal(A,B)结果。
我认为我们可以使用vfixupimmps的唯一方法是分别检测每个源操作数中的NaN,如vcmpunord_qps但更糟糕。 或者作为一个非常愚蠢的替代andps ,在前面比较的掩码结果中检测到0或全部(NaN)。
AVX512屏蔽寄存器
使用AVX512屏蔽寄存器可以帮助结合比较结果。 大多数AVX512比较指令将结果放入一个掩码寄存器,而不是一个矢量区中的掩码矢量,所以如果我们想要以512b块进行操作,我们实际上必须这样做。
VFPCLASSPS k2 {k1}, xmm2, imm8写入一个屏蔽寄存器,可选地由不同的屏蔽寄存器屏蔽。 通过只设置imm8的QNaN和SNaN位,我们可以得到一个向量中NaN位置的掩码。 通过设置所有其他位,我们可以得到相反的结果。
通过使用A中的掩码作为B上的vfpclassps的零掩码,我们可以只用2条指令找到两个NaN位置,而不是通常的cmp / cmp / combine。 因此,我们保存or或andn指令。 顺便提一句,我想知道为什么没有OR-NOT操作。 可能它的出现次数比AND-NOT少,或者他们不想在指令集中使用porn 。
yasm和nasm都不能组装这个,所以我甚至不确定我的语法是否正确!
; I think this works
; 0x81 = CLASS_QNAN|CLASS_SNAN (first and last bits of the imm8)
VFPCLASSPS k1, zmm0, 0x81 ; k1 = 1:NaN in A. 0:non-NaN
VFPCLASSPS k2{k1}, zmm1, 0x81 ; k2 = 1:NaNs in BOTH
;; where A doesn't have a NaN, k2 will be zero because of the zeromask
;; where B doesn't have a NaN, k2 will be zero because that's the FPCLASS result
;; so k2 is like the bitwise-equal result from pcmpeqd: it's an override for ieee_equal
vcmpNEQ_UQps k3, zmm0, zmm1
;; k3= 0 only where IEEE equal (because of cmpneqps normal operation)
; k2 k3 ; same logic table as the pcmpeqd bitwise-NaN version
; 0 0 -> equal (ieee equal)
; 0 1 -> unequal (neither)
; 1 0 -> equal (ieee equal and both-NaN (impossible))
; 1 1 -> equal (both NaN)
; not(k2) AND k3 is true only when the element is unequal (bitwise and ieee)
KTESTW k2, k3 ; same as PTEST: set CF from 0 == (NOT(k2) AND k2)
jc .reflexive_equal
我们可以重复使用相同的屏蔽寄存器作为第二个vfpclassps insn的零掩码和目标寄存器,但是我使用了不同的寄存器以防我在注释中区分它们。 该代码至少需要两个掩码寄存器,但不需要额外的向量寄存器。 我们也可以使用k0而不是k3作为vcmpps的目的地,因为我们不需要使用它作为谓词,只能作为dest和src。 ( k0是不能用作谓词的寄存器,因为该编码手段意味着“无掩码”。)
我不确定我们能否为每个元素创建一个具有reflexive_equal结果的单个掩码,而不需要k...指令在某个点结合两个掩码(例如kandnw而不是ktestw )。 掩码只能作为零掩码,而不能将结果强制为一个掩码,所以将vfpclassps结果组合起来只能用作AND。 所以我认为我们坚持使用1-both-both-NaN,这是将它用作vcmpps的vcmpps的错误vcmpps 。 首先执行vcmpps ,然后将掩码寄存器用作vfpclassps目标和谓词,也无济于事。 合并掩码而不是零掩码可以做到这一点,但在写入掩码寄存器时不可用。
;;; Demonstrate that it's hard (probably impossible) to avoid using any k... instructions
vcmpneq_uqps k1, zmm0, zmm1 ; 0:ieee equal 1:unequal or unordered
vfpclassps k2{k1}, zmm0, 0x81 ; 0:ieee equal or A is NaN. 1:unequal
vfpclassps k2{k2}, zmm1, 0x81 ; 0:ieee equal | A is NaN | B is NaN. 1:unequal
;; This is just a slow way to do vcmpneq_Oqps: ordered and unequal.
vfpclassps k3{k1}, zmm0, ~0x81 ; 0:ieee equal or A is not NaN. 1:unequal and A is NaN
vfpclassps k3{k3}, zmm1, ~0x81 ; 0:ieee equal | A is not NaN | B is not NaN. 1:unequal & A is NaN & B is NaN
;; nope, mixes the conditions the wrong way.
;; The bits that remain set don't have any information from vcmpneqps left: both-NaN is always ieee-unequal.
如果ktest最终成为像ptest这样的2个ktest ,并且不能进行宏观融合,那么kmov eax, k2 / test-and-branch可能会比ktest k1,k2 / jcc便宜。 希望它只会是一个uop,因为掩码寄存器更像是整数寄存器,并且可以从一开始就设计为与标志间隔“接近”。 ptest只在SSE4.1中添加,经过多代设计,载体和EFLAGS之间没有相互作用。
kmov确实为你设置了popcnt,bsf或bsr。 ( bsf / jcc没有宏定义,所以在搜索循环中,你可能仍然想测试/ jcc,只有bsf发现非零时,编码tzcnt的额外字节不会购买你任何东西,除非你正在做无bsf东西,因为即使dest寄存器是未定义的, bsf仍然将ZF设置为零输入,但lzcnt给出了32 - bsr ,所以即使知道输入为非零时也是如此。)
我们也可以使用vcmpEQps并以不同的方式结合我们的结果:
VFPCLASSPS k1, zmm0, 0x81 ; k1 = set where there are NaNs in A
VFPCLASSPS k2{k1}, zmm1, 0x81 ; k2 = set where there are NaNs in BOTH
;; where A doesn't have a NaN, k2 will be zero because of the zeromask
;; where B doesn't have a NaN, k2 will be zero because that's the FPCLASS result
vcmpEQ_OQps k3, zmm0, zmm1
;; k3= 1 only where IEEE equal and ordered (cmpeqps normal operation)
; k3 k2
; 1 0 -> equal (ieee equal)
; 1 1 -> equal (ieee equal and both-NaN (impossible))
; 0 0 -> unequal (neither)
; 0 1 -> equal (both NaN)
KORTESTW k3, k2 ; CF = set iff k3|k2 is all-ones.
jc .reflexive_equal
这种方式只适用于与我们的向量中的元素数量完全匹配的kortest大小。 例如,双精度元素的256b矢量只有4个元素,但kortestb仍然根据输入掩码寄存器的低8位设置CF。
仅使用整数运算
除NaN外,+/- 0是IEEE_equal与bitwise_equal不同的唯一时间。 (除非我错过了一些东西,在使用之前仔细检查这个假设!) +0和-0所有位都为零,除了-0的符号位已置位(MSB)。
如果我们忽略不同的NaN编码,那么bitwise_equal就是我们想要的结果,除了+/- 0的情况。 如果A和B是+/- 0,则A OR B将全部为零,除非符号位。左移1会使其全部为零或不全为零,取决于我们是否需要覆盖按位 - 相等的测试。
这比cmpneqps使用更多的指令,因为我们使用por / paddD模拟我们需要的功能。 (或者pslld加1,但是这比一个字节更长,它运行在不同于pcmpeq端口上,但是您需要考虑周围代码的端口分布以将其纳入决策。)
该算法可能对不同的SIMD架构非常有用,该架构不提供用于检测NaN的相同矢量FP测试。
;inputs in xmm0:A xmm1:B
movaps xmm2, xmm0
pcmpeqd xmm2, xmm1 ; xmm2=bitwise_equal. (0:unequal -1:equal)
por xmm0, xmm1
paddD xmm0, xmm0 ; left-shift by 1 (one byte shorter than pslld xmm0, 1, and can run on more ports).
; xmm0=all-zero only in the +/- 0 case (where A and B are IEEE equal)
; xmm2 xmm0 desired result (0 means "no difference found")
; -1 0 -> 0 ; bitwise equal and +/-0 equal
; -1 non-zero -> 0 ; just bitwise equal
; 0 0 -> 0 ; just +/-0 equal
; 0 non-zero -> non-zero ; neither
ptest xmm2, xmm0 ; CF = ( (not(xmm2) AND xmm0) == 0)
jc reflexive_equal
等待时间比上面的cmpneqps版本低一个或两个周期。
我们真的在这里充分利用了PTEST :在两个不同的操作数之间使用ANDN,并使用它与整个事物的比较零。 我们不能用pandn / movmskps替换它,因为我们需要检查所有位,而不仅仅是每个元素的符号位。
我没有真正测试过,所以即使我的结论+/- 0是唯一一次IEEE_equal不同于bitwise_equal(NaN除外)的结论也可能是错误的。
使用整数运算处理非按位相同的NaN可能不值得。 编码与+/- Inf非常类似,我不能想到任何简单的检查,不会需要几个指示。 Inf有所有的指数位设置,以及一个全零的尾数。 NaN具有设置的所有指数位,具有非零尾数aka有效数(因此有23位有效载荷)。 尾数的MSB被解释为is_quiet标志以区分信令/安静的NaN。 另请参阅英特尔手册vol1,表4-3( Floating-Point Number and NaN Encodings )。
如果它不是用-Inf使用前9位的编码,我们可以用A > 0x7f800000的无符号比较来检查NaN。 ( 0x7f800000是单精度+ Inf)。 但是请注意, pcmpgtd / pcmpgtq是有符号整数比较。 AVX512F VPCMPUD是一个无符号比较(dest =掩码寄存器)。
OP的想法: !(a<b) && !(b<a)
OP对!(a<b) && !(b<a)无法正常工作,也没有任何变化。 你不能分辨出一个NaN和两个NaN之间的区别,只是从两个相反的操作数进行比较。 即使混合谓词也VCMPPS : 没有VCMPPS谓词将两个操作数中的一个操作数区VCMPPS NaN ,或者取决于它是第一个还是第二个操作数是NaN。 因此,他们的组合不可能拥有这些信息。
Paul R的将矢量与自身进行比较的解决方案可以让我们检测出NaN的位置并“手动”处理它们。 两个操作数之间没有VCMPPS结果的组合是足够的,但使用除A和B以外A操作数确实有帮助。 (一种已知的非NaN载体或两次相同的操作数)。
没有反转,当至少有一个元素相等时,按位NaN代码会发现。 (对于pcmpeqd没有任何pcmpeqd ,所以我们不能使用不同的逻辑运算符,仍然可以得到全等的测试):
; inputs in xmm0, xmm1
movaps xmm2, xmm0
cmpeqps xmm2, xmm1 ; -1:ieee_equal. EQ_OQ predicate in the expanded notation for VEX encoding
pcmpeqd xmm0, xmm1 ; -1:bitwise equal
orps xmm0, xmm2
; xmm0 = -1:(where an element is bitwise or ieee equal) 0:elsewhere
movmskps eax, xmm0
test eax, eax
jnz at_least_one_equal
; else all different
这种方式不适用于PTEST ,因为与OR结合是唯一有用的东西。
// UNFINISHED start of an idea
bitdiff = _mm_xor_si128(A, B);
signbitdiff = _mm_srai_epi32(bitdiff, 31); // broadcast the diff in sign bit to the whole vector
signbitdiff = _mm_srli_epi32(bitdiff, 1); // zero the sign bit
something = _mm_and_si128(bitdiff, signbitdiff);
这是一种可能的解决方案 - 但效率不高,需要6条指令:
__m128 v0, v1; // float vectors
__m128 v0nan = _mm_cmpeq_ps(v0, v0); // test v0 for NaNs
__m128 v1nan = _mm_cmpeq_ps(v1, v1); // test v1 for NaNs
__m128 vnan = _mm_or_si128(v0nan, v1nan); // combine
__m128 vcmp = _mm_cmpneq_ps(v0, v1); // compare floats
vcmp = _mm_and_si128(vcmp, vnan); // combine NaN test
bool cmp = _mm_testz_si128(vcmp, vcmp); // return true if all equal
请注意,上面的所有逻辑都是反转的,这可能会使代码有点难以遵循( OR s实际上是AND s,反之亦然)。
上一篇: SIMD instructions for floating point equality comparison (with NaN == NaN)
下一篇: What is the difference between (NaN != NaN) and (NaN !== NaN)?
