C编译器剖析_1.5 结合C语言来学汇编_浮点数运算(比较大小)

来源:互联网 发布:雅马哈f310知乎 编辑:程序博客网 时间:2024/06/02 18:06

    接下来,让我们再看一下浮点数之间比较大小的操作,我们以图1.31中的代码为例。在浮点数的编码中,IEEE754引入了一些特殊的编码来表示正无穷大、负无穷大,因为一个正浮点数除以0则得到正无穷大,而一个负浮点数除以0则得到负无穷大;而在实数范畴内,是不能对一个负数进行开根号的,所以IEEE754也引入了一个称为Not ANumber的特殊编码来表示这样的运算结果为“不是一个实数”,简写为NaN。


图1.31   NaN和Inf

    运行图1.31的代码,我们会得到以下结果,图1.31的第7行打印出来的结果是个负无穷大”-inf”,而第10打印出来的结果是正穷大”inf”,但第13行打印出来的结果是NaN。

iron@ubuntu:1.5$ gccnan.c -o nan -lm

iron@ubuntu:1.5$./nan

-inf

inf

-nan

iron@ubuntu:1.5$ uccnan.c -o nan

iron@ubuntu:1.5$./nan

-inf

inf

-nan

    我们还注意到,图1.31第14行至第19行之间的三条输出语句都没有被执行。这与我们从两个整数之间比较中得到的直觉相违背。两个整数之间的比较,其最基本的结果要么是大于,要么是小于,要么是等于,三者中一定有一个成立。但通过图1.31的例子,我们竟然发现,两个浮点数之间的比较竟然存在“大于、小于和等于这三者都不成立的情况”。比如sqrt(-16.0)的结果应是复数”4i”,其结果已经是复数范畴,并不属于实数范畴,而IEEE 754浮点数标准是对实数进行编码的。我们知道一个复数对应的是平面上的一个点,平面上的两个点之间(两个复数之间)是不存在大小关系的。当然,我们可以比较两个复数与坐标原点之间的距离,但这需要对运算符>、<或 ==进行重载。在实数这个集合中进行开根号运算,其结果可能会超出实数范围,这也是IEEE754引入NaN在数学上的出发点,用数学上的表述就是“在实数上的开根号运算不是封闭的”。由此,我们就不难理解Intel CPU手册中的Table3-31,表中的”Unordered”正是用于处理两个浮点数比较时,其中一个浮点数为NaN的情况,此时不存在大小关系,所以称为无序的Unordered。


Table3-31

    有了这个概念后,我们再举一个浮点数比较的代码,及其对应的汇编代码,如图1.32所示。要理解其中的汇编代码,我们需要再介绍一下x87浮点芯片的状态寄存器。一般而言,芯片内部的寄存器可分为控制寄存器、数据寄存器和状态寄存器这三大类。程序员通过设置控制寄存器的值,来发送命令字或者改变芯片的行为,如前文所述的x87 Control Word寄存器;而数据寄存器中一般存放操作数,如前文Figure8-2中的x87数据寄存器栈,有时需要寻址芯片的片内地址,也会把相应地址送到数据寄存器中,所以有些芯片手册中也会引入地址寄存器的概念;状态寄存器则反映了芯片当前所处的状态,例如芯片是否处于BUSY状态。x86 CPU 内部实际上有一个名为EFLAGS的状态寄存器,我们在使用cmp指令进行整数之间大小比较时,会改变EFLAGS寄存器中的标志位,条件跳转指令实际上是根据这些标志位去跳转的。前文,我们有意忽略了EFLAGS寄存器中的标志位这样的细节,因为完全可以站在汇编指令的语义上来理解有符号和无符号整数之间的比较,就如图1.26和图1.27之间的段落所述,如果需要做更深入理解,可查看Intel CPU手册的“Figure3-8Eflags Register”。


图1.32 浮点数比较

    浮点单元x87内部也有相应的状态寄存器,如Intel芯片手册的Figure8-4所示。而浮点数之间比较后的结果主要由x87状态寄存器的C0、C2和C3这三位来决定,如前文Table3-31所示。


Figure 8-4

    图1.32中的第12和13行用于把浮点数a和b从内存中加载到x87数据寄存器栈的栈顶。而第14行则进行处于x87数据寄存器栈顶的两个浮点数之间的比较,fucompp是COMpare Floatingpoint values的助记符,后缀”pp”表示需要进行两次pop操作,即浮点数比较后,把栈顶的两个浮点数出栈,指令中的”u”是unordered的缩写,译为“无序的”,即此指令允许两浮点数比较时,出现Table3-31中除了“大于、小于和等于”之外的第四种情况“unordered”,无序的。通过第15行的指令fnstsw,我们把x87的状态寄存器的值传送到了x86的16位寄存器ax中,寄存器ax实际上是32位寄存器eax的低16位,而8位寄存器ah是寄存器ax的高8位,而fnstsw是STore x87 FPU Status Word的助记符,其中”n”表示” without checking forpending unmasked floating-point exceptions”。第16行的test指令实际上进行的是“按位与”运算,$0x5对应的是标志位C2和C0,且由第12和13行可知,b位于x87寄存器栈的栈顶st(0),而a位于次栈顶st(1),结合Table3-31,我们可以下表:

 

 

“C2和C0”非0的位数

奇偶性

b > a

0

b < a

1

b == a

0

unorderd

2

     四种比较结果中,”b<a”即”a>b”与其他三种不同的地方在于:图1.32第15行的test指令执行之后,得到的“标志位C2和C0”中非0的位数为奇数个,而其他三种情况为偶数个。由此我们就可以产生第17行的代码” jp  .BB2”,表示当有“偶数个1”时,跳转到基本块”.BB2”处;否则第4行的if条件成立,则执行”c++;”的运算。

而第21至24行完成了把整数转换成浮点数的运算,第22行的指令fildl是LoaDInteger的助记符,后缀”l”表示要加载的是32位操作数。

    图1.32的代码中解释了比较(a>b)的情况,至于(a >= b)等其他比较操作对应的汇编代码可参见X86Linux.tpl的第67行至107行。

0 0
原创粉丝点击