Linux汇编之浮点数

汇编 2015年03月19日 ,

Linux汇编浮点数 Intel处理器是从80486开始支持浮点数运算,在这之前需要在软件中用整数模拟浮点值,或者使用专门用于执行浮点运算的单独的FPU芯片。本文中枫竹梦介绍Linux汇编语言(ASM)中浮点数据类型,并演示如何使用它。

浮点数是什么

前面介绍的数字系统都和整数相关。然而使用整数不能描述所有的数字关系。有时需要使用小数(有限的或者无限的)的概念,它们共同填满整个数轴,一起称为实数(Real number)。在计算机中处理实数是很困难的,浮点格式就是为了在计算机系统中描述实数。

浮点格式

浮点格式使用科学计数法表示实数。科学计数法把数字表示为系数(Coefficient)和指数(exponent),如2.525*10^2。系数范围为[1, 10)。十进制中,指数的基数值为10。

二进制浮点格式

计算机中使用二进制浮点数,指数基数为2。如1.1011101*2^2。处理系统的小数部分容易引起混乱。为了对二进制浮点值进行译码,必须了解二进制小数数字的意义。在十进制领域中,0.159表示是0+(1/10)+(5/100)+(9/1000)。对于二进制小数同样适用。如二进制101.01,十进制整数表示5,小数为0/2+1/4,合在一起表示5.25。对应关系如下:

二进制 十进制分数 十进制值
0.1 1/2 0.5
0.01 1/4 0.25
0.001 1/8 0.125
0.0001 1/16 0.0625
0.00001 1/32 0.03125
0.000001 1/64 0.015625

如进制数10.101表示十进制数2+1/2+1/8=2.625,那么表示十进制1/3该如何处理呢?显然无法通过有限的位数来表示这个数,需要在表示足够多的位数时截断小数,并且只能挖的表达。 编写二进制浮点值时,二进制值通常被规格化了。如10.101变成1.0101*2^1。

标准浮点数据类型

虽然实数值的数量是无限的,但是处理器用来处理的值数量是有限的,创建一个标准的系统用于在计算机环境中近似地表示实数。虽然近似方案不完美,但至少提供了处理现实的实数系统的子集的系统。 IEEE标准754规定的浮点格式,用于在计算机系统中通用地表示实数:

  • 符号
  • 有效数字
  • 指数

符号位表示值是负的还是正的。符号位中的1表示负值,0表示正值。 有效数字表示浮点数的系数。可以是规格化的,也可以是非规格化的。规格化的是指小数点前为1。指数表示浮点数的指数部分,由于指数值可以是正值,也可以是负值,通过一个偏差值保证指数字段只能是无符号正整数。 二进制浮点数的一般格式为: 二进制浮点数一般格式 浮点数的三个部分被包含在固定长度数据格式之内。IEEE标准754定义了浮点数的两种长度:

  • 32位(单精度)
  • 64位(双精度)

如下两图分别为两种长度下的各部分所占的位数。 IEEE标准754浮点格式-单精度 IEEE标准754浮点格式-双精度 单精度浮点数使用23位有效数字。但是,浮点格式假设有效数字的整数值永远是1,并且不在有效数字值中使用它。这样实际使有效数字的精度达到了24位。指数使用8位值,偏差为127,指数值的范围达到了-128~127。这样浮点数的范围是1.18*10^-38~3.4*10^38。 双精度浮点数使用52位有效数字。它提供的有效数字精度为53位。指数使用11位值,偏差为1023,指数值的范围达到了-1024~1023。这样浮点数的范围是2.23*10^-308~1.79*10^308。

IA-32浮点值

IA-32平台使用IEEE标准的754的单精度和双精度浮点格式,同时增加80位浮点格式,称为扩展双精度浮点格式。扩展双精度浮点格式使用在80位的FPU寄存器内。三种格式的表示范围下表所示:

