2020.3 update: 這段文字是我2018年10月大一的時候寫的,當時大概在程設課上想到,以為自己發現了什么新大陸。但其實,這無非是補碼的原理在我們的計算機教學中沒有體現的緣故,這里所講所想的,在Computer System: A Programmer's Perspective這本計算機系統科普入門書的第2章 Representing and Manipulating Information里都有提到,而且講解更仔細。一年后發現我這篇短文章的閱讀量竟然不小,可見中文互聯網對優秀內容的需求還是很大的。重讀當時的文字,感覺當時寫的基本原理都是對的,只是行文很不流暢,留下青澀的痕跡,排版也有錯漏。在此打磨一下。
===========================================================================我是分割線=
C++里面帶符號整數類型(例如int)對負數的二進制存儲是用補碼進行的.對補碼的解釋一般是"對負數的絕對值按位取反再+1".
不過我覺得"按位取反再+1"只是用到補碼的時候一種正確的求法.
更符合補碼的原理,對補碼更加自然的解釋是:對於負數\(-x\),"若數據類型為n個二進制位,則補碼為\(2^n-x\)".也就是說,這個補碼其實就是\(-x\),只不過是對\(2^n\)取模之后的\(-x\).這樣就能理解,為什么減去一個數等於加上這個數的補碼,因為補碼無非是這個數在模意義下的相反數.
自行驗證一下,按位取反再+1和\(2^n - x\)的結果是完全一樣的. 這是因為按位取反就相當於\(2^n-1-x\).
#include<cstdio>
int main(){
unsigned int a = -1;
printf("%d\n",a);
printf("%u\n",a);
printf("%x\n",a);
return 0;
}
unsigned int a = -1
這個賦值語句首先要把-1轉化為二進制補碼表示,然后把這串補碼表示的二進制數直接賦值給a,賦給a的值當然就是\(2^{32}-1\).
printf("%d\n",a)
卻能完美地輸出-1.
這里涉及到int如何分辨正數和負數.int有32個二進制位,用最高位分辨正負.如果最高位為1,就是負數,最高位為0,就是正數(或者0).
int存儲范圍是\([-2^{31},2^{31}-1]\) 我們把負數(-x)都用補碼表示,也就是說用\(2^{32}-x\)表示\([-2^{31},-1]\)這個范圍的整數.可以看到,這個范圍的數字得到的補碼恰好都是最高位為1的.(\(-2^{31}\)的補碼是"最小"的補碼,只有最高的二進制位是1,其他二進制位都是0,而-1的補碼所有二進制位都是1).而int能表示的最大正數\(2^{31}-1\),就是令最高位為0,其他數位都是1.
0的補碼仍為0,所以補碼表示法解決了0的正負問題.
回過頭來看那段代碼,printf("%d\n",a)
里面的%d把a的二進制表示按照int類型解釋,自然就解釋出了-1的值,輸出結果是-1.
總結一下,int補碼表示法的的本質是在模2的n次方的意義下表示負數(n通常為32), 也就是把 \(-x\)表示為\((-x)\mod 2^n\)