很表面很淺薄的問(wèn)題。
簡(jiǎn)單說(shuō)愛(ài)怎么規定就怎么規定,甚至-1到254都行。無(wú)非是顯示時(shí)通過(guò)編碼表做個(gè)轉換的問(wèn)題而已。
不過(guò),當初選擇“補碼”這種編碼形式,卻并不像表面看起來(lái)那么淺薄。背后的道道可多著(zhù)呢。
首先,8位二進(jìn)制一共可以提供256個(gè)“碼點(diǎn)”;那么我們就總可以用這些“碼點(diǎn)”來(lái)編碼256種符號。
這種編碼方案有很多。最著(zhù)名的大概就是ASCII碼方案了,這個(gè)方案規定了英文字符(區分大小寫(xiě))、0~9這10個(gè)數字、標點(diǎn)符號以及一些控制字符如何編碼:
但ASCII碼用來(lái)編碼字符效果不錯;拿來(lái)存儲數字卻極為浪費。比如它需要三個(gè)字節才能表示123。
為了編碼數字,我們需要一個(gè)更有效的方案。
一種很自然的想法是,我們就直接把二進(jìn)制對應的數字值拿來(lái)用,這就是最好的編碼方案!
于是,8個(gè)二進(jìn)制位就可以表示0~255之間的所有數字——用ASCII碼三個(gè)字節才能表示的123,直接用二進(jìn)制編碼就是01111011,一個(gè)字節足夠了。
這個(gè)方案只能表示正數;遇到負數怎么辦呢?
簡(jiǎn)單,第一個(gè)二進(jìn)制位拿出來(lái)當符號位,剩下七位仍然當數字用,就能表示±127之間的任何數字了。
這個(gè)方案就叫“原碼”;其中不帶符號位的就是無(wú)符號數(unsigned)。
原碼是一種很初級的編碼方案,它僅僅解決了編碼問(wèn)題——從此數字有辦法二進(jìn)制表示了。
但我們在計算機內部表示數字是用來(lái)計算的;那么想用原碼計算的話(huà),那可就麻煩了……
我們知道,最初CPU的內部最重要的核心器件叫ALU(Arithmetic and Logic Unit算術(shù)邏輯單元),其中的A就是數學(xué)。
ALU的核心是加法器,這是個(gè)隨參與計算的數值的二進(jìn)制位數指數增長(cháng)的數字電路。較早期的CPU里面絕大多數的邏輯門(mén)都被拿來(lái)做這個(gè)加法器了。
加法器顧名思義只能拿來(lái)做加法。
但是沒(méi)關(guān)系,如果你調過(guò)機械表,就知道從8點(diǎn)調到1點(diǎn)的方式有兩種:一種是往后撥7個(gè)小時(shí),一種是往前撥5個(gè)小時(shí)。
換句話(huà)說(shuō),在時(shí)鐘鐘面上,8-7和8+(12-7)效果相同,最終得到的都是1.
類(lèi)似的,1個(gè)字節的加減法,如果計算結果超過(guò)255就會(huì )造成溢出,溢出的高位二進(jìn)制數據無(wú)處存放自動(dòng)丟棄,計算結果就出錯了——但反過(guò)來(lái)想,這不恰恰就是一個(gè)邏輯鐘面嗎?
顯然,我們也可以利用這個(gè)性質(zhì)做減法:減32完全可以當成加(256-32)來(lái)算嘛;而由于二進(jìn)制的特點(diǎn),256-32恰好又等于32這個(gè)數值取反。
類(lèi)似的,有符號數其實(shí)是一個(gè)符號位和7個(gè)二進(jìn)制位,7個(gè)二進(jìn)制位能表示的最大數是127;因此減32就可以用加(128-32)代替(和表盤(pán)上的12點(diǎn)/0點(diǎn)一樣)。
于是,減法器就可以不做,一個(gè)加法器就足夠用了——省了好大一坨門(mén)電路,CPU的制造成本一下子就去了一大塊。
既然最終減法一定要這樣做……那么從一開(kāi)始就不應該用原碼表示負數,對吧。
不然每次計算都還得用一條指令判斷判斷符號位,然后該取反取反……這速度可就慢下去了。
如果從一開(kāi)始,負數就取反表示,那么負數加法完全無(wú)需判斷,拎起來(lái)就加——圓滿(mǎn)。
這個(gè)編碼方案就是所謂的反碼。
反碼是一個(gè)充滿(mǎn)了工程師的惡臭味的優(yōu)秀方案。
說(shuō)它優(yōu)秀,是因為它的確解決問(wèn)題;說(shuō)它惡臭,是因為它用起來(lái)實(shí)在麻煩,需要很多“微妙”的調整才能得到正確結果。
比如,它的符號位相加后,如果產(chǎn)生了進(jìn)位,就要把進(jìn)位送回去加到最低位上——你得搞一大張真值表才能確定這個(gè)做法的正確性。
嗯……這就是最容易產(chǎn)生沒(méi)人看得懂但絕對不能動(dòng)不然就會(huì )出錯的神奇代碼的重災區——反正它就是能工作;剛開(kāi)始我還知道為什么得這樣做,一段時(shí)間后就只有上帝知道了。
反碼行為奇特的根本原因在于,它有兩個(gè)零:+0和-0,分別對應于00000000和10000000——還記得嗎?我們規定第一位是符號位。因此最前面的0/1是±號,并不是數值。
但+0和-0都是0.它們是同一個(gè)數據,卻得到了兩個(gè)碼點(diǎn)。
打個(gè)比方的話(huà),這就好像夜里12點(diǎn)就是0點(diǎn)一樣;結果我們的鐘匠師傅沒(méi)想明白,偏偏要在鐘面上、12點(diǎn)和1點(diǎn)之間添加一個(gè)零點(diǎn)——然而邏輯上我們仍然需要12小時(shí)是一圈。
現在,你還想好好調表嗎?算的準準的,8點(diǎn)前擰5個(gè)小時(shí)就是1點(diǎn)了;結果擰完一看,0點(diǎn)?
徹底亂套了,對吧。
而反碼的計算規則呢,無(wú)異于規定了過(guò)12點(diǎn)的方向——正著(zhù)過(guò)正常去1點(diǎn),反著(zhù)過(guò)會(huì )先停在-0點(diǎn)上,所以必須推一把。
注意這個(gè)調整是計算過(guò)程的一部分,每次計算都必須即時(shí)調整。這是一個(gè)額外的負擔——和顯示時(shí)查表轉換到光學(xué)點(diǎn)陣/向量是不想干的兩個(gè)過(guò)程。
或者說(shuō),數據的內部表示和外部顯示之間的轉換是另外一個(gè)必不可少的流程。這里只要不是太過(guò)復雜就不能算額外負擔;而原碼/反碼這兩個(gè)編碼方案已經(jīng)影響了計算過(guò)程,造成了額外的性能消耗。
一言以蔽之:能解決問(wèn)題,但是太難看、太復雜。
一個(gè)更好的方案叫補碼。
但是在介紹補碼之前,我先來(lái)講一個(gè)數學(xué)概念—群。
群大概來(lái)源于“算術(shù)運算以及適用算法運算的集合”的抽象,但又超脫于簡(jiǎn)單的四則運算,是一切計算/變換類(lèi)似行為的總綱。
在群的觀(guān)念里,加減乘除都是一種“二元運算”;二元運算是一個(gè)集合G中任意兩個(gè)元素向群中另一個(gè)元素的映射。比如1+1就映射到了2。
注意群有“封閉性”,意思是群中任意兩個(gè)元素經(jīng)過(guò)二元運算后,映射的那個(gè)元素都還要在群中。因此(自然數,加減法)就不是一個(gè)群,因為減法會(huì )映射到負數。
此外,二元運算需要滿(mǎn)足結合律,要有單位元(任何元素與之執行二元運算后都會(huì )映射到該元素自身),等等。
更復雜的東西我也還看不懂(對不起,俺數學(xué)水平太弱雞了);但了解這么多其實(shí)也已經(jīng)夠了:反碼存在兩個(gè)0,意味著(zhù)對于加法運算來(lái)說(shuō),它存在兩個(gè)不同的單位元;而根據群的定義,群里面有且只有一個(gè)單位元。
因此,在反碼這個(gè)基礎上無(wú)法定義一個(gè)群——用人話(huà)說(shuō)就是,你不可能期望找到一種不需要判斷的算法,從而基于反碼模擬加減法運算。
沒(méi)錯,反碼有兩個(gè)零這事并不像外行想象的那樣無(wú)關(guān)痛癢——它并不僅僅是浪費了一個(gè)碼點(diǎn)的問(wèn)題,而是破壞了相關(guān)結構的性質(zhì)的問(wèn)題。
如何解決這個(gè)問(wèn)題呢?
不妨返璞歸真,看看這個(gè)問(wèn)題的本質(zhì)。
很簡(jiǎn)單,和上面等待寫(xiě)入時(shí)間信息的無(wú)字鐘面一樣:這里有256個(gè)不同的二進(jìn)制編碼,我們需要給它們分別指定一個(gè)意義。
我們希望它們是連續的編碼,且基于二進(jìn)制的排序不能打亂——這樣我們才能使得基于這些碼點(diǎn)的、拋棄溢出位的加減法運算構成一個(gè)群。
只有它們是一個(gè)群,我們才能簡(jiǎn)單明了的在加法器上支持加減法運算——而不是先算一個(gè)瑕疵值然后想辦法彌補、把硬件/軟件變得復雜。
打個(gè)比方的話(huà),就是把這些二進(jìn)制編碼按順序排于鐘面,我們要在上面填上帶±號的數字。
原碼的問(wèn)題在于,它的編碼排列“不按固定順序”,使得因此必須把負數先“顛倒”一下(實(shí)際上取反)才能用;而反碼頭疼醫頭腳疼醫腳,大致保證了編碼順序,卻沒(méi)能消除額外的無(wú)效碼點(diǎn),造成在±0這個(gè)位置兩個(gè)碼點(diǎn)對應一個(gè)編碼。
這兩個(gè)編碼都沒(méi)法自然構造出加法群。
借用@任衛同學(xué)的這張圖:
可以很清晰的看出補碼編碼的連續性。
(相比之下,原碼是0 1 2 3……127 -0 -1 -2……-127,順序上一會(huì )兒從小到大一會(huì )兒從大到??;補碼按照一定的順序編碼但是多了個(gè)-0;
只有補碼,嚴格按照統一的順序連續排列數字)。
既然連續,那么通過(guò)加一個(gè)值(可能為負)調整對稱(chēng)中心(比如0的位置是00000000還是11111111)、然后再引入模運算剔除高位溢出,這個(gè)群就建立起來(lái)了。
換句話(huà)說(shuō),隨便你如何編碼,只要別改變底層的二進(jìn)制順序、不要有跳躍/重復碼點(diǎn),那么這個(gè)計算就仍然是一個(gè)群。
這個(gè)計算過(guò)程和最終的顯示是完全脫鉤的,你不需要在計算時(shí)做任何調整——溢出就隨它溢出,反正(在模運算的層面上)算出來(lái)的值總是對的。這是群的性質(zhì)所保證的。
(注意是“模運算的層面上”,換句話(huà)說(shuō)算出來(lái)的實(shí)際意義是什么還是得你自己解釋?zhuān)挥绕涫钱a(chǎn)生溢出之后。)
比如,哪怕你把它的編碼范圍改成[-129, 126]或者[-1, 254],這也僅僅是一個(gè)加/減一個(gè)整數的映射操作而已;核心計算法則仍然會(huì )滿(mǎn)足你的需求)。
甚至,你規定0代表1、1代表0,最終也不過(guò)是顯示時(shí)換一個(gè)不同的譯碼表而已,并不改變問(wèn)題性質(zhì)。
這個(gè)性質(zhì)是普適的。
7位、8位或者32位、128位二進(jìn)制全都適用。
一旦明白了這個(gè)……再寫(xiě)環(huán)形緩沖時(shí),你還要費勁巴拉的檢查什么時(shí)候需要繞回嗎?求個(gè)余(或許還需要再視情況不同增減一個(gè)常數),完事。
你看,數學(xué)這種東西厲害吧?
哪怕群論門(mén)檻都摸不到的這么一點(diǎn)點(diǎn)皮毛知識,帶來(lái)的就是眼界水平的差異。
一旦了解了這點(diǎn)皮毛,關(guān)于補碼的種種清規戒律神奇規則,也就平常。
但沒(méi)有這個(gè)眼界,就容易像反碼那樣動(dòng)輒得咎;反之,隨你怎么玩都不會(huì )出界。
沒(méi)錯。別看這東西簡(jiǎn)單;
但想要做第一個(gè)提出的人,你還是需要強悍的洞察力的。
站在群論的肩膀上、反向碾壓這個(gè)問(wèn)題,這是伽羅瓦之后的現代人特有的福利。
審核編輯:黃飛
-
cpu
+關(guān)注
關(guān)注
68文章
10525瀏覽量
207451 -
邏輯門(mén)
+關(guān)注
關(guān)注
1文章
127瀏覽量
23886 -
加法器
+關(guān)注
關(guān)注
6文章
179瀏覽量
29835 -
ASCII
+關(guān)注
關(guān)注
5文章
169瀏覽量
34723 -
減法器
+關(guān)注
關(guān)注
1文章
26瀏覽量
16725
原文標題:為什么8位數據范圍是-128到127,而不是-127到128?
文章出處:【微信號:嵌入式情報局,微信公眾號:嵌入式情報局】歡迎添加關(guān)注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論