计算机如何进行负数运算?原码、反码、补码详解

如果没有原码、反码、补码的概念怎么表示一个负数?

原本机器只能表示正数,八位二进制就是0~255,也就是[00000000 , 1111 1111]。

为了让计算机理解负数于是科学家就想出了个办法,

现在要表示负数,取最高位0表示正数、1表示负数,本来有8位表示一个数的大小,现在变成了7位,其中[0 ~ 127]不变,原本的[128 ~ 255]就被用来表示负数了。其中原来的128就是-128,原来的255就是-1了。比如255在16位下的低8位为[11111111],如果强转为byte类型就会变成-1[11111111];

什么是原码、反码、补码?

一个数在计算机中的二进制表示形式, 叫做这个数的机器数。

有符号数和无符号数是针对符号出现的两种机器数表示方法。同一个二进制数,对有符号数和无符号数具有不同的含义。

  • 有符号数如: char, short ,int, long等类型的变量

  • 无符号数(c语言下)如:unsigned char, unsigned short , unsigned int, unsigned long, 指针等类型的变量

计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同 。在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。

  • 原码是在数值二进制表示下从左到右第一位,即最高位为符号位:正数该位为0,负数该位为1(注意原来的0有两种表示:+0和-0),其余位表示数值的大小。

  • 反码表示法规定:正数的反码与其原码相同;负数的反码是对正数逐位取反,符号位保持为1。

    换句话就是说:反码是对原码除了符号位以外所有位分别对1进行异或(^)操作得到的结果。

  • 补码就是在反码的基础上加1得到的结果。

如何看了以上定义还不懂,那我们以32位的整数1和-1举例:

在这里插入图片描述

给定一个int整数i,如果i为负数,那么i的反码的值就是(i ^ Integer.MAX_VALUE),补码的值就是(i ^ Integer.MAX_VALUE)+1,若i为正数,则反码和补码同原码 。

其中Integer.MAX_VALUE = 2147483647,其原码:[ 01111111 11111111 11111111 11111111 ]

说白了,反码和补码的就是为负数而设定的,而反码只是补码的一个过渡产物,并无什么实际意义,我们需要记住的就是负数的原码和补码的关系和推导过程,因为计算机中负数都是用补码表示,所以我们要记得就是负数的补码如何推导出原码。

如何通过原码推导出补码?

因为int太长太繁琐,下面我们以byte类型(8位)来举例:

  • 1
    原码:[ 00000001 ]

    ​ ⇓ (不变)

    反码:[ 00000001 ]

    ​ ⇓ (不变)

    补码:[ 00000001 ]

  • -1
    原码:[ 10000001 ]

    ⇓ (除符号位外逐位取反)

    反码:[ 11111110 ]

    ​ ⇓ (+1)

    补码:[ 11111111 ]

  • 127
    原码:[ 01111111 ]

    ​ ⇓

    反码:[ 01111111 ]

    ​ ⇓

    补码:[ 01111111 ]

因为原码第一位是符号位,所以byte类型的8位二进制数的取值范围就是:

  • [1111 1111 , 0111 1111]

  • [-127 , 127]

    (这时可能有的同学会说博主你个水货,明明byte整形取值范围是-128~127,别急,请继续往下看)

如何通过补码推导出原码
  • -1
    补码:[ 11111111 ]

    ⇓ (除符号位外逐位取反)

    反码:[ 1000000 ]

    ​ ⇓ (+1)

    原码:[ 10000001 ]

是不是和负数原码推导补码的过程一样,所以,记住补码的补码便是原码

总结:

  1. 二进制数中,两数的补码之和等于两数和的补码.

  2. 负数的反码=原码除符号位外其它位的数值取反,即“0”变“1”,“1”变“0”.

  3. 负数的补码=反码+1

  4. 负数的原码=负数补码的反码+1

  5. 任何正数的原码、反码和补码完全相同(即都是自身不变)

  6. 在计算机中,有符号的数都是采用补码来表示的.

  7. 计算的时候,符号位也可以参与运算.

那么原码, 反码和补码的作用是什么呢?

1.使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。

2.使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]。
举个例子
(-1) + (-127) = 原码:[1000 0001] + 原码:[1111 1111]

                    = 补码: [1111 1111] + 补码:[1000 0001]

                    = 补码: [1000 0000]

                    = -128

-1-127的结果就是-128, 有符号的数都是采用补码来表示的, 所以补码:[1000 0000] 就是-128.。

但是注意[1000 0000]实际上是使用以前的-0的补码来表示-128, 所以现在-0是被当作0来处理了(虽然有点绕,你只要记得-0被丢弃就行了)。

对于-128在有符号的8位进制下是得不到补码的,因为按照约定最高位是符号位,而它的低7位都是0,

所以-128其实是数溢出了,对于补码表示[1000 0000]逆推得到的原码是[0000 0000], 这又是不正确的。

假如-128是32位整数,那么他此时的对应的补码就是[11111111 11111111 11111111 10000000]。

其中:

  • 0 (在编译器中-0会被转为0表示,规定-0的补码用来表示当前位数下最大的负数)
    原码:[ 00000000 ]
    反码:[ 00000000 ]
    补码:[ 00000000 ]
另一个我需要强调的就是数的溢出问题,

