原碼,反碼,補碼大家都知道,下面通過解析為什麼當初要這樣設計,讓你更透徹的理解它們的原理。
文章參考:
https://blog.csdn.net/afsvsv/article/details/94553228
https://blog.csdn.net/wu_nan_nan/article/details/54633506
https://www.zhihu.com/question/28685048
原文:https://blog.vchar.top/base/1611834985.html
計算方式
原碼:是計算機機器數中最簡單的一種形式,數值位就是真值的絕對值,即二進位制表示的格式;其中最高位是符號位,符號位中0表示正數,1表示負數。
反碼:正數的反碼和原碼一樣;負數的反碼是它原碼除符號位外,其他位按位取反。
補碼:正數的補碼和原碼一樣;負數的補碼等於它反碼+1,或者說是等於它的原碼自低位向高位,尾數的第一個‘1’及其右邊的‘0’保持不變,左邊的各位按位取反,符號位不變。
為什麼這樣要計算呢?
由於計算機的硬體設計決定,其本質都是以二進位制碼來儲存和運算的;根據馮~諾依曼提出的經典計算機體系結構框架。一臺計算機由運算器,控制器,儲存器,輸入和輸出裝置組成。其中運算器,只有加法運算器,沒有減法運算器(據說一開始是有的,後來由於減法器硬體開銷太大,被廢了 )。
因此在計算機中是沒有減法的,只有加法運算。即減一個數相當於加上一個負數。
所以需要設計一種新的計算規則來實現計算機的加法運算;注意我們需要的是一個新的規則來適配計算機的加法運算,使其最終結果和我們現有的計算規則的結果一樣!!
下面我們以4位的二進位制數為例做設計。
原碼
原碼:是最簡單的機器數表示法。用最高位表示符號位,‘1’表示負號,‘0’表示正號。其他位存放該數的二進位制的絕對值。
下面這個以原碼的規則計算機中儲存的資料
/ | 正數 | / | 負數 |
---|---|---|---|
0 | 0000 | -0 | 1000 |
1 | 0001 | -1 | 1001 |
2 | 0010 | -2 | 1010 |
3 | 0011 | -3 | 1011 |
4 | 0100 | -4 | 1100 |
5 | 0101 | -5 | 1101 |
6 | 0110 | -6 | 1110 |
7 | 0111 | -7 | 1111 |
這種設計方式很簡單,雖然出現了-0和+0,但是還能接受;下面我們開始做運算:
0001+0010=0011 ==>> 1+2=3 沒得問題
0000+1000=1000 ==>> 0+(-0)=-0 可以接受
0001+1001=1010 ==>> 1+(-1)=-2 哦,這個...
這種方式在正數之間進行沒得問題,但是有負數的運算就不行了,看來原碼幹不了這個活啊;於是反碼來了。
反碼
我們知道,在十進位制中一個數和其相反數相加等於0,對應的減法也可定義為一個數加上另一個數的相反數;基於這一點反碼的設計思路出來,那就是定義二進位制的相反數求法。
直接按十進位制的套用明顯不行,那麼讓它的原碼除符號位外,按位取反;由於正數使用原碼進行計算沒得問題,就暫時不動它,只讓其適用於負數。得到如下的結果:
現在再來計算:
0001+1110=1111 ==>> 1+(-1)=-0 現在正確了
注意現在計算機中實際儲存的是反碼了。
1110+1101=1011 ==>> -1+(-2)=--4 哦,這個...
這種方式好像在計算負數+負數的時候不得行啊。
不過我們已經解決了相反數相加的問題了,對於負數我們直接讓其符號位固定為1即可達到正確結果。
0001+1110=1111 ==>> 1+(-1)=-0 這個看著怪彆扭的
這種負0看著怪彆扭的,同時需要在負數+負數的時候還要做個符號位強制位1的操作,太麻煩了,要想個辦法偷懶(平時程式設計中也應當有個偷懶的思維 hhh); 於是補碼出現了。
補碼
由於正數是沒得問題的,不做修改,所以正數的補碼等於他的原碼;負數的補碼等於反碼+1。(這只是一種算補碼的方式,多數書對於補碼就是這句話)
負數的補碼等於他的原碼自低位向高位,尾數的第一個‘1’及其右邊的‘0’保持不變,左邊的各位按位取反,符號位不變。
想想當年那些計算機學家(高階專業偷懶戶),並不會心血來潮的把反碼+1就定義為補碼。下面來看看其設計原因。
由於使用十進位制的計算方式已經不能滿足二進位制的需求了,因此我們需要跳出來,重新找靈感。
生活中的時鐘有12個刻度,如果時針現在在10點的位置,那麼什麼時候會停止在8點鐘的位置呢?
這個很簡單再過10個小時,或者2個小時前,那麼得到如下公式:
10-2=8=10+10 時間超過12就會重新開始,這種稱為模。
在時鐘運算中,減去一個數,其實就相當於加上另外一個數(這個數與減數相加正好等於12,也稱為同餘數)
通過時鐘的例子可以發現最終轉換後的計算表示式是2個正數相加,而從前面的結論中2個正數進行運算其符號位並不是必須的,那麼現在設計補碼時我們暫時就將符號位去掉。
這也是為什麼正數的符號位是0,負數的符號位是1的原因;因為如果正數的符號位是1的話,由於其符號位被忽略,當其參與運算時就會發生進位的情況,而使用0就不會有這種情況。
- 同餘數
現在就是需要求這個同餘數的問題,根據數學中對同餘數的定義:
兩個整數a,b,若它們除以整數m所得的餘數相等,則稱a,b對於模m同餘。
例如,當m=12時,3跟15是同餘的,因為3mod12=3=15mod12,對於同餘,有如下結論:
a,b是關於m同餘的,當且僅當,二者相差m的整數倍,
a−b=k×m, with k=……−2,−1,0,1,2,……
即,
a=b+k×m, with k=……−2,−1,0,1,2,……
一個數x加a對m取餘,等於x加a的同餘b對m取餘,即,
(x+a) mod m=(x+b) mod m.
由1.易知2.是成立的。
將引數帶入:
3-15=-1*12 === 3=15+(-1)*12
(x+3)%12=(x+15)%12
將上面的表示式在簡化下:
若:a=b+m,則 (x+a) mod m = (x+b) mod m
那麼現在該如何來求這個同餘數呢?也就是找到負數補碼的求法。
若b為一個負數,表示式可以轉為類似如下:
a-b=m ===>> 設c=-b,則:a+c=m
現在要 求a;通過上面的推導,我們在運算時可以減法轉換為加法,相當於將負數轉換為了正數,那麼符合位也就沒有用了;因此我們新設計的補碼就可以不要符合位(因為都是正數的運算了)。
參考時鐘的案例,我們最終其實只關注二進位制位數能夠表示的數(因為多餘的位數也沒地方儲存),即時鐘刻度的最大值就是m,也就是二進位制位數表達的最大值,例如4位的最大值就是16。
所以求a即計算m-c就是求其二進位制的另一半。而c的另一半就是把二進位制位上的0變1、1變0即取反,它們相加後全是1,而m是其最大值+1,因此還需要加1才行。而這就是補碼的求法。這個計算的方法還可以參考時鐘的情況。下面來實際計算驗證一下:
示例1:
3-5=-2=(3+11)%16
0011(3的補碼) + 1011(-5的補碼,11的原碼) = 1110
1110是一個補碼(此時最高位就是符號位)轉為原碼:1010 即為-2
示例2:
5-3=2=(5+13)%16
0101(5的補碼) + 1101(-3的補碼,13的原碼) = 1 0010
由於總共只有4位,產生的進位會被丟掉,因此最終的補碼為 0010 轉為原碼:0010即為2
示例3:
0001+1111=1 0000 ==>> 1+(-1)=0 這樣之前的-0問題也解決了
最終使用補碼滿足了我們的要求,因此在計算機中實際儲存的是補碼,進行操作的時候也是通過補碼來進行操作。