数据类型 长度 有效数字位 指数位 范围
单精度 32 24 8 1.18*10^-38~3.4*10^38
双精度 64 53 11 2.23*10^-308~1.79*10^308
扩展双精度 80 64 15 3.37*10^-4932~1.18*10^4932

在汇编中定义浮点值

浮点值按照小尾数格式存储在内在中。数组按照命令中定义值的顺序存储。.float命令用于创建32位单精度值,.double命令用于创建64位双精度值。

传送浮点值

FLD指令用于把浮点值传送入和传送出FPU寄存器。FLD指令的格式是:

fld source

其中source可以是32位、64位或者80位的内在位置。 如下演示如何在汇编程序中定义和使用浮点值:

# float.s - By furzoom @ Mar 17, 2015
.section .data
value1:
.float 12.34
value2:
.double 2353.631
.section .bss
.lcomm data, 8
.section .text
.globl _start
_start:
nop
flds value1
fldl value2
fstl data

movl $1, %eax
movl $0, %ebx
int $0x80

该程序首先在标签value1指向的内存位置定义单精度浮点值,标签value2指向的内存位置定义双精度浮点值,标签data指向内存8个字节长度的空缓冲区。IA-32使用FLD指令将存储在内存位置的单精度和双精度浮点数加载到FPU寄存器堆栈中。FLDS指令用于加载单精度浮点数,FLDL指令用于加载双精度浮点数。FST指令用于将FPU寄存器堆栈中顶部的值,放到内存位置中,FSTS用于单精度浮点数,FSTL用于双精度浮点数。 编译调试程序,查看内存中的浮点值的存储状态:

[mn@furzoom asm]$ as -gstabs -o float.o float.s
[mn@furzoom asm]$ ld -o float float.o
[mn@furzoom asm]$ gdb -q float
Reading symbols from /home/mn/Desktop/Documents/asm/float...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file float.s, line 13.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/float

Breakpoint 1, _start () at float.s:13
13        flds value1
(gdb) x/4xb &value1
0x8049094 :     0xa4    0x70    0x45    0x41
(gdb) x/8xb &value2
0x8049098 :     0x8d    0x97    0x6e    0x12    0x43    0x63    0xa2    0x40
(gdb) x/fw &value1
0x8049094 :     12.3400002
(gdb) x/fg &value2
0x8049098 :     2353.6309999999999
(gdb)

注意到,内存中存储的值已经是近似的值。x命令的f参数用于显示浮点数,w表示4个字节一组,这里为单精度浮点数,g表示8个字节一组,这里为双精度浮点数。接着单步执行,并查看FPU寄存器的值:

(gdb) step
14        fldl value2
(gdb) print $st0
$1 = 12.340000152587890625
(gdb) step
15        fstl data
(gdb) print $st0
$2 = 2353.6309999999998581188265234231949
(gdb) print $st1
$3 = 12.340000152587890625
(gdb)

FLD指令将浮点值压入FPU寄存器堆栈中,ST0表示栈顶,当加载新值时,ST0的值被加载到ST1寄存器。查看执行FST指令前后的data标签内存的值:

(gdb) x/fg &data
0x80490a0 <data>:       0
(gdb) step
17        movl $1, %eax
(gdb) x/fg &amp;data
0x80490a0 <data>:       2353.6309999999999
(gdb)

FSTL指令将ST0寄存器中的值加载到了data标签指向的内存位置。

使用预置浮点值

IA-32指令集包含一些预置的浮点值,如下。

指令 描述
FLD1 把1.0压入FPU堆栈中
FLDL2T 把10的对数(底数2)压入FPU堆栈中
FLDL2E 把e的对数(底数2)压入FPU堆栈中
FLDPI 把pi的值压入FPU堆栈中
FLDLG2 把2的对数(底数10)压入FPU堆栈中
FLDLN2 把2的对数(底数e)压入FPU堆栈中
FLDZ 把+0.0压入FPU堆栈中

