一篇帶給你~0 == -1 問(wèn)題全解
今天帶大家復(fù)習(xí)一個(gè)容易遺忘的C語(yǔ)言知識(shí)點(diǎn),負(fù)數(shù)的存儲(chǔ)及其一個(gè)典型考察形式。
我們先看一個(gè)題目
- #include<stdio.h>
- int main()
- {
- int a,b,c,d; //或者 int a,b,c,d;
- a=0x8;
- b=a>>1;
- c=~(~0<<1);
- d=b&c;
- printf("c is %d\n",c);
- printf("d is %d\n",d);
- }
解析:
假設(shè)計(jì)算機(jī)是存儲(chǔ)八位
- 0的存儲(chǔ)是 0b 0000 0000
- ~按位取反 0b 1111 1111
- 左移一位 0b 1111 1110 空檔處補(bǔ)0
- 再按位取反 0b 0000 0001
- 所以c就等于1
這里是一步一步推導(dǎo)過(guò)來(lái)的,你會(huì)忽略一個(gè)關(guān)鍵的過(guò)程,就是~0你算出來(lái)的,是0b 1111 1111,一個(gè)很大的負(fù)數(shù),這和你以為的常理違背,我們下面討論。
再次舉例
- #include <stdio.h>
- int main()
- {
- printf("~0 == %d\n", ~0);
- }
- 0的存儲(chǔ)是 0b 0000 0000
- ~按位取反 0b 1111 1111
這個(gè)題目比較干脆,直接是~0 == -1,有的時(shí)候我們不明白,為什么0b 1111 1111在內(nèi)存中代表-1,因?yàn)樗麩o(wú)論如何也是一個(gè)很大的負(fù)數(shù)才對(duì)。
可是實(shí)際上,負(fù)數(shù)在內(nèi)存中是按照補(bǔ)碼的形式存儲(chǔ)的,也就是說(shuō)0b 1111 1111是一個(gè)補(bǔ)碼,那么它的反碼就是0b 1111 1110,原碼就是0b 1000 0001,也就是-1(注意負(fù)數(shù)求反碼補(bǔ)碼的時(shí)候符號(hào)位不變)
結(jié)論
- 0b 1111 1111 == -1 (~0)
- 0b 1111 1110 == -2 (~1)
- 0b 1111 1101 == -3 (~2)
可以把它當(dāng)做一個(gè)公式 ~a == -【a+1】
補(bǔ)充說(shuō)明
為什么整數(shù)要在內(nèi)存中按照補(bǔ)碼儲(chǔ)存。因?yàn)檎龜?shù)的原碼反碼補(bǔ)碼都一樣,所以我們主要討論問(wèn)什么負(fù)數(shù)在內(nèi)存中按照補(bǔ)碼方式存儲(chǔ)。
采用補(bǔ)碼的原因或好處如下,采用補(bǔ)碼運(yùn)算具有如下兩個(gè)特征:
1)因?yàn)槭褂醚a(bǔ)碼可以將符號(hào)位和其他位統(tǒng)一處理,同時(shí),減法也可以按加法來(lái)處理,即如果是補(bǔ)碼表示的數(shù),不管是加減法都直接用加法運(yùn)算即可實(shí)現(xiàn)。
2)兩個(gè)用補(bǔ)碼表示的數(shù)相加時(shí),如果最高位(符號(hào)位)有進(jìn)位,則進(jìn)位被舍棄。
這樣的運(yùn)算有兩個(gè)好處:
1)使符號(hào)位能與有效值部分一起參加運(yùn)算,從而簡(jiǎn)化運(yùn)算規(guī)則。從而可以簡(jiǎn)化運(yùn)算器的結(jié)構(gòu),提高運(yùn)算速度。(減法運(yùn)算可以用加法運(yùn)算表示出來(lái))
2)加法運(yùn)算比減法運(yùn)算更易于實(shí)現(xiàn)。使減法運(yùn)算轉(zhuǎn)換為加法運(yùn)算,進(jìn)一步簡(jiǎn)化計(jì)算機(jī)中運(yùn)算器的線路設(shè)計(jì)。
下圖最能說(shuō)明為什么用補(bǔ)碼可以使整數(shù)的減法運(yùn)算(即負(fù)數(shù)的運(yùn)算)變?yōu)榧臃ㄟ\(yùn)算:

