Skip to content

Latest commit

 

History

History
268 lines (208 loc) · 8.91 KB

File metadata and controls

268 lines (208 loc) · 8.91 KB

9 高阶数学函数

在很早以前算浮点数是很麻烦的一件事情.这一章将介绍如何FPU如何计算浮点数.

FPU 环境

FPU 寄存器栈

FPU是一个自给自足的单元,用一些独立于标准寄存器的寄存器进行浮点数的运算.

FPU数据寄存器叫做R0一直到R7(你会发现,它们不是通过这些名字来获取的).这些寄存器不同的是,它们链接在一起构成一个栈.除FPU寄存器之外还包含8个80-bit的寄存器,和3个16-bit的寄存器,叫做control,status,tag

FPU 数据寄存器是R0到R7.这几个寄存器连在一起构成一个环形的的栈.栈顶就作ST(0),寄存器根据栈顶的相对位置叫做ST(x),x可以是1到7.

如果有第几个数存入了栈中,就会覆盖第一个寄存器,然后报一个FPU的异常错误.

第七章讲了浮点数是如何通过FLD命令被放置在FPU栈上的.FILD 是整数,FBLD是BCD数据.

FPU status control tag 寄存器

因为FPU是独立于处理器的,所以不使用EFLAGS来表示行为结果.FPU拥有一套相关的寄存器来体现这些功能. status,control,tag 寄存器就是用来表示FPU的状态的.

status 寄存器

表示FPU的操作状态

control 寄存器

控制浮点数精度

tag 寄存器

检测FPU寄存器中的数据的有效性,能够知道什么类型的数据存在了对应的寄存球中

使用FPU栈

以例子驱动

# stacktest.s - An example of working
.section .data
value1:
        .int 40
value2:
        .float 92.4405
value3:
        .double 211.440321
.section .bss
        .lcomm int1,    4   
        .lcomm control, 2    
        .lcomm status,  2    
        .lcomm result,  4
.section .text
.globl _start
_start:
        nop
        finit           #initilize FPU
        fstcw   control #copy control register to memory location
        fstsw   status  #copy status register to memory location 
        filds   value1  #loads a doubleword integer value into the FPU register stack
        fists   int1    #retrieves the value at the top of the register stack,and places it into int1 memory location
        flds    value2  #load a single-precision floating-point data located in the value2 memory location
        fldl    value3  #load a dluble-precision floating-point data located in the value3 memory location
        fst %st(4)      #move data from the ST0 register to ST4 register.
        fxch %st(1)     #exchange value of ST0 register with ST1 register
        fstps result    #create signle-precision floating-point value stored in from the value in the ST0 position
        mov $60,%rax
        mov $0, %rdi
        syscall

FINIT初始化FPU.把control和status寄存器设置成默认值,但是不影响存储在FPU寄存器里面的值. 接下来两条命令将control和status寄存器赋值到内存当中 通过gdb查看值可以看到

(gdb) x/2b &control
0x600110 <control>:	127	3
(gdb) x/2b &status
0x600112 <status>:	0	0

control的默认值是0x37f(上面显示的10进制的,而且是小端存储),status的默认值是0x0000. FILDS 把一个doubleword 整型存到FPU的寄存器栈中.

(gdb) info all
.
.
st0            40	(raw 0x4004a000000000000000)
.
.
(gdb) x/d &int1
0x60010c <int1>:	40

40被存到了栈顶,也就是ST0寄存器中,这个值被转换成了double-extended 浮点数.但是转存会内存的时候还是会自动转换成对应的整型的.接下来的命令首先使用了FLDS将内存中的一个单精度的浮点数加载进来,FLDL将一个双精度的浮点数加载了进来. 现在有三个值存储在FPU栈中,当每个数据加载的时候,每个值都会相对于栈顶向下移动.

(gdb) info all
st0            211.44032100000001150874595623463392	(raw 0x4006d370b8e086bdf800)
st1            92.44049835205078125	(raw 0x4005b8e1890000000000)
st2            40	(raw 0x4004a000000000000000)

最后三个指令用于数据的移动,FST用来把ST0寄存器的值移动到另一个FPU寄存器里面.而FXCH用来将ST0寄存器的值和另一个FPU寄存器交换的值和另一个FPU寄存器交换. 运行完这些命令之后,就可以看到如下结果


(gdb) info all
st0            92.44049835205078125	(raw 0x4005b8e1890000000000)
st1            211.44032100000001150874595623463392	(raw 0x4006d370b8e086bdf800)
st2            40	(raw 0x4004a000000000000000)
st3            0	(raw 0x00000000000000000000)
st4            211.44032100000001150874595623463392	(raw 0x4006d370b8e086bdf800)

FST 命令用来拷贝栈顶的值到内存中. FSTP 命令也会赋值ST0寄存器的值,但是会弹栈.弹栈之后下面的值都会提升. 现在我们基本掌握了操作FPU栈的方法,下面就来进入运算部分.