这些指令提供了把数学值压入FPU堆栈中的简便方式。FLDZ指令压入的是正的0.0,一般计算中+0.0与-0.0是没有差别的,但是在除法中,是有区别的,会产生正无穷大和负无穷大的值。如下程序演示对预置浮点值的使用:

# fpuvals.s - By furzoom @ Mar 18, 2015
.section .text
.globl _start
_start:
nop
fld1
fldl2t
fldl2e
fldpi
fldlg2
fldln2
fldz

movl $1, %eax
movl $0, %ebx
int $0x80

该程序依次将几个预置浮点值压入堆栈,调试运行如下:

[mn@furzoom asm]$ as -gstabs -o fpuvals.o fpuvals.s
[mn@furzoom asm]$ ld -o fpuvals fpuvals.o
[mn@furzoom asm]$ gdb -q fpuvals
Reading symbols from /home/mn/Desktop/Documents/asm/fpuvals...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048055: file fpuvals.s, line 6.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/fpuvals

Breakpoint 1, _start () at fpuvals.s:6
6         fld1
(gdb) step
7         fldl2t
(gdb)
8         fldl2e
(gdb)
9         fldpi
(gdb)
10        fldlg2
(gdb)
11        fldln2
(gdb)
12        fldz
(gdb)
14        movl $1, %eax
(gdb) info all
st0            0        (raw 0x00000000000000000000)
st1            0.6931471805599453094286904741849753     (raw 0x3ffeb17217f7d1cf79ac)
st2            0.30102999566398119522564642835948945    (raw 0x3ffd9a209a84fbcff799)
st3            3.1415926535897932385128089594061862     (raw 0x4000c90fdaa22168c235)
st4            1.4426950408889634073876517827983434     (raw 0x3fffb8aa3b295c17f0bc)
st5            3.3219280948873623478083405569094566     (raw 0x4000d49a784bcd1b8afe)
st6            1        (raw 0x3fff8000000000000000)
st7            0        (raw 0x00000000000000000000)
(gdb)

值的顺序和它们入栈的顺序相反。

SSE浮点数据类型

除了3种标准浮点数据类型以外,Intel还支持两种高级的浮点数类型,打包浮点数。打包浮点数允许多个浮点值存储在单一寄存器中。2种新的浮点数为128位的浮点数据类型:

  • 128位打包单精度浮点(SSE中)
  • 128位打包双精度浮点(SSE2中)

128位的寄存器可以保存4个打包的单精度浮点值,或者2个打包的双精度浮点值。与打包BCD类似。

传送SSE浮点值

IA-32提供了对打包浮点值操作的指令,SSE指令用于操作打包单精度浮点数,SSE2用于操作打包双精度浮点值。

SSE浮点值

用于在内存和处理器上XMM寄存器间传送128位打包单精度浮点数的指令如下。

指令 描述
MOVAPS 把4个对准的打包单精度值传送到XMM寄存器或者内存
MOVUPS 把4个不对准的打包单精度值传送到XMM寄存器或者内存
MOVSS 把1个单精度值传送到内存或者寄存器的低双字
MOVLPS 把2个单精度值传送到内存或者寄存器的低四字
MOVHPS 把2个单精度值传送到内存或者寄存器的高四字
MOVLHPS 把2个单精度值从低四字传送到高四字
MOVHLPS 把2个单精度值从高四字传送到低四字

如下示例演示如下使用上述命令传送浮点值。

# ssefloat.s - By furzoom @ Mar 19, 2015
.section .data
value1:
.float 12.34, 2345.543, -3493.2, 0.44901
value2:
.float -5439.234, 32121.4, 1.0094, 0.000003
.section .bss
.lcomm data, 16
.section .text
.globl _start
_start:
nop
movups value1, %xmm0
movups value2, %xmm1
movups %xmm0, %xmm2
movups %xmm0, data

movl $1, %eax
movl $0, %ebx
int $0x80

