原码,反码,补码,你理解到位了吗?
如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里
首先声明,写一篇博客,不代表知识一定是对的,只是在梳理自己学习在过程的理解,尽量做到正确
- 前提概念
- 如何理解原码,反码,补码?
- 机器数与真值
- 什么是机器数?
- 什么是真值?
- 同余定理的应用
- 什么是同余定理?
- 模,互为补数,同余
- 原码,反码,补码
- 原码
- 反码
- 补码
- 为什么需要原码,反码,补码?
- 相关问题
- 补码计算的溢出
- 为什么java语言中的byte的范围是从-128~127?
前提概念
字节
字长为8的计算机中,一个字节,有8位。2^8 = 256
, 既8位空间进行二进制的排列组合,最多可以有256种可能。
- 无符号位情况下,可表示最大的值为255,最小值为0
- 有符号为情况下,可表示最大的值为127,最小值为-127(8)
8位二进制, 使用原码或反码表示的范围为[-127, +127]
, 而使用补码表示的范围为[-128, 127]
二进制运算
计算机默认只会做加法,例: 5 - 3 => 5 + (-3)
乘法除法:是通过左移 << 和右移 >> 来实现
&
与运算。 全1为1, 有0为0|
或运算。 有1为1, 全0为0~
非运算。 逐位取反^
异或运算。 相同为0,相异为1
实践:
- 1000 mod 256 等同于 1000 & (256 - 1) ,通式是
a mod b = a & (b - 1)
,不过需要在特定的条件下才能成立,需要满足b
是2的次方数值
如何理解原码,反码,补码?
- 首先我们必须要先知道现代操作系统的数值运算是以
补码
的形式进行的 - 在学习原码,反码,补码之前,我们有必要先来了解一下
机器数
,真值
的知识 - 然后了解一下
同余定理
在机器数中的运用,知道模
,互补数
,这能让我们更好的理解补码运算中的溢出
和为什么能化减为加
的行为 - 然后我们就可以学习一下原码,反码,补码的基本概念
- 再了解一下为什么需要原码,反码和补码,以及他们的具体作用是什么
机器数与真值
什么是机器数?
一个数在"计算机"中的"二进制"表示形式, 叫做这个数的机器数。 机器数有以下两个特点
数的符号数值化
我们知道数值具有正负数之分,由于计算机内部的硬件只能表示0,1两种物理状态,因此数值的正负数,通常在机器中使用最高位的0,1来区分表示。正数为0, 负数为1二进制的位数受机器设备的限制
机器设备一次能表示的二进制位数叫机器的字长,一台机器的字长是固定的。字长8位叫一个字节,机器字长一般都是字节的整数倍,如字长8位、16位、32位、64位
说白了,机器数由两部分组成,最高位的符号位
和剩余位的数值位
。
- 举个粟子,十进制的
+8
, 转换成机器数就是00001000
。十进制的-8
,转换成机器数就是10001000
- 机器数 - @百度百科
什么是真值和形式值?
我们知道机器数的重点是,一个数以 “二进制的形式” ,在 “计算机” 中存储,存在 “符号位”。所以我们要区别机器数和数学意义上的二进制数。
因为机器数是带有符号位的,普通数学角度的二进制数是没有符合位概念的。所以机器数在计算机中所表示的真实值和机器数所表示的形式值
是不相同的。比如说,十进制的-8
,转换成机器数就是10001000
,而10001000
按照数学角度去转换为10进制应该是136
, 而不是-8
。
十进制数 | 机器数 | 2进制数 |
---|---|---|
+8 | 00001000 | +00001000 |
-8 | 10001000 | -00001000 |
机器数 | 形式值 | 真值 |
---|---|---|
10001000 | +136 | -8 |
10001111 | +143 | -15 |
从上表看,我们可以知道机器数的形式值并不是机器数在计算机中真实表示的数值。所以为了区别起见,所以将带符号位的机器数所表示的真正数值称为机器数的 真值
总结一下,什么是真值和形式值呢?
真值
当我们把机器数当做有符号位的二进制数值去计算,得到的值就是机器数真实所表示的值,称之为真值形式值
当我们把机器数当做数学角度的没有符合位的二进制数去计算,得到的值只是一个纯数学角度的数值,并非机器数真实代表的值,称之为形式值
机器数和原码,反码,补码的关系
- 机器数是一个概念,只要符合该概念定义的二进制数,我们就称为机器数。
- 而原码,反码,补码都是机器数的一种表现形式,他们的定义都符合机器数的定义。
- 也可以说,对于一个数, 计算机要使用一定的编码方式进行存储。原码, 反码, 补码是机器存储一个具体数字的编码方式.
同余定理的应用
什么是同余定理?
计算机补码运算背后是具有一定的数理知识的,例如同余定理就是补码运算背后的基石
同余定理:
数学上,两个整数除以同一个整数,若得相同余数,则二整数同余
- 既两个整数a,b 分别除以一个整数m所得到的余数如果相同,则a,b对于模m同余,同余的意思就是效果是等价的
(6 - 12) mod 12 = 6
,(6 + 12) mod 12 = 6
, 所以 -6和18对模12同余 ,也就是说-12
和+12
的行为在这个计量系统中是等价,可以互相代替的,既减12是可以被加12替代的
了解同余定理可以让我们更好的理解为什么能化减为加的行为
- 同余定理在计算机补码中的运用,就是为了解决化减为加,解决计算机只认识加法的问题,就像上面的例子,-12是可以被+12锁代替的。
模,互为补数,同余
将同余定理运用到计算机补码运算中,我们需要先了解模,互为补数,同余的概念
模?
模
就像是一个计量系统的计数范围,如时钟只能表示0~11点的数值,12则是该时钟的模,当值大于等于12时,则需要对时钟的模(12)进行取余运算,得到该计量系统的值。- 例如时钟的计量范围是
0~11
,模为12
。那么计算机补码运算中,n
位计算机,其计算系统的模为2^n
,补码取值范围就是-2^n~(2^n)-1
。例如8位的补码运算中,模为2^8 = 256
,补码取值范围是-128 ~ 127
- 计算机补码运算也可以看成一个计量机器,补码也有一个计量范围,即也存在一个“模”
互为补数
“模” 实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。任何有模的计量器,均可化减法为加法运算。
假设一个时钟,当前时针指向10点,而准确时间是6点,需要调整时间为正确的时间,那么就可以有以下两种拨法:一种是倒拨4小时,即
10-4=6
;另一种是顺拨8小时,既10+8=12+6=6
- 所以在模为12的计量系统中,加8和减4效果是一样的,因此凡是减4运算,都可以用加8来代替。对“模”而言,+8和-4互为补数,+8和-4的行为是同余的,也可以说6和18模12的行为是同余的。实际上以12模的系统中,11和1,10和2,9和3,7和5,6和6都有这个特性。共同的特点是两者相加等于模
.对于计算机,其概念和方法完全一样。
- 8位计算机所能表示的最大数是
1111 1111
,若再加1成为1 0000 0000
,但因计算机定长8位,所以最高位1自然丢失,又回了0000 0000
,所以8位二进制系统的模为2^8 = 256
。在这样的系统中减法问题也可以化成加法问题,只需把减数用相应的补数表示就可以了。把补数用到计算机对数的处理上,就是补码。
So,我们的结论是,当某个值A
, 需要做一个减法运算(A - B = X)
,得到某个想要的结果X
时。我们可以化减为加,使得A + C
也能得到想要的结果X
。那么B
和C
就是互为补数,他们相加得到的值就是该计量系统的模,减B和加C的行为是同余的,既(A - B) mod 模 = X
,(A + C) mod 模 = X
原码,反码,补码
我们首先要知道计算机的二进制计算,都是使用补码进行计算的,所以在我们
原码
什么是原码?
- 原码是人脑最容易理解和计算的表示方式
- 原码是机器数的一种表现形式,由符号位 + 数值位组成 ,数值位是真值的绝对值
- 字长8位的1字节原码的取值范围是
[-127,127]
, 0有两种表示编码
实例展示:
十进制真值 | 原码 |
---|---|
+1 | 0 000 0001 |
-1 | 1 000 0001 |
+123 | 0 111 1011 |
-123 | 1 111 1011 |
原码的作用:
- 计算机中无法直接使用原码进行计算,计算机内部运算使用的是补码,非原码。但如果要知道某个补码对应的机器数真值是什么,就需要先转换为原码,再通过原码快速的转换为真值
- 通过原码,我们可以快速的知道其对应的真值是什么,这就是需要原码的主要原因
反码
什么是反码?
- 反码是在原码的基础上,符号位不变,数值位全部取反的结果
- 正数的反码是其本身,一定意义上说只有负数才有反码
- 反码没有直接用于计算,并且通过反码也无法得知真值,反码是一个中间值
- 字长8位的1字节反码的取值范围是
[-127,127]
,0有两种表示编码
实例展示:
十进制真值 | 原码 | 反码 |
---|---|---|
+1 | 0 000 0001 | 0 000 0001 |
-1 | 1 000 0001 | 1 111 1110 |
+123 | 0 111 1011 | 0 111 1011 |
-123 | 1 111 1011 | 1 000 0100 |
反码的作用
- 反码是一个中间值,既不能直接计算,也不能直接推断出真值
- 其实反码已经可以表示负数了,既本质可以用来化减为加的去做机器数运算。只是反码运算留有一个小缺陷,既没有解决正负0的问题,所以最后没有被采用做计算的编码,而是作为原码和补码之间的中间值。
补码
什么是补码?
- 补码是原码符号位不变,数值位取反之后再 + 1得到的结果值,既在反码的基础上+1
- 正数的反码,补码都是其本身
- 补码的出现是为了解决正负0的问题,补充反码的缺陷
- 补码是计算机存储和运算的直接编码,不过不能直接表示出真值,所以对外展示,还需要转换为原码
- 字长8位的1字节反码的取值范围是
[-128,127]
,解决了正负0的问题,只有0 0000000
代表0 - 8位空间中,人为的定义原负0的编码
1 0000000
为-(2^8) = -128
,所以-128
,虽然是负的,但没有反码和补码。这个人为定义是可以通用扩展的n位空间的补码最小值是-(2^n)
,如16位空间,原负0编码则为-(2^16) = -65536
实例展示:
十进制真值 | 原码 | 反码 | 补码 |
---|---|---|---|
+1 | 0 000 0001 | 0 000 0001 | 0 000 0001 |
-1 | 1 000 0001 | 1 111 1110 | 1 111 1111 |
+123 | 0 111 1011 | 0 111 1011 | 0 111 1011 |
-123 | 1 111 1011 | 1 000 0100 | 1 000 0101 |
补码的作用
- 补码是为了解决反码中遗留的正负0问题。为了不浪费宝贵的空间资源,负0的二进制表示
1 0000000
为人为认定是-128
,扩展多一位的新内容 - 计算机最后采用的机器数存储方式就是补码方案,所以计算机中所有的二进制运算都是使用补码进行的。
- 补码计算得到的结果也是补码,如果要想知道补码对应的机器数真值,需要把补码转换成原码,才能让人脑更好的识别
为什么需要原码,反码,补码?
为什么需要原码?
因为计算机只能识别0和1,使用的是二进制。而在日常生活中人们使用的是十进制,并且有正负之分,由正负符号来表示。所以只有两种物理状态的计算机想要模拟出正负的概念,并以二进制的形式去存储,也就催生了机器数 原码
的出现,既用一个数的最高位来表示符号,0为正,1为负,剩余位存储数值
既然有了原码,为什么又还要反码呢?
因为原码的出现,我们在计算机中就有了可以表示有符号数值的方式。有了表现数值的方式,我们就可以进行加减乘除的计算。但是经过计算的实践,我们发现,直接拿原码进行四则运算中的减法运算
(加乘除都可以)会得出错误的结果。比如
1 - 1
=1 + (-1)
=0 0000001
+1 0000001
=1 0000010
=-2
我们期待的结果是0 (1 0000000 或 0 0000000)
, 但是却给得到了-2 (1 0000010)
的答案,所以这明显是一个错误。为什么会出现错误呢?
- 对于人脑来说,在计算的时候,我们可以根据符号位, 选择对数值位进行加减的操作。但是对于计算机而言,四则运算属于最底层的基础计算,需要设计的尽量简单,高效率。既根据运算法则,减去一个正数等于加上一个负数,比如
1 - 1
=1 + (-1)
。 所以底层电路设计时,为了避免基础电路设计变得十分的复杂,就放弃了减法的概念,只有保留了加法。所以计算机运算中只有加法,没有减法,减去一个正数会被替换成加上一个负数。 - 通过原码进行四则运算的实践,我们发现,乘除加法都没有问题,只有减法有问题。问题就出在了计算机中没有减法,只有加法,减去一个正数会被替换成了加上一个负数,这是原码设计之初本身就不能满足的事情。 既带有符号位的原码进行减法(加上一个负数)运算,很可能计算出错误的结果。
- Maybe,当初基础电路设计设计的更复杂点,可以容忍减法运算,那么可能原码就可以通过真正的
“减法”
进行减法运算了
为了解决原码无法满足计算机减法运算的问题,反码就出现了
1 - 1
=> 1 + (-1)
=> [0 0000001]原 + [1 0000001]原
=> [0 0000001]反 + [1 1111110]反
=>
[1 1111111]反
=> [1 0000000]反
=> 0
很好,反码的出现就解决了原码无法进行减法运算的问题,真棒
既然有了反码,还要补码做什么?
我们知道反码是在原码的基础上,符号位不变,数值位取反得到的结果。反码解决了原码中无法与负数相加的问题。反码很棒,但是反码依然有一个小缺陷,就是没有解决原码留下来正负0的问题,所以正负0问题也就成为了反码的缺陷。人类总是精益求精的,为了解决反码留下来正负0缺陷,补码就出现了。
正负0问题:
- 反码中+0由
0 0000000
表示,-0由1 0000000
表示,然而-0在数学的角度来说是没有意义的,在计算机存储的角度也是多余的。
- 在8位的补码中,只有+0的概念,+0由
0 0000000
所表示,原-0的编码被人为的指定是-128
。所以8位的补码最小值是-128,取值范围不同于原码和反码的[-127,127]
,而是[-128,127]
小小的总结一下:
- 原码就是计算机为了存储带符号的二进制数值而出现的机器数形式
- 反码就是为了解决原码无法进行减法运算的问题,说白了就是为了解决与负数相加的问题
- 补码就是为了解决反码遗留的正负0问题而出现的新机器数形式
总之,原码,反码,补码是编码方案逐渐进步和完善的过程,计算机机器数存储最终选择了以补码的方式进行
为什么要使用原码,反码,补码 - @百度知道
原码、反码、补码的产生、应用以及优缺点有哪些?- @知乎
相关问题
补码计算的溢出
因为计算出来的结果值大于该计量系统能显示的值的范围,所以我们要得到与结果值同余,且计量系统能显示出来的值
重点是同余,所以理解同余定理可以更好的理解补码计算的溢出情况。
Java的byte超过了[-128.127]时,怎么处理?
我们在进行byte计算的时候,肯定有会这样的思考
- 我们知道-1-127的运算等于-128,刚好等于byte的最小值。那么-127-127得到的结果大于1个字节时,又怎么去计算呢?
- 当计算出来的结果值大于8位二进制系统能存储的值时,怎么办?
所以我们就要来思考一下,在Java语言中如果出现了以上情况, 会怎么样?
Java的byte是以补码进行计算的,当byte变量被赋予[-128,127]
范围之外的值时,这就属于补码计算溢出的情况。那么Java是怎么处理的呢?
它会以补码的形式值
比如,我们将一个十进制值129
赋予给byte, 129
已经超过Java byte类型的值范围[-128,127]
, 所以Java最终输出的byte类型结果是129的补码形式值-127
。过程如图
public class ByteTest {
public static void main(String[] args) {
byte a = (byte) (129);
System.out.println(a);
}
}
//output:
//-127
- 129转换为原码形式为
0 10000001(9位)
,因为byte类型最多支持8位,所以溢出,最高位自然丢失,129在byte的原码形式成为1 0000001(8位)
- 原码
1 0000001(8位)
的数值位取反,得到反码1 1111110(8位)
- 反码
1 11111110(8位)
的数值位 + 1,得到补码1 11111111(8位)
- 补码
1 11111111(8位)
的十进制形式值是-127
,真值是-1
所以在Java中,如果值大于byte的取值范围,Java会以其补码的形式值
方式展示。 当然Java是这么去做的,但不代表其他语言也是这么干的,所以要区别一下奥
那么Java为什么要这么去做呢?
我们知道出现超范围值的情况赋予给byte,本质上是一种补码运算的溢出,溢出的解决方案多半就是寻找该值在该计量系统取值范围内的替代品去表示。
- Java的byte类型是一个8位空间的补码计量系统,取值范围
[-128,127]
, 模为2^7 = 128
(数值位只有7位)。当一些值超过byte计量系统的取值范围时,显然是值的溢出,那么byte计量系统就会为该值寻找一个同余于计量系统的模的数值去代替。就像时钟一样,模是12
,显示范围是[0,11]
, 时钟如何显示16点呢?就找同余于模16的点数,4 mod 12
=16 mod 12
=4
。4,16
对模12
同余,且4点在时钟计量系统的取值范围内,所以16点在时钟上显示就是4点 - 同理Java中的byte也是一样,
129 mod 128
=-127 mod 128
=1
。129,-127
对模128
同余。所以129在byte能显示范围中的替代品就是-127
补充一个小知识点
我们知道Java语言中的8位byte类型是有符号位的,取值范围是[-128,127]
, 模为2^7 = 127
,而有一些语言的8位是byte是无符号位的,取值范围是[0,255]
,模为2^8 = 256
。
- 当我们用Java的有符号位byte去接收其他语言无符号位的byte数据时,就会导致原义是正数的值在Java的byte中显示为负数情况。此时通常情况下的解决方案就是
int = (byte & 0xff)
, 0xff其实就是255的十六进制哈。byte & 0xff
的意思本质就是byte mod (255 +1)
- 该解决方案的本质就是,负数不在无符号位byte计量系统的取值范围内,既不在[0,255]范围内。 所以我需要找出负数于模
2^8 = 256
同余,且在[0,255]
范围内的值 - 例如-135于模256同余,且在[0,255]范围内的值是121, -129于模256同余,且在[0,255]范围内的值是127
为什么java语言中的byte的范围是从-128~127?
在没有学习补码概念的时候,我们只能进行如下分析
我们知道1个字节最多只有8位,无符号位的1字节,所有的8位都可以用来表示正数,取值范围是[0,255]
那么有符号数值怎么表示呢?1个字节如何来表示正负数呢?所以1个字节的8位就要分为符号位
和数值位
- 符号位: 最高位1代表负数,0代表正数
- 数值位: 剩余7位代表数值
所以我们知道了:
1 0000000 ~ 1 1111111
可以表示-0
~-127
0 0000000 ~ 0 1111111
可以表示+0
~+127
- 整体的无符号范围是
-127
~+127
那么问题来了,为什么有-0
和+0
的表示呢?
- 既
-0
由(1 0000000
)表示,+0
由(0 0000000
)来表示。但是在数学的角度看,-0
是没有意义的。而站在计算机存储的角度看,1个字节的空间有限且宝贵,-0
的表示浪费了一个存储空间。所以综上所述,既然-0
的表示没有意义,那么我们又不能浪费这么一个宝贵的空间,所以就人为的去规定了0
只有一种表示手段,既0 0000000
,而1 0000000
则固定认为是-128
在我们学习了补码概念之后,我们就可以很简单的得出结论
- 要问我为什么要把
1 0000000
表示为-128
,这也是为何出现补码的原因,自行看上面为何需要补码 - 我们知道计算机内存中的数值运算是补码运算,包括Java语言,所以Java的byte类型的计算自然也是补码形式进行的
- 因为采用的是补码运算,而补码的取值范围是
[-128,127]
,不同于原码和反码的[-127,127]
所以一个byte的有符号取值范围就是-128~127 , 当然, 你也可以简单的当做这就是规定,就像1 + 1为什么等于2一样记住就好了。
参考资料
- 【秒懂】byte的取值范围为什么是-128~127? - @作者:你等过上帝吗
- byte类型取值范围为什么是127到-128? - @知乎
- 原码, 反码, 补码 详解 - @作者:张子秋的博客
- 补码 - @百度百科
- 如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!