從補(bǔ)碼談計(jì)算機(jī)的數(shù)值存儲和展示

上下文約束

默認(rèn)圍繞8位計(jì)算機(jī)展開討論。

問題

在進(jìn)入正文之前,先提三個問題:

  1. 計(jì)算機(jī)中的數(shù)為什么用補(bǔ)碼(2's complement)來表示和存儲?
  2. 補(bǔ)碼的計(jì)算規(guī)則是怎么來的?
  3. 計(jì)算機(jī)是如何區(qū)分unsigned int和int?

眾所周知,二進(jìn)制是一種記數(shù)系統(tǒng)(類比十進(jìn)制),而補(bǔ)碼就是該系統(tǒng)之上的編碼協(xié)議。協(xié)議是為了無序信息流變得規(guī)整,讓人能夠控制它。從這方面猜測,補(bǔ)碼產(chǎn)生的原因是為了最小化硬件設(shè)計(jì)的成本,這大概也是最初的軟件定義硬件(SDH)。

當(dāng)我們想象比特流的存儲過程時,不免好奇自己頭腦中的數(shù)值概念(尤其是負(fù)數(shù)和小數(shù))怎么被計(jì)算機(jī)編碼成有意義的比特流?這些比特流如何被正確地計(jì)算成另一種比特流?在更高層次上,編程語言中的short, int, unsigned int, long, long long等數(shù)值類型是怎樣被計(jì)算機(jī)正確地識別的?

通常,協(xié)議是處理復(fù)雜度的好方法,但隱藏在協(xié)議背后的原因比它本身更有探索的價值。如果你也感同身受,那么閱讀下文就是合適的。

為什么用補(bǔ)碼?

概念解釋

  1. 原碼[1](True form)
    原碼是指一個二進(jìn)制數(shù)左邊加上符號位后所得到的碼,且當(dāng)二進(jìn)制數(shù)大于0時,符號位為0;二進(jìn)制數(shù)小于0時,符號位為1;二進(jìn)制數(shù)等于0時,符號位可以為0(+0)或1(-0)。

  2. 反碼[2](One's complement)
    反碼是帶有符號位的二進(jìn)制數(shù)表示;負(fù)數(shù)的反碼是將其對應(yīng)正數(shù)按位取反,正數(shù)和0的反碼就是該數(shù)字本身。

  3. 補(bǔ)碼[3](Two's complement)
    補(bǔ)碼也是帶有符號位的二進(jìn)制表示;負(fù)數(shù)的補(bǔ)碼是將其對應(yīng)正數(shù)按位取反再加1,正數(shù)和0的補(bǔ)碼就是該數(shù)字本身。

三者的關(guān)系很密切,是1對1的單射關(guān)系。準(zhǔn)確地說,補(bǔ)碼下可表示的最大負(fù)數(shù)沒有對應(yīng)的原碼和反碼。具體原因,待會兒做詳細(xì)討論。

從實(shí)際應(yīng)用的角度提問,現(xiàn)代計(jì)算機(jī)中數(shù)據(jù)的存儲形式是原碼、反碼還是補(bǔ)碼?這個問題其實(shí)可以規(guī)約到另一個問題上:哪種存儲形式最有利于計(jì)算機(jī)進(jìn)行運(yùn)算?從這個角度思考就不難得出答案,當(dāng)然是補(bǔ)碼,不然,要是原碼和反碼都夠用,為啥設(shè)計(jì)出補(bǔ)碼?

那么,為什么原碼和反碼不夠用?單獨(dú)從數(shù)據(jù)表示來看是無法得出結(jié)論的,需要從計(jì)算的角度思考。我們都知道,二進(jìn)制是以2為基數(shù)的記數(shù)系統(tǒng),和十進(jìn)制、六十進(jìn)制的記數(shù)本質(zhì)相同。在最開始,記數(shù)系統(tǒng)中是沒有負(fù)數(shù)的,引入負(fù)數(shù)是為了統(tǒng)一概念相反的事物。比如:收入和支出,支出就可以視為負(fù)的收入。如果用恒等式表達(dá)收支平衡收入 = 支出,將支出移項(xiàng)到左邊就得變號收入-支出 = 0,換句話說,收入+(-支出) = 0,此時表達(dá)了總收入為0這個事實(shí)。相同的概念對于運(yùn)算的意義重大,以前需要設(shè)計(jì)減法的運(yùn)算規(guī)則,現(xiàn)在用加法規(guī)則就可以替代。在人類看來,這意味著概念的統(tǒng)一,而對于計(jì)算機(jī)而言,這簡化了ALU(算術(shù)邏輯單元)中集成電路的設(shè)計(jì)。

既然如此,負(fù)數(shù)在二進(jìn)制中的表示就尤為重要。按照原碼的定義,-1的二進(jìn)制表示是1000 0001,那么計(jì)算1-1,也就是計(jì)算0000 0001 + 1000 0001 = 1000 0010,這個數(shù)表示的是十進(jìn)制中的-2。1-1=-2當(dāng)然是錯的,它為什么是錯的呢?因?yàn)榉栁灰矃⑴c運(yùn)算了,上面的算式其實(shí)表達(dá)的是1+129=130這個算式。

看到這里,或許需要思考的是數(shù)的表示和它們之間的運(yùn)算是不對稱的疑問?究其緣由是,數(shù)在計(jì)算機(jī)中表示是人為定義的,我們規(guī)定了左邊的1是符號位,但是ALU不知道,而且也不應(yīng)該知道。為什么呢?因?yàn)槿耸巧谱兊模竺孢€會出現(xiàn)無符號數(shù),那時候左邊的1可就不能視作符號位。

在繼續(xù)探索之前,我們先補(bǔ)充點(diǎn)數(shù)學(xué)知識。

同余

當(dāng)兩個整數(shù)除以同一個正整數(shù),若得相同余數(shù),則這兩個整數(shù)同余[4]。記為:
a\equiv b \pmod m

讀作a與b關(guān)于模m同余。同余的數(shù)具備很多性質(zhì),其中有一條保持基本運(yùn)算:
\left.{ \begin{matrix} a\equiv b{\pmod {m}}\\c\equiv d{\pmod {m}}\end{matrix} }\right\} \Rightarrow \left\{{ \begin{matrix} a\pm c\equiv b\pm d{\pmod {m}}\\ac\equiv bd{\pmod {m}} \end{matrix} }\right.

我們用這個性質(zhì)來反觀一下日常生活中的12小時制計(jì)時法。
\begin{matrix} 0\equiv 12{\pmod {12}} \\ 5\equiv 5{\pmod {12}} \end{matrix}
0點(diǎn)和12點(diǎn)關(guān)于12點(diǎn)同余,5和5當(dāng)然關(guān)于12點(diǎn)同余,那么有:
0\pm 5\equiv 12\pm 5{\pmod {12}}
所以5和17關(guān)于12同余。當(dāng)我們說下午5點(diǎn)時,其實(shí)也就是在說17點(diǎn)。負(fù)數(shù)也滿足這樣的關(guān)系,即-5和7關(guān)于12同余。

負(fù)數(shù)的反碼

按照定義我們求得-1的反碼,1000 0001的反碼為1111 1110即254,-1和254相對于255(1111 1111)同余。依據(jù)同余的基本性質(zhì),
-1\pm c\equiv 254\pm c{\pmod {255}}

取c為1,那么
-1\pm 1\equiv 254\pm 1{\pmod {255}}
即0和255相對于255同余。當(dāng)然,正確性顯而易見。但是,這里面也出現(xiàn)了一個問題,0和255(-0)都是0這個數(shù)在反碼中的正確表達(dá),這也是負(fù)數(shù)的反碼表示數(shù)的范圍是[-127, 127],總數(shù)是255個的由來,就像12小時制計(jì)時0和12都是零點(diǎn)的表達(dá),那么對于判0運(yùn)算而言,就得有兩手準(zhǔn)備。簡單起見,有沒有其他方式去掉這種特殊情況呢?于是,補(bǔ)碼順勢上位。

負(fù)數(shù)的補(bǔ)碼

由于0和255都是8位計(jì)算機(jī)中0的合法表達(dá),容易想到的一種方法就是去掉一個,而且還得保留同余的性質(zhì)。

假如把相對于255的同余變成256的同余是否有幫助呢?此時,0和256就相對于256同余了,但是因?yàn)?位二進(jìn)制只能表示[0, 255]范圍的數(shù),再多就溢出了,256是沒法表示的。我們還是以-1舉例子:
-1\pm c\equiv 255\pm c{\pmod {256}}

