临时实数的加法运算模拟了整数的加法运算,把符号位和尾数部分整合成一个signed整数,在利用add指令完成加法操作。
一 正数与负数
这里说一下我自己理解的另一种补码的表现方式,以下用一个byte来做解释,先来举个例子
5的补码:0b00000101,它对应的无符号值也是为5
-5的补码:0b00000101 -> 取反-> 0b11111010 -> +1 ->0b11111011,它对应的无符号值是251
细心的你应该早已发现,-5的补码其实就是256-5的无符号值,8位二进制可以表示256种数值,也即8位的无符号值可以对应出8位带符号的值
0 -> 0
1 -> 1
...
127 -> 127
128 -> -128
129 -> -127
...
255 -> -1
可以发现负数的补码其实是256-(-负数)的二进制形式。
计算机课程中,-5的补码形式是将原码的符号位不变,数值位取反,然后加1,我们将256-(-负数)变化一下就是255-(-负数)+1,有没有感觉,255-(-负数)其实就是数值位取反。还有一个非常妙的地方,251(-5的无符号数值)的补码是什么,我们来计算一下,256-(-5)=5,也就是说5的正数和负数的补码运算是可逆的。
二 求补码
/* 求补码,正数的补码为本身, 负数的补码等于其原码的符号位不变,数值部分的各位取反,然后整个数加1。 */ #define NEGINT(a) \ __asm__("notl %0 ; notl %1 ; addl $1,%0 ; adcl $0,%1" \ :"=r" (a->a),"=r" (a->b) \ :"0" (a->a),"1" (a->b))
三 符号化
static void signify(temp_real * a) { /* 增加2位防止溢出,一位用来表示符号,一位用来防止溢出 */ a->exponent += 2; /* 如果指数已经达到最大值怎么办? 这样直接加会导致溢出到符号位,应该这样 if ((a->exponent & 0x7fff) < 0x7ffe) a->exponent += 2; */ __asm__("shrdl $2,%1,%0 ; shrl $2,%1" :"=r" (a->a),"=r" (a->b) :"0" (a->a),"1" (a->b)); /* 指数+2,对应的尾数向右移动2位 */ if (a->exponent < 0) /* 判断符号位负数(最高位为1)*/ NEGINT(a); /* 求a(负数)的补码 */ a->exponent &= 0x7fff; } static void unsignify(temp_real * a) { if (!(a->a || a->b)) { /* zero */ a->exponent = 0; return; } a->exponent &= 0x7fff; if (a->b < 0) { /* 最高位为1,为负数 */ NEGINT(a); a->exponent |= 0x8000; } while (a->b >= 0) { /* 规范化,指数部分不能<0?*/ /* while ((a->exponent & 0x7fff) && a->b >=0) */ a->exponent--; __asm__("addl %0,%0 ; adcl %1,%1" :"=r" (a->a),"=r" (a->b) :"0" (a->a),"1" (a->b)); } }
四 加法
void fadd(const temp_real * src1, const temp_real * src2, temp_real * result) { temp_real a,b; int x1,x2,shift; x1 = src1->exponent & 0x7fff; x2 = src2->exponent & 0x7fff; if (x1 > x2) { a = *src1; b = *src2; shift = x1-x2; } else { a = *src2; b = *src1; shift = x2-x1; } if (shift >= 64) { *result = a; return; } /* 当 x1 =x2时,b的64个有效bit需要向右移动shift位数, 若shift超过了64,那么b = 0,因此a + b = a + 0 = a。 */ if (shift >= 32) { b.a = b.b; b.b = 0; shift -= 32; } /* 若shift超过了32,那么b的后32bit尾数会被抹掉。 经过两轮条件后,保证了shift<32。 */ __asm__("shrdl %4,%1,%0 ; shrl %4,%1" :"=r" (b.a),"=r" (b.b) :"0" (b.a),"1" (b.b),"c" ((char) shift)); /* shrd指令:双精度右移指令,只能在80386以上处理器使用,将目的操作数 向右移动相应位数,空出来的位由源操作数的低位填充。 EX: shrd $10,%eax,%ebx 将ebx向右移动10位,ebx的高10位由eax的低10位补充。 到这里后,a和b的指数已经是一样的了, 下面就开始把它们符号化模拟正常数的加法。 */ signify(&a); /* 加上符号 */ signify(&b); __asm__("addl %4,%0 ; adcl %5,%1" :"=r" (a.a),"=r" (a.b) :"0" (a.a),"1" (a.b),"g" (b.a),"g" (b.b)); unsignify(&a); /* 去掉符号 */ *result = a; }