java中原碼、反碼和補碼--時鐘理解法

若想練得上乘功夫,必先基礎功紮實。就像現在練吉他一樣,想把和絃轉換地流暢就得先把左手的按鈕練好,否則可能連練下去的信心都沒有了,或者勉強去轉換和絃得到的音色肯定也不會理想,或者斷斷續續。雖然,從大學開始就接觸java了,但是基本功一直不怎麼紮實。以至於對現在的框架、設計模型等等理解的都比較淺。因此,我打算從新拾起這些基礎知識好些研究下。因此,推出我個人博客的這個系列《靜下心來學java》。

1、原碼
第一位爲符號位,餘下的表示數的絕對值。
以byte類型舉例:
0000 0001 表示+1
1000 0001 表示-1

2、反碼
反碼要分兩種情況討論。
正數的反碼和原碼相同。
負數的反碼在原碼的基礎上,保持符號位不變,其他位取反。
以byte類型舉例:
0000 0001的反碼是0000 0001
1000 0001的反碼是1111 1110

3、補碼
同樣,補碼也分兩種情況討論。
正數的補碼和原碼相同。
負數的補碼在其反碼的基礎上+1。
以byte類型舉例:
0000 0001的補碼是 0000 0001
1000 0001的補碼是 1111 1111

上面只是對原碼、反碼和補碼三者概念的介紹。那麼,計算機中爲什麼要設計補碼這一概念呢?直接用原碼涉及到減法操作,這增加了算機底層電路涉及的複雜性。而用補碼操作時,我們減去一個數時,可以看做加上一個負數,然後轉變爲加上這個負數的補碼。

因此,現在我們只要知道一件事情,那就是計算機中沒有減法操作,所有的減法操作都被轉變爲加法操作。下面,以一個時鐘問題來探究補碼的意義:
這裏寫圖片描述

正如,我們上面看到的這個時鐘這樣,它的週期是12,這裏我們把12看做0。-1點表示11點、-2點表示10點、以此類推,-11點表示一點。
對於這個時鐘的該怎麼看,有下面幾點:
一、時鐘上的可見性表示我們運算的數的範圍,即0-11。
二、在時鐘上的運算操作包括兩種方式:往回撥(用一個負數表示),往前撥(用一個正數表示)。

下面通過時鐘的操作來理解第一個問題–減少底層電路設計的複雜性。
爲了實現時鐘的運算,即往回撥和往前撥。第一種,用普通的方式(就相當於在計算機中用原碼進行操作),我們得在時鐘上安裝兩個撥片,一個向前撥動指針,一個向後撥動指針。
比如:+1操作,就需要用前撥撥片向前撥動一格。-2操作,需要後撥撥片向後撥動2格。
爲了簡化時鐘設計的複雜性,我們決定只保留時鐘上的一個前撥撥片,即指針只能被向前撥。這樣的話,當進行+1操作的時候不變,向前撥一個。那麼,-2操作該如何實現呢?上面我們提到-2表示10(這裏10相當於-2的補碼),那麼-2操作就可以轉變爲向前撥動10格。比如,現在時間是6點整,那麼-2應該是4點整。現在往前撥10格後,發現確實還是4點整。
哇!這樣一來後撥撥片就完全被取代了(減法操作取代了),我們只需要一個前撥撥片即可(只需要加法操作)。
通過時鐘,我們要明白週期這個東西,爲什麼-2可以用10來代替呢?因爲這裏的週期是12, -2+12 = 10。因此,-2的補碼可以用+10表示。如同這個道理一樣,所有的負數都可以用該週期內對應的一個正數來表示。而這個正數b的計算公式爲:

c (週期)
a (週期內的一個負數)
b (a在c週期內對應的正數)
b = a+c

下面結合時鐘問題,分析下java中正負數的加減問題。在java中,最小的整數類型是byte類型。一個字節,8位。表示的數的範圍是-128~127。
爲了方便分析,我現在假設有一種更小的數據類型只有4位,那麼它的二進制的範圍是0000~1111。由於第一位即高位是用來表示符號位的,那麼表示正數的只能是0000~0111。換算爲10進製表示的範圍是0~7,共8個數字。4位二進制總共能表示16位數字,那麼剩下的二進制就只能表示-1~-8了。
下面我們來試着用原碼的方式來標註下-8~+7這幾個數,看看會有什麼意想不到的事情發生呢。

     原碼    反碼   補碼
