你真的了解補(bǔ)碼嗎

1、在計(jì)算機(jī)內(nèi),有符號(hào)數(shù)有3種表示法:原碼、反碼和補(bǔ)碼

有符號(hào)數(shù)在計(jì)算機(jī)中存儲(chǔ),用數(shù)的最高位存放符號(hào), 正數(shù)為0, 負(fù)數(shù)為1
例如:有符號(hào)數(shù) 1000 0011,其最高位1代表負(fù),其真正數(shù)值是 -3,而不是形式值131(無符號(hào)數(shù)1000 0011轉(zhuǎn)換成十進(jìn)制等于131)
原碼:

原碼就是符號(hào)位加上真值的絕對值,即用第一個(gè)二進(jìn)制位表示符號(hào)(正數(shù)該位為0,負(fù)數(shù)該位為1),其余位表示值。

反碼:
  • 正數(shù)的反碼與其原碼相同;
  • 負(fù)數(shù)的反碼是對其原碼逐位取反,但符號(hào)位除外。
補(bǔ)碼:
  • 正數(shù)的補(bǔ)碼就是其本身;
  • 負(fù)數(shù)的補(bǔ)碼是在其反碼的基礎(chǔ)上+1

原碼反碼補(bǔ)碼的定義,舉個(gè)例子如下:

[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]補(bǔ)
[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]補(bǔ) 

2、計(jì)算機(jī)內(nèi),為何要使用補(bǔ)碼

對于計(jì)算機(jī)而言,加減乘數(shù)是最基礎(chǔ)的運(yùn)算, 要設(shè)計(jì)的盡量簡單。我們知道,根據(jù)運(yùn)算法則,減去一個(gè)正數(shù)等于加上一個(gè)負(fù)數(shù),即:1 - 1 = 1 + (-1) = 0,所以計(jì)算機(jī)內(nèi)部可以只有加法而沒有減法,這樣計(jì)算機(jī)的設(shè)計(jì)就簡單了,于是人們開始探索將符號(hào)位也參與運(yùn)算,將減法用加法替代。

我們分別用 原碼 反碼 補(bǔ)碼 來計(jì)算下1 - 1的結(jié)果:

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [1000 0010]原 = -2
1 - 1 = 1 + (-1) = [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
1 - 1 = 1 + (-1) = [0000 0001]補(bǔ) + [1111 1111]補(bǔ) = [0000 0000]補(bǔ) = [0000 0000]原 = 0

我們可以看到,使用原碼和反碼都不能正確的進(jìn)行1 - 1的減法運(yùn)算。

補(bǔ)碼的優(yōu)點(diǎn):

  • 可將減法變?yōu)榧臃?,省去減法器;
  • 無符號(hào)數(shù)及帶符號(hào)數(shù)的加法運(yùn)算可以用同一電路完成;
  • 使用補(bǔ)碼,修復(fù)了原碼中0的符號(hào)(有 [+0] [-0] 之分)以及存在兩個(gè)編碼(0000 0000 和 1000 0000)的問題,而且還能夠多表示一個(gè)最低數(shù)。

在8位二進(jìn)制中:

  • 使用原碼/反碼表示的范圍為[-127,+127],包含 [+0] [-0] 一共256個(gè);
  • 使用補(bǔ)碼表示的范圍為[-128,+127],0沒有符號(hào)。

3、補(bǔ)碼的本質(zhì)

補(bǔ)碼為什么能正確實(shí)現(xiàn)加法運(yùn)算呢?我們先從補(bǔ)碼的由來開始說起

補(bǔ)碼的由來

負(fù)數(shù),其實(shí)就是0減去這個(gè)數(shù)的絕對值,比如 -8 = 0 - 8。8的二進(jìn)制是 0000 0100,-8就可以用下面的式子求出:

  0 0 0 0 0 0 0 0 
- 0 0 0 0 1 0 0 0
-----------

因?yàn)閇0000 0000](被減數(shù))小于[0000 1000](減數(shù)),所以不夠減。請回憶一下小學(xué)算術(shù),如果被減數(shù)的某一位小于減數(shù),我們怎么辦?很簡單,問上一位借1就可以了。

  1 0 0 0 0 0 0 0 0
 -  0 0 0 0 1 0 0 0
-----------
    1 1 1 1 1 0 0 0

進(jìn)一步觀察,可以發(fā)現(xiàn)1 0000 0000 = 1111 1111 + 1,所以上面的式子可以拆成兩個(gè):

  1 1 1 1 1 1 1 1
- 0 0 0 0 1 0 0 0
-----------各位取反
  1 1 1 1 0 1 1 1
+ 0 0 0 0 0 0 0 1
-----------加1
  1 1 1 1 1 0 0 0

以上就是-8的補(bǔ)碼的轉(zhuǎn)換過程。

因此負(fù)數(shù)的補(bǔ)碼也可以表示為:1111 1111 + 1 - 負(fù)數(shù)的絕對值

用補(bǔ)碼的方式將減法轉(zhuǎn)換為加法的正確性驗(yàn)證

