探讨计算机中使用补码表示负数的点滴思考
这里总结关于为什么负数运算使用补码的一些思考。
问题一:补码是什么?
补码是为了方便计算机对负数进行运算而发明出来的,对于正数没有意义。补码其实就是使用进行运算的数字的模(比如:一个8位二进制的模是1 0000 0000即2^8)减去当前负数的绝对值得到的结果。因为一个负数的绝对值加上他的反码是当前位数所能表示的最大值,也就是摸减去一,所以,一个负数的补码公式为:补码=反码+1。
问题二:为什么负数需要换成补码运算?
一个8位的二进制,最高位0代表正数,1代表负数,那么:
1 + 2 = 3
0000 0001 + 0000 0010 = 0000 0011 //3两个正数相加结果正确
-1 + 2 = 1
1000 0001 + 0000 0010 = 1000 0011 //-3一个负数+正数结果错误
-1 + (-2) = -3
1000 0001 + 1000 0010 = 0000 0011 //3两个负数相加结果错误
由于负数的原码直接参与运算得出的结果都是错误的,所以,现在有两个解决办法:
1.在加法器的基础上再实现一个减法器
2.在不增加减法器的基础上通过其他方法实现负数的运算
总的来说,大佬们采取了后面一种办法来曲线救国,这个就是补码。
1 + 2 = 3
0000 0001 + 0000 0010 = 0000 0011 //3两个正数相加结果正确
-1 + 2 = 1
1111 1111 + 0000 0010 = 0000 0001 //原码1一个负数+正数结果正确
-1 + (-2) = -3
1111 1111 + 1111 1110 = 1111 1101 //将该补码取反加一转换成原码为-3两个负数相加结果正确
那么为什么将负数变成补码就可以正确运算呢?这就要用到模的概念了。
举个比较好理解的例子,时钟。一个时钟有12个大的刻度,假如现在是6点,那么把指针拨到3点有两种办法,一种是逆时针拨动3格,也就是6-3=3,或者顺时针波动9格,即6+9=3(15-12=3),这两种方法是等价的。很明显,时钟的模为12,那么从当前刻度转一圈12刻度其实回到了原点,所以,15点其实就是3点,即6-3=6+(12-3)-12=6+9-12,因为12超出显示范围所以被丢弃,即减去一个数等于加上一个数的补数,同理二进制的负数运算也可以转换成加法。
问题三:计算结果正负以及溢出
先看结果:
1.关于相加结果正负
两个正数相加为正数和两个负数相加为负数,这两个应该没有异议。主要是正数+负数,从操作来看,正负符号相加为1,那么如果最高位进位1和符号位1相加,则为0,结果是正的;如果最高位没有进位,则符号位为1,结果是负的。从原理来讲,正数+负数=正数+补码,有进位说明正数+补码>=模, 因为|负数|+补码=模,那么正数>=|负数|,所以结果为正;同理没有进位说明正数+补码<模,那么正数<|负数|,所以结果为负。
2.关于相加结果溢出
两正数相加,最高位有进位就是超模了,所以,溢出。一正一负相加是不会溢出的,因为结果最大和最小值的绝对值也比模小一。两负数相加,最高位没有进位,说明两负数的补码相加没有超模,那么两负数的原码相加就肯定超模了,因为负数和其补码相加结果等于模,那么两负数加两补码就等于两个模,那么,两负数补码相加没有超模,两负数相加肯定超模,所以,两负数相加,最高位没有进位,结果就溢出了。
问题四:一个8位有符号整型最小值是-128
按照规则一个8位有符号整型的表示范围为:-127~127,但是这里有个问题就是0的唯一性问题,这里1000 0000和0000 0000都表示0,那么运算的时候用哪个表示0比较好,这个计算机无法判断,所以,干脆就把1000 0000表示-128,不仅可以表示多一位数,还可以解决0的唯一性问题,很合理。
以上就是我遇到关于负数补码的一些问题的思考,仅供参考。
参考:为什么计算机中的负数要用补码表示?
下一篇: 解读为何正数的补码就等于它自身
推荐阅读
-
探讨计算机中使用补码表示负数的点滴思考
-
数的机器码表示:原码、反码、补码、变形补码、移码和浮点数编码-数学定义:例:+111的原码为0111,-101的原码为1101 (2) 纯小数的原码表示 纯小数的原码首位同样为符号位,后面的数值则表示小数的尾数,纯小数的整数位为默认为0无需表示。 例:+0.111的原码为0111,-0.101的原码为1101 可以看到,+111和+0.111的原码同为0111,这是因为约定的小数点位置不同,整数的原码的小数点约定在末尾,纯小数的原码的小数点约定在数值的最前面,这样通过约定小数点的位置来表示数的方法就称为定点数表示法,约定小数点位置实际上就是约定编码中每一位的权重。 二、反码 正数的反码与其原码相同。 负数的反码是其对应原码的符号位不变,数值位按位取反。 数学定义:例: 真值 +111 -101 +0.111 -0.101 原码 0111 1101 0111 1101 反码 0111 1010 0111 1010 三、补码 原码虽然转换很简单,但是在做减法时操作很复杂(减不够还要借位),因此计算机在做加负数操作时会先将负数的原码转换为补码再做加法。 先举个栗子,假设时钟现在是9点钟,我把时针往回拨3个小时是6点钟,或者顺时针往后拨9个小时还是6点钟,也就是说9-3的结果等同于9+9(mod 12),对于模数12,-3的补码为+9,这就引申出了一种将减法转换为加法的思想,把减去一个正数视为加上一个负数(例如9+(-3)),再将负数转换为对应的补码,最后就可以和补码做加法了,若结果超出了模数则丢弃一个模数即可。 如图所示:9减去灰色的部分(-3)就等同于加上蓝色的部分,即-3的补码即为蓝色部分的长度9(mod 12)。即补码=模数+真值(超出模数则舍弃一个模数) (1) 整数的补码表示 对于一个n位的二进制真值x,则取模数为2^(n+1),若x为正数则补码和原码相同(加上一个模数又需舍弃一个模数 故相同),若为负数则补码为模数加上x。相对于原码,补码这里的首位就不仅代表原数真值的符号了,也是补码自己的一个数值位。 取模数为2^(n+1)是因为在需要舍弃模数时只需要舍弃运算结果(二进制数)的最高位即可,这在计算机中很容易实现 数学定义:例:三位二进制数的模数2^4就是10000,故+111的补码为0111(即10000 + 111 = 0111 (舍弃模数位)),-101的补码为1011(即10000 - 101 = 1011) 补码运算示例:那么+111 - 101 = +111 + (-101) = 0111 + 1011 = 10010,运算结果只保留后四位(即舍弃模数位),故计算结果为0010。这样就通过加法实现了减法运算。 补码可表示数据范围:由数学定义可知,n位二进制补码可表示的数据范围为 -2n-1~2n-1-1。以8位的byte类型数为例,可表示的数据范围为 -27~27-1,即-128至+127,最小负数-128(补码:1000 0000),最大负数-1(补码:1111 1111),0(补码:0000 0000),最小正数1(补码:0000 0001),最大正数127(补码:0111 1111)。 由补码求真值:正数的补码即为原码即为真值,负数的真值由计算规则可知 负数真值= - (模数 - 补码),以补码1111 1111为例,其真值 = - (1 0000 0000 - 1111 1111) = - 0000 0001 = -1 (2) 纯小数的补码表示 对于一个纯小数x,则取模数为2^1,正数的补码和原码相同,负数的补码为模数2加上x。同样补码的首位不仅代表原数真值的符号,也是补码的数值位。 数学定义:例:纯小数的模数2就是10,故+0.111的补码为0111,-0.101的补码为1011(小数点约定在符号位后) 计算机中求补码的规则 可以注意到求负数的补码时还是要做减法,这在计算机中就很不方便了,但是通过其数学定义可以看到无论是整数还是纯小数,负数的补码都等于反码的末尾加1,而这又等同于原码数值位从右向左遇到第一个1后,这个1左边的数值位都按位取反,故实际计算机中求补码的规则如下:正数的补码等于原码负数的补码等于原码的数值位从右向左的第一个1左边的所有数值位按位取反(例:byte类型值-6的原码为1000 0110,则其补码为1111 1010) 四、变形补码 两个补码在运算时可能会溢出从而产生错误的结果,比如0111+0101 = 1100,两个正数相加反而得到了一个负数,那么在计算机中要如何判断运算结果是否溢出了呢,这就引申出了变形补码。从直观上看,相对于补码来说变形补码就是用两位来表示符号位,00表示正数,11表示负数。运算结果符号位为01表示正溢出,10表示负溢出。