取c為1,那么
-1\pm 1\equiv 255\pm 1{\pmod {256}}
0和256同余,即0000 00001 0000 0000同余,溢出位1被舍去,就變成了0000 0000

負(fù)數(shù)的補(bǔ)碼數(shù)的表示范圍為[-128, 127],這個-128是怎么來的呢?它的補(bǔ)碼表示是1000 0000。要解答這個問題,得看看原碼和反碼中的0的表示。

先看看原碼,左邊第一位是符號位,說明它把0~255個數(shù)分成了兩半,分別是0~127, 128~255。其中,0000 00001000 0000都表示0,所以它只能表示255個數(shù)。即,-127~127.

類似地,反碼中的0000 00001111 1111也都表示0,其中1000 0000表示-127,所以它也只能表示255個數(shù)。即,-127~127.

但是補(bǔ)碼就不同,它里面的0就只有一個0000 0000,而-128和128相對于256同余,但是128(1 0000 0000_{[2]})在8位機(jī)器上沒法表示,所以干脆把1000 0000直接拿來當(dāng)-128,可想而知,這個數(shù)是沒法通過原碼取反加1計(jì)算出來的,因?yàn)樗鼘?yīng)的原碼并不存在(整數(shù)128是不存在的)。

補(bǔ)碼的計(jì)算規(guī)則是怎么來的?

