如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里
首先声明,写一篇博客,不代表知识一定是对的,只是在梳理自己学习在过程的理解,尽量做到正确
字长为8的计算机中,一个字节,有8位。2^8 = 256
, 既8位空间进行二进制的排列组合,最多可以有256种可能。
[-127, +127]
, 而使用补码表示的范围为[-128, 127]
计算机默认只会做加法,例: 5 - 3 => 5 + (-3)
乘法除法:是通过左移 << 和右移 >> 来实现
&
与运算。 全1为1, 有0为0|
或运算。 有1为1, 全0为0~
非运算。 逐位取反^
异或运算。 相同为0,相异为1实践:
a mod b = a & (b - 1)
,不过需要在特定的条件下才能成立,需要满足b
是2的次方数值补码
的形式进行的机器数
,真值
的知识同余定理
在机器数中的运用,知道模
, 互补数
,这能让我们更好的理解补码运算中的溢出
和为什么能化减为加
的行为一个数在"计算机"中的"二进制"表示形式, 叫做这个数的机器数。 机器数有以下两个特点
数的符号数值化
二进制的位数受机器设备的限制
说白了,机器数由两部分组成,最高位的符号位
和剩余位的数值位
。
+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替代的
了解同余定理可以让我们更好的理解为什么能化减为加的行为
将同余定理运用到计算机补码运算中,我们需要先了解模,互为补数,同余的概念
模?
模
就像是一个计量系统的计数范围,如时钟只能表示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
我们首先要知道计算机的二进制计算,都是使用补码进行计算的,所以在我们
什么是原码?
实例展示:
十进制真值 | 原码 |
---|---|
+1 |
0 000 0001 |
-1 |
1 000 0001 |
+123 |
0 111 1011 |
-123 |
1 111 1011 |
原码的作用:
什么是反码?
[-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 |
反码的作用
什么是补码?
[-128,127]
,解决了正负0的问题,只有0 0000000
代表01 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 |
补码的作用
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)
。 所以底层电路设计时,为了避免基础电路设计变得十分的复杂,就放弃了减法的概念,只有保留了加法。所以计算机运算中只有加法,没有减法,减去一个正数会被替换成加上一个负数。“减法”
进行减法运算了为了解决原码无法满足计算机减法运算的问题,反码就出现了
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在数学的角度来说是没有意义的,在计算机存储的角度也是多余的。
0 0000000
所表示,原-0的编码被人为的指定是-128
。所以8位的补码最小值是-128,取值范围不同于原码和反码的[-127,127]
,而是[-128,127]
小小的总结一下:
总之,原码,反码,补码是编码方案逐渐进步和完善的过程,计算机机器数存储最终选择了以补码的方式进行
为什么要使用原码,反码,补码 - @百度知道
原码、反码、补码的产生、应用以及优缺点有哪些?- @知乎
因为计算出来的结果值大于该计量系统能显示的值的范围,所以我们要得到与结果值同余,且计量系统能显示出来的值
重点是同余,所以理解同余定理可以更好的理解补码计算的溢出情况。
Java的byte超过了[-128.127]时,怎么处理?
我们在进行byte计算的时候,肯定有会这样的思考
所以我们就要来思考一下,在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
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,本质上是一种补码运算的溢出,溢出的解决方案多半就是寻找该值在该计量系统取值范围内的替代品去表示。
[-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点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
。
int = (byte & 0xff)
, 0xff其实就是255的十六进制哈。byte & 0xff
的意思本质就是byte mod (255 +1)
2^8 = 256
同余,且在[0,255]
范围内的值在没有学习补码概念的时候,我们只能进行如下分析
我们知道1个字节最多只有8位,无符号位的1字节,所有的8位都可以用来表示正数,取值范围是[0,255]
那么有符号数值怎么表示呢?1个字节如何来表示正负数呢?所以1个字节的8位就要分为符号位
和数值位
所以我们知道了:
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
,这也是为何出现补码的原因,自行看上面为何需要补码[-128,127]
,不同于原码和反码的[-127,127]
所以一个byte的有符号取值范围就是-128~127 , 当然, 你也可以简单的当做这就是规定,就像1 + 1为什么等于2一样记住就好了。