深入證明:
用帶符號(hào)位的原碼進(jìn)行乘除運(yùn)算時(shí)結(jié)果正確,而在加減運(yùn)算的時(shí)候就出現(xiàn)了問(wèn)題,如下:假設(shè)字長(zhǎng)為8bits
- ( 1 ) - ( 1 ) = ( 1 ) + ( -1 ) = ( 0 )
- 可以表示為:
- (00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 顯然不正確.。
因?yàn)樵趦蓚€(gè)整數(shù)的加法運(yùn)算中是沒(méi)有問(wèn)題的,于是就發(fā)現(xiàn)問(wèn)題出現(xiàn)在帶符號(hào)位的負(fù)數(shù)身上,對(duì)除符號(hào)位外的其余各位逐位取反就產(chǎn)生了反碼。反碼的取值空間和原碼相同且一一對(duì)應(yīng)。下面是反碼的減法運(yùn)算:
- ( 1 ) - ( 1 ) = ( 1 ) + ( -1 ) = ( 0 )
- 可以表示為:
- (00000001) 反+ (11111110)反 = (11111111)反 = ( -0 ) 有問(wèn)題。
- ( 1 ) - ( 2) = ( 1 ) + ( -2 ) = ( -1 )
- 可以表示為:
- (00000001) 反+ (11111101)反 = (11111110)反 = ( -1 ) 正確
問(wèn)題出現(xiàn)在(+0)和(-0)上,在人們的計(jì)算概念中零是沒(méi)有正負(fù)之分的。
于是就引入了補(bǔ)碼概念。負(fù)數(shù)的補(bǔ)碼就是對(duì)反碼加一,而正數(shù)不變,正數(shù)的原碼反碼補(bǔ)碼是一樣的。在補(bǔ)碼中用(-128)代替了(-0),所以補(bǔ)碼的表示范圍為:
(-128~127)共256個(gè)。
注意:(-128)沒(méi)有相對(duì)應(yīng)的原碼和反碼, (-128) = (10000000) 補(bǔ)碼的加減運(yùn)算如下:
- ( 1 ) - ( 1 ) = ( 1 ) + ( -1 ) = ( 0 )
- 可以表示為:
- (00000001)補(bǔ) + (11111111)補(bǔ) = (00000000)補(bǔ) = ( 0 ) 正確
- ( 1 ) - ( 2) = ( 1 ) + ( -2 ) = ( -1 )
- 可以表示為:
- (00000001) 補(bǔ)+ (11111110) 補(bǔ)= (11111111)補(bǔ) = ( -1 ) 正確
采用補(bǔ)碼表示還有另外一個(gè)原因,那就是為了防止0的機(jī)器數(shù)有兩個(gè)編碼。原碼和反碼表示的0有兩種形式+0和-0,而我們知道,+0和-0是相同的。這樣,8位的原碼和反碼表示的整數(shù)的范圍就是-127-127(11111111-01111111),而采用補(bǔ)碼表示的時(shí)候,00000000是+0,即0;10000000不再是-0,而是-128,這樣,補(bǔ)碼表示的數(shù)的范圍就是-128~+127了,不但增加了一個(gè)數(shù)得表示范圍,而且還保證了0編碼的唯一性。
為什么正數(shù)的反碼,補(bǔ)碼和原碼一樣?這是規(guī)定或者說(shuō)這是約定,沒(méi)有多少道理,你算是算不出來(lái)的。
補(bǔ)碼只是為負(fù)數(shù)想出來(lái)的辦法,目的是減法變加法。是減法可以用加補(bǔ)碼的方法實(shí)現(xiàn)。補(bǔ)碼可用反碼加1得來(lái)。于是道又有了負(fù)數(shù)的反碼。
計(jì)算機(jī)里有硬件“加法器”,有了補(bǔ)碼,減法也可以用加法器做了。計(jì)算機(jī)里運(yùn)算速度,硬件遠(yuǎn)快于軟件。這是弄出反碼,補(bǔ)碼和原碼花樣的原因。
形象說(shuō)明
引進(jìn)補(bǔ)碼的作用是為了讓計(jì)算機(jī)更方便做減法,比如說(shuō),按時(shí)間12個(gè)小時(shí)來(lái)算,現(xiàn)在的準(zhǔn)確時(shí)間是4點(diǎn),有一個(gè)表顯示的是7點(diǎn),要校準(zhǔn)時(shí)間,我們可以將時(shí)針退7-4=3格,也可以向前撥12-3=9格,計(jì)算機(jī)做減法就可以轉(zhuǎn)化成-3=+9,這樣可以簡(jiǎn)化計(jì)算機(jī)的硬件設(shè)備去做復(fù)雜的減法。