參考文章
參考文章1
https://blog.csdn.net/zl10086111/article/details/80907428
作者:張子秋
出處:http://www.cnblogs.com/zhangziqiu/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
參考文章2
https://blog.csdn.net/afsvsv/article/details/94553228
參考文章3
https://www.cnblogs.com/Jamesjiang/p/8947252.html
一、預備知識
在學習原碼, 反碼和補碼之前, 需要先了解一些概念.
1、機器數
一個數在計算機中的二進制表示形式, 叫做這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號, 正數為0, 負數為1.
比如,十進制中的數 +3 ,計算機字長為8位,轉換成二進制就是00000011。如果是 -3 ,就是 10000011 。
那么,這里的 00000011 和 10000011 就是機器數。
2、真值
因為第一位是符號位,所以機器數的形式值就不等於真正的數值。例如上面的有符號數 10000011,其最高位1代表負,其真正數值是 -3,而不是形式值131(10000011轉換成十進制等於131)。所以,為區別起見,將帶符號位的機器數對應的真正數值稱為機器數的真值。
例:
0000 0001的真值 = +000 0001 = +1
1000 0001的真值 = –000 0001 = –1
3、原理
我們學習之前還得認識二進制,十六進制。會二進制與十進制的相互轉化運算。
進制轉換可以參考我的博客:https://www.cnblogs.com/Zzbj/p/10905744.html
由計算機的硬件決定,任何存儲於計算機中的數據,其本質都是以二進制碼存儲。
根據馮~諾依曼提出的經典計算機體系結構框架。一台計算機由運算器,控制器,存儲器,輸入和輸出設備組成。其中運算器,只有加法運算器,沒有減法運算器。
所以,計算機中的沒法直接做減法的,它的減法是通過加法來實現的。
你也許會說,現實世界中所有的減法也可以當成加法的,減去一個數,可以看作加上這個數的相反數。當然沒錯,但是前提是要先有負數的概念。這就為什么不得不引入一個該死的符號位。
-
而且從硬件的角度上看,只有正數加負數才算減法。
-
正數與正數相加,負數與負數相加,其實都可以通過加法器直接相加。
原碼,反碼,補碼的產生過程,就是為了解決,計算機做減法和引入符號位(正號和負號)的問題。
二、原碼, 反碼, 補碼的基礎概念和計算方法
對於一個數, 計算機要使用一定的編碼方式進行存儲. 原碼, 反碼, 補碼是機器存儲一個具體數字的編碼方式.
1、原碼
原碼:是最簡單的機器數表示法。用最高位表示符號位,‘1’表示負號,‘0’表示正號。其他位存放該數的二進制的絕對值。
例如:
帶符號的8位二進制:
[+1]原 = 0000 0001 = +1
[-1]原 = 1000 0001 = -1
若以帶符號位的四位二進值數為例
-
1010 : 最高位為‘1’,表示這是一個負數,其他三位為‘010’,
-
即(0*2^2)+(1*2^1)+(0*2^0)=2(‘^’表示冪運算符)
-
所以1010表示十進制數(-2)。
下圖給出部份正負數數的二進制原碼表示法
首先大概了解一下二級制加法運算
二進制運算規則:逢2進1 0+0=0,0+1=1,1+0=1,1+1=10 也就是當兩個數相加的二進制位僅一位為1時,相加的結果為1; 如果兩個二進制位全是0,相加的結果仍為0; 而如果兩個相加的二進制位均為1,則結果為10(相當於十進制中的2) 例如: # 這里暫時不考慮符號 1010 + 0110 = 10 + 6 = 16 1010 + 0110 ---------- 10000 逢2進1(從右到左): 0+0=0 1+1=10 # 進位1,剩余0 0+1+1(這個1是進位) = 10 # 進位1,剩余0 1+0+1(這個1是進位) = 10 # 進位1,剩余0 1 # 最后進位得到的1 再比如 111 + 111 = 7 + 7 = 14 111 + 111 ---------- 1110 逢2進1(從右到左): 1+1=10 # 進位1,剩余0 1+1+1(這個1是進位)=10+1=11 # 進位1,剩余1,這種情況的運算規則是:先算原本的1+1=10,再算10+1(進位)=11 1+1+1(這個1是進位)=10+1=11 # 進位1,剩余1 1 # 最后進位得到的1
二進制減法運算
二進制減法如何借位 例如:100 - 1 = 4 - 1 = 3 二進制在減法運算時,當前一位為0就再向前借位。 直到不為零的位數,借1位當2算。 所以 100 - 1 借位后相當於 012 (實際二進制中沒有2,這里需要自己體會一下) - 1 ---------- 011 即:3
OK,原碼表示法很簡單有沒有,雖然出現了+0和-0,但是直觀易懂。 於是,我們高興的開始運算。 0001+0010=0011 (1+2=3)OK 0000+1000=1000 (+0+(-0)=-0) 額,問題不大 0001+1001=1010 (1+(-1)=-2) 噢,1+(-1)=-2,這仿佛是在逗我呢。 於是我們可以看到其實正數之間的加法通常是不會出錯的,因為它就是一個很簡單的二進制加法。 而正數與負數相加,或負數與負數相加,就要引起莫名其妙的結果,這都是該死的符號位引起的。0分為+0和-0也是因他而起。 所以原碼,雖然直觀易懂,易於正值轉換。但用來實現加減法的話,運算規則總歸是太復雜。於是反碼來了。
2、反碼
我們知道,原碼最大的問題就在於一個數加上他的相反數不等於零。
例如:
0000 0001 + 1000 0001 = 1000 0010 (1+(-1)=-2)
0000 0010 + 1000 0010 = 1000 0100 (2+(-2)=-4)
於是反碼的設計思想就是沖着解決這一點,既然一個負數是一個正數的相反數,那我們干脆用一個正數按位取反來表示負數試試。
反碼:
正數的反碼還是等於原碼 負數的反碼就是他的原碼除符號位外,按位取反。
例如:
帶符號的8位二進制:
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
那么我們再試着用反碼的方式解決一下原碼的問題
把原碼都轉成反碼進行計算,得到反碼,把結果得到的反碼再換算回原碼即可
反碼轉換回原碼:
正數:反碼就是原碼
負數:符號位不變,其他位按位取反
# 正數和負數相加 1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0 互為相反數相加等於0,解決,雖然是得到的結果是-0 1 - 2 = 1 + (-2) = [0000 0001]原 + [1000 0010]原 = [0000 0001]反 + [1111 1101]反 = [1111 1110]反 = [1000 0001]原 = -1 結果也是正確的 # 再試着做一下兩個負數相加 (-1) + (-2) = [1000 0001]原 + [1000 0010]原 = [1111 1110]反 + [1111 1101]反 = [1 1111 1011]反 = [1 0000 0100]原 = -4 噢,好像又出現了新問題 (-1)+(-2)=(-4)?
看來相反數問題是解決了,但是卻讓兩個負數相加的出錯了。
但是實際兩個正數相加和兩個負數相加,其實都是一個加法問題,只是有無符號位罷了。而正數+負數才是真正的減法問題.
也就是說只要正數+負數不會出錯,那么就沒問題了。負數加負數出錯沒關系的,負數的本質就是正數加上一個符號位而已。
在原碼表示法中兩個負數相加,其實在不溢出的情況下結果就只有符號位出錯而已(1001+1010=0011)
實際反碼表示法其實已經解決了減法的問題,他不僅不會像原碼那樣出現兩個相反數相加不為零的情況,而且對於任意的一個正數加負數的計算結果是正確的。
所以反碼與原碼比較,最大的優點,就在於解決了減法的問題。
但我們還有一個負數相加的問題,此時就需要用補碼了。
3、補碼
補碼:
- 正數的補碼等於他的原碼
- 負數的補碼是在其原碼的基礎上, 符號位不變, 其余各位取反, 最后+1. (即在反碼的基礎上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]補
[-1] = [10000001]原 = [11111110]反 = [11111111]補
補碼運算規則是:
- X+Y = [X]補 + [Y]補 = [X+Y]補 = 再轉換為原碼即可
- X-Y = [X]補 + [-Y]補 = [X-Y]補 = 再轉換為原碼即可
- -X-Y = [-X]補 + [-Y]補 = [-X-Y]補 = 再轉換為原碼即可
例如:
正數和負數相加: 1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]補 + [1111 1111]補 = [1 0000 0000]補 = [0 0000 0000]原 = 0 兩個負數相加: (-1) + (-2) = [1000 0001]原 + [1000 0010]原 = [1111 1111]補 + [1111 1110]補 = [1 1111 1101]補 = [1 1111 1100]反 = [1 0000 0011]原 = -3 # 由此可見,補碼即解決了符號問題,也解決了負數加負數的情況
4、移碼
移碼(又叫增碼或偏置碼)通常用於表示浮點數的階碼,其表示形式與補碼相似,只是其符號位用“1”表示正數,用“0”表示負數,數值部分與補碼相同。
[X]原= 1010_1011 ,[X]反=1101_0100,[X]補=1101_0101,[X]移=0101_0101
三、溢出
1、溢出的常用處理方法
使用雙符號位法計算需要遵循以下兩個規則:
- 兩個符號位都看做數碼一樣參加運算;
- 兩數進行以2^n+2為模的加法,即最高符號位上產生的進位要丟掉;
- 計算結果以符號位高位為結果符號
符號位 | 結果 |
---|---|
00 | 正數 |
01 | 溢出 |
10 | 溢出 |
11 | 負數 |
2、溢出解析
1.說到溢出,還是要先提一下自然丟棄。
請看下面這個例子:
-3 - 6
-3 --> 1101[補]
-6 --> 1010[補]
相加 --------
結果 (1)0111
結果的位數比原先的多出了一位,此處最左邊的1,是會被自然丟棄的(就是不要了)。再看結果,對0111[補]=0111[原](也就是+7)。這和我們想要的-9有天壤之別。為什么會出現這個情況呢?
原因就是這里出現了溢出!
首先來看溢出的定義:
對一個N位有符號的二進制補碼,其可以表達的范圍是 [ - 2N-1 ~ 2N-1 - 1 ] 之間。如果超出這個范圍就稱為溢出了。
拿上面的-3-6來說,我們剛剛在計算時,轉換為四位二進制補碼(算上符號位),那么它的取值范圍是-8~+7之間。而我們想要的結果是-9,比范圍的最小值還要小,這個叫做負溢出。同理如果想要的結果比最大值還要大,那么就叫做正溢出,如取值范圍是-8~+7之間,想要的結果是+9,那么就是正溢出。
說完了溢出的定義,我們來說說溢出的判定,就是怎么在計算開始時知道自己算的結果是不是溢出了?
采用雙符號形式,我們再計算一次 -3-6
11 101[補]
11 010[補]
(1)10 111[補]
得到結果是 10 111[補](最高位超出位寬,自然舍棄),采用雙符號運算得到的最后結果應該是單符號的,
就是:-3 = 11 101 前面雙符號11代表負數, -6 = 11 010 前面雙符號11代表負數,但是得到的結果10 111(發生負溢出),只有前面1是符號位,代表負數,真值是0 111
因此最后結果:10 111[補] = 1 0110[反] = 1 1001[原] = -9
四、反碼與補碼的原理
計算機中的數值是以補碼形式存儲的(只不過正數的補碼跟原碼一樣。
強調原碼,反碼,補碼的引入是為了解決做減法的問題。
在原碼,反碼表示法中,我們把減法化為加法的思維是減去一個數,等於加上一個數的相反數,結果發現引入了符號位,卻因為符號位造成了各種意向不到的問題。
反碼+1 ,它只是補碼的另外一種求法,不是補碼的定義。
1、補碼的原理
為了便於理解可以用時鍾計算(12小時制的)
9要撥到5,可以減4,也可以加8 ,所以此時 -4和+8是等價的 。
那么 ,我們可以認為 8是4的補碼
那么這兩個數有什么聯系呢? 沒錯他們和在一起就繞了時鍾一圈,用數學表達的話就是 兩數的相加的絕對值 為 12
類比到補碼:首先的明白一圈是多少 ?假設機器一圈為128(2的7次方)
於是用128減真值 也就是所謂的 數值位取反,末位加一 的操作了。
再回到時鍾, 9-4=9+[4]補=(9+8)%12 因為他會多走一圈,所以我們再在這里還要 %12,而計算機補碼是有周期的,不用這一步。
也就是,當前9點,我們希望撥到5點,做法如下
1. 往回撥4個小時: 9 - 4 = 5
2. 往前撥8個小時: (9 + 8) mod 12 = 5
3. 往前撥8+12=20個小時: (9+20) mod 12 =5
2,3方法中的mod是指取模操作, 17 mod 12 =5 即用17除以12后的余數是5.
所以鍾表往回撥(減法)的結果可以用往前撥(加法)替代!
現在的焦點就落在了如何用一個正數, 來替代一個負數. 上面的例子我們能感覺出來一些端倪, 發現一些規律. 但是數學是嚴謹的. 不能靠感覺.
首先介紹一個數學中相關的概念: 同余
兩個整數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 同余.
負數取模
-1 mod 4 = (-1 + 4*n) mod 4,n取正整數,一直到括號里的數不為負數。
例如:
(-2) mod 12 = 12-2=10
(-4) mod 12 = 12-4 = 8
(-5) mod 12 = 12 - 5 = 7
再回到時鍾的問題上:
回撥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)
如果想看這個定理的證明, 請看:http://baike.baidu.com/view/79282.htm
所以:
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]