已知:X、Y都為正整數(shù),Z = X - Y = X + (-Y)
證明:Z = X的補(bǔ)碼 + (-Y的補(bǔ)碼)

X的補(bǔ)碼 = X; // 正數(shù)的補(bǔ)碼為其自身
-Y的補(bǔ)碼 = (1111 1111 - Y) + 1; // 負(fù)數(shù)的補(bǔ)碼為除符號(hào)位的其它位取反加1
Z = X的補(bǔ)碼 + (-Y的補(bǔ)碼) 
  = X +  (1111 1111 - Y) + 1 
  = X - Y + 1 0000 0000
  = X - Y + 0000 0000
  = X - Y
// 1 0000 0000就相當(dāng)于0000 0000(舍去了最高位) 

這就證明了,在正常的加法規(guī)則下,可以利用2的補(bǔ)碼得到正數(shù)與負(fù)數(shù)相加的正確結(jié)果。換言之,計(jì)算機(jī)只要部署加法電路和補(bǔ)碼電路,就可以完成所有整數(shù)的加法。

4、補(bǔ)碼表示的溢出問題

同號(hào)數(shù)相加如果結(jié)果的符號(hào)位和兩加數(shù)不同,既是溢出。

例如8bit的byte類型的表示范圍為[-128,+127],那么+128、+129、-129、-130等超出范圍的數(shù)該怎么表示呢?

 // 超上限 溢出
+128 = 127 + 1 = [0111 1111]補(bǔ) + [0000 0001]補(bǔ) = [1000 0000]補(bǔ) = -128
+129 = 127 + 2 = [0111 1111]補(bǔ) + [0000 0010]補(bǔ) = [1000 0001]補(bǔ) = [1111 1111]原 = -127
// 超下限  溢出
-129 = -128 + (-1) = [1000 0000]補(bǔ) + [1111 1111]補(bǔ) = [0111 1111]補(bǔ) = 127
-130 = -128 + (-2) = [1000 0000]補(bǔ) + [1111 1110]補(bǔ) = [0111 1110]補(bǔ) = 126

通過上述計(jì)算我們可以看出,對于8bit的數(shù)據(jù)(一共2^8 = 256個(gè)):

  • 超上限的數(shù) x = x - 256;
  • 超下限的數(shù) x = x + 256;
  • 下限的相反數(shù)與下限相等;
  • 上限的相反數(shù)是上限直接取負(fù)值。

證明舉例如下:

byte a = -128, b = (byte) 128, c = (byte) 129, d = (byte) 130;
byte e = (byte) -129, f = (byte) -130;
System.out.println(a == ((byte)-a));    // true
System.out.println(b);  // -128
System.out.println(c);  // -127
System.out.println(d);  // -126
System.out.println(e);  // 127
System.out.println(f);  // 126

5、符號(hào)擴(kuò)展與多重轉(zhuǎn)型

下面的代碼的輸出是什么?能看懂結(jié)果么?

byte b = -1;
System.out.println((int)(char)b); // 65535
我們先來聊聊符號(hào)擴(kuò)展(Sign Extension)

符號(hào)擴(kuò)展用于在數(shù)值類型轉(zhuǎn)換時(shí)擴(kuò)展二進(jìn)制位的長度,以保證轉(zhuǎn)換后的數(shù)值和原數(shù)值的符號(hào)(正或負(fù))和大小相同,一般用于較窄的類型(如byte)向較寬的類型(如int)轉(zhuǎn)換。擴(kuò)展二進(jìn)制位長度指的是,在原數(shù)值的二進(jìn)制位左邊補(bǔ)齊若干個(gè)符號(hào)位(0表示正,1表示負(fù))

Java中整型字面量種類
  • 十進(jìn)制方式,直接書寫十進(jìn)制數(shù)字
  • 八進(jìn)制方式,格式以0打頭,例如012表示十進(jìn)制10
  • 十六進(jìn)制方式,格式為0x打頭,例如0xff表示十進(jìn)制255

需要注意的是,在Java中012和0xff返回的都是int型數(shù)據(jù),即長度是32位。

Java類型轉(zhuǎn)換規(guī)則(出自《Java解惑》一書)

1、如果最初的數(shù)值類型是有符號(hào)的,那么就執(zhí)行符號(hào)擴(kuò)展;
2、char是無符號(hào)類型,不管它要被轉(zhuǎn)換成什么類型,都執(zhí)行零擴(kuò)展;
3、如果目標(biāo)類型的長度小于源類型的長度,則直接截取目標(biāo)類型的長度。例如將int型轉(zhuǎn)換成byte型,直接截取int型的右邊8位。

