爲了簡化,以8位二進制有符號整數爲例。
1、原碼、反碼、補碼的基礎知識
原碼:
口口口口 口口口口 //最高位是符號位,0表示正數,1表示負數
所以表示的範圍是-128~127(其中-128是用1000 0000表示)
反碼:
正數的反碼是其原碼;
負數的反碼是其原碼的符號位不變,其它爲按位取反。
補碼:
正數的補碼是其原碼;
負數的補碼是其反碼加1。
1)爲了解決計算機中的減法運算,提出了“補碼”概念。“反碼”只是從原碼求補碼過程的一個產物。
2)因此,計算機中數的存儲是按補碼進行的。(正數也是,因爲三個碼一樣的)
3)從原碼能快速看出具體數值是多少。若給出負數的16進制反碼和補碼,習慣是把它轉成原碼看。
4)負數的原碼轉補碼口訣,對其絕對值按位取反再加1(有人也有這種說法,除了符號位按位取反再加1)。
【例子】求-2補碼.
-2=0-2=(0xff+1)-2=0xff-2+1,即對2按位取反再加1。
5)負數的原碼和補碼的快速轉換。利用滿週期歸0的思想,就不需在草稿紙上逐位求反加1這麼費力得計算。
【例子】求-2的補碼(假設以int類型變量保存).
因爲2+(-2)=0,由int的表示範圍也知道2+(0xffff fffe)=0,故-2的補碼是0xffff fffe
2、加法和減法運算
加法運算:
(假設a和b都是正整數)
a+b
1)8位都按位相加
2)正數相加使符號位變1,表示溢出,結果爲負數。
【例1】96+64=0110 0000 +0100 0000=1010 0000
注意上述得到的是計算機存儲的值,對於負數它就是補碼。那麼它的反碼是補碼減1,即1001 1111;它的原碼是反碼除了符號位按位取反,
即1110 0000,是-96。
減法運算:
(假設a和b都是正整數)
a-b=a+(-b) =a+(0-b)=a+(0xFF+1-b)=a+(0xFF-b+1)
1)對正整數b的原碼按位取反再加1,即對負整數-b除了符號位外按位取反再加1。 然後再和a相加。
2)正數和負數相加不會有溢出現象。
3)如果是求-a-b,其實可以轉換爲0-a-b分兩次求出,也等於-a的補碼和-b的補碼相加。負數相加若溢出,結果爲正數。
【例2】-80-64=(-80)+(-64)=1011 0000 +1100 0000=0111 0000=0011 00
同理,上邊得到的是計算機存儲的值。因爲是正數,直接看出等於112。
【例3】-16-64=(-80)+(-64)=1111 0000 +1100 0000=1011 0000
同理,上邊得到的是計算機存儲的值,對於負數它就是補碼。那麼它的反碼是補碼減1,即1010 1111;它的原碼是反碼除了符號位按位取反,
即1101 0000,即-80。
3、程序測試
【例1】正數相加溢出
void main()
{
char i = 96+64;
printf("%d\n", (char)i);
printf("0x%x\n", (char)i);
}
執行結果:
分析:
1) 對於unsigned char 8位符號整數,在VS2015上通過printf(“%x”,a);輸出顯示,個人覺得因爲是負數(最高位符號爲1),所以要隱含轉換爲長度32位的類型,所以纔有0xffff ffa0。
2)不過沒關係,因爲它還是補碼,對應的反碼是補碼減1,即0xffff ff9f; 對應的原碼是反碼除了符號位位取反,即0x8000 0060,依然是-96。
【例2】負數相加溢出
void main()
{
char i = -80-64;
printf("%d\n", (char)i);
printf("0x%x\n", (char)i);
}
執行結果:
分析:
1) 對於unsigned char 8位符號整數,在VS2015上通過printf(“%x”,a);輸出顯示,因爲符號爲正(是0),只有最低8位是有效的,所以才顯示8位,得到0x70,即112。
【例3】負數相加未溢出
void main()
{
char i = -16-64;
printf("%d\n", (char)i);
printf("0x%x\n", (char)i);
}
執行結果:
分析:
1) 對於unsigned char 8位符號整數,在VS2015上通過printf(“%x”,a);輸出顯示,個人覺得是負數(最高位符號爲1),所以要隱含轉換爲長度32位的類型,所以纔有0xffff ffb0。
2)不過沒關係,因爲它還是補碼,對應的反碼是補碼減1,即0xffff ffaf; 對應的原碼是反碼除了符號位位取反,是0x8000 0050,即-80。