|
部分余数的概念与如何执行浮点除法有关。除法操作的余数由被除数对除数的一系列减法决定。在每次减法迭代时,中间余数称为部分余数(Partial remainder)。当部分余数小于除数时( 不能再执行减法操作,否则就会产生负数 ),迭代停止。在除法操作结束时,最终的答案是代表减法迭代次数的整数值(商(quotient)),以及表示最终的部分余数的浮点值(余数(remainder))。
根据执行除法需要多少次迭代,可能有很多部分余数。迭代的次数取决于被除数和除数的指数值之间的差值。每次减法不能使被除数的指数值的减少量超过 63 ( 双精度的指数范围 )。
确定除法余数的基本方法是确定被除数和除数的除法的浮点商,然后把这个值舍入到最近的整数。那么,余数就是除数和商相乘的结果与被除数之间的差值。例如,为了计算 20.65 除以 3.97 的余数,可以执行如下步骤:1) 20.65/3.97=5.201511335
2) 5*3.97=19.85
3) 20.65-19.85=0.8 计算浮点除法余数有两个指令: FPREM 和 FPREM1 ,它们的工作方法稍有区别。
在创建任何标准之前,Intel 就开发了 FPREM 指令,它使用默认的 FPU 向零舍入的方法,用于计算整数商值,然后确定余数。
但是,在 IEEE 创建标准时,它选择在计算余数之前,使商值向上舍入到最近的整数值。
虽然两者看起来只有细微的区别,但是在处理过程中对计算部分余数会造成很大的影响。出于这个原因,Intel 不但保持了 FPREM 指令的原始形式,而且另外创建了 FPREM1 指令,后者使用 IEEE 方法计算部分余数。
计算部分余数的问题在于必须知道迭代过程在什么时候完成。FPREM 和 FPREM1 指令都使用 FPU 状态寄存器的条件代码 2 (状态寄存器的第 10 位)表示迭代何时完成。当需要更多的迭代时,就设置 C2 位,当迭代完成时,就清空 C2 位。
位了检查 C2 位,必须首先使用 FSTSW 指令把状态寄存器的内容复制到内存位置或者 AX 寄存器中,然后使用 TEST 指令判断这一位是否设置了。
下面程序使用 FPREM1 指令执行简单的浮点除法:
.section .data
value1:
.float 20.65
value2:
.float 3.97
.section .bss
.lcomm result, 4
.section .text
.global _start
_start:
nop
finit
flds value2
flds value1
loop:
fprem1
fstsw %ax
testb $4, %ah
jnz loop
fsts result
movl $1, %eax
movl $0, %ebx
int $0x80 说明:
注意,FPREM1 是一个迭代的处理过程,它可能不是一步到位的,也就是不能保证它经过第一次执行就能得到最终的答案。所以,必须使用 TEST 指令检查 C2 条件位的值 (先使用 FSTSW 指令把它送到 AX 寄存器)。如果这一位被设置了,那么 TEST 指令就会产生非 0 值,这样 JNZ 会继续返回 loop 进行下一个迭代。反之,如果这一位被清零了,那么 JNZ 就不会跳转,而是顺序执行下去。余数值存储在 ST0 寄存器中,程序中还将余数保存到 result 内存处。
在 GDB 中可以看到余数值:(gdb) x/f &result
0x80490a8 <result>: 0.799999475
虽然余数存储在 ST0 寄存器中,但实际的商没有存储在寄存器中。商值的最后 3 个有效位存储在状态寄存器中,使用状态寄存器中剩余的条件码位,如下:
必须手动地提取这些位以便构成商值的最低 3 位。
现在跟踪一下这个商:
在 fprem1 这里下断,然后运行至此,执行 fprem1 之前,先看一下状态寄存器内容:发现C0, C1, C2, C3都为0。
现在执行 fprem1 指令,然后再看一下相关条件位:我们不关心 C2,就看一下 C3, C1, C0的值,分别为 0, 1, 1 。按照上面所说的商的位置,组合起来为: 101 ,即为 5。所以,商为 5 。
这里,只能提取商的低 3 位,超出了不能从此获得商的值。
|
|