跳转到内容

x86 汇编/浮点数

来自维基教科书,开放的书籍,开放的世界

ALU 只能处理整数值。虽然整数对于某些应用程序来说已经足够了,但通常需要使用小数。一个高度专业化的协处理器,是 FPU(浮点单元)的一部分,将允许您操作带有小数部分的数字。

x87 协处理器

[编辑 | 编辑源代码]

最初的 x86 家族成员拥有一个独立的数学协处理器,用于处理浮点运算。原始的协处理器是 8087,从那时起,所有 FPU 都被称为“x87”芯片。后来,变体将 FPU 集成到微处理器本身。能够管理浮点数意味着几件事

  1. 微处理器必须有空间来存储浮点数。
  2. 微处理器必须有指令来操作浮点数。

即使 FPU 被集成到 x86 芯片中,它仍然被称为“x87”部分。例如,该主题的文献通常将 FPU 寄存器堆栈称为“x87 堆栈”,并且 FPU 操作通常被称为“x87 指令集”。

可以使用 cpuid 指令检查集成 x87 FPU 的存在。

; after you have verified
; that the cpuid instruction is indeed available:
mov eax, 1     ; argument request feature report
cpuid
xor rax, rax   ; wipe clean accumulator register
bt edx, rax    ; CF ≔ edx[rax]    retrieve bit 0
setc al        ; al ≔ CF

FPU 寄存器堆栈

[编辑 | 编辑源代码]

FPU 有一个包含 *8 个* 寄存器的数组,可以作为堆栈访问。有一个 *top* 索引指示堆栈的当前顶部。将项目推入或弹出堆栈只会更改 *top* 索引,并分别存储或擦除数据。

st(0) 或简称为 st 指的是当前位于堆栈顶部的寄存器。如果堆栈上存储了 8 个值,则 st(7) 指的是堆栈上的最后一个元素(即底部)。

数字从 *内存中* 推入堆栈,并从堆栈弹出回 *内存中*。没有指令允许直接将值传输到或从 ALU 寄存器。x87 堆栈只能通过 FPU 指令访问 - 您不能编写 mov eax, st(0) - 有必要将值存储到内存中,例如,如果您想打印它们。

FPU 指令通常会从堆栈中弹出前两个项目,对它们进行操作,并将答案推回堆栈顶部。

浮点数通常可以是 32 位长,即编程语言 C 中的 float 数据类型,或者 64 位长,即 C 中的 double。但是,为了减少舍入误差,FPU 堆栈寄存器都 *80 位宽*。

大多数 调用约定st(0) 寄存器中返回浮点值。

以下程序(使用 NASM 语法)计算 123.45 的平方根。

[org 0x7c00]
[bits 16]

global _start

section .data
	val: dq 123.45   ; define quadword (double precision)

section .bss
	res: resq 1      ; reserve 1 quadword for result

section .text


_start:
    ;initilizes the FPU, avoids inconsistent behavior
    fninit
	; load value into st(0)
	fld qword [val]  ; treat val as an address to a qword
	; compute square root of st(0) and store the result in st(0)
	fsqrt
	; store st(0) at res, and pop it off the x87 stack
	fstp qword [res]
	; the FPU stack is now empty again

	; end of program

本质上,使用 FPU 的程序使用 fld 及其变体将值加载到堆栈上,对这些值执行操作,然后使用 fst 的一种形式将它们存储到内存中,最常见的是在您完成 x87 后使用 fstp,以根据大多数调用约定清理 x87 堆栈。

这是一个更复杂的示例,它评估 余弦定律

;; c^2 = a^2 + b^2 - cos(C)*2*a*b
;; C is stored in ang

global _start

section .data
    a: dq 4.56   ;length of side a
    b: dq 7.89   ;length of side b
    ang: dq 1.5  ;opposite angle to side c (around 85.94 degrees)

section .bss
    c: resq 1    ;the result ‒ length of side c

