原碼,反碼,補碼,你理解到位了嗎?
如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!博客目錄 | 先點這裏
首先聲明,寫一篇博客,不代表知識一定是對的,只是在梳理自己學習在過程的理解,儘量做到正確
- 前提概念
- 如何理解原碼,反碼,補碼?
- 機器數與真值
- 什麼是機器數?
- 什麼是真值?
- 同餘定理的應用
- 什麼是同餘定理?
- 模,互爲補數,同餘
- 原碼,反碼,補碼
- 原碼
- 反碼
- 補碼
- 爲什麼需要原碼,反碼,補碼?
- 相關問題
- 補碼計算的溢出
- 爲什麼java語言中的byte的範圍是從-128~127?
前提概念
字節
字長爲8的計算機中,一個字節,有8位。2^8 = 256
, 既8位空間進行二進制的排列組合,最多可以有256種可能。
- 無符號位情況下,可表示最大的值爲255,最小值爲0
- 有符號爲情況下,可表示最大的值爲127,最小值爲-127(8)
8位二進制, 使用原碼或反碼錶示的範圍爲[-127, +127]
, 而使用補碼錶示的範圍爲[-128, 127]
二進制運算
計算機默認只會做加法,例: 5 - 3 => 5 + (-3)
乘法除法:是通過左移 << 和右移 >> 來實現
&
與運算。 全1爲1, 有0爲0|
或運算。 有1爲1, 全0爲0~
非運算。 逐位取反^
異或運算。 相同爲0,相異爲1
實踐:
- 1000 mod 256 等同於 1000 & (256 - 1) ,通式是
a mod b = a & (b - 1)
,不過需要在特定的條件下才能成立,需要滿足b
是2的次方數值
如何理解原碼,反碼,補碼?
- 首先我們必須要先知道現代操作系統的數值運算是以
補碼
的形式進行的 - 在學習原碼,反碼,補碼之前,我們有必要先來了解一下
機器數
,真值
的知識 - 然後瞭解一下
同餘定理
在機器數中的運用,知道模
,互補數
,這能讓我們更好的理解補碼運算中的溢出
和爲什麼能化減爲加
的行爲 - 然後我們就可以學習一下原碼,反碼,補碼的基本概念
- 再瞭解一下爲什麼需要原碼,反碼和補碼,以及他們的具體作用是什麼
機器數與真值
什麼是機器數?
一個數在"計算機"中的"二進制"表示形式, 叫做這個數的機器數。 機器數有以下兩個特點
數的符號數值化
我們知道數值具有正負數之分,由於計算機內部的硬件只能表示0,1兩種物理狀態,因此數值的正負數,通常在機器中使用最高位的0,1來區分表示。正數爲0, 負數爲1二進制的位數受機器設備的限制
機器設備一次能表示的二進制位數叫機器的字長,一臺機器的字長是固定的。字長8位叫一個字節,機器字長一般都是字節的整數倍,如字長8位、16位、32位、64位
說白了,機器數由兩部分組成,最高位的符號位
和剩餘位的數值位
。
- 舉個粟子,十進制的
+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替代的
瞭解同餘定理可以讓我們更好的理解爲什麼能化減爲加的行爲
- 同餘定理在計算機補碼中的運用,就是爲了解決化減爲加,解決計算機只認識加法的問題,就像上面的例子,-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
原碼,反碼,補碼
我們首先要知道計算機的二進制計算,都是使用補碼進行計算的,所以在我們
原碼
什麼是原碼?
- 原碼是人腦最容易理解和計算的表示方式
- 原碼是機器數的一種表現形式,由符號位 + 數值位組成 ,數值位是真值的絕對值
- 字長8位的1字節原碼的取值範圍是
[-127,127]
, 0有兩種表示編碼
實例展示:
十進制真值 | 原碼 |
---|---|
+1 |
0 000 0001 |
-1 |
1 000 0001 |
+123 |
0 111 1011 |
-123 |
1 111 1011 |
原碼的作用:
- 計算機中無法直接使用原碼進行計算,計算機內部運算使用的是補碼,非原碼。但如果要知道某個補碼對應的機器數真值是什麼,就需要先轉換爲原碼,再通過原碼快速的轉換爲真值
- 通過原碼,我們可以快速的知道其對應的真值是什麼,這就是需要原碼的主要原因
反碼
什麼是反碼?
- 反碼是在原碼的基礎上,符號位不變,數值位全部取反的結果
- 正數的反碼是其本身,一定意義上說只有負數纔有反碼
- 反碼沒有直接用於計算,並且通過反碼也無法得知真值,反碼是一箇中間值
- 字長8位的1字節反碼的取值範圍是
[-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 |
反碼的作用
- 反碼是一箇中間值,既不能直接計算,也不能直接推斷出真值
- 其實反碼已經可以表示負數了,既本質可以用來化減爲加的去做機器數運算。只是反碼運算留有一個小缺陷,既沒有解決正負0的問題,所以最後沒有被採用做計算的編碼,而是作爲原碼和補碼之間的中間值。
補碼
什麼是補碼?
- 補碼是原碼符號位不變,數值位取反之後再 + 1得到的結果值,既在反碼的基礎上+1
- 正數的反碼,補碼都是其本身
- 補碼的出現是爲了解決正負0的問題,補充反碼的缺陷
- 補碼是計算機存儲和運算的直接編碼,不過不能直接表示出真值,所以對外展示,還需要轉換爲原碼
- 字長8位的1字節反碼的取值範圍是
[-128,127]
,解決了正負0的問題,只有0 0000000
代表0 - 8位空間中,人爲的定義原負0的編碼
1 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 |
補碼的作用
- 補碼是爲了解決反碼中遺留的正負0問題。爲了不浪費寶貴的空間資源,負0的二進制表示
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)
。 所以底層電路設計時,爲了避免基礎電路設計變得十分的複雜,就放棄了減法的概念,只有保留了加法。所以計算機運算中只有加法,沒有減法,減去一個正數會被替換成加上一個負數。 - 通過原碼進行四則運算的實踐,我們發現,乘除加法都沒有問題,只有減法有問題。問題就出在了計算機中沒有減法,只有加法,減去一個正數會被替換成了加上一個負數,這是原碼設計之初本身就不能滿足的事情。 既帶有符號位的原碼進行減法(加上一個負數)運算,很可能計算出錯誤的結果。
- Maybe,當初基礎電路設計設計的更復雜點,可以容忍減法運算,那麼可能原碼就可以通過真正的
“減法”
進行減法運算了
爲了解決原碼無法滿足計算機減法運算的問題,反碼就出現了
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在數學的角度來說是沒有意義的,在計算機存儲的角度也是多餘的。
- 在8位的補碼中,只有+0的概念,+0由
0 0000000
所表示,原-0的編碼被人爲的指定是-128
。所以8位的補碼最小值是-128,取值範圍不同於原碼和反碼的[-127,127]
,而是[-128,127]
小小的總結一下:
- 原碼就是計算機爲了存儲帶符號的二進制數值而出現的機器數形式
- 反碼就是爲了解決原碼無法進行減法運算的問題,說白了就是爲了解決與負數相加的問題
- 補碼就是爲了解決反碼遺留的正負0問題而出現的新機器數形式
總之,原碼,反碼,補碼是編碼方案逐漸進步和完善的過程,計算機機器數存儲最終選擇了以補碼的方式進行
爲什麼要使用原碼,反碼,補碼 - @百度知道
原碼、反碼、補碼的產生、應用以及優缺點有哪些?- @知乎
相關問題
補碼計算的溢出
因爲計算出來的結果值大於該計量系統能顯示的值的範圍,所以我們要得到與結果值同餘,且計量系統能顯示出來的值
重點是同餘,所以理解同餘定理可以更好的理解補碼計算的溢出情況。
Java的byte超過了[-128.127]時,怎麼處理?
我們在進行byte計算的時候,肯定有會這樣的思考
- 我們知道-1-127的運算等於-128,剛好等於byte的最小值。那麼-127-127得到的結果大於1個字節時,又怎麼去計算呢?
- 當計算出來的結果值大於8位二進制系統能存儲的值時,怎麼辦?
所以我們就要來思考一下,在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
- 129轉換爲原碼形式爲
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,本質上是一種補碼運算的溢出,溢出的解決方案多半就是尋找該值在該計量系統取值範圍內的替代品去表示。
- Java的byte類型是一個8位空間的補碼計量系統,取值範圍
[-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點 - 同理Java中的byte也是一樣,
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
。
- 當我們用Java的有符號位byte去接收其他語言無符號位的byte數據時,就會導致原義是正數的值在Java的byte中顯示爲負數情況。此時通常情況下的解決方案就是
int = (byte & 0xff)
, 0xff其實就是255的十六進制哈。byte & 0xff
的意思本質就是byte mod (255 +1)
- 該解決方案的本質就是,負數不在無符號位byte計量系統的取值範圍內,既不在[0,255]範圍內。 所以我需要找出負數於模
2^8 = 256
同餘,且在[0,255]
範圍內的值 - 例如-135於模256同餘,且在[0,255]範圍內的值是121, -129於模256同餘,且在[0,255]範圍內的值是127
爲什麼java語言中的byte的範圍是從-128~127?
在沒有學習補碼概念的時候,我們只能進行如下分析
我們知道1個字節最多隻有8位,無符號位的1字節,所有的8位都可以用來表示正數,取值範圍是[0,255]
那麼有符號數值怎麼表示呢?1個字節如何來表示正負數呢?所以1個字節的8位就要分爲符號位
和數值位
- 符號位: 最高位1代表負數,0代表正數
- 數值位: 剩餘7位代表數值
所以我們知道了:
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
,這也是爲何出現補碼的原因,自行看上面爲何需要補碼 - 我們知道計算機內存中的數值運算是補碼運算,包括Java語言,所以Java的byte類型的計算自然也是補碼形式進行的
- 因爲採用的是補碼運算,而補碼的取值範圍是
[-128,127]
,不同於原碼和反碼的[-127,127]
所以一個byte的有符號取值範圍就是-128~127 , 當然, 你也可以簡單的當做這就是規定,就像1 + 1爲什麼等於2一樣記住就好了。
參考資料
- 【秒懂】byte的取值範圍爲什麼是-128~127? - @作者:你等過上帝嗎
- byte類型取值範圍爲什麼是127到-128? - @知乎
- 原碼, 反碼, 補碼 詳解 - @作者:張子秋的博客
- 補碼 - @百度百科
- 如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!