该程序定义了2组数据,每组数据由4个单精度值组成,还在缓冲区定义了一个16字节空间,用于保存SSE浮点值。对程序进行汇编调试如下:

[mn@furzoom asm]$ as -gstabs -o ssefloat.o ssefloat.s
[mn@furzoom asm]$ ld -o ssefloat ssefloat.o
[mn@furzoom asm]$ gdb -q ssefloat
Reading symbols from /home/mn/Desktop/Documents/asm/ssefloat...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file ssefloat.s, line 13.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/ssefloat

Breakpoint 1, _start () at ssefloat.s:13
13        movups value1, %xmm0
(gdb) step
14        movups value2, %xmm1
(gdb)
15        movups %xmm0, %xmm2
(gdb)
16        movups %xmm0, data
(gdb)
18        movl $1, %eax
(gdb) print $xmm0
$1 = {v4_float = {12.3400002, 2345.54297, -3493.19995, 0.449010015},
  v2_double = {5.6204289471764299e+24, 1.0439462282443856e-05}, v16_int8 = {
    -92, 112, 69, 65, -80, -104, 18, 69, 51, 83, 90, -59, -92, -28, -27, 62},
  v8_int16 = {28836, 16709, -26448, 17682, 21299, -15014, -7004, 16101},
  v4_int32 = {1095069860, 1158846640, -983936205, 1055253668}, v2_int64 = {
    4977208420974555300, 4532279996355072819},
  uint128 = 0x3ee5e4a4c55a5333451298b0414570a4}
(gdb) print $xmm1
$2 = {v4_float = {-5439.23389, 32121.4004, 1.00940001, 3.00000011e-06},
  v2_double = {8.7452727745837517e+33, 3.4658329842889617e-47}, v16_int8 = {
    -33, -7, -87, -59, -51, -14, -6, 70, 5, 52, -127, 63, -100, 83, 73, 54},
  v8_int16 = {-1569, -14935, -3379, 18170, 13317, 16257, 21404, 13897},
  v4_int32 = {-978716193, 1190851277, 1065432069, 910775196}, v2_int64 = {
    5114667292431088095, 3911749681893422085},
  uint128 = 0x3649539c3f81340546faf2cdc5a9f9df}
(gdb) print $xmm2
$3 = {v4_float = {12.3400002, 2345.54297, -3493.19995, 0.449010015},
  v2_double = {5.6204289471764299e+24, 1.0439462282443856e-05}, v16_int8 = {
    -92, 112, 69, 65, -80, -104, 18, 69, 51, 83, 90, -59, -92, -28, -27, 62},
  v8_int16 = {28836, 16709, -26448, 17682, 21299, -15014, -7004, 16101},
  v4_int32 = {1095069860, 1158846640, -983936205, 1055253668}, v2_int64 = {
    4977208420974555300, 4532279996355072819},
  uint128 = 0x3ee5e4a4c55a5333451298b0414570a4}
(gdb) x /4fw &data
0x80490c0 <data>:       12.3400002      2345.54297      -3493.19995     0.449010015
(gdb)

所有数据都被正确的传送到XMM寄存器中。v4_float格式显示使用的打包单精度浮点值。查看data数据也是正确的结果。

SSE2浮点值

和SSE数据类型相似,用于传送SSE2打包双精度浮点值的指令如下所示。

指令 描述
MOVAPD 把2个对准的双精度值传送到内存或者寄存器
MOVUPD 把2个不对准的双精度值传送到内存或者寄存器
MOVSD 把1个双精度值传送到内存或者寄存器的低四字
MOVHPD 把2个双精度值传送到内存或者寄存器的高四字
MOVLPD 把1个双精度值传送到内存或者寄存器的低四字

这些指令的每一条都使用128位XMM寄存器传送64位双精度浮点值。如下代码演示如何使用上述指令。

