北京商地网站建设公司,wordpress做资源分享站,如何给公司做网站,app软件免费模板下载网站浮点数如何存储浮点寄存器浮点数指令浮点计算例子浮点高级运算CMOV移动指令 浮点数如何存储 浮点数的运算完全不同于整数#xff0c;从寄存器到指令#xff0c;都有一套独特的处理流程#xff0c;浮点单元也称作x87 FPU。 现在看浮点数的表示方式#xff0c;我们所知道的从寄存器到指令都有一套独特的处理流程浮点单元也称作x87 FPU。 现在看浮点数的表示方式我们所知道的计算机使用二进制存储数据所表示的数字都具有确定性那是如何表示浮点这种具有近似效果的数据呢答案是通过科学计数科学计数由符号尾数和指数表示这三部分都是一个整数值具体来看一下IEEE二进制浮点标准 格式 说明单精度32位符号占1位指数占8位尾数中的小数部分占23位双精度64位符号占1位指数占11位尾数中的小数部分占52位扩展精度80位符号占1位指数占16位尾数中的小数部分占63位 以单精度为例在内存中的储存格式如下左边为高位 | 1位符号 | 8位指数 | 23位尾数 |其中符号位1表示负数0表示正数这与整数形式的符号位意义相同 科学计数法表示形式如 m * (b ^ e)m为尾数b为基数e是指数再二进制中基数毫无疑问是2对单精度指数为中间8位二进制表示的数字其中的尾数是形如1.1101 小数点后面的整数值。 关于指数由于需要表示正负两种数据IEEE标准规定单精度指数以127为分割线实际存储的数据是指数加127所得结果127为高位为零后7位为1所得其他双精度也以此方式计算。 为了解释内存中浮点数的存储方式举一个浮点数的例子说明
float test 123.456;int main()
{return 0;
}例子再简单不过了仅仅定义了一个全局的float类型我们通过gcc -S test.c来生成汇编看看123.456是如何存储的打开反汇编后的文件看到符号_test后定义的数字是 1123477881这里gcc定义成了long类型不过没有关系因为都是四字节数字具体的类型还得看如何使用。可以使用计算器把十进制数字转化为二进制0 10000101 11101101110100101111001这里根据单精度的划分方式把32位划分成三部分符号位为0为正数指数为 133减去127得6尾数加上1.形式为1.11101101110100101111001扩大2 ^ 23次方为111101101110100101111001十进制16181625后除以2 ^ (23 – 6) 131072结果为123.45600128173828125与我们所定义的浮点数正好相符。 浮点寄存器 这里介绍了浮点数的二进制表示前面说过浮点单元计算使用独立的寄存器在寄存器那篇也稍有提及这里详细说明一下浮点单元的寄存器设施。 FPU有 8 个独立寻址的80位寄存器名称分别为r0, r1, …, r7他们以堆栈形式组织在一起统称为寄存器栈编写浮点指令时栈顶也写为st(0)最后一个寄存器写作st(7)。 FPU另有3个16位的寄存器分别为控制寄存器、状态寄存器、标记寄存器现一一详细说明此三个寄存器的作用 状态寄存器为用户记录浮点计算过程中的状态其中各位的含义如下
0 —— 非法操作异常
1 —— 非规格化操作数异常
2 —— 除数为0异常
3 —— 溢出标志异常
4 —— 下溢标志异常
5 —— 精度异常标志
6 —— 堆栈错误
7 —— 错误汇总状态
8 —— 条件代码位0c0
9 —— 条件代码位1c1
10 —— 条件代码位2 c2
11-13 —— 堆栈顶指针
14 —— 条件代码位3c3
15 —— 繁忙标志其中读取状态寄存器内容可使用 fstsw %ax 控制寄存器的位含义如下
0 —— 非法操作异常掩码
1 —— 非法格式化异常掩码
2 —— 除数为0异常掩码
3 —— 溢出异常掩码
4 —— 下溢异常掩码
5 —— 精度异常亚曼
6-7 —— 保留
8-9 —— 精度控制00单精度01未使用10双精度11扩展精度
10-11 —— 舍入控制00舍入到最近01向下舍入10向上舍入11向0舍入
12 —— 无穷大控制
13–15 —— 保留其中读取控制寄存器和设置控制寄存器的指令如下
# 加载到内存
fstcw control
# 加载到控制器
fldcw control最后的标志寄存器最为简单分别0-15位分别标志r0-r7共8个寄存器每个寄存器占2位这两位的含义如下
11 —— 合法扩展精度
01 —— 零
10 —— 特殊浮点
11 —— 无内容另外对浮点寄存器的一些控制指令如下
# 初始化fpu控制、状态设为默认值但不改变fpu的数据
finit# 恢复保存环境
fldenv buffer
fstenv buffer#清空浮点异常
fnclex#fpu状态保存
fssavefstenv 保存控制寄存器、状态寄存器、标记寄存器、FPU指令指针偏移量、FPU数据指针FPU最后执行的操作码到内存中。 浮点数指令 接下来将要详细说明其计算过程要计算数据首先得看如何从内存中加载数据到寄存器同时把结果从寄存器取出到内存除了加载内存中的浮点数据指令另外还有一些常量的加载现列举如下 指令 说明finit初始化控制和状态寄存器不改变fpu数据寄存器fstcw control将控制寄存器内容放到内存control处fstsw status将状态寄存器内容放到内存status处flds value加载内存中的单精浮点到fpu寄存器堆栈fldl value加载内存中的双精浮点到fpu寄存器堆栈fldt value加载内存中的扩展精度点到fpu寄存器堆栈fld %st(i)将%st(i)寄存器数据压入fpu寄存器堆栈fsts value单精度数据保存到value不出栈fstl value双精度数据保存到value不出栈fstt value扩展精度数据保存到value不出栈fstps value单精度数据保存到value出栈fstpl value双精度数据保存到value出栈fstpt value扩展精度数据保存到value出栈fxch %st(i)交换%st(0)和%st(i)fld1把 1.0 压入 FPU 堆栈中fldl2t把 10 的对数(底数2)压入 FPU 堆栈中fldl2e把 e 的对数(底数2)压入 FPU 堆栈中fldpi把 pi 的值压入 FPU 堆栈中fldlg2把 2 的对数(底数10)压入 FPU 堆栈中fldln2把 2 的对数(底数e) 压入堆栈中fldz把 0.0 压入压入堆栈中 以上指令虽多但是还是很有规律前缀f表示fpu操作ld加载st保存设置p后缀弹出堆栈s、l、t后缀表示单精度双精度扩展精度c后缀表 示控制寄存器s后缀表示状态寄存器。当然这仅仅是对ATT语法而言对MASM语法没有slt之分需要使用type ptr来指明精度即内存大小。 学会灵活的加载弹出数据堆栈后接下来就要看一些基本的计算
fadd 浮点加法
fdiv 浮点除法
fdivr 反向浮点除法
fmul 浮点乘法
fsub 浮点减法
fsubr 反向浮点减法对于以上的每种指令有几种指令格式以fadd为例列举如下
# 内从中的32位或者64位值和%st(0)相加
fadd source# 把%st(x)和%st(0)相加结果存入%st(0)
fadd %st(x), %st(0)# 把%st(0)和%st(x)相加结果存入%st(x)
fadd %st(0), %st(x)# 把%st(0)和%st(x)相加结果存入%st(x)弹出%st(0)
faddp %st(0), %st(x)# 把%st(0)和%st(1)相加结果存入%st(1)弹出%st(0)
faddp# 把16位或32位整数与%st(0)相加结果存入%st(0)
fiadd source这仅仅是对ATT语法而言对MASM源操作数与目的操作数相反另外对ATT与内存相关指令可加s、l指定内存精度。其中反向加法和反向除法是计算过程中目的与源反向计算。 浮点计算例子 接下来举一个ATT语法的例子来计算表达式的值 ( 12.34 * 13 ) 334.75 ) / 17.8
# ( 12.34 * 13 ) 334.75 ) / 17.8
.section .datavalues: .float 12.34, 13, 334.75, 17.8result: .double 0.0outstring: .asciz result is %f\n
.section .text
.globl _main
_main:leal values, %ebxflds 12(%ebx)flds 8(%ebx)flds 4(%ebx)flds (%ebx)fmulpfaddpfdivp %st(0), %st(1)fstl resultleal result, %ebxpushl 4(%ebx)pushl (%ebx)pushl $outstringcall _printf
end:pushl $0call _exit前四个flds加载所有的数据到寄存器堆栈可以单步运行并是用gdb的print $st0打印堆栈寄存器的值可以看到为什么是堆栈寄存器。需要说明的是由于printf的%f是double类型的输出所以最后要把一个8字节浮点放 到栈中传递最终结果为27.818541可以看到与计算器计算的结果近似相等。 浮点高级运算 除了基本的浮点计算x87还提供了一些诸如余弦运算等高级计算功能 指令 说明f2xm1计算2的乘方次数为st0中的值减去1fabs计算st0中的绝对值fchs改变st0中的值的符号fcos计算st0中的值的余弦fpatan计算st0中的值的部分反正切fprem计算st0中的值除以st1的值的部分余数fprem1计算st0中的值除以st1的值的IEEE部分余弦fptan计算st0中的值的部分正切frndint把st0中的值舍入到最近的整数fscale计算st0乘以2的st1次方fsin计算st0中的值的正弦fsincos计算st0中的值的正弦和余弦fsqrt计算st0中的值的平方根fyl2x计算st1*log st0 以2为底fyl2xp1计算st1*log (st0 1) 以2为底 下面来看一下浮点条件分支浮点数的比较不像整数可以容易的使用cmp指令比较判断eflags的值关于浮点数比较fpu提供独立的比较机制和指令现对这组比较指令进行说明 指令 说明fcom比较st0和st1寄存器的值fcom %st(x)比较st0和stx寄存器的值fcom source比较st0和32/64位内存值fcomp比较st0和st1寄存器的值并弹出堆栈fcomp %st(x)比较st0和stx寄存器的值并弹出堆栈fcomp source比较st0和32/64位内存值并弹出堆栈fcompp比较st0和st1寄存器的值并两次弹出堆栈ftst比较st0和0.0 浮点数比较的结果放入状态寄存器的c0c2c3条件代码位中其值如下 结果 c3 c2 c0st0 source000st0 source001st0 source100 如此倘若直接判断c0c2c3的值比较繁琐所以可以使用一些技巧首先使用fstsw指令获得fpu状态寄存器的值并存入ax再使用sahf指令把 ah寄存器中的值加载到eflags寄存器中sahf指令把ah寄存器的第0、2、4、6、7分别传送至进位、奇偶、对准、零、符号位不影响其他标 志ah寄存器中这些位刚好包含fpu状态寄存器的条件代码值所以通过fstsw和sahf指令组合可以传送如下值
把c0位传送到eflags的进位标志
把c2位传送到eflags的奇偶校验标志
把c3位传送到eflags的零标志传送完毕后可以用条件跳转使用不同的结果值另外需要说明的是浮点数相等判断因为浮点数本身存储结构决定了它仅仅是一个近似值所以不能直接判断是否相 等这样可能与自己预期的结果不同应该判断两个浮点数之差是否在一个很小的误差范围内来决定这两个浮点数是否相等。 根据上面的技巧使用fstsw和fpu指令组合可以方便的使用浮点判断结果这对我们是一种便利而intel的工程师又为我们设计了一个组合指令fcomi指令执行浮点比较结果并把结果存放到eflags寄存器的进位奇偶和零标志。 指令 说明fcomi比较st0和stx寄存器的值fcomip比较st0和stx寄存器并弹出堆栈fucomi比较之前检查无序值fucomip比较之前检查无序值之后弹出堆栈 判断结束后eflags的标志设置如下 结果 ZF PF CFst0 st(x)000st0 st(x)001st0 st(x)100 CMOV移动指令 最后介绍的是类似cmov的指令根据判断结果决定是否需要移动数据其ATT格式为 fcmovxx source, destination其中source是st(x)寄存器destination是st(0)寄存器。 指令 说明fcmovb如果st(0)小于st(x)则进行传送fcmove如果st(0)等于st(x)则进行传送fcmovbe如果st(0)小于或等于st(x)则进行传送fcmovu如果st(0)无序则进行传送fcmovnb如果st(0)不小于st(x)则进行传送fcmovne如果st(0)不等于st(x)则进行传送fcmovnbe如果st(0)不小于或等于st(x)则进行传送fcmovnu如果st(0)非无序则进行传送 以上可以看出无论从寄存器的操作还是计算过程都比整数运算要繁琐的多而且看似很简单的一个表达式转化成浮点汇编需要做很多工作由于其复杂性同 一个表达式可以有多种运算过程当然其中的效率相差很大这依赖于对浮点汇编的理解程度好在有高级语言处理相关工作编写浮点指令的情况比较少见。