zyl910

优化技巧、硬件体系、图像处理、图形学、游戏编程、国际化与文本信息处理。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

  在传统印象中,数字的比较关系只有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

posted on 2012-04-19 15:50  zyl910  阅读(4933)  评论(0编辑  收藏  举报