原码反码补码

一、机器数和真值

在学习原码、反码和补码之前,我们首先需要了解机器数和真值的概念。

1.1 机器数

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号,正数为0,负数为1。

比如十进制中的+3,在计算机字长为8位的情况下,转换成二进制,就是00000011,如果是-3,就是10000011.

那么,这里的00000011和10000011就是机器数。

1.2 真值

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数10000011,其最高位1代表负,其真正的数值是-3而不是形式值131(10000011转换成十进制等于131)。所以,为了区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

例如:
00000001的真值=1,10000001的真值=-1.

二、原码、反码、补码的基础概念和计算方法

2.1 原码

原码用最高位表示符号位,’1‘表示负号,’0‘表示正号。其他位存放该数的二进制的绝对值。(注意0有+0和-0)

原码是人脑最容易理解和计算的表示方式,但当用原码计算正数和负数相加时,会出现问题:

0001 + 1001 = 1010 ( 1 + ( − 1 ) = − 2 ) 0001+1001=1010(1+(-1)=-2) 0001+1001=1010(1+(1)=2)

用原码来实现加减法因为符号位的存在会很复杂。

2.2 反码

原码的最大的问题就在于一个数加上它的相反数不等于0。于是反码的设计思想就是冲着解决这一点,既然负数是一个正数的相反数,那我们干脆用一个正数按位取反来表示负数。

所以有:

  • 正数的反码还是等于原码
  • 负数的反码就是它的原码除符号位外,按位取反

再次解决上面的加法问题,此时-1的反码为1110:

0001 + 1110 = 1111 ( 1 + ( − 1 ) = − 0 ) 0001+1110=1111 (1+(-1)=-0) 0001+1110=1111(1+(1)=0)

此时解决了互为相反数相加等于0的情况。此时我们再测试下两个负数相加的情况:

1110 + 1101 = 1011 ( ( − 1 ) + ( − 2 ) = ( − 4 ) ) 1110+1101=1011((-1)+(-2)=(-4)) 1110+1101=1011((1)+(2)=(4))

再举个例子:

1110 + 1100 = 1010 ( ( − 1 ) + ( − 3 ) = ( − 5 ) ) 1110+1100=1010((-1)+(-3)=(-5)) 1110+1100=1010((1)+(3)=(5))

反码的负数相加出错,其实问题不大,我们只需要在实现两个负数加法时,将两个负数反码包括符号位全部按位取反相加,然后再给他的符号位强行置’1‘就可以了。

2.3 补码

补码的表示方法是:

  • 正数的补码等于它的原码
  • 负数的补码是在其原码的基础上,符号位不变,其余各位按位取反,最后+1(即在反码的基础上+1)

[ + 1 ] = [ 00000001 ] 原 = [ 00000001 ] 反 = [ 00000001 ] 补 [+1]=[00000001]_原=[00000001]_反=[00000001]_补 [+1]=[00000001]=[00000001]=[00000001]

[ − 1 ] = [ 10000001 ] 原 = [ 11111110 ] 反 = [ 11111111 ] 补 [-1]=[10000001]_原=[11111110]_反=[11111111]_补 [1]=[10000001]=[11111110]=[11111111]

参考

三、为何要使用原码、反码和补码

根据冯~诺依曼提出的经典计算机体系结构框架。一台计算机由运算器,控制器,存储器,输入和输出设备组成。其中运算器,只有加法运算器,没有减法运算器(据说一开始是有的,后来由于减法器硬件开销太大,被废弃了 )

所以,计算机是没法直接做减法的,它的减法是通过加法来实现的。其实现实世界中所有的减法也可以被当成加法,减去一个数,可以看作加上这个数的相反数。但是前提是要先有负数的概念。这就是计算机引入符号位的原因。

对于人脑来讲,人脑可以知道第一位是符号位,在计算的时候我们会根据符号位,选择对真值区域的加减。但是对于计算机,加减乘除已经是最基础的运算,要设计的尽量简单。计算机辨别”符号位“显然会让计算机的基础电路设计变得十分复杂!于是人们想出了将符号位也参与运算的方法。

减去一个正数等于加上一个负数,如果我们利用原码进行计算,可以得到:

1 − 1 = 1 + ( − 1 ) = [ 00000001 ] 原 + [ 10000001 ] 原 = [ 10000010 ] 原 = − 2 1 - 1 = 1 + (-1) = [00000001]_原 + [10000001]_原 = [10000010]_原 = -2 11=1+(1)=[00000001]+[10000001]=[10000010]=2

所以如果在原码的表示下,让符号位也参与运算,对于减法来说,结果显然是不正确的。这也就是为什么计算机内部不使用原码表示一个数。

为了解决原码做减法的问题,出现了反码:

1 − 1 = 1 + ( − 1 ) = [ 00000001 ] 反 + [ 11111110 ] 反 = [ 11111111 ] 反 = [ 10000000 ] 原 = − 0 1 - 1 = 1 + (-1) = [0000 0001]_反 + [1111 1110]_反 = [1111 1111]_反 = [1000 0000]_原 = -0 11=1+(1)=[00000001]+[11111110]=[11111111]=[10000000]=0

我们发现利用反码计算减法,结果的真值部分是正确的。而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0.

于是补码的出现, 解决了0的符号以及两个编码的问题:

1 − 1 = 1 + ( − 1 ) = [ 00000001 ] 补 + [ 11111111 ] 补 = [ 00000000 ] 补 = [ 00000000 ] 原 1-1 = 1 + (-1) = [0000 0001]_补 + [1111 1111]_补 = [0000 0000]_补=[0000 0000]_原 11=1+(1)=[00000001]+[11111111]=[00000000]=[00000000]

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128

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

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

四、原码、反码、补码再深入

将钟表想象为一个1位的12进数。如果当前时间是6点,这时我们希望将时间设置成四点,需要怎么做呢?我们可以:

  1. 往回拨2个小时:6-2=4
  2. 往前拨10个小时:(6+10)mod 12=4
  3. 往前拨10+12=22个小时:(6+22)mod 12=4

所以我们可以看出钟表回拨(减法)的结果可以用钟表前拨(加法)替代。

现在的焦点就落在了如何用一个正数,来替代一个负数,接下来介绍一个数学中相关的概念:同余

4.1 同余的概念

两个整数a和b,若它们除以整数m所得的余数相等,则称a和b对于模m同余,记作:

a ≡ b ( m o d m ) a\equiv b\quad(mod\enspace m) ab(modm)

举例说明:

  • 4 mod 12 = 4
  • 16 mod 12 = 4
  • 28 mod 12 = 4

所以:4,16,28关于模12同余

4.2 负数取模

正数进行mod运算是很简单的,但是负数呢?

下面是关于mod运算的数学定义:

x m o d y = x − y ⌊ x / y ⌋ , f o r y ≠ 0 x\enspace mod\enspace y=x-y\lfloor x/y\rfloor,\quad for\enspace y\ne0 xmody=xyx/y,fory=0

上面公式的含义是:

x mod y等于 x 减去 y 乘上 x与y的商的下界.

以-3 mod 2举例

− 3 m o d 2 = − 3 − 2 × ⌊ − 3 / 2 ⌋ = − 3 − 2 × ⌊ − 1.5 ⌋ = − 3 − 2 × ( − 2 ) = − 3 + 4 = 1 -3\enspace mod\enspace2 \\=-3-2\times\lfloor-3/2\rfloor\\=-3-2\times\lfloor-1.5\rfloor\\=-3-2\times(-2)\\=-3+4=1 3mod2=32×3/2=32×1.5=32×(2)=3+4=1

利用上面的计算方式,我们可以得到:

(-2) mod 12 = 12-2=10

(-4) mod 12 = 12-4 = 8

(-5) mod 12 = 12 - 5 = 7

4.3 正式证明

再回到时钟的问题上:

回拨2小时 = 前拨10小时
回拨4小时 = 前拨8小时
回拨5小时= 前拨7小时

结合前面的同余的概念,实际上:

(-2) mod 12 = 10
10 mod 12 = 10

-2与10是同余的。

(-4) mod 12 = 8
8 mod 12 = 8

-4与8是同余的。

我们要实现用正数替代负数,只需要运用同余数的两个定理:

反身性:

a ≡ a (mod m)

线性运算定理:

如果a ≡ b (mod m),c ≡ d (mod m) 那么:

(1)a ± c ≡ b ± d (mod m)

(2)a * c ≡ b * d (mod m)

所以:

7 ≡ 7 (mod 12)

(-2) ≡ 10 (mod 12)

7 -2 ≡ 7 + 10 (mod 12)

现在我们为一个负数, 找到了它的正数同余数. 但是并不是7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即计算结果的余数相等.

接下来回到二进制的问题上, 看一下: 2-1=1的问题.

2-1=2+(-1) = [0000 0010]原 + [1000 0001]原= [0000 0010]反 + [1111 1110]反

先到这一步, -1的反码表示是1111 1110. 如果这里将[1111 1110]认为是原码, 则[1111 1110]原 = -126, 这里将符号位除去, 即认为是126.

发现有如下规律:

(-1) mod 127 = 126

126 mod 127 = 126

即:

(-1) ≡ 126 (mod 127)

2-1 ≡ 2+126 (mod 127)

2-1 与 2+126的余数结果是相同的! 而这个余数, 正是我们的期望的计算结果: 2-1=1

所以说一个数的反码, 实际上是这个数对于一个模的同余数. 而这个模并不是我们的二进制, 而是所能表示的最大值! 这就和钟表一样, 转了一圈后总能找到在可表示范围内的一个正确的数值!

而2+126很显然相当于钟表转过了一轮, 而因为符号位是参与计算的, 正好和溢出的最高位形成正确的运算结果.

既然反码可以将减法变成加法, 那么现在计算机使用的补码呢? 为什么在反码的基础上加1, 还能得到正确的结果?

2-1=2+(-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]补 + [1111 1111]补

如果把[1111 1111]当成原码, 去除符号位, 则:

[0111 1111]原 = 127

其实, 在反码的基础上+1, 只是相当于增加了模的值:

(-1) mod 128 = 127

127 mod 128 = 127

2-1 ≡ 2+127 (mod 128)

此时, 表盘相当于每128个刻度转一轮. 所以用补码表示的运算结果最小值和最大值应该是[-128, 128].

但是由于0的特殊情况, 没有办法表示128, 所以补码的取值范围是[-128, 127]

反码、原码、补码详解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值