原码 反码 补码
博客整理自
原码, 反码, 补码 详解 - ziqiu.zhang - 博客园 (cnblogs.com)
机器数与真值
在学习原码、反码和补码之前,需要先了解什么是机器数,什么是真值
机器数
- 一个数在计算机中的二进制表示形式,叫做这个数的机器数
- 机器数是带符号的,在计算机用一个数的最高位存放符号,正数为0,负数为1,计算机字长为8的整数倍,最小字长是8位
- 十进制数 +3 ,转换成二进制是 00000011
- 十进制数 -3 ,转换成二进制是 10000011
- 00000011 和 10000011 我们叫它机器数
真值
- 由于第一位为符号位,所以机器数就不等于真正的数值
- 10000011 机器数真正的数值是-3,而不是直接换算为十进制的 131
- 为了进行区分,将带有符号位的机器数对应的真正数值称为真值
- 0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1
原码、反码和补码基础概念及计算方法
在知道计算机为什么要使用补码之前,让我们先了解原码,反码和补码的概念
对于一个数, 计算机要使用一定的编码方式进行存储,原码、反码和补码是机器存储一个具体数字的编码方式
原码
- 原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值
- 以8位二进制数为例,[+1]原 = 0000 0001
- 以8位二进制数为例,[-1]原 = 1000 0001
- 因为第一位是符号位,所以8位二进制数的取值范围就是[1111 1111, 0111 1111],即[-127, 127]
- 原码是人最容易理解和计算的表示方式
反码
- 正数的反码是原码本身
- 负数的反码是在原码基础上,符号位不变,其余各位数取反
- 以8位二进制数为例,[+1] = [00000001]原 = [00000001]反
- 以8位二进制数为例,[-1] = [10000001]原 = [11111110]反
- 一个反码表示的是负数,人无法直观看出它的数值,通常要将它转换成原码再计算
补码
- 正数的补码就是原码本身
- 负数的补码是在原码的基础上,符号位不变,其余各位取反,最后+1(即在反码的基础上+1)
- 以8位二进制数为例,[+1] = [00000001]原 = [00000001]反 = [00000001]补
- 以8位二进制数为例,[-1] = [10000001]原 = [11111110]反 = [11111111]补
- 一个补码表示的是负数,人无法直观看出它的数值, 通常要将它转换成原码再计算
为什么要使用原码、反码和补码
计算机可以有三种编码方式表示一个数,对于正数三种编码方式结果都相同,负数的原码、反码和补码是完全不相同的
原码缺陷
- 既然原码才是被人直接识别并用于计算的,为什么还需要有反码和补码呢?
- 人可以知道第一位是符号位,在计算时会根据符号位,选择对真值进行加减
- 相对于计算机来说,加减乘除是最基础的运算,计算机辨别“符号位”会让计算机基础电路设计变得复杂
- 人们想出了将符号位也参与运算的方法,运算法则得知,减去一个整数等于加上一个负数,即 1 - 1 = 1 + (-1) = 0
- 计算机可以需要加法而不需要减法,这样使得计算机的电路设计变得更加简单
- 于是人们开始探索,将符号位参与运算,并且只保留加法的方法
- 以十进制为例,1 - 1 = 0,即 1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
- 如果仅仅只是用原码进行表示,很明显结果计算的结果不正确,这也就是为什么计算机内部不使用原码表示数值
反码出现及缺陷
- 为了解决原码做减法的问题,出现了反码
- 以十进制为例,1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
- 使用反码进行计算,虽然结果的真值部分正确,但是唯一的问题出现在结果值0这个特殊值上,人们理解的+0和-0结果是一样的,但是0带符号是没有意义的,而且有00000000和10000000两个原码表示,很明显结果还是不正确
补码出现
- 补码的出现解决了0的符号以及两个编码的问题
- 以十进制为例,1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
- 0用[0000 0000]表示,-0的问题也得到了解决,且可以用[1000 0000]表示-128
- 即(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
- -1-127的结果应该是-128,在用补码运算的结果中,[1000 0000]补 就是-128,但值得注意的是因为实际上是使用以前的-0的补码来表示-128,所以-128并没有原码和反码去表示,对-128的补码表示 [1000 0000]补 算出来的原码是 [0000 0000]原 结果是不正确的
- 使用补码不仅仅修复了0的符号以及两个编码的问题,而且还能多表示一个最低数,这就是为什么8位二进制使用原码或反码表是的范围是 [-127, 127] ,而使用补码表示的范围是 [-128, 127]
- 计算机使用补码,对于编程中常用到的32位int类型,可以表示的范围就是 [-231, 231-1] ,因为第一位表示的是符号位,而是用补码表示时,又可以多保存一个最小值
原码、反码和补码深入解析
举例解析
- 将钟表想象成是一个1位的12进制数
- 如果当前时间是6点,我希望将时间设置成4点,我们可以怎么做
- 第一种,往回拨2个小时: 6 - 2 = 4
- 第二种,往前拨10个小时: (6 + 10) mod 12 = 4
- 第三种,往前拨10+12=22个小时: (6+22) mod 12 =4
- 第二种和第三种是指取模运算(求余),16 mod 12 = 4 即用16除以12后的余数是4
- 钟表往回拨(减法)的结果可以用往前拨(加法)来替代
- 那么关键点就在于如何使用正数去替代负数
- 在数学计算中有一个叫同余的概念
同余
- 两个整数a,b,如果它们除以整数m所得余数相等,则称a和b对于模m同余
- 即 a = b (mod m),读作a与b关于模m同余
- 4 mod 12 = 4,16 mod 12 = 4,28 mod 12 = 4,所以4,16,28模12同余
负数取模
-
正数取模相对简单,那负数呢?
-
下面是数学中定义的取模运算公式,x mod y = x - y ⌊ x / y ⌋,⌊ x / y ⌋就是得到的商向下取整
- 向下取整运算称为Floor,用数学符号⌊⌋表示
- 向上取整运算称为Ceiling,用数学符号⌈⌉表示
- 向上取整(Ceiling),不管四舍五入规则,只要数值后面有小数,则前面的整数就加1,得到结果值
- 向下取整(Floor),不管四舍五入规则,只要数值后面有小数,则后面的小数忽略,取整数部分作为结果值
- 举例:正数1.5,向上取整值为2,向下取整值为1,负数-1.5,向上取整值为-1,向下取整值为-2,0无论向下取整还是向上取整都为0
- 由此可以得知,正负数向上向下取整是相反的
-
此处以-3 mod 2 举例:
-3 mod 2
= -3 - 2 × ⌊ -3/2 ⌋
= -3 - 2 × ⌊ -1.5 ⌋
= -3 - 2 × (-2)
= -3 + 4 = 1
-
相对应的 -2 mod 12 = 10,-4 mod 12 = 8, -5 mod 12 = 7
-
接着依然使用将钟表想象成是一个1位的12进制数,进行问题探讨
- 10点钟 ,往回拨2小时 = 往前拨10小时
- 8点钟,往回拨4小时 = 往前拨8小时
- 7点钟,往回拨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), 则 a ± c = b ± d (mod m);同余式相乘,若a ≡ b (mod m),c ≡ d (mod m), 则 a × c = b × d (mod m)
故
7 ≡ 7 (mod 12)
-2 ≡ 10 (mod 12)
7 - 2 ≡ 7 + 10 (mod 12)
这样就可以将负数用正数来进行表示
-
返回到最初的二进制问题,2 - 1 = 1
-
2 -1 = 2 = (-1) = [0000 0010]原 + [1000 0001]原= [0000 0010]反 + [1111 1110]反
-
-1 的反码为 1111 1110,假设将 -1 的反码认为是原码,则 [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]
-