從原碼,反碼,補碼的設計理念來深入理解其原理

原碼,反碼,補碼大家都知道,下面通過解析為什麼當初要這樣設計,讓你更透徹的理解它們的原理。

文章參考:

//blog.csdn.net/afsvsv/article/details/94553228
//blog.csdn.net/wu_nan_nan/article/details/54633506
//www.zhihu.com/question/28685048

計算方式

原碼:是計算機機器數中最簡單的一種形式,數值位就是真值的絕對值,即二進制表示的格式;其中最高位是符號位,符號位中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就是b的補碼

現在要求a, 上面表達式中c是一個正數,那麼在補碼中對應的符號位就沒有用了,因為負數變為了正數。
參考時鐘的案例,我們最終其實只關注二進制位數能夠表示的數,即時鐘刻度的最大值就是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的補碼) = 1110
    1110是一個補碼(此時最高位就是符號位)轉為原碼:1010 即為-2

示例2:

    5-3=2=(5+13)%16
    0101(5的補碼) + 1101(-3的補碼) = 1 0010
    由於總共只有4位,因此最終的補碼為 0010 轉為原碼:0010即為2

因此在計算機中實際存儲的是補碼,進行操作的時候也是通過補碼來進行操作。