今天這篇文章就從八十年前偷襲使用的飛機(jī)開(kāi)始吧。
那場(chǎng)偷襲早在 2001 年已經(jīng)被拍攝成了電影 ——《珍珠港》,絕對(duì)是一部精彩的空戰(zhàn)電影。但是在觀影的過(guò)程中,你有沒(méi)有想過(guò)這個(gè)問(wèn)題:二戰(zhàn)時(shí)期的飛機(jī),在進(jìn)行空戰(zhàn)過(guò)程中自己飛機(jī)射出的子彈難道不會(huì)擊中自己飛機(jī)的槳葉嗎?其實(shí)我小時(shí)候看這部電影就有這樣的疑問(wèn),直到后來(lái)我在大學(xué)學(xué)習(xí)了《機(jī)械原理》后,解其背后的原理后:
機(jī)槍射擊協(xié)調(diào)器:它是一戰(zhàn)中在盟軍中服役的荷蘭人安東尼???税l(fā)明的,他把凸輪安裝在螺旋槳軸上面,凸輪的三個(gè)凸起與槳葉剛好錯(cuò)開(kāi)一個(gè)角度,當(dāng)突起部分碰到金屬棒后,金屬棒后端連接的機(jī)槍發(fā)射裝置就會(huì)被激活,繼而完成子彈發(fā)射,反之,當(dāng)槳葉與槍管形成一條直線時(shí),機(jī)槍自動(dòng)停止射擊。這個(gè)巧妙的設(shè)計(jì)完美地避開(kāi)了戰(zhàn)斗機(jī)出現(xiàn)自殘的事故。
當(dāng)時(shí)我知道它的原理后,反應(yīng)是這樣的:
臥槽,還有這種操作!
臥槽,這么簡(jiǎn)單我怎么沒(méi)想到!
的確,讓人虎軀一震的發(fā)明往往看上去都非常簡(jiǎn)單,而且你經(jīng)常會(huì)想,這么簡(jiǎn)單,嗯,我怎么沒(méi)想到?
我還是那句話,簡(jiǎn)單即是美,但是簡(jiǎn)單往往比復(fù)雜困難得多。
其實(shí)計(jì)算機(jī)的設(shè)計(jì)也是一門(mén)藝術(shù)的博弈,我們今天聊的話題就是計(jì)算機(jī)補(bǔ)碼的運(yùn)算,它看似簡(jiǎn)單,但是這個(gè)設(shè)計(jì)也是精妙絕倫。
一,邏輯電路是如何計(jì)算加法的
1938 年,香農(nóng)(這個(gè)人不用說(shuō)了吧,計(jì)算機(jī)行業(yè)的人沒(méi)人沒(méi)聽(tīng)過(guò)他吧)在麻省理工學(xué)院發(fā)表了那篇題為《繼電器和開(kāi)關(guān)電路的符號(hào)分析》(A Symbolic Analysis of Relay and Switching Circuits)的著名碩士論文,這是一篇具有劃時(shí)代意義的論文,他在文中清晰地闡述:電子工程師可以運(yùn)用布爾代數(shù)的所有工具去設(shè)計(jì)開(kāi)關(guān)電路。也就是說(shuō)邏輯運(yùn)算居然可以用電路來(lái)進(jìn)行實(shí)現(xiàn),隨后人們根據(jù)這一理論設(shè)計(jì)出了各種邏輯門(mén)(Logic Gate)來(lái)進(jìn)行數(shù)據(jù)運(yùn)算,后期的電子計(jì)算機(jī)的運(yùn)算原理都是基于這一理論進(jìn)行實(shí)現(xiàn)的,比如人們根據(jù)繼電器或者晶體管的特性,設(shè)計(jì)了異或門(mén)(關(guān)于異或運(yùn)算的本質(zhì)請(qǐng)參考 如何通俗理解異或運(yùn)算 ):
當(dāng)開(kāi)關(guān) A 閉合,線圈產(chǎn)生磁性將開(kāi)關(guān) M 吸合,接通燈泡的回路,燈泡就會(huì)亮,這是一個(gè)最簡(jiǎn)單的邏輯回路。你能想象人類(lèi)發(fā)明 CPU 甚至所有的存儲(chǔ)設(shè)備其實(shí)就是這一堆堆開(kāi)關(guān)組合成的嗎,雖然現(xiàn)代 CPU 用的是晶體管(速度更快、體積更?。窃矶际且粯拥?。比如蘋(píng)果最新發(fā)布的 M2 芯片上面集成了 200 億個(gè)晶體管,翻譯成人話就是上面放了 200 億個(gè)開(kāi)關(guān)。
這就像咱們老祖宗說(shuō)的那句話,一生二,二生三,三生萬(wàn)物。
后來(lái)人們根據(jù)上面那個(gè)電路進(jìn)行簡(jiǎn)單改造,無(wú)非就是開(kāi)關(guān)的常開(kāi)變常閉,或者常閉變常開(kāi)等等,發(fā)明出了各種不同的邏輯門(mén),可以實(shí)現(xiàn)更多的邏輯回路,比如與門(mén)(AND)、與非門(mén)(NAND)、或門(mén)(OR)、或非門(mén)(NOR)、異或門(mén)(XOR)等等。
比如下面這個(gè)與門(mén)就是連個(gè)開(kāi)關(guān) A 與 B 必須同時(shí)閉合燈泡才能亮:
這樣的電路人們沒(méi)想到居然會(huì)與二進(jìn)制的加法存在著某些聯(lián)系,比如二進(jìn)制 1+1=10 的進(jìn)位是 1,而這個(gè)與門(mén)電路雙開(kāi)關(guān)必須同時(shí)閉合才會(huì)亮,如果閉合代表 1,斷開(kāi)代表 2,那么邏輯關(guān)系就是 1 AND 1 = 1.
有一天,人們驚奇地發(fā)現(xiàn),一個(gè)異或門(mén)并聯(lián)一個(gè)與門(mén)居然能做簡(jiǎn)單的二進(jìn)制位的加法運(yùn)算,給它命名叫半加器。之所以叫半加器,是因?yàn)樗€沒(méi)有辦法將進(jìn)位的輸出納入下一位的運(yùn)算,比如 1+1=10,等號(hào)右邊的進(jìn)位暫時(shí)還不能納入下一位的運(yùn)算。
我們把這一堆符號(hào)合成一個(gè)整體:
后來(lái),人們改進(jìn)了這個(gè)電路,用兩個(gè)半加器再加一個(gè)或門(mén),組成一個(gè)全加器,這次就厲害了,全加器彌補(bǔ)了半加器不能計(jì)算讓進(jìn)位參與運(yùn)算的缺點(diǎn),可以將前一位的進(jìn)位納入本位進(jìn)行一塊計(jì)算,所以全加器輸入端有三個(gè)輸入:
我們把上面這一堆符號(hào)合成一個(gè)整體:
多個(gè)全加器組合在一塊就能計(jì)算多位的二進(jìn)制加法,下面這組加法器就能計(jì)算四位二進(jìn)制的加法:
通過(guò)這組加法器的組合,我們就能計(jì)算十進(jìn)制的 5+3=8 運(yùn)算,很難想象,這樣的運(yùn)算居然是我們通過(guò)幾個(gè)開(kāi)關(guān)實(shí)現(xiàn)的!實(shí)際上這正是現(xiàn)代計(jì)算機(jī)進(jìn)行加法計(jì)算的原理。
這里,你有沒(méi)驚呼:
臥槽,還有這種操作!
臥槽,這么簡(jiǎn)單我怎么沒(méi)想到!
不過(guò)先別驚訝的太早,后面還有更讓你驚訝的。
到這里,我們已經(jīng)能夠通過(guò)我們?cè)O(shè)計(jì)的邏輯電路來(lái)計(jì)算加法了,但是還有個(gè)重要的問(wèn)題:減法如何計(jì)算呢?因?yàn)橛?jì)算減法涉及到借位這種繁瑣的操作,而上面我們?cè)O(shè)計(jì)的電路只能進(jìn)位,難道我們還要為減法設(shè)計(jì)特定的邏輯電路嗎,答案肯定是否定的,那樣我們的電路就會(huì)非常復(fù)雜,我們考慮的是如何通過(guò)現(xiàn)有的邏輯電路,也就是如何通過(guò)加法來(lái)計(jì)算減法呢?
這個(gè)問(wèn)題特別有意思,有人會(huì)說(shuō)了,減去一個(gè)數(shù)等于加上這個(gè)數(shù)的負(fù)數(shù),比如 5-3=2 這個(gè)式子,可問(wèn)題是這樣的說(shuō)法實(shí)際上還是在計(jì)算減法,按照我們目前設(shè)計(jì)的開(kāi)關(guān)電路是實(shí)現(xiàn)不了的,那怎么辦呢?
想象一下,我們上小學(xué)的時(shí)候,剛開(kāi)始學(xué)習(xí)三位數(shù)的減法的時(shí)候,我們都不喜歡一些帶有借位的減法,比如 這個(gè)算式讓我們計(jì)算起來(lái)很不舒服,首先從個(gè)位,3 小于 7,所以要從十位進(jìn)位, 而十位數(shù)借位后還小于 4,還要從百位借位。
我們這里用一個(gè)技巧,先用 999 減去減數(shù) 147,顯然這個(gè)算式不會(huì)產(chǎn)生借位:這個(gè) 852 我們稱(chēng)為 9 的補(bǔ)數(shù),用這個(gè)結(jié)果與被減數(shù) 213 相加 最后將結(jié)果加 1,然后再減去 1000: 居然得到了我們想要的答案,而且沒(méi)用到借位。
為什么這個(gè)間接的運(yùn)算會(huì)正確呢?這是因?yàn)榈脑}目可以化成下面的運(yùn)算: 看到了吧,實(shí)際上是加了 1000 最后又給減去了,我們?cè)侔焉鲜浇M合一下: 其實(shí)計(jì)算結(jié)果是一樣的,而且避免了借位的運(yùn)算。
到這里,你可能會(huì)有疑惑:可這個(gè)式子還用到了減法啊,而且是兩次,難道計(jì)算機(jī)在計(jì)算的時(shí)候還會(huì)有技巧跳過(guò)這個(gè)減法嗎?
在這里,神奇的事情發(fā)生了,由于計(jì)算機(jī)采用的是二進(jìn)制,第一個(gè)減法也就是求補(bǔ)數(shù)是從一串 1 的數(shù)字中減去的,而二進(jìn)制求補(bǔ)的運(yùn)算不像十進(jìn)制那樣,前者根本不需要做減法,而是將原來(lái)二進(jìn)制中的數(shù)字 1 變?yōu)?0, 0 變?yōu)?1 即可(這與直接計(jì)算減法結(jié)果是一樣的,但是這個(gè)技巧對(duì)計(jì)算機(jī)來(lái)說(shuō)就省下了做減法的運(yùn)算),這個(gè)求相反數(shù)我們可以稱(chēng)為反碼,可以通過(guò)邏輯電路中的反向器來(lái)實(shí)現(xiàn),第二個(gè)減法在二進(jìn)制中減的是最高位,而這個(gè)對(duì)計(jì)算機(jī)來(lái)說(shuō)我們只需要通過(guò)一個(gè)邏輯門(mén)電路來(lái)限制最高位輸出即可實(shí)現(xiàn)。
下面我們來(lái)看一下使用二進(jìn)制計(jì)算這一過(guò)程有多奇妙
第一步,求補(bǔ)運(yùn)算:
第二步,將結(jié)果加上被減數(shù) 213:
第三步,將第二步的結(jié)果加 1:
第四步,將第三步的結(jié)果最高位取反,相當(dāng)于減去了 256:
這樣就最終得出了我們想要的結(jié)果:66,整個(gè)過(guò)程雖然采用了兩次減法,但是在二進(jìn)制看來(lái),根本沒(méi)有使用減法。
二,為什么采用補(bǔ)碼來(lái)存儲(chǔ)整數(shù)
但是,上面這個(gè)電路還有局限性,它只能計(jì)算被減數(shù)大于減數(shù)的運(yùn)算,而且不能表示負(fù)數(shù),我們想要的結(jié)果是使用現(xiàn)有的電路,讓它能夠計(jì)算加法、減法、還有負(fù)數(shù),換句話說(shuō),讓所有的運(yùn)算都按照加法來(lái)實(shí)現(xiàn),該如何實(shí)現(xiàn)呢?
這時(shí)候,補(bǔ)碼運(yùn)算就登場(chǎng)了。
首先,計(jì)算機(jī)為了區(qū)分整數(shù)與負(fù)數(shù),規(guī)定了符號(hào)位,規(guī)定最高位為「符號(hào)位」,0 代表正數(shù),1 代表負(fù)數(shù),剩下的才是「數(shù)字位」。例如對(duì)于兩個(gè)字節(jié) short 類(lèi)型數(shù)字 1 在計(jì)算機(jī)內(nèi)部是這樣表示的:
而整數(shù) -1 的表示方法是這樣的,只是符號(hào)位變?yōu)榱?1:
但這樣做是有代價(jià)的,意味著我們數(shù)據(jù)位的表示實(shí)際上是少了一位,導(dǎo)致我們?cè)灸鼙硎镜臄?shù)字沒(méi)那么大了。例如單字節(jié)原本能表示 0 ~ 255 之間的數(shù)字,但是因?yàn)榉?hào)位占據(jù)了 1 位,實(shí)際我們表示數(shù)據(jù)的位數(shù)變?yōu)榱?7 位,最大只能表示 127.
這時(shí)候,我們引出反碼還有補(bǔ)碼這個(gè)概念:正數(shù)的反碼補(bǔ)碼都是其原碼,而復(fù)數(shù)的反碼比較特殊,符號(hào)位不變,數(shù)據(jù)位取反就是反碼,反碼加 1 就是補(bǔ)碼:
計(jì)算機(jī)內(nèi)部所有的運(yùn)算都采用補(bǔ)碼的形式,那么為什么要這樣呢?
我們先來(lái)看如果采用原碼的形式進(jìn)行計(jì)算,假設(shè)我們要計(jì)算 1 - 3,實(shí)際上就是 1+(-3):
這樣得出的結(jié)果竟然是 1 + (-3) = -4,結(jié)果顯然是不正確的
那么如果我們采用反碼進(jìn)行計(jì)算,會(huì)怎樣呢?
這樣得出的結(jié)果就是正確的,與我們預(yù)期的一樣,但是如果我們計(jì)算 3-1 會(huì)怎么樣呢,再試試:
最后居然得出 3+(-1)=1 的結(jié)果,這說(shuō)明采用反碼運(yùn)算,小數(shù)減大數(shù)沒(méi)問(wèn)題,但是大數(shù)減小數(shù)結(jié)果就出了問(wèn)題,直覺(jué)告訴我們,結(jié)果差了 1.
隨后,人們想出了補(bǔ)碼這種神奇般的操作,我們看一下它的結(jié)果是怎樣的:
這樣計(jì)算的結(jié)果就與我們期待的一樣,是正確的。
再細(xì)品一下,為什么補(bǔ)碼運(yùn)算會(huì)正確呢,我們仔細(xì)分析一下:
當(dāng)大數(shù)減去小數(shù)的時(shí)候,結(jié)果一定是正數(shù)。而之前我們采用的反碼運(yùn)算,結(jié)果總是少了 1,如果采用補(bǔ)碼來(lái)計(jì)算的話,負(fù)數(shù)從反碼轉(zhuǎn)為補(bǔ)碼要加上 1,在計(jì)算出結(jié)果后,因?yàn)檎龜?shù)的補(bǔ)碼與反碼相同,所以不用再減去,所以剛好相當(dāng)于把結(jié)果加了 1. 妙,不可言;
當(dāng)小數(shù)減去大數(shù)的時(shí)候,結(jié)果一定是負(fù)數(shù)。如果采用補(bǔ)碼運(yùn)算,負(fù)數(shù)從反碼轉(zhuǎn)化為補(bǔ)碼要加上 1,而恰恰,結(jié)果是負(fù)數(shù),這個(gè)負(fù)數(shù)從補(bǔ)碼轉(zhuǎn)為原碼又要減去 1, 剛好抵消,結(jié)果不受影響。妙,不可言。
補(bǔ)碼的發(fā)明,徹底簡(jiǎn)化了我們的硬件電路,不必為減法設(shè)計(jì)額外的電路,讓我們僅僅通過(guò)加法電路就能計(jì)算減法,真是太神奇了。
看到這里,你有沒(méi)有驚呼開(kāi)頭那兩句話:
臥槽,還有這種操作!
臥槽,這么簡(jiǎn)單我怎么沒(méi)想到!
本文來(lái)自微信公眾號(hào):編碼珠璣 (ID:gh_f65e0111d17a),作者:劉亞曦
廣告聲明:文內(nèi)含有的對(duì)外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時(shí)間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。