# sse2float.s - By furzoom @ Mar 19, 2015
.section .data
value1:
.double 12.34, 2345.543
value2:
.double -5439.234, 32121.4
.section .bss
.lcomm data, 16
.section .text
.globl _start
_start:
nop
movupd value1, %xmm0
movupd value2, %xmm1
movupd %xmm0, %xmm2
movupd %xmm0, data

movl $1, %eax
movl $0, %ebx
int $0x80

该程序将2组双精度浮点数据传送给XMM寄存器,再将其传送给内存位置。调试运行如下:

[mn@furzoom asm]$ as -gstabs -o sse2float.o sse2float.s
[mn@furzoom asm]$ ld -o sse2float sse2float.o
[mn@furzoom asm]$ gdb -q sse2float
Reading symbols from /home/mn/Desktop/Documents/asm/sse2float...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file sse2float.s, line 13.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/sse2float

Breakpoint 1, _start () at sse2float.s:13
13        movupd value1, %xmm0
(gdb) step
14        movupd value2, %xmm1
(gdb)
15        movupd %xmm0, %xmm2
(gdb)
16        movupd %xmm0, data
(gdb)
18        movl $1, %eax
(gdb) print $xmm0
$1 = {v4_float = {5.84860315e+35, 2.63562489, 1.79352231e-36, 5.07264233},
  v2_double = {12.34, 2345.5430000000001}, v16_int8 = {-82, 71, -31, 122, 20,
    -82, 40, 64, 117, -109, 24, 4, 22, 83, -94, 64}, v8_int16 = {18350, 31457,
    -20972, 16424, -27787, 1048, 21270, 16546}, v4_int32 = {2061584302,
    1076407828, 68719477, 1084379926}, v2_int64 = {4623136420479977390,
    4657376318677619573}, uint128 = 0x40a25316041893754028ae147ae147ae}
(gdb) print $xmm1
$2 = {v4_float = {-1.11704749e+24, -5.66396856, -1.58818684e-23, 6.98026705},
  v2_double = {-5439.2340000000004, 32121.400000000001}, v16_int8 = {68, -117,
    108, -25, 59, 63, -75, -64, -102, -103, -103, -103, 89, 94, -33, 64},
  v8_int16 = {-29884, -6292, 16187, -16203, -26214, -26215, 24153, 16607},
  v4_int32 = {-412316860, -1061863621, -1717986918, 1088380505}, v2_int64 = {
    -4560669521124488380, 4674558677155944858},
  uint128 = 0x40df5e599999999ac0b53f3be76c8b44}
(gdb) print $xmm2
$3 = {v4_float = {5.84860315e+35, 2.63562489, 1.79352231e-36, 5.07264233},
  v2_double = {12.34, 2345.5430000000001}, v16_int8 = {-82, 71, -31, 122, 20,
    -82, 40, 64, 117, -109, 24, 4, 22, 83, -94, 64}, v8_int16 = {18350, 31457,
    -20972, 16424, -27787, 1048, 21270, 16546}, v4_int32 = {2061584302,
    1076407828, 68719477, 1084379926}, v2_int64 = {4623136420479977390,
    4657376318677619573}, uint128 = 0x40a25316041893754028ae147ae147ae}
(gdb) x /2fg &data
0x80490c0 <data>:       12.34   2345.5430000000001
(gdb)  

其中v2_double是双精度浮点数据。同样得到期望的结果。

其他指令SSE指令

还有SSE3指令、SSSE3指令、SSE4指令、SSE4.1指令、SSE4.2指令、AVX EM64T指令等,增强对浮点数据的操作,不做详细介绍。

(完)

如无特别说明,本站文章皆为原创,若要转载,务必请注明以下原文信息:
日志标题:《Linux汇编之浮点数》
日志链接:http://furzoom.com/linux-asm-float/
博客名称:枫竹梦

发表评论

插入图片

NOTICE1:请申请gravatar头像,没有头像的评论可能不会被回复!

回到顶部