解析“多重轉(zhuǎn)型”問題
(int)(char)(byte) -1
  • int(32位) -> byte(8位)
    -1是int型的字面量,編碼結(jié)果為0xffff ffff,即32位全部置1。轉(zhuǎn)換成byte類型時(shí),直接截取最后8位,所以轉(zhuǎn)換后的結(jié)果為0xff,對應(yīng)的十進(jìn)制值是-1。
  • byte(8位) -> char(16位)
    由于byte是有符號(hào)類型,所以在轉(zhuǎn)換成char型(16位)時(shí)需要進(jìn)行符號(hào)擴(kuò)展,即在0xff左邊連續(xù)補(bǔ)上8個(gè)1(1是0xff的符號(hào)位),結(jié)果是0xffff,對應(yīng)的十進(jìn)制數(shù)是65535(char是無符號(hào)類型)。
  • char(16位) -> int(32位)
    由于char是無符號(hào)類型,轉(zhuǎn)換成int型時(shí)進(jìn)行零擴(kuò)展,即在0xffff左邊連續(xù)補(bǔ)上16個(gè)0,結(jié)果是0x0000 ffff,對應(yīng)的十進(jìn)制數(shù)是65535

6、幾個(gè)轉(zhuǎn)型的例子

先看下面代碼的輸出:

byte b=-1;
System.out.println((int)(char)(b & 0xff)); // 255

1、byte型數(shù)值b -> char型:
char c = (char)(b & 0xff); // 不希望有符號(hào)擴(kuò)展
char c = (char)b; // 希望有符號(hào)擴(kuò)展

  • 0xff是int型字面量(0x0000 00ff),(b & 0xff)的結(jié)果是32位的int類型,前24被強(qiáng)制置0,后8位保持不變,然后轉(zhuǎn)換成char型時(shí),直接截取后16位。這樣不管b是正數(shù)還是負(fù)數(shù),轉(zhuǎn)換成char時(shí),都相當(dāng)于是在左邊補(bǔ)上8個(gè)0,即進(jìn)行零擴(kuò)展而不是符號(hào)擴(kuò)展。
  • byte類型(8位)的b擴(kuò)展成char型(16位)時(shí)需要進(jìn)行符號(hào)擴(kuò)展。

2、char型數(shù)值c -> int型:
int i = c & 0xffff; // 不希望有符號(hào)擴(kuò)展
int i = (short)c; // 希望有符號(hào)擴(kuò)展

  • 0xffff是int型字面量(0x0000 ffff),所以在進(jìn)行&操作之前,編譯器會(huì)自動(dòng)將c(16位)轉(zhuǎn)型成int型(32位),即在c的二進(jìn)制編碼前添加16個(gè)0,然后再和0xffff進(jìn)行&操作,所表達(dá)的意圖是強(qiáng)制將前16位置0,后16位保持不變。
  • 首先將c轉(zhuǎn)換成short類型,它和char是 等寬度的,并且是有符號(hào)類型,再將short類型轉(zhuǎn)換成int類型時(shí),會(huì)自動(dòng)進(jìn)行符號(hào)擴(kuò)展,即如果short為負(fù)數(shù),則在左邊補(bǔ)上16個(gè)1,否則補(bǔ)上16個(gè)0。

最后,介紹一種簡單的 2的補(bǔ)碼對應(yīng)十進(jìn)制數(shù) 的記憶方式:

0000 0000 = 0
那么在 0 的基礎(chǔ)上 -1 就是(想象成借位減法):
1111 1111 = -1
以此類推:
1111 1110 = -2
1111 1101 = -3
...

參考資料:
你真的了解Java中的負(fù)數(shù)?
關(guān)于2的補(bǔ)碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 進(jìn)制基本概念 什么是進(jìn)制?進(jìn)制是一種計(jì)數(shù)的方式,數(shù)值的表示形式 常見的進(jìn)制十進(jìn)制、二進(jìn)制、八進(jìn)制、十六進(jìn)制 進(jìn)制書...
    極客江南閱讀 2,187評(píng)論 0 11
  • 「WTF系列」深入Java中的位操作 關(guān)于WTF系列 引 學(xué)完本章節(jié)你將學(xué)會(huì)位的基礎(chǔ)概念與語法,并且還會(huì)一些騷操作...
    qiujuer閱讀 1,074評(píng)論 0 5
  • 進(jìn)制基本概念 什么是進(jìn)制?進(jìn)制是一種計(jì)數(shù)的方式,數(shù)值的表示形式 常見的進(jìn)制十進(jìn)制、二進(jìn)制、八進(jìn)制、十六進(jìn)制 進(jìn)制書...
    低頭看云閱讀 947評(píng)論 0 1
  • 不知不覺,已經(jīng)從烈日炎炎的夏天來到了風(fēng)輕云淡的秋天,晝夜的交點(diǎn)--秋分,俗話說:“秋分一到,谷場見稻?!眮?..
    陳家瑞1閱讀 561評(píng)論 3 2
  • 不知道大家是否遇到點(diǎn)擊tableViewCell彈出提醒框的時(shí)候發(fā)現(xiàn)延遲問題,第一次點(diǎn)擊的時(shí)候會(huì)正常的彈出,再點(diǎn)擊...
    其實(shí)你懂De閱讀 639評(píng)論 3 1

友情鏈接更多精彩內(nèi)容