两个正数相加可能会变负?

本文并没有系统性的介绍计算机中信息的表示方式,只介绍了一些用户编程时需要注意的的小问题。

1、为什么有符号整数负数可表示范围比正数多一个?

有符号整数是用补码表示的。具体的表示形式如下,其中,向量的每个元素表示了整数的每一位。
在这里插入图片描述

负数的最高位是1,非负数最高位是0。
假设有符号整数共w位,那么最小的负数最高位为1,其他为0,即 − 2 w -2^w 2w。 最大的正数最高位为0,其他位为1,即 2 w − 1 2^w-1 2w1
因此有符号整数可表示的范围为 [ − 2 w , 2 w − 1 ] [-2^w, 2^w-1] [2w,2w1]。负数和非负数各占了取值范围的一半,而非负数中有一个数是0,这也就导致了负数和正数不对称。

2、C语言中有符号整数如何转换成无符号整数?

如果将一个有符号的整数强制类型转换成一个无符号的整数时,结果保持每个位的值不变,但改变解释这些位的方式。有符号整数是用补码方式来解释,而无符号整数则是最高位跟其他位作相同的运算。

比如,一个整数 -12345,用补码表示为 1100111111000111,而强制转换为无符号整数后,其表示依然为 1100111111000111,但解释方式变了,最后得到的结果是 53191

3、扩展整数的位

示例代码:

short sx = -12345;
unsigned short usx = sx; //53191
int x = sx;              //-12345
unsigned ux = usx;			 //53191

首先,将有符号short类型的数sx转换为无符号short,则是通过位保持不变,将补码解释改为无符号的方式,得到结果 53191

其次,将无符号short转换为更宽的类型有符号的int,则是通过执行符号扩展,在表示中添加最高有效位的值的方式,这里最高有效位为1,那么将多扩展的16位高位都置为1,得到结果 -12345

然后,将无符号short类型转换为无符号int类型,则是通过零扩展的方式,在高16位添加0,得到结果 53191

4、截断整数的位

示例代码:

int x = 53191;
short sx = (short) x; //-12345
int y = sx;           //-12345

首先,将有符号int类型转换为有符号short类型,将32位截断为16位,也就是将高16位的0全部去掉,剩余的最高有效位为1,得到结果 -12345

然后,我们又将sx强制转换为有符号int类型,采用符号扩展的方式,高16位全部置1,得到结果 -12345

然而,实际上除了C语言外,很少有语言支持无符号整数。java只支持有符号整数,并且用补码表示。java中右移运算符“>>”被定义为执行算术右移,特殊运算符“>>>”被指定为执行逻辑右移。算术右移指在左端补k个最高有效位的值,逻辑右移指在左端补k个0

5、整数运算中的溢出行为

①无符号数加法

任意两个处于 0 到 ( 2 w − 1 ) (2^w -1) (2w1)之间的无符号数 x 和 y,如果相加,可能会由于“字长膨胀”而导致溢出。溢出时,第w位(最低位为第0位,最高位为第w-1位)会被截断,那么最后显示的结果=原结果 − 2 w -2^w 2w
在这里插入图片描述

②补码加法

与无符号数加法一样,当补码加法由于字长膨胀而导致溢出时,最高位会被截断,因此也就有可能出现两个正整数相加得到负数的情况。

在这里插入图片描述

③无符号乘法

任意两个处于 0 到 ( 2 w − 1 ) (2^w -1) (2w1) 之间的无符号整数 x 和 y相乘,可能会需要2w位来表示。溢出时,高w位会被截断,因此得到的结果是原结果模 2 w 2^w 2w

在这里插入图片描述

④补码乘法

补码乘法在出现溢出时,会先将该值模 2 w 2^w 2w,再把无符号数转为补码。补码乘法和无符号乘法的位级表示相同,只解释不同。

⑤乘以常数

由于大多数机器上执行整数乘法指令非常慢,所以编译器在优化乘法操作时,通常会尝试使用移位和加法运算的组合来代替乘以成熟因子的乘法。比如:

  • x乘以2的k次方——>x左移k位。
  • x乘以14———>利用 14 = 2 3 + 2 2 + 2 1 14 = 2^3 + 2^2 + 2^1 14=23+22+21,编译器将乘法重写为 (x<<3)+(x<<2)+(x<<1)。

⑥除以2的幂

除法是比乘法更慢的操作,除以2的幂通常使用右移操作替代。无符号数采用逻辑右移,补码数采用算术右移。

整数除法总是舍入到零。并作以下规定,

  • 当x>=0, y>0,x/y得到的结果是该值向下取整。
  • 当x<0, y>0,x/y得到的结果是该值向上取整。

1)除以2的幂的无符号除法,由于采用的逻辑右移,所以比较简单。得到的结果满足上述规则。

2)但是除以2的幂的补码除法中,如果x>=0,则其算术右移的效果与逻辑右移一致。但是如果x<0,做除法时,会导致得到的结果向下取整。比如:-12340的二进制表示为1100111111001100,算术右移4位后,得到的二进制表示为1111110011111100,转换成十进制为-772,而实际上-12340/16=-771.25。

所以,我们需要做一些调整,来修正这个不合适的行为。在移位之前“偏置”这个值,即,x右移k位,执行表达式**(x+(1<<k)-1)>>k**,就可以得到期待的值。原理是:对于整数x和y (y>0),(x/y)向上取整=[(x+y-1)/y]向下取整。

6、浮点数强制转换

C语言中单精度的浮点数float共32位,符号位占1位,阶码占8位,小数字段占23位。双精度的浮点数double共64位,符号位占1位,阶码占11位,小数字段占52位。

  • 从int转换为float,数字不会溢出,但是会被舍入,因为float的小数字段只有23位的精度。
  • 从int或float转换为double,由于double的小数字段占52位,精度很高,所以能够保留精确的数值。
  • 从double转换为float,不仅精度可能缩小,会被舍入,而且还会出现溢出成+∞或-∞。
  • 从float或者double转换为int,值会向0舍入。

参考资料:《深入理解计算机系统》

猜你喜欢

转载自blog.csdn.net/Longstar_L/article/details/109078464
今日推荐