正數(shù)的補(bǔ)碼就是其本身;
負(fù)數(shù)的補(bǔ)碼是在其原碼的基礎(chǔ)上, 符號位不變, 其余各位取反, 最后+1

先說說反碼計(jì)算方式的由來。以-1為例,其原碼表示如下:

1000 0001

那么為何對它符號位之外進(jìn)行取反,就得到了它相對于255(1111 1111)的反碼呢?

我們用255減去-1的絕對值,試試看

  1111 1111
- 0000 0001
-----------
  1111 1110

1111 1110就是254,它和1000 0001保留符號位后各位取反的結(jié)果一致。這是巧合嗎?顯然不是,因?yàn)?code>1111 1111是8位中最大的數(shù),所以就不存在借位操作,那么各位減下來,位是0的自然得到1,位是1的自然得到0.

-1和254相對于255同余,所以-1是254的補(bǔ)(One's complement)。不信的話,可以去clojure repl中運(yùn)行下面的表達(dá)式。

(mod -1 255) #=> 254

知道到了反碼的求值方法的由來,我們不難推斷出補(bǔ)碼的計(jì)算方法。因?yàn)?56是255+1,取反就是用255減去該數(shù),那么用256減去該數(shù),也就等價于255減去該數(shù)再加一。
256-a = 255-a+1

計(jì)算機(jī)是如何區(qū)分unsigned int和int的?

我們已經(jīng)知道了計(jì)算機(jī)存儲數(shù)據(jù)全部用的補(bǔ)碼的形式,所以從內(nèi)存中拿出來的數(shù)就是補(bǔ)碼,那么-1的補(bǔ)碼是1111 1111,也就是數(shù)2^8-1=255.

如果說unsigned int的存儲形式和int的一致,那意味著int負(fù)數(shù)轉(zhuǎn)換成unsigned int的值就是它補(bǔ)碼的字面值。我們使用Solidity語言程序驗(yàn)證,在驗(yàn)證之前,先要安裝ganache-clisolidity-repl.

$ ganache-cli
...
$ solr
Welcome to the Solidity REPL!
> int8 c = -1
> uint8 d = uint8(c)
> d
255

-1的補(bǔ)碼是1111 1111,也就是十進(jìn)制的255,所以從結(jié)果中不難得出如下結(jié)論:在計(jì)算機(jī)中,數(shù)的存儲和表示是分開的,存儲的是補(bǔ)碼,計(jì)算過程也使用補(bǔ)碼,但是最后的表示由程序員來決定。所以毫不夸張地說,程序員是規(guī)則的締造者,也是規(guī)則的解讀者。


順帶一提
solidity中的int和uint是成對的,而且從8, 16, 24, ..., 256,一共有32個。正確性可以通過它的詞法分析程序得出來。

//solidity/liblangutil/Token.cpp

if (0 < m && m <= 256 && m % 8 == 0 && positionX == _literal.end())
{
    if (keyword == Token::UInt)
        return make_tuple(Token::UIntM, m, 0);
    else
        return make_tuple(Token::IntM, m, 0);
}

  1. https://zh.wikipedia.org/wiki/原碼 ?

  2. https://zh.wikipedia.org/wiki/反碼 ?

  3. https://zh.wikipedia.org/wiki/二補(bǔ)數(shù) ?

  4. https://zh.wikipedia.org/wiki/同餘 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容