n位bit位的整数,所能表示的范围是[-2^(n-1), 2^(n-1)-1]

一旦数字超过了这个限定,就会发生溢出,超过上限,叫做上溢出。超过下限,叫做下溢出。上溢出之后,又从下限开始:最大的数加1,就变成了最小的数。下溢出之后又从上限开始,最小的数减去1就变成了最大的数,如此循环往复。

如果一个N位数经过运算得到了一个N+1位的数,那么此时他的最高位就是溢出状态,是直接被舍弃的,举个例子:
一个无符号数255在byte(8位)类型中的二进制形式为255[ 11111111 ],如果此时对它进行加1操作,则会变成0[ 00000000 ],此时就发生了数的溢出,而如果它是一个short(16位)类型,其二进制形式便是255[ 00000000 11111111],加1后的结果为256[ 00000001 00000000],便没有发生溢出。

也许我说了这么多,你还是没明白,简而言之,反码只是原码得到补码的一个过渡产物,并无什么实际意义,你需要记住的是原码是给人看的,而补码是给机器看的。对于机器而言,它其实并没有负数的概念,在对负数的原码进行运算的时候,它其实是无能为力的,所以需要引入负数的补码表示一个负数。
那么问题来了,为什么机器对负数的补码进行算术运算操作就能得出正确的结果呢?
这边要引入同余的概念
定义:两个整数a、b,若它们除以整数m所得的余数相等,则称a与b对于模m同余或a同余于b模m。
或者说:给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。

定义三个变量分别为a、b、m.
我们取a=-1 ,m= 256;
假设 (a-b)/m = -1,可以得到 b = 255,所以我们可以说-1和255同余
我们再以 c = a + b举例,其中c为有符号的byte类型,显然c小于256,可以得到:
c = c ^ (256 - 1) = c mod 256
   = (-1 + 127)mod 256
   = (255 + 127) mod 256
   = (255 + 1 + 126) mod 256
   = (256 + 126) mod 256
   = (0 + 126) mod 256
   = 126 [ 01111110 ]
我们用a+b得到结果126,对于机器而言过程如下:
在这里插入图片描述

讲到这你应该明白为什么负数的补码=原码的反码加1了吧

定义一个byte x = -1 ;
原码:[ 10000001 ]
反码: [ 11111110 ] = 254 = 256 -1 - |x|
补码: [ 11111111 ] = 255 = 256 - |x|
对于byte类型x;它的补码就是2^8 - x的绝对值

想一想要是你要原码直接做加法,会是什么情况?

(-1) + 127 = 原码:[ 1000 0001 ] + 原码:[ 1111 1111 ]
                 = 原码:[ 10000000 ]
                 = -128

  • 你没看错结果就是-128,显然这是不对的,那么你又要问了,难道机器不会做减法吗?我要说的是----没错,机器还真的不会减法,现在你看到的减法其实就是对负数做加法运算而已。
同样是上面的例子我们再来变通一下:

(-1) + 127 = 原码:[ 1000 0001 ] + 原码:[ 0111 1111 ]
                 = 补码:[ 1111 1111 ] + 补码: [ 0111 1111 ]
                 = 255 + 127 (还是那句话计算机不理解负数,对它而言-1的补码其实就是255的原码,这也是为什么byte类型下127+1结果显示-128的原因)
                 = 126

  • 对,你还是没看错,255+127对于机器得到的结果就是126,原理就是前面讲过的数的溢出问题。

  • 我们把byte类型的取值区间想象成一个圆环,它的周长是256,我们把它分为256份也就是256个点,每相邻两个点间距为1,如下图:
    在这里插入图片描述

  • 所以127 + 255 相到于从127这个点顺时针走了255个点,最后正好落在了126这个点位上,同理127 - 1 就是127这个点逆时针走了一个点,也是落在了126上。

  • (-1) + 127 则相当于从-1这个点顺时针出发经过127个点到了126上。

  • 题外话:古人对南辕北辙有误解,因为地球是圆的,所以,只要一直往南,那么是能到北的。

说了这么多,其实同余理不理解已经无所谓了
对于我们可以这样理解(mod为取模操作):

(-1) + 127 = ((-1) + 127)mod 256
                 =([ 11111111 ] + 127 )mod 256
                 =(255 + 127 )mod 256
                 =    126

重要的事情再说一遍:一个8位数(16位或更高位的同理)可以表示的数其实是[0 ~ 255],但是因为我们要让计算机可以表示负数于是规定了[0 ~ 127]不变,原本的[128 ~ 255]就被用来表示负数了(对应上面的圆)。

我们站在计算机的角度看它如何理解这一个过程:

  • 我们看到的是实际值:(-1) ,计算机看到的是补码: [ 11111111 ] ,所以计算机理解的值就是255[ 11111111 ],但是由于最高位表示符号,就取低7位的值127,而-127的原码即为-1。
  • 但是我们注意到在8位字节数中,是不存在256的,且小于256,所以255 mod 256的这部操作就相当于:
    255[ 11111111 ] ^ 127[ 01111111 ] + 1[ 00000001] = 128[ 10000001] = -[ 0000001 ] = -1
  • 最高位若是1输出符号位(-),剩下低位则正常输出,就是这么一个过程。 、
  • 若有不理解的请在下方留言哈…
  • 23
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值