-8   
-7   1111
-6   1110
-5   1101
-4   1100
-3   1011
-2   1010
-1   1001
0    (1|0)000
1    0001
2    0010
3    0011
4    0100
5    0101
6    0110
7    0111

果然,是有意想不到的事情發生。-8並沒有一種原碼能表示,而0恰好有兩種表示方法+0(0000)和-0(1000)。因此,當用原碼來表示負數時,真是窮的窮死(-8沒有二進制來表示),富的富的流油(0有兩種表示方式)。
下面,我們繼續把他們的反碼和補碼寫出來:

     原碼         反碼        補碼
-8   
-7   1111        1000        1001
-6   1110        1001        1010
-5   1101        1010        1011
-4   1100        1011        1100
-3   1011        1100        1101
-2   1010        1101        1110
-1   1001        1110        1111
0    (1|0)000    (1|0)111    (1|0)000
1    0001        
2    0010
3    0011
4    0100
5    0101
6    0110
7    0111

正數的反碼不補碼是相同,就不寫出來了。看到這裏,你們可以在腦海中腦補一個0~15的時鐘,那麼這個時鐘的週期就是16。只不過這個時鐘的可見性是-8~7,因此它在鐘錶上的表示是這樣的-8來代替指針上的8,用-7來代替9,因此類推用-1代替15。因此得到的鐘表如下所示:
這裏寫圖片描述
另外,根據補碼的計算公式,-8的補碼爲-8+16=+8,二進制位1000。這真的巧了,剛好是0的兩個補碼中的一個,因此,我們將這個0多出來的補碼交給-8。得到更新後的原碼、反碼、補碼如下:

     原碼         反碼        補碼
-8                           1000
-7   1111        1000        1001
-6   1110        1001        1010
-5   1101        1010        1011
-4   1100        1011        1100
-3   1011        1100        1101
-2   1010        1101        1110
-1   1001        1110        1111
0    (1|0)000    (1|0)111    0000
1    0001        
2    0010
3    0011
4    0100
5    0101
6    0110
7    0111

雖然-8有補碼了,但是它還是沒有原碼和反碼。還有一點需要強調的是,指針的可見性,即數的表示範圍-8~7。也就是說,我們計算出的結果的範圍只能在這之間,如果超過的話,會被轉化到這個範圍之內,轉換的公式。

a 待轉換的數
c週期
b轉換後的數
b = a%c

注意,在這裏週期爲16,被轉化後b一定落在0~15之間,在8-15範圍內的數字,如我的圖所畫的那樣表示爲對應的負數,也就是將補碼轉化爲原碼的過程。這個是因爲,最高的以爲被用來表示符號位了,所以有一半的二進制被用來表示負數了,所以最大值也由本來的15變爲了7了。
因此,在手工所畫的時鐘裏。7點整,在進行+2操作,得到的應該是9,但是9已經超出整數部分能表示的範圍了,因此,這個9其實是-7的補碼,它表示的是-7(原碼)。

因此,我們可以這個擴展到byte字節來進行驗證。byte所表示的整數的範圍是-128~127。同樣你可以在腦海中,腦補出一個週期爲256的時鐘畫面,同樣注意它的可見性,從128開始被-128代替,129被-127代替,一次類推,255被-1代替。
若在127點上進行+2操作,前面一格是-128,再前面一格是-127,你可以驗證127+2得到的結果應該是-127。我們設計補碼的作用還是爲了進行減法操作。
例:
127點進行-3操作就相當於127加上一個-3在週期256範圍內的補碼是253。127+253 = 380。對380進行模運算380%256的結果爲124, 124並沒有超過最大的整數範圍。因爲結果位124。
根據上面的幾種情況,我總結了java中正負數加法的規則:
1、計算機中沒有減法操作,所有的減法都被替換爲加法操作。
2、加上一個負數,這個負數可以表示爲這個週期內對應的正數。
3、如果得到的結果是一個正數,且範圍沒有超過週期的大小,則不需要進行求模操作。如果沒超過週期的範圍,但是超過了最大整數的範圍,那麼這個結果其實是負數的補碼,它的值應當是一個負數。
4、在二進制中,補碼和補碼的運算結果仍然是一個補碼,以0開頭的補碼錶示的是一個正數,以1開頭的補碼錶示的是一個負數。補碼轉爲原碼的規則是先減1,然後取反。

補碼設計原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章