某君不無興奮地跟我說他有個重大發(fā)現(xiàn): java里最小負整數(shù)的絕對值等于它本身! 程序員朋友可以試試以下判斷:
Math.abs(int.MIN_VALUE) == int.MIN_VALUE
不管怎樣, 取絕對值后一定是個正數(shù), 怎么可能等于一個負數(shù)呢.乍一看, 太二和尚摸不著頭腦. 向來以精準著稱計算機世界, 怎么會得出如此可笑的結(jié)論?
其中一定有什么隱情! 憑直覺, 這應(yīng)該和補碼有關(guān). 補碼, 初中時代的知識, 不出意外地, 忘得差不多了, 先溫故一下, 沒準能知新.
正數(shù)的補碼等于它自己, 負數(shù)的補碼等于其反碼+1.
計算機是以二進制方式表示數(shù)字的, 其中負數(shù)又以補碼的方式表示. 當回憶起這點的時候, 有一個問題吸引了我:為什么負數(shù)要用補碼表示呢?, 這似乎是一個和1+1為什么等于2一樣傻的問題, 所以當年聰明的我沒敢問老師為什么. 如今智商越發(fā)下降, 類似的問題也越來越多, 然后, 像小孩子發(fā)現(xiàn)更好玩的玩具一樣, 開頭那個問題就被晾在一邊了…
大概一開始人們也沒設(shè)計出補碼這玩意兒, 畢竟誰會上來就找麻煩搞這么復(fù)雜的東西. 應(yīng)該是很自然地第一位是符號位, 0表示正數(shù), 1表示負數(shù), 剩下是表示值的多少. 當然, 對于人類輕而易舉想到的答案, 上帝一般是要發(fā)笑的. 這個方案會有什么問題呢? 讓我們簡單地假定整數(shù)是用3個比特表示的, 其表示的所有數(shù)字, 排列組合如下:
000: 0, 001: 1, 010: 2, 011: 3, 100: -0, 101: -1, 110: -2, 111: -3
這樣也能work. 但很顯然的不足是, 0有兩種表示法, 有點浪費空間了. 還有一個更隱蔽的缺點, 用一道題來說明. 按上述規(guī)則計算下1-1, 也就是1+(-1)的結(jié)果.
001+101=110
110就是-2, 顯然不對. 那么如何設(shè)計才能讓同時彌補上述兩個缺點呢? 這時候, 補碼就隆重登場了. 按補碼的規(guī)則重新窮舉上述排列:
000: 0, 001: 1, 010: 2, 011: 3, 100: -4, 101: -3, 110: -2, 111: -1
0不再重復(fù)了, 表示的空間從[-3, 3], 擴大到[-4, 3]. 而且1-1的計算, 變得簡單自然, 絲般順滑(最高位溢出):
001+111=000
因此, 不必為加法和減法設(shè)計兩套計算電路! 兩個看似不同, 甚至對立的邏輯, 得到了統(tǒng)一. 設(shè)計之巧妙, 令人嘆服. 看似很傻的問題, 細細研究, 原來這般微妙, 優(yōu)雅得簡直像藝術(shù)品, 不得不佩服提出補碼那位兄臺.
繞了個圈, 回到最開始的問題. 絕對值的計算大家都清楚: 正數(shù), 直接返回它自己, 負數(shù), 返回其相反數(shù). 而java計算相反數(shù), 就是取其補碼. 仍然拿3比特的為例, 最小的負數(shù)是100(-4), 取反是011, 加1后是100, 還是-4. int類型一般是32比特, 但演算過程是一樣的.
寫代碼如何能少出bug? 做架構(gòu)如何能更靈活適應(yīng)需求的變化? 一個共同的答案是, 發(fā)現(xiàn)和歸納事物的規(guī)律, 通過巧妙的設(shè)計, 或更高的抽象, 減少差異性, 提高普適性.
設(shè)計行業(yè)里流行少即是多的理念. 我想, 少不是刻意刪除, 不是空洞無物. 少, 是在充分理解的前提下, 找到那個支點, 四兩撥千斤, 用最小的付出, 撬動最大的收益. 九九歸一, 少得下來, 一定是看透了規(guī)律, 抓住了本質(zhì).
上面對補碼的理解, 足夠概括和普適了嗎? 并沒有.
先試試在進制上做點延展. 小學生都會的減法, 能否統(tǒng)一成加法呢? 答案是肯定的. 事實上,
兩整數(shù) A撬即、B 用同一個正整數(shù) M (M 稱為模)去除而余數(shù)相等,則稱 A度迂、B 對 M同余,記作: A=B (MOD M). 具有同余關(guān)系的兩個數(shù)為互補關(guān)系,其中一個稱為另一個的補碼.
那么, 一個更加概括的理解是:減去一個數(shù), 等于加上這個數(shù)的補碼.
數(shù)學源于生活, 讓我們舉個日常生活的例子. 時鐘連3歲小孩都能看懂, 比11點早3小時是8點, 比11點晚9小時也是8點, 嘿嘿, 說的就是這個理, 只不過這里的模是12. 而計算機用的是二進制, 以2為模: 1-1 = 1+1 = 2+0 = 0 (2被抹掉了).
所以下回教小孩子做減法可以這么說: 7-3 = 7+7 = 10+4 = 4. 當然, 要解釋為什么10被任性地抹掉了, 也挺費勁.