整数四则运算溢出及溢出后的转换

£可爱£侵袭症+ 提交于 2020-01-22 20:03:25

无符号数溢出

  1. 加法运算
    对于无符号的短整型(unsigned short int),假设其为1个字节,其取值范围为0 ~ 255(即0 ~ 2^8 - 1),当两个无符号数相加溢出时,舍弃高位保留低位。
    a=255=(1111 1111)2b=10=(0000 1010)2a = 255 = (1111\ 1111)_2,b = 10 = (0000\ 1010)_2,则在不溢出的情况下a+b=265=(0000 0001 0000 1001)a + b = 265 = (0000\ 0001\ 0000\ 1001),但实际上最终的结果为a+b=9=(0000 1001)a + b = 9 = (0000\ 1001),可以看到其结果就是265 - 256的差(265 % 256)。

  2. 乘法运算
    a=64=(0100 0000)2b=4=(0000 0100)2a = 64 = (0100\ 0000)_2,b = 4 = (0000\ 0100)_2,则在不溢出的情况下ab=256=(0000 0001 0000 0000)2a * b = 256 = (0000\ 0001\ 0000\ 0000)_2,但实际上最终的结果为ab=0=(0000 0000)a * b = 0 = (0000\ 0000),可以看到其结果就是256 - 256的差(256 % 256)。

  3. 减法和除法运算不会出现溢出的情况

有符号数溢出

  1. 加法运算
    对于有符号的整型(signed short int),假设其为2个字节,则取值范围为-32768 ~ 32767(即2^16 ~ 2^16 - 1),当两个有符号的运算时,结果的处理更加复杂一些。
    a=32767=(1111 1111 1111 1111)2b=10=(0000 0000 0000 1010)2a = 32767 = (1111\ 1111\ 1111\ 1111)_2,b = 10 = (0000\ 0000\ 0000\ 1010)_2,则在不溢出的情况下a+b=32778=(0000 0001 0000 0000 0000 0101)2a + b = 32778 = (0000\ 0001\ 0000\ 0000\ 0000\ 0101)_2,但实际的结果为a+b=32759complementa+b=(1000 0000 0000 1001)232759=(0111 1111 1111 0111)2a + b = -32759,complement_{a+b}=(1000\ 0000\ 0000\ 1001)_2,32759=(0111\ 1111\ 1111\ 0111)_2,我们知道在计算机存储中,负数表示为其正数的补码+1,所以这里应该有 (complement1)=32759~(complement - 1) = 32759,验证如下:
    complementa+b1=(1000000000001001)21=(1000000000001000)2 complement_{a+b} - 1 = (1000 0000 0000 1001)_2 - 1 = (1000 0000 0000 1000)_2
    (1000000000001000)2=(0111 1111 1111 0111)2=32759 (1000 0000 0000 1000)_2 = (0111\ 1111\ 1111\ 0111)_2 = 32759

我们知道,对于一个有符号的int型的整数,其取值范围为[-32768, 32767],且-1的二进制表示形式为1111 1111 1111 1111,但为什么不用1000 0000 0000 0001这样的形式描述呢?因为如果还是以正数表示的方式,那么当一个值为0时,就可能有两种形式,即1000 0000 0000 0000和0000 0000 000 0000,虽然我们知道这两种情况都表示的一个数,但在计算机世界我们不得不通过额外的判定过程来区分这两个结果;另外如果低位采用跟正数一样的表示逻辑,会少容纳-128这个负数,我们知道128=2^16,而如果想要表达-128时,我们不得不多使用一个bit位,共17bit来表示这个数,这就会造成空间的浪费。
即是说+0依然用0000 0000 0000 0000没有问题,但是-0用1000 000 0000 0000表示不合适,因为这里把-0认为了最大的负数,但可不可以把反过来想,用1000 0000 0000 0000表示最小的负数呢?这正是补码/原码/反码的意义所在,就可以避免掉前面提到的问题。
所以一个负数比如-1在计算机中就存储为1111 1111 1111 1111,即+1的反码再加1

上面的加法过程可以这样来看,一个带符号的short int的整数N,当其为正数时的值超过SHORT_INT_MAX=32767时,可以向前面一位进1,即最高位的符号位由0 => 1,同时溢出了N - SHORT_INT_MAX个数(即32777 - 32767 = 10,0到9),那么就需要有10个不同的负数来表示这一溢出的,由于负数是反向表示的,即需要用[-32768, -32767, -32766, -32765, -32764, -32763, -32762, -32761, -32760, -32759]这10个数表示:
32768(32759)+1=>3276832769+1=9+1=10 -32768 - (- 32759) + 1 => 32768 - 32769 + 1 = 9 + 1 = 10
推导出:正向溢出,求INT_MIN ^ ((a+b) - n*(INT_MAX + 1)),加1等于正数的个数,包含0

同理当其为负数时,如-32768 - 10 = -32778,且小于SHORT_INT_MIN=-32768时,可以向前进位,则最高位由0 => 1,变为了正数,由于负数是反向表示的,那么由负到正应当也做一次变换,即需要前10个最大的正数来表示这一溢出值,即[32767, 32766, 32765, 32764, 32763, 32762, 32761, 32760, 32759, 32758],因此最终有:
3276810=32758=(0111 1111 1111 0111)2 -32768 - 10 = 32758 = (0111\ 1111\ 1111\ 0111)_2
推导出:负向溢出,求INT_MAX ^ ((a + b) - n*INT_MIN - 1),减1是指去除全0的,因为负数是从-1开始统计的,所以需要在正数范围内去除0的表示形式

对于正负数的二进制表示,我们可以想象中在0和-128之前有一个暗箱B,所有的正数(包括0)表示,第几次向B投入一个球,比如0,表示第一次投入一个球,1表示第二次,直到最大次或是溢出;所有的负数,可以想象成此时B内已经塞满了球(结过了[0, INT_MAX]次掀放入),如果想要把B内的球全部取出,需要多少次,如(1000 0000 0000 0000)= -128表示已经取了127次,再取一次就会使得B为空,最终变为0 = (0000 0000 0000 0000)。

  1. 乘法运算
    乘法溢出直接取余即可:
    正向溢出:(ab) % INT_MAX
    铜币溢出:(a
    b) % INT_MIN

  2. 减法或除法没有溢出

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!