第62部分- Linux x86 64位汇编 浮点数
单精度是这样的格式,1位符号,8位指数,23位小数。
指数是从-128到127的8位有符号整数,或者是从0到255的8位无符号整数。指数部分采用的偏置码(biased)的形式来表示正负指数,若小于127则为负的指数,否则为非负的指数。所以0到126代表-127到-1,127代表零,128-255代表1-128。
另外补码,一个数字的补码就是将该数字作比特反相运算(即反码),再将结果加1。在补码系统中,一个负数就是用其对应正数的补码来表示。
补码系统的最大优点是可以在加法或减法处理中,不需数字的正负而使用不同的计算方式。只要一种加法电路就可以处理各种有号数加法,而且减法可以用一个数加上另一个数的补码来表示,因此只要有加法电路及补码电路即可完成各种有号数加法及减法,在电路设计上相当方便。
单精度公式:(-1)^S*(1.M)*2^(E-127)
S是符号。
M是尾数。
E是指数。
双精度是1位符号,11位指数,52位小数。
双精度公式: (-1)^S*(1.M)*2^(E-1023)
扩展双精度是80位的,1位符号,15位指数,112位小数。
f、j、e 和 s 四个字段中的位模式值将决定整个位模式所表示的值。
计算模式相对复杂一点,大家可以忽略之。
双精度扩展位模式 (x86) | 值 |
j = 0, 0 < e <32767 | 不支持 |
j = 1, 0 < e < 32767 | |
j = 0, e = 0; f ≠ 0(f 中至少有一位不为零) | |
j = 1, e = 0 | (–1)s × 2–16382 × 1.f(伪非正规数) |
j = 0, e = 0, f = 0(f 中的所有位均为零) | (–1)s × 0.0(有符号的零) |
j = 1; s = 0; e = 32767; f = 0(f 中的所有位均为零) | |
j = 1; s = 1; e = 32767; f = 0(f 中的所有位均为零) | –INF(负无穷大) |
j = 1; s = u; e = 32767; f = .1uuu — uu | QNaN(quiet NaN,静态 NaN) |
j = 1; s = u; e = 32767; f = .0uuu — uu ≠ 0 (f 中至少一个 u 不为零) | SNaN(signaling NaN,信号 NaN) |
SSE浮点数据类型
除了3种标准浮点数据类型之外,SSE技术的INTEL处理器还包含两种高级浮点数据类型。8个128位XMM寄存器可以保存打包浮点数。类似打包BCD概念。
数据移动如下:
SSE2数据传送指令如下:
数据类型转换
PS是打包单精度FP
PD是打包双精度FP
PI是打包双字整数,使用MMX
DQ是打包双字整数
示例
转换数据类型如下:
.section .data
value1:
.float 1.25, 124.79, 200.0, -312.5
value2:
.int 1, -435, 0, -25
.section .bss
data:
.lcomm datas, 16
.section .text
.globl _start
_start:
nop
cvtps2dq value1, %xmm0;//打包单精度到打包双字整型
cvttps2dq value1, %xmm1;//打包单精度到打包双字整型,会截断
cvtdq2ps value2, %xmm2;//打包双字整型到打包单精度,会截断
movdqu %xmm0, datas;//保存到datas变量中。
movl $1, %eax
movl $0, %ebx
int $0x80
在value1中定义了一个打包单精度浮点值。
value2中定义了打包双字整数值。
使用gdb进行调试,可以看到如下xmm寄存器。
(gdb) print $xmm0
$8 = {v4_float = {1.40129846e-45, 1.75162308e-43, 2.80259693e-43, -nan(0x7ffec8)}, v2_double = {2.6524947387115311e-312, -nan(0xffec8000000c8)}, v16_int8 = {1, 0, 0, 0, 125, 0,
0, 0, -56, 0, 0, 0, -56, -2, -1, -1}, v8_int16 = {1, 0, 125, 0, 200, 0, -312, -1}, v4_int32 = {1, 125, 200, -312}, v2_int64 = {536870912001, -1340029796152},
uint128 = 340282342201751762702250093524836941825}
(gdb) print $xmm1
$9 = {v4_float = {1.40129846e-45, 1.7376101e-43, 2.80259693e-43, -nan(0x7ffec8)}, v2_double = {2.6312747808018783e-312, -nan(0xffec8000000c8)}, v16_int8 = {1, 0, 0, 0, 124, 0,
0, 0, -56, 0, 0, 0, -56, -2, -1, -1}, v8_int16 = {1, 0, 124, 0, 200, 0, -312, -1}, v4_int32 = {1, 124, 200, -312}, v2_int64 = {532575944705, -1340029796152},
uint128 = 340282342201751762702250093520541974529}
(gdb) print $xmm2
$10 = {v4_float = {1, -435, 0, -25}, v2_double = {-7.3498756827903427e+18, -805306368}, v16_int8 = {0, 0, -128, 63, 0, -128, -39, -61, 0, 0, 0, 0, 0, 0, -56, -63}, v8_int16 = {
0, 16256, -32768, -15399, 0, 0, 0, -15928}, v4_int32 = {1065353216, -1009156096, 0, -1043857408}, v2_int64 = {-4334292427813683200, -4483333429047328768},
uint128 = 257579462558195729010253313545846390784}
可以看到cvttps2dq指令会进行截断。
而cvtps2dq指令会进行舍入。
另外,最后在调试的时候大家会发现,xmm寄存器已经被扩展了,已经变成了256位的zmm了。