section .text
    _start:

    fld    qword [a]   ;load a into st0
    fmul   st0, st0    ;st0 = a * a = a^2

    fld    qword [b]   ;load b into st0   (pushing the a^2 result up to st1)
    fmul   st0, st0    ;st0 = b * b = b^2,   st1 = a^2

    faddp              ;add and pop, leaving st0 = old_st0 + old_st1 = a^2 + b^2.  (st1 is freed / empty now)

    fld    qword [ang] ;load angle into st0.  (st1 = a^2 + b^2 which we'll leave alone until later)
    fcos               ;st0 = cos(ang)

    fmul   qword [a]   ;st0 = cos(ang) * a
    fmul   qword [b]   ;st0 = cos(ang) * a * b
    fadd   st0, st0    ;st0 = cos(ang) * a * b + cos(ang) * a * b = 2(cos(ang) * a * b)

    fsubp  st1, st0    ;st1 = st1 - st0 = (a^2 + b^2) - (2 * a * b * cos(ang))
                       ;and pop st0

    fsqrt              ;take square root of st0 = c

    fstp   qword [c]   ;store st0 in c and pop, leaving the x87 stack empty again ‒ and we're done!

    ; don't forget to make an exit system call for your OS,
    ; or execution will fall off the end and decode whatever garbage bytes are next.
    mov   eax, 1                ; __NR_exit
    xor   ebx, ebx
    int   0x80                  ; i386 Linux sys_exit(0)
    ;end program

浮点数指令集

[编辑 | 编辑源代码]

您可能会注意到,以下某些指令在名称上只差一个字母:在末尾附加一个 **P**。该后缀表示除了执行正常操作之外,它们还会在执行完成后 **P**op x87 堆栈。

原始 8087 指令

[编辑 | 编辑源代码]

FDISI, FENI, FLDENVW, FLDPI, FNCLEX, FNDISI, FNENI, FNINIT, FNSAVEW, FNSTENVW, FRSTORW, FSAVEW, FSTENVW

数据传输指令

[编辑 | 编辑源代码]
  • fld: 加载浮点数
  • fild: 加载整数
  • fbld
  • fbstp
  • 在堆栈顶部加载一个常数
    • fld1:
    • fldld2e:
    • fldl2t:
    • fldlg2:
    • flln2:
    • fldz: “正”
  • fst, fstp
  • fist, fistp: 存储整数
  • fxch: 交换
  • fisttp: 存储截断的整数

算术指令

[edit | edit source]
  • fabs: 绝对值
  • fchs: 改变符号
  • fxtract: 拆分指数和有效数
  • fadd, faddp, fiadd: 加法
  • fsub, fsubp, fisub: 减法
  • fsubr, fsubrp, fisubr: 反向减法
  • finit: 初始化 FPU
  • fldcw
  • flenv
  • frstor
  • fsave, fnsave
  • fstcw, fnstcw
  • fstenv, fnstenv
  • fstsw, fnstsw
  • finccstpfdecstp: 增加或减少 top
  • ffree: 将寄存器标记为可用
  • ftst: 测试
  • fcom, fcomp, fcompp: 比较浮点数
  • ficom, ficomp: 与整数比较
  • fxam: 检查寄存器
  • fclex: 清除异常
  • fnop
  • fwaitwait 相同。

在特定处理器中添加

[edit | edit source]

随 80287 添加

[edit | edit source]

FSETPM

随 80387 添加

[edit | edit source]

FCOS, FLDENVD, FNSAVED, FNSTENVD, FPREM1, FRSTORD, FSAVED, FSIN, FSINCOS, FSTENVD, FUCOM, FUCOMP, FUCOMPP

随奔腾 Pro 添加

[edit | edit source]

FCMOVB, FCMOVBE, FCMOVE, FCMOVNB, FCMOVNBE, FCMOVNE, FCMOVNU, FCMOVU, FCOMI, FCOMIP, FUCOMI, FUCOMIP, FXRSTOR, FXSAVE

随 SSE 添加

[编辑 | 编辑源代码]

FXRSTOR,FXSAVE

这些指令也在没有 SSE 支持的后期奔腾 II 中得到支持

随 SSE3 添加

[编辑 | 编辑源代码]

FISTTP(x87 到整数转换,无论状态字如何都进行截断)

未公开指令

[编辑 | 编辑源代码]
  • ffreep: 执行 ffree st(i) 并弹出堆栈

进一步阅读

[编辑 | 编辑源代码]
华夏公益教科书