基本浮点运算

FPU能够进行基本的浮点运算,基本功能如下

Instruction 	Description
FADD 	Floating-point addition
FDIV 	Floating-point division
FDIVR 	Reverse floating-point division
FMUL 	Floating-point multiplication
FSUB 	Floating-point subtraction
FSUBR 	Reverse floating-point subtraction

FSUBR和FDIVR指令用来执行反转的减法和除法,结果是destination减去(或者除以)source,结果存在destination当中.这两个指令可以不需要额外的开销就能把操作数调换位置. 为了说明这些命令是如何工作的,用一个复杂的式子来表示一下.

((43.65 / 22) + (76.34 * 3.1)) / ((12.43 * 6) – (140.2 / 94.21))

这个就是通过FPU栈用逆波兰表达式来算结果.通过上节讲到的基本操作和这节的基本运算就能完成整个逆波兰表达式的运算了.

高阶浮点数运算

浮点数的运算不仅仅是加减乘除.通过FPU可以进行很多更高阶的运算.如果你在为科学计算或者工程应用写汇编的话,你会非常愿意和这些高阶的函数打交到.


F2XM1 	Computes 2 to the power of the value in ST0, minus 1
FABS 	Computes the absolute value of the value in ST0
FCHS 	Changes the sign of the value in ST0
FCOS 	Computes the cosine of the value in ST0
FPATAN 	Computes the partial arctangent of the value in ST0
FPREM 	Computes the partial remainders from dividing the value in ST0 by the value in ST1
FPREM1 	Computes the IEEE partial remainders from dividing the value in ST0 by the value in ST1
FPTAN 	Computes the partial tangent of the value in ST0
FRNDINT Rounds the value in ST0 to the nearest integer
FSCALE 	Computes ST0 to the ST1st power
FSIN 	Computes the sine of the value in ST0
FSINCOS Computes both the sine and cosine of the value in ST0
FSQRT 	Computes the square root of the value in ST0
FYL2X 	Computes the value ST1 * log ST0 (base 2 log)
FYL2XP1 Computes the value ST1 * log (ST0 + 1) (base 2 log)

大部分都已经说的很明显了,下面一节会具体讲解其中的一些.

partial remainder 部分余数?

三角函数

FPU还能算三角函数

FSIN和FCOS命令

用来算三角函数的,以后补例子.

FPTAN和FPATAN命令

相应的三角函数,以后补例子.

对数函数

浮点数条件分支

不幸的是,比较浮点数不像整型那么简单.浮点数用专有的方法来进行比较.

FCOM 指令族


FCOM		Compare the ST0 register with the ST1 register.
FCOM ST(x) 	Compare the ST0 register with another FPU register.
FCOM source 	Compare the ST0 register with a 32- or 64-bit memory value.
FCOMP 		Compare the ST0 register with the ST1 register value and pop the stack.
FCOMP ST(x) 	Compare the ST0 register with another FPU register value and pop the stack.
FCOMP source 	Compare the ST0 register with a 32 or 64-bit memory value and pop the stack.
FCOMPP 		Compare the ST0 register with the ST1 register and pop the stack twice.
FTST 		Compare the ST0 register with the value 0.0.

结果是存在C0,C1和C3这几个status寄存器的条件位中


Condition 	C3 	C2 	C0
ST0 > source 	0 	0 	0
ST0 < source 	0 	0 	1
ST0 = source 	1 	0 	0

必须使用FSTSW指令拷贝status寄存器到AX寄存器当中或者其他的内存地址,然后使用TEST指令来决定比较的结果.

# fcomtest.s - An example of the FCOM instruction

.section .data

value1:
	.float 10.923
value2:
	.float 4.5532
.section .text
	.global _start
_start:
	nop
	flds value1
	fcoms value2
	fstsw
	sahf
	ja greater
	jb lessthan
	movq $60,%rax
	movq $0	,%rdi
	syscall
greater:
	movq $60,%rax
	movq $1 ,%rdi
	syscall

lessthan:
	movq $60,%rax
	movq $2 ,%rdi
	syscall
./fcomtest
echo $?
1

SAHF 移动AH寄存器的0,2,4,6,7位,分别放到carry,parity,aligned,zero和signed标志中.其他在EFLAGS的位不会受影响.

C0  到 carry
C1  到 parity
C3  到 zero

所以相应的跳转就是JA,JB,JZ了

FCOMI 指令族

很多人会有疑问干吗不把那套东西用一条指令解决?这不来了么 FCOMI就是干这个的.

FCOMI	Compare the ST0 register with the ST(x) register.
FCOMIP 	Compare the ST0 register with the ST(x) register and pop the stack.
FUCOMI 	Check for unordered values before the comparison.
FUCOMIP Check for unordered values before the comparison and pop the stack afterward.

FCOMV 指令族

存取FPU的状态