在传统印象中,数字的比较关系只有6种。但在AVX指令集中,Intel一下给出了32种浮点比较谓词,详见下图——
(Intel手册:Table 3-9. Comparison Predicate for VCMPPD and VCMPPS Instructions)
为什么会有这么多种比较谓词呢?我为此困惑困惑了很久。
直到最近翻阅了不少资料后,才终于将它们弄懂了。
一、浮点数据类型
Intel使用的是IEEE 754规范的浮点数据类型。对于浮点数据类型来说,除了可以存储数字、无穷之外,还可以存储 NaN(not a number。非数)。
NaN(非数)分为两大类——
1.QNaN:quiet NaN,静默非数。当禁止异常时,若浮点运算无效(例如 “零除以零”、“对负数开方”等),就返回QNaN表示运算无效。
2.SNaN:signaling NaN,信号非数。当浮点运算遇到SNaN时,会立即抛出异常。例如可以用来检查变量是否被初始化(定义变量时设为SNaN,如果用户没有将它初始化就调用浮点运算,会抛出异常)。
在Intel手册上有张浮点数编码表,位于“Volume 1: Basic Architecture ”\“Chapter 4 Data Types”\“4.2 Numeric Data Types”\“4.2.2 Floating-Point Data Types”——
(Intel手册:Table 4-3. Floating-Point Number and NaN Encodings)
从上表中可以得知——
1.NaN的阶码为全1,符号位被忽略。通过尾数来判断是SNaN或QNaN。
2.SNaN的尾数的最高为0,其他位非0。
3.QNaN的尾数的最高为1,其他位非0。
4.还存在一个特殊的QNaN——QNaN Floating-Point Indefinite:表示“不确定浮点值”的静默非数。可参考周明德《64位微处理器应用编程》“5.4.4数的编码”中“3、不确定值”。
关于NaN的运算细节,可参考Intel手册中“4.8.3.4 NaNs”至“4.8.3.7 QNaN Floating-Point Indefinite”的内容——
二、两种特殊的比较关系——“无序”、“有序”
传统的数字的比较关系是这6种——
1.等于(Equal)。谓词缩写为“EQ”。C语言运算符为“==”。
2.小于(Less than)。谓词缩写为“LT”。C语言运算符为“<”。
3.小于等于(Less than or equal)。谓词缩写为“LE”。C语言运算符为“<=”。
4.大于(Greater than)。谓词缩写为“GT”。C语言运算符为“>”。
5.大于等于(Greater than or equal)。谓词缩写为“GE”。C语言运算符为“>=”。
6.不等于(Not equal)。谓词缩写为“NEQ”。C语言运算符为“!=”。
一般来说,上面的6种比较关系只对数字(包括无穷大)有效。即两边的操作数都必须是数字,任何一个都不能为NaN。
若任一操作数是NaN(非数),那么将无法进行比较,前5种比较关系都返回False。包括“NaN==NaN”的返回值都是False。
而“不等于”就有点微妙,因为它是“等于”的相反值。所以当任一操作数是NaN时,“不等于”也返回True。因此,将它称为“不等于或无序”将会更好。
“无序”是专门为NaN而设计的比较关系,详细定义如下——
无序(Unordered)。当至少一个操作数是NaN时,返回True。若操作数都不是NaN时,返回False。谓词缩写为“UNORD”。一般借用符号“?”表示“无序”关系。
既然有了“无序”,那么可以定义一个跟它效果相反的运算符“有序”——
有序(Ordered)。当操作数都不是Nan时,返回True。若至少一个操作数是NaN时,返回False。谓词缩写为“ORD”。一般借用符号“!?”表示“有序”关系。
可这样理解——
有序:能比较大小。
无序:不能比较大小。
三、SIMD(SSE/AVX)中的NaN与异常
在使用AVX指令集时,依然是靠MXCSR寄存器来控制浮点运算的。在Intel手册“10.2.3 MXCSR Control and Status Register”中有该寄存器的详细定义——
(Intel手册:Figure 10-3. MXCSR Control/Status Register)
当CPU发现浮点运算无效时——
1. 首先将MXCSR寄存器的最低位(IE:Invalid Operation Flag)设为1。
2. 随后CPU检查MXCSR寄存器的第7位(IM:Invalid Operation Mask),若IM为1(masked,屏蔽),就返回QNaN(或特殊值);若IM为0(unmasked,非屏蔽),就触发#IA异常。
注:#IA是无效算术操作异常,详见Intel手册“8.5.1.2 Invalid Arithmetic Operand Exception (#IA)”。
一般来说,等于、小于等比较操作遇到NaN时会引发上述流程——
1.将IE设为1。
2.若IM为1,不会触发异常,返回比较结果;若IM为0,会触发异常。
而对于“无序”比较来说,因为它本来就是用来检查NaN,不希望触发异常。所以CPU对这一类操作做了特殊处理,不会执行上述操作。
即浮点比较会因是否抛出异常而分为两类——
1.signaling(发信号):遇到NaN时,设置IE,根据IM决定是返回比较结果,还是抛出异常。
2.non-signaling(不发信号): 遇到NaN时,总是返回比较结果,从不抛出异常。
四、32种浮点比较关系详解
学习了上面那些知识点后,现在可以读懂AVX指令集中的32种浮点比较关系了。
Predicate:比较谓词。
imm8 Value:8位立即数。
Description:描述。
Result: A Is 1st Operand, B Is 2nd Operand:返回值:A是第一个操作数,B是第二个操作数。
A > B:大于。
A < B:小于。
A = B:等于。
Unordered:无序。
Signals #IA on QNAN:当遇到QNaN时是否发信号#IA。
注:较复杂的比较关系可以由“大于”、“小于”、“等于”、“无序”这四种基本关系所组成。例如“小于等于”由“小于”、“等于”组成。
我将该表格翻译了一下,并增加了符号形式描述——
谓词 | imm8 | 符 | 描述 | 符2 | 描述2 | A > B | A < B | A = B | 无序 | #IA |
EQ_OQ (EQ) | 0H | == | 等于 | FALSE | FALSE | TRUE | FALSE | No | ||
LT_OS (LT) | 1H | < | 小于 | FALSE | TRUE | FALSE | FALSE | Yes | ||
LE_OS (LE) | 2H | <= | 小于等于 | FALSE | TRUE | TRUE | FALSE | Yes | ||
UNORD_Q (UNORD) | 3H | ? | 无序 | FALSE | FALSE | FALSE | TRUE | No | ||
NEQ_UQ (NEQ) | 4H | ?!= | 不等于或无序 | TRUE | TRUE | FALSE | TRUE | No | ||
NLT_US (NLT) | 5H | !< | 不小于 | ?>= | 大于等于或无序 | TRUE | FALSE | TRUE | TRUE | Yes |
NLE_US (NLE) | 6H | !<= | 不小于等于 | ?> | 大于或无序 | TRUE | FALSE | FALSE | TRUE | Yes |
ORD_Q (ORD) | 7H | !? | 有序 | TRUE | TRUE | TRUE | FALSE | No | ||
EQ_UQ | 8H | ?== | 等于或无序 | FALSE | FALSE | TRUE | TRUE | No | ||
NGE_US (NGE) | 9H | !>= | 不大于等于 | ?< | 小于或无序 | FALSE | TRUE | FALSE | TRUE | Yes |
NGT_US (NGT) | AH | !> | 不大于 | ?<= | 小于等于或无序 | FALSE | TRUE | TRUE | TRUE | Yes |
FALSE_OQ(FALSE) | BH | 0 | FALSE | FALSE | FALSE | FALSE | FALSE | No | ||
NEQ_OQ | CH | != | 不等于 | TRUE | TRUE | FALSE | FALSE | No | ||
GE_OS (GE) | DH | >= | 大于等于 | TRUE | FALSE | TRUE | FALSE | Yes | ||
GT_OS (GT) | EH | > | 大于 | TRUE | FALSE | FALSE | FALSE | Yes | ||
TRUE_UQ(TRUE) | FH | 1 | TRUE | TRUE | TRUE | TRUE | TRUE | No | ||
EQ_OS | 10H | == | 等于 | FALSE | FALSE | TRUE | FALSE | Yes | ||
LT_OQ | 11H | < | 小于 | FALSE | TRUE | FALSE | FALSE | No | ||
LE_OQ | 12H | <= | 小于等于 | FALSE | TRUE | TRUE | FALSE | No | ||
UNORD_S | 13H | ? | 无序 | FALSE | FALSE | FALSE | TRUE | Yes | ||
NEQ_US | 14H | ?!= | 不等于或无序 | TRUE | TRUE | FALSE | TRUE | Yes | ||
NLT_UQ | 15H | !< | 不小于 | ?>= | 大于等于或无序 | TRUE | FALSE | TRUE | TRUE | No |
NLE_UQ | 16H | !<= | 不小于等于 | ?> | 大于或无序 | TRUE | FALSE | FALSE | TRUE | No |
ORD_S | 17H | !? | 有序 | TRUE | TRUE | TRUE | FALSE | Yes | ||
EQ_US | 18H | ?== | 等于或无序 | FALSE | FALSE | TRUE | TRUE | Yes | ||
NGE_UQ | 19H | !>= | 不大于等于 | ?< | 小于或无序 | FALSE | TRUE | FALSE | TRUE | No |
NGT_UQ | 1AH | !> | 不大于 | ?<= | 小于等于或无序 | FALSE | TRUE | TRUE | TRUE | No |
FALSE_OS | 1BH | 0 | FALSE | FALSE | FALSE | FALSE | FALSE | Yes | ||
NEQ_OS | 1CH | != | 不等于 | TRUE | TRUE | FALSE | FALSE | Yes | ||
GE_OQ | 1DH | >= | 大于等于 | TRUE | FALSE | TRUE | FALSE | No | ||
GT_OQ | 1EH | > | 大于 | TRUE | FALSE | FALSE | FALSE | No | ||
TRUE_US | 1FH | 1 | TRUE | TRUE | TRUE | TRUE | TRUE | Yes |
谓词:比较谓词。
imm8:8位立即数。
符:符号形式描述。
描述:文字形式描述。
符2:另一种符号形式描述。
描述2:另一种文字形式描述。
A > B:大于。
A < B:小于。
A = B:等于。
无序:无序。
#IA:Signals #IA on QNAN。
观察这张表格,会发现谓词的命名是有规则的,它由三部分组成,格式大致为“①_②③”,含义如下——
①.下划线前的是第一部分,它是谓词缩写,用于描述功能。例如“EQ_OQ”中的“EQ”代表“等于”。
②.在下划线与最后字母之间的是第二部分,只能是O、U或忽略。O代表有序,U代表无序。
③.最后的那个字母是第三部分,只能是Q或S,分别代表non-signaling和signaling。例如“EQ_OQ”中的“Q”代表“non-signaling”。该字母其实代表的是#IA这一列,为“Q”时“#IA”列为“FALSE”,为“S”时“#IA”列为“TRUE”。
AVX指令集中的VCMPPS等比较指令是继承自SSE指令集中的CMPPS等指令的,它们靠一个8位立即数(imm8)来区分比较谓词。
SSE指令集只定义了前8种比较谓词,所以imm8只有最低3位有效。上表“谓词”后面括号内的就是SSE时代的谓词名称。
AVX指令集将其扩展到32种比较谓词,所以imm8只有最低5位有效。
Intel在设计AVX指令集的比较谓词时,先根据无序、有序和FALSE、TRUE,将比较谓词扩充到16种。然后再反转non-signaling/signaling,又定义了16种比较谓词。合计32种比较谓词。
参考文献——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. December 2011. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《IEEE Standard for Floating-Point Arithmetic (IEEE 754-2008)》。 Institute of Electrical and Electronics Engineers (IEEE)。2008年8月。
《GB/T 17966-2000 微处理器系统的二进制浮点运算》。国家质量技术监督局。2000年1月。
《Pentium Ⅱ/Ⅲ体系结构及扩展技术》。刘清森、马鸣锦、吴灏等著。国防工业出版社,2000年7月。
《64位微处理器应用编程》。周明德著。清华大学出版社,2005 年9月。
NaN:http://en.wikipedia.org/wiki/NaN