赞
踩
十进制浮点数有三个部分组成:符号,有效数字和阶码。比如,在-1.23154*105中,符号为负,有效数字为1.23154,阶码为5
x86处理器使用的三种浮点数二进制存储格式都是由IEEE标准754-1985——二进制浮点运算一一所制定。下表列出了他们的特点
精度 | 范围 |
---|---|
单精度 | 32位:1位符号位,8位阶码,23位为有效数字的小数部分。大致的规格化范围:2-126—2127 也被称为短实数 |
双精度 | 64位:1位符号位,11位阶码,52位为有效数字的小数部分。大致的规格化范围为:2-1022—21023 也被称为长实数 |
扩展双精度 | 80位:1位符号位,15位阶码,1位为整数部分,63位为有效数字的小数部分。大致的规格化范围:2-16382—216383 也被称为扩展实数 |
由于三种格式比较相似,因此本节将重点关注单精度格式。
如果符号位为1,则该数为负;如果符号位为0,则该数为正。
浮点数的有效数字由小数点的左右的十进制数字构成。十进制的数123.154用加权位计数法可以表示为下面的累加和形式
123.154=(1x102)+(2x101)+(3x100)+(1x10-1)+(5x10-2)+(4x10-3)
小数点左边的数字的阶码都位正,右边的数字阶码都为负
小数点右边数字还有一种表达方式,即把他们列为分数之和,其中分母为2的幂,例如:
.1011=1/2+0/4+1/8+1/16=11/16
用有限位数表示的任何浮点数都无法表示完整的连续的实数。例如:假设一个简单的浮点数格式有5位有效数字,那么将无法表示范围在1.1111-10.000之间的二进制数。比如,二进制数1.11111就需要更精确的有效数字。将这个思想扩展到IEEE双精度格式,就会发现53位有效数字无法表示需要54位或更多二进制数值。
单精度数用8位无符号整数存放阶码,引入的偏差为127,因此必须在数的实际阶码上再加上127
大多数二进制浮点数都以规格化格式存放,以便将有效数字的精度最大化。给定任意二进制浮点数,都可以进行规格化,方法是将小数点移位,直到小数点左边只有一个1。阶码表示的是二进制小数点向左或向右移动的位数。示例如下:
非规格化 | 规格化 |
---|---|
1110.1 | 1.1101x23 |
000101 | 1.01x2-4 |
1010001 | 1.010001x2-6 |
反规格化数:规格化操作的逆操作是将二进制浮点数反规格化。移动二进制小数点,直到阶码为0。如果阶码为正数,则将小数点右移,如果阶码为负数,则将二进制小数点左移,并在需要的位置前填充导数0。
一旦符号位 阶码和有效数字字段完成格式化和编码后,生成一个完整的二进制IEEE段实数就很容易了。首先设置符号位,然后是阶码字段,最后是有效数字部分。例如:下面表示的是二进制1.101x20
偏移码(01111111)是十进制数127的二进制形式。所有规格化有效数字在二进制小数点的左边都有个1,因此,不需要对这一位进行显示编码。
单精度数位编码示例
二进制数值 | 偏移阶码 | 符号 阶码 小数部分 |
---|---|---|
-1.11 | 127 | 1 01111111 11000000000000000000000 |
1101.101 | 130 | 0 10000010 10110100000000000000000 |
IEEE规范包含了多钟实数和非数字编码
规格化和非规格化: 规格化有限数是指所有非零有限值,这些数能被编码为零到无穷之间的规格化实数。尽管看上去全部有限非零浮点数都应被规格化,但若数值接近于零,则无法规格化,当阶码范围造成的限制使得FPU不能将二进制小数点移动到规格化位置时,就会发生这种情况。假设FPU计算结果为1.0101111x2-129,其阶码太小,无法用单精度数形式存放。此时产生一个下溢异常,数值则每次将二进制小数点左移一位逐步进行非规格化,直到阶码达到有效范围
正无穷和负无穷:正无穷表示最大正实数,负无穷表示最大负实数。无穷可以和其他数值比较。负无穷小于正无穷,负无穷小于任意有限实数。任一无穷都可以表示浮点溢出条件。运算结果不能格式化的原因是,结果的阶码太大而无法用有效阶码的位数来表示。
NaN:NaN是不表示任何有效实数的位模式
特定编码:在浮点运算中,常常会出现一些特定的数值编码
IEEE单精度数转换为十进制时,建议步骤如下:
示例IEEE(0 10000010 01011000000000000000000)转换为十进制:
Inter8086处理器设计使之只能处理整数运算。这对于使用浮点运算的图形和计算密集型软件来说就变成了麻烦。尽管也可以纯粹地通过软件来模拟浮点运算,但这样会带来严重的性能损失
FPU不使用通用寄存器,反之,它有自己的一组寄存器,称为寄存器栈。数值从内存加载到寄存器栈,然后执行计算,再将堆栈数值保存到内存。FPU指令用后缀形式计算算术表达式,这和惠普计算器的方法大致相同。比如,现有一个中缀表达式:(5*6)+4,其后缀表达式为:5 6 *4 +
中缀表达式(A+B)*C要用括号来覆盖默认的优先规则,与之等效的后缀表达式则不需要括号:A B + C *
中缀转为后缀的例子
中缀 | 后缀 | 中缀 | 后缀 |
---|---|---|---|
A+B | AB+ | (A+B)*(C+D) | AB+CD+* |
(A-B)/D | AB-D/ | ((A+B)/C)*(E-F) | AB+C/EF-* |
表达式堆栈:在计算后缀表达式的过程中,用堆栈来保存中间结果
FPU有8个独立的 可寻址的80位数据寄存器R0-R7,这些寄存器合称为寄存器栈。FPU状态字中名为TOP的一个3位字段给出了当前处于栈顶的寄存器编号。例如 当TOP=011时 表示栈顶为R3。在编写浮点指令时,这个位置也称为ST(0)。最后一个寄存器为ST(7)
如同想的一样,入栈操作将top-1,并把操作数复制到标识为ST(0)的寄存器中,如果在入栈之前,TOP等于0,那么TOP就回绕到寄存器R7。出栈操作把ST(0)的数据复制到操作数,再将TOP+1。如果在出栈之前TOP=7,则TOP就回绕到寄存器R0。如果加载到堆栈的数值覆盖了寄存器栈内的原有数据,就会产生一个浮点异常
尽管理解FPU如何利用一组有限数量的寄存器实现堆栈很有意思,但这里只需要关注ST(n),其中ST(0)总是表示栈顶。从这里开始,引用栈寄存器时将使用ST(0) ST(1),以此类推。指令操作数不能直接引用寄存器编号
寄存器中浮点数使用的是IEEE10字节扩展实数格式,也被称为临时实数。当FPU把算术运算结果存入内存时,它会把结果转换成如下格式之一:整数 长整数 单精度 双精度 或者压缩二进制编码的十进制数
FPU有6个专用寄存器
FPU尝试从浮点运算中产生非常精确的运算结果,但是在很多情况下这是不可能的,因为目标操作数可能无法精确表示计算结果。FPU可以在四种舍入方法中进行选择
FPU控制字用两位指明使用的舍入方法,这两位被称为RC字段。字段数值如下:
每个程序都可能出错,而FPU就需要处理这些结果。因而,它要识别并检测6种类型的异常条件:无效操作 除零 非规格化操作数 数字上溢 数字下溢以及模糊精度。前三个在全部运算操作发生前进行检测,后三个在操作发生后进行检测。
每种异常都有对应的标志位和屏蔽位。当检测到浮点异常时,处理器将与之匹配的标志位置1。每个被处理器标志的异常都有两种可能的操作:
大多数程序普遍都可以接受处理器的屏蔽响应。如果应用程序需要特殊响应,那么可以使用自定义异常处理程序,一条指令能触发多个异常,因此处理器要持续保存自上一次异常清零后所发生的全部异常。完成一系列计算后,可以检测是否发生了异常。
FPU指令集有些复杂,因此本节尝试对齐功能进行概述,并用具体例子给出编译器通常会生成的代码。此外,本节还将看到如何通过改变舍入模式来控制FPU。指令集包括如下基本指令类型:
浮点指令名用字母F开头,以区别CPU指令,指令助记符的第二个字母(通常为B或I)指明如何解释内存操作数:B表示BCD操作数,I表示二进制整数操作数。如果这两个字母都没有使用,则内存操作数被认为是实数。比如,FBLD操作对象为BCD数值,FILD操作对象为整数,而FLD操作对象为实数
操作数:浮点指令可以包含零操作数 单操作数和双操作数。如果是双操作数,那么其中一个必然为浮点寄存器。指令中没有立即操作数,但是某些预定义常数可以加载到堆栈。通用寄存器EAX EBX…不能作为操作数。
整数操作数从内存加载到FPU,并自动转换为浮点格式。同样,将浮点数保存到整数内存操作数时,该数值也会被自动截断或舍入为整数。
FINIT指令对FPU进行初始化。将FPU控制字设置为037Fh,即屏蔽了所有浮点异常,舍入模式设置为最近偶数,计算精度设置为64位。建议在程序开始时调用FINIT,这样就可以了解处理器的其实状态
MASM支持的浮点类型有:
FLD指令将浮点操作数复制到FPU堆栈栈顶(ST(0))。操作数可以是32位 64位 80位的内存操作数或另一个FPU寄存器。FLD支持的内存操作数类型与MOV指令一样
FILD指令将16位 32位或者64位有符号整数源操作数转换为双精度浮点数,并加载到ST(0)。源操作数符号保留。FILD支持的内存操作数类型和MOV一致
下面的指令将特定常数加载到堆栈,这些指令没有操作数
FST指令将浮点操作数从FPU栈顶复制到内存。FST支持的内存操作数类型和FLD一致。操作数可以为32位 64位 80位内存操作数或另外一个FPU寄存器
FSTP(保存浮点值并将其出栈)指令将ST(0)的值复制到内存并将ST(0)弹出堆栈
FIST(保存整数)指令将ST(0)的值转换为有符号整数,并把结果保存到目标操作数。保存的值可以为字或者双字。FIST支持的内存操作数类型与FST一致
下表列出了基本算术运算操作。所有算术运算指令支持的内存操作数类型与FLD(加载)和FST(保存)一致,因此操作数可以是间接操作数 变址操作数和基址变址操作数等等
指令 | 作用 |
---|---|
FCHS | 修改符号 |
FADD | 源操作数与目的操作数相加 |
FSUB | 从目的操作数中减去源操作数 |
FSUBR | 从源操作数中减去目的操作数 |
FMUL | 源操作数和目的操作数相乘 |
FDIV | 目的操作数除以源操作数 |
FDIVR | 源操作数除以目的操作数 |
FCHS(修改符号)指令将ST(0)中的浮点值的符号取反。FABS(绝对值)指令清除ST(0)中数值的符号,以得到它的绝对值,这两条指令都没有操作数
FADD(加法),如果FADD没有操作数,则ST(0)与ST(1)相加,结果暂存在ST(1)。然后ST(0)弹出堆栈,把加法结果保留在栈顶。如果是寄存器操作数,从同样的栈开始,将ST(0)加到ST(1)。如果是内存操作数,FADD将操作数与ST(0)相加
FADDP(相加并出栈)指令先执行加法操作,再将ST(0)弹出堆栈
FIADD(整数加法)指令先将源操作数转换为扩展双精度浮点数,再与ST(0)相加
FUSB指令从目的操作数中减去源操作数,并把结果保存到目的操作数。目的操作数总是一个FPU寄存器,源操作数可以是FPU寄存器或内存操作数。该指令操作数类型和FADD指令一致。
FUSB的操作与FADD相似,只不过它进行的是减法而不是加法。比如,无参数FUSB实现ST(1)-ST(0),结果暂存与ST(1)。然后ST(0)弹出堆栈,将减法结果留在栈顶。若FSUB使用内存操作数,则从ST(0)中减去内存操作数,且不再弹出堆栈
FSUBP(相减并出栈)指令先执行减法,再将ST(0)弹出堆栈
FISUB(整数减法)指令先把源操作数转为扩展双精度浮点数,再从ST(0)中减去该操作数
FMUL指令将源操作数与目的操作数相乘,乘积保存在目的操作数中。目的操作数总是一个FPU寄存器,源操作数可以为寄存器或者内存操作数。除了执行的是乘法不是加法外,FMUL的操作与FADD相同。比如,无参数FMUL将ST(0)与ST(1)相乘,乘积暂存于ST(1),然后将ST(0)弹出堆栈,将乘积留在栈顶。
FMULP(相乘并出栈)指令先执行乘法,再将ST(0)弹出堆栈
FIMUL与FIADD相同,只是它执行的是乘法不是加法
FDIV指令执行目的操作数除以源操作数,被除数保存在目的操作数中。目的操作数总是一个寄存器,源操作数可以为寄存器或者内存操作数。其语法与FADD和FSUB相同。
除了执行的是除法不是加法外,FDIV的操作和FADD相同。比如,无参数FDIV执行ST(1)除以ST(0)。然后ST(0)弹出堆栈,将被除数留在栈顶。使用内存操作数的FDIV将ST(0)除以内存操作数。
若操作数为零 则产生除零异常。若源操作数为正 负无穷 零 或者NaN,则使用一些特殊情况
FIDIV指令先将整数源操作数转换为扩展双精度浮点数,再执行与ST(0)的除法
浮点数不能使用CMP进行比较,因为CMP是通过整数减法来执行比较的。取而代之,必须使用FCOM指令,执行FCOM。执行FCOM指令后,还需要采取特殊步骤,然后再使用JCC跳转指令。由于所有的浮点数都为隐含的有符号数,因此FCOM执行的是有符号的比较。
FCOM(比较浮点数)指令将源操作数与ST(0)进行比较。源操作数可以为内存操作数或者FPU寄存器
FCOMP指令的操作数类型和执行的操作与FCOM指令相同,但是它要将ST(0)弹出堆栈
FCOMPP指令与FCOMP相同,但是它有两次出栈操作
FPU条件码标识有三个:C3 C2和C0,用以说明浮点数的比较结果。C3 C2和C0的功能分别与零标志位(ZF) 奇偶标志位(PF)和进位标志位(CF)相同。
在比较了两个数值并设置了FPU条件码之后,遇到的主要挑战就是怎样根据条件分支到相应标号。这包括两个步骤
条件码送入EFLAGS之后,就可以根据ZF CF和PF进行条件跳转
浮点数比较的运行时开销大于整数比较。考虑到这一点,InterP6系列引入了FCOMI指令。该指令比较浮点数值,并直接设置ZF PF CF
整数(CPU)和FPU是相互独立的单元,因此,在执行整数和系统指令的同时可以执行浮点指令。这个功能被称为并行性,当发生未屏蔽的浮点异常时,它可能是一个潜在的问题。反之,已屏蔽异常则不成问题,因为FPU总是可以完成当前操作并保存结果。
发生未屏蔽异常时,中断当前的浮点指令,FPU发异常事件信号。当下一条浮点指令或者FWAIT指令将要被执行时,FPU检查待处理的异常。如果发现有这样的异常,FPU就调用浮点异常处理程序
如果引发异常的浮点指令后面跟的是整数或系统指令,则指令不会检查待处理异常——它们会立即执行
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。