本文主要论述了计算机中原码、补码、反码等相关知识点、及其应用与计算。仅个人理解,如有错误,望诸位指正于评论区。
KR
先在这里提前写下结论,以方便于以后查阅,尚未了解原码、补码等可跳过这部分。
-
真值:数值的大小,包括符号
-
机器数(二进制数形式,包括原码、反码、补码、):
- 原码:正数符号位0,负数符号位1,数值按权展开。8位范围:
-127D ~ 127D
- 反码:正数的反码与原码一致,负数的反码是,仅将原码的数值位置取反。8位范围:
-127D ~ 127D
- 补码:正数与原码相同,负数的补码是,反码加1。8位范围:
-128D ~ 127D
- 原码:正数符号位0,负数符号位1,数值按权展开。8位范围:
-
补码计算:
- 二进制:正不变,负取反加一。
- 十进制:正不变,负数总范围量减去数值,即
2^n - xxx
- 十六进制:十进制转十六进制即可。(负数也可以用
全F减,再加一
)
前储认知
首先,我们需要了解一件事:计算机是电子设备,只认识0与1,不认识我们人类所用的汉语、英文、涂鸦等。
可以设想,电的信号有两个状态,你可以理解成路的“开”与“关”,电位的“高”与“低”等,我们就可以用0与1表示这两个状态。所以电子设备就有了属于自己的“语言”描述它们的认知。
但是只有0与1两个状态是远远不足以描述很多信息的,所以我们需要很多的0与1不同排列,这样就可以描述更多的信息。
位:0与1不能共存,一个位只能放一个状态。(这可不能用薛定谔的猫来解释)。
历史上的常见的计算机位数有8位、16位、32位、64位,现代计算机大多采用的64位。至于为什么没有12位?15位?这个暂时先不管。。。
这样一来,1
位,可以描述2
个“情形”。2
位则可以描述2*2=4
个情形,以此类推8
位则可以描述2^8
,即256
个“状态”。
字节Byte:我们将8位记作一个字节。
字Word:我们将16位记作一个字,显然,1个字的占位等于2个字节的占位。
我们点到为止。
这样一来,通过对8位0与1的排列,这256个状态,用来描述英文的26个大小写字母、阿拉伯数字等绰绰有余,而汉字可能有3万多个,所以需要扩充位数,常见的位数中,至少扩充到 16位 方可满足要求,所以你可以这样记,一个字等于16位。(仅仅是提供了一种记忆方法)
计算机,顾名思义,它的一生都在计算。所以,接下来,我们暂时只关注与思考“数字”与“运算”。
二进制
既然计算机只能通过0与1描述这个世界,所以计算机的计数方式是二进制,逢2进一位,而我们人类生来有10个手指,所以计数方式是十进制,逢10进一位。
计算机的字节长度为8位,所以我们一般将二进制前面补充0,直到满足8位,例如:十进制的6,表示为0000 0110
。
十进制到二进制
数字的起点都是从0开始,我们的数字0到9,则可以在二进制中逐一增加排序依次对应,即从000
到1001
,这根据进制换算,可以很容易理解,至于进制如何换算,可以百度一下,或者我有时间了也总结一下。
真值与机器码
纯数字,计算机可以用二进制表示了,但事实上,我们使用的数字是有正负号的,例如+2,-2.
如果只是无符号的、单独的数字2
,用十进制表述为2 D
,用二进制表示为:0000 0010 B
(这里的D与B用于区分进制,D是十进制的后缀,B代表二进制,为了看清数字,我留了间隔)。
但是-2
呢?难不成是-0010 B
?这对于人类是可以理解的,但是对于计算机,其中一个位要么是0要么是1,不会出现负号-
的,因此,我们约定,最前面的正负号单独占据一位,用 0
与 1
表示,所以-2D = 1000 0010B
真值: -000 0010B
这种
机器码:1000 0010B
这种,首位的0
代表正号,1
代表负号。
将代表正负号的位,放在纯数字形成的二进制码的最左边(即,最高位),称作 符号位。
为什么讨论0?
本文的案例中,无论是二进制表示、还是计算结果,我都选择了0这个数字。
第一,0 的正负不影响结果,因此,在原码与反码的表示中,0都有两种方式表述。
第二,任何两个数相加的结果,都可以拆解成 “0加上某一个数”。而减法计算中,0的结果很容易引发歧义,因此,解决了0的结果,那么和的结果也就顺利成章的解决了。
正文
原码
按我们的正常思维,符号位+数值位
就组成了原码。
原码: 符号位 + 二进制的数值
例如:
0000 0111
表示 +7
1000 0110
表示 -6
……
这没什么问题,很和谐,每个数字都有对应的一个编码,除了0:
0000 0000
表示 0
,
1000 0000
表示 0
。
这样的话,0具有两种表述情况了,还是分正负的,表面上看去没什么问题,0的正负也的确还是0,如果单单只考虑数字数据在计算机中的存储,这没什么问题。
不过,我们还需要考虑到数字的运算。
加法运算
为了实现数字的计算,所以我们需要进行最简单的加减运算,而实现计算机的减法,涉及到借位,这处理起来比较麻烦,显然,通过加上一个负数实现减法,将减法转变成加法运算,就会使问题简单化了。所以,计算机中二进制的减法运算,就是与负数的加法运算。
不过,举一个很简单的例子,如果计算(+2)+(-2)
:
0000 0010
:+2
1000 0010
:- 2
1000 0100
:- 4
这里出现了问题,结果并不等于0
。
所以,,,
反码
如何让上述的结果正确?
我们看到,符号还没有影响结果,但在数值位上,出现了问题。如果这是两个正数相加,例如:2+2
在数值上,结果将4
,这没问题;所以问题点在于减数,又或者说,问题出现在运算的时候负数的表述上。
反码: 无论正负,符号位不变。对于数值位,正数与原码表示相同,负数的数值位取反。
如果像这样进行编码,正数+负数,结果则为负数(符号位为1),
这样,再次进行计算(+2)+(-2)
:
0000 0010
:[+2]反
1111 1101
:[- 2]反
1111 1111
:[-0]反
这样的反码表示,正数我们很容易理解,也可以直观计算得出十进制数+2
。但是对于负数,因为数值位取反,所以我们需要再取反,反推出负数的十进制结果,这会很让人头痛。
你可能会说:
”我们只在运算的时候进行反码处理,计算结果依旧采用原码进行存储”,这样计算的结果就是:
1000 0000
: [-0]原
这样,既保证了加减运算不会出错,结果又很符合本来的思维习惯,这看起来似乎没什么问题啊。
但我们仔细思考,0有两种表示,如果计算0+1
呢?这会出现两种情况。
+0-1
的计算:
运算过程:
0000 0000
: [+0]反
0000 0001
:[+1]反
0000 0001
: [+1]反
结果存储:
0000 0001
: [+1]原
-0+1
的计算:
运算过程:
1111 1111
: [-0]反
0000 0001
: [+1]反
0000 0000
: [+0]反
结果存储:
0000 0000
: [+0]原
这出现了两个结果,使用 +0
的运算结果是正确的,使用-0
的结果反而缺少了1。
上述问题的结果出错也不难理解,从反码的表示上看,+0
可以由-0
进一位得到,所以使用-0
计算的结果会比正确的结果少1
。
我们在测试一下,计算0-1
呢?
+0-1
的计算:
运算过程:
0000 0000
:[+0]反
1111 1110
:[-1]反
1111 1110
:[-1]反
1000 0001
:[-1]原
-0-1
的计算:
1111 1111
:[-0]反
1111 1110
:[-1]反
0000 0001
:[+1]反
0000 0001
:[+1]原
对比一下,发现使用+0
计算是不会出现问题的,所以,无论是在运算还是存储中,0都只使用 +0
的形态是不会出现问题的,而-0
的意义并不大。
但是,我们不可避免的会遇到运算结果为-0
的形态,那该这个-0
该怎么办?我们需要对其重新约定。
补码
刚才已经了解到-0
的反码进一位便是+0
,按照这个思路,针对-0
取反码的结果,再加一,就会变成+0
的形态,这样就可以继续用于运算与存储了。
这样一来,加法的结果为-0
,则自动进一位。我们回忆一下,什么样的运算会导致加法的结果为-0
?如果在计算过程中提前进一位,结果是否也就是+0
了?
在进行计算(+2)+(-2)
的时候,
0000 0010
:[+2]反
1111 1101
:[- 2]反
1111 1111
:[-0]反
结果为-0
这只是个例,对于任何相反数这个结果,都是等价的。我们以这个算式作为例子,权衡一下两个方案的利弊。
首先+0
的编码不动它,因为我们的目的就是为了让-0
变成+0
,即0000 0000
依旧表示0
。
如果对+2
的反码进一位,相应的,背后的意思是对所有的正数都进一位,最小的正数本来的0000 0001
就会留空,0000 0002
则表示1
。
当然,如果非要保证连贯性,那么对
+0
也重新定义为0000 0001
,-0
也将定义为0000 0000
,这还是没有消去0存在两种表示这件事。
而如果对-2
的反码进一位,相应的,意味着所有的负数都进一位,整个范围内的编码没有留空,并且正数的原有表示也不会改变。这很合乎常规思路。
所以,
补码: 正数的补码与原码一致;负数的补码是该数的反码加1。
所以。补码的规则就解决了反码的漏洞,这样一来,任何两个数的加法都不会出现问题了。(当然,运算结果在范围之内,不考虑溢出的情况)
回想刚才的问题,设想一下,如果运算与存储采用两种方案,运算时用补码,存储时采用原码,这看起来似乎两全其美,因为通过原码我们可以更容易计算出人类熟悉的十进制,而运算的过程我们交给计算机无需过多关注。
但是为什么计算机的存储与运算都采用统一的补码的方案?
斯以为,有这些理由:
第一,使用两种方案,是否意味着计算需要多走两步?(即原码到补码,补码返回原码),这会增加计算时长,影响了计算效率。如果每一次计算都这样,累加起来的亿万次运算将会浪费更久。
第二,我们熟悉的终究是十进制,为了保证可读性,电脑呈现给我们的也只能是十进制,因此我们无需自己去将那些机器码换算成十进制,背后的换算交给计算机即可,这就无所谓编码的形态了。
~最后,为了统一约定,干脆对所有的数都进行统一的编码方案,即 补码。
总结
- 原码: 符号位 + 二进制的数值
- 反码: 无论正负,符号位不变。对于数值位,正数与原码表示相同,负数的数值位取反。
- 补码: 正数的补码与原码一致;负数的补码是该数的反码加1。
补充:
对于这三个理解,还有模运算方面的解释,时钟方面的解释,可以搜索了解,往后有时间的话,也会记录一下…