C++數(shù)字編碼

2023-09-20 09:23 更新

數(shù)字編碼 *

Note

在本書中,標(biāo)題帶有的 * 符號的是選讀章節(jié)。如果你時間有限或感到理解困難,可以先跳過,等學(xué)完必讀章節(jié)后再單獨攻克。

3.3.1   原碼、反碼和補碼

在上一節(jié)的表格中我們發(fā)現(xiàn),所有整數(shù)類型能夠表示的負(fù)數(shù)都比正數(shù)多一個,例如 byte 的取值范圍是 [?128,127] 。這個現(xiàn)象比較反直覺,它的內(nèi)在原因涉及到原碼、反碼、補碼的相關(guān)知識。

首先需要指出,數(shù)字是以“補碼”的形式存儲在計算機中的。在分析這樣做的原因之前,我們首先給出三者的定義。

  • 原碼:我們將數(shù)字的二進制表示的最高位視為符號位,其中 0 表示正數(shù),1 表示負(fù)數(shù),其余位表示數(shù)字的值。
  • 反碼:正數(shù)的反碼與其原碼相同,負(fù)數(shù)的反碼是對其原碼除符號位外的所有位取反。
  • 補碼:正數(shù)的補碼與其原碼相同,負(fù)數(shù)的補碼是在其反碼的基礎(chǔ)上加 1 。

圖 3-4 展示了原碼、反碼和補碼之間的轉(zhuǎn)換方法。

原碼、反碼與補碼之間的相互轉(zhuǎn)換

圖 3-4   原碼、反碼與補碼之間的相互轉(zhuǎn)換

「原碼 true form」雖然最直觀,但存在一些局限性。一方面,負(fù)數(shù)的原碼不能直接用于運算。例如在原碼下計算 1+(?2) ,得到的結(jié)果是 ?3 ,這顯然是不對的。



1+(?2)

00000001+10000010
=
10000011

?3

為了解決此問題,計算機引入了「反碼 1's complement code」。如果我們先將原碼轉(zhuǎn)換為反碼,并在反碼下計算 1+(?2) ,最后將結(jié)果從反碼轉(zhuǎn)化回原碼,則可得到正確結(jié)果 ?1 。

1+(?2)00000001(原碼)+10000010(原碼)=00000001(反碼)+11111101(反碼)=11111110(反碼)=10000001(原碼)?1

另一方面,數(shù)字零的原碼有 +0 和 ?0 兩種表示方式。這意味著數(shù)字零對應(yīng)著兩個不同的二進制編碼,其可能會帶來歧義。比如在條件判斷中,如果沒有區(qū)分正零和負(fù)零,則可能會導(dǎo)致判斷結(jié)果出錯。而如果我們想要處理正零和負(fù)零歧義,則需要引入額外的判斷操作,其可能會降低計算機的運算效率。

+000000000?010000000

與原碼一樣,反碼也存在正負(fù)零歧義問題,因此計算機進一步引入了「補碼 2's complement code」。我們先來觀察一下負(fù)零的原碼、反碼、補碼的轉(zhuǎn)換過程:

?010000000(原碼)=11111111(反碼)=100000000(補碼)

在負(fù)零的反碼基礎(chǔ)上加 1 會產(chǎn)生進位,但 byte 類型的長度只有 8 位,因此溢出到第 9 位的 1 會被舍棄。也就是說,負(fù)零的補碼為 00000000 ,與正零的補碼相同。這意味著在補碼表示中只存在一個零,正負(fù)零歧義從而得到解決。

還剩余最后一個疑惑:byte 類型的取值范圍是 [?128,127] ,多出來的一個負(fù)數(shù) ?128 是如何得到的呢?我們注意到,區(qū)間 [?127,+127] 內(nèi)的所有整數(shù)都有對應(yīng)的原碼、反碼和補碼,并且原碼和補碼之間是可以互相轉(zhuǎn)換的。

然而,補碼 10000000 是一個例外,它并沒有對應(yīng)的原碼。根據(jù)轉(zhuǎn)換方法,我們得到該補碼的原碼為 00000000 。這顯然是矛盾的,因為該原碼表示數(shù)字 0 ,它的補碼應(yīng)該是自身。計算機規(guī)定這個特殊的補碼 10000000 代表 ?128 。實際上,(?1)+(?127) 在補碼下的計算結(jié)果就是 ?128 。

(?127)+(?1)11111111(原碼)+10000001(原碼)=10000000(反碼)+11111110(反碼)=10000001(補碼)+11111111(補碼)=10000000(補碼)?128

你可能已經(jīng)發(fā)現(xiàn),上述的所有計算都是加法運算。這暗示著一個重要事實:計算機內(nèi)部的硬件電路主要是基于加法運算設(shè)計的。這是因為加法運算相對于其他運算(比如乘法、除法和減法)來說,硬件實現(xiàn)起來更簡單,更容易進行并行化處理,運算速度更快。

請注意,這并不意味著計算機只能做加法。通過將加法與一些基本邏輯運算結(jié)合,計算機能夠?qū)崿F(xiàn)各種其他的數(shù)學(xué)運算。例如,計算減法 a?b 可以轉(zhuǎn)換為計算加法 a+(?b) ;計算乘法和除法可以轉(zhuǎn)換為計算多次加法或減法。

現(xiàn)在我們可以總結(jié)出計算機使用補碼的原因:基于補碼表示,計算機可以用同樣的電路和操作來處理正數(shù)和負(fù)數(shù)的加法,不需要設(shè)計特殊的硬件電路來處理減法,并且無須特別處理正負(fù)零的歧義問題。這大大簡化了硬件設(shè)計,提高了運算效率。

補碼的設(shè)計非常精妙,因篇幅關(guān)系我們就先介紹到這里,建議有興趣的讀者進一步深度了解。

3.3.2   浮點數(shù)編碼?

細(xì)心的你可能會發(fā)現(xiàn):int 和 float 長度相同,都是 4 bytes,但為什么 float 的取值范圍遠(yuǎn)大于 int ?這非常反直覺,因為按理說 float 需要表示小數(shù),取值范圍應(yīng)該變小才對。

實際上,這是因為浮點數(shù) float 采用了不同的表示方式。記一個 32-bit 長度的二進制數(shù)為:

b31b30b29b2b1b0

根據(jù) IEEE 754 標(biāo)準(zhǔn),32-bit 長度的 float 由以下三個部分構(gòu)成。

  • 符號位 S :占 1 bit ,對應(yīng) 
    b31
     。
  • 指數(shù)位 E :占 8 bits ,對應(yīng)
     
    b30b29b23
    。
  • 分?jǐn)?shù)位 N :占 23 bits ,對應(yīng) b22b21b0。
     。

二進制數(shù) float 對應(yīng)的值的計算方法:

val=(?1)b31×2( b30 b29b23)2?127×(1.b22b21b0)2

轉(zhuǎn)化到十進制下的計算公式:

val=(?1)S×2E?127×(1+N)

其中各項的取值范圍:



                   S
{0,1},E{1,2,,254}



















(1+N)=(1+i=123b23?i2? i)?[1,2?2?23]


IEEE 754 標(biāo)準(zhǔn)下的 float 的計算示例

圖 3-5   IEEE 754 標(biāo)準(zhǔn)下的 float 的計算示例

觀察圖 3-5 ,給定一個示例數(shù)據(jù) S=0 , E=124 ,N= 2^(?2)+2^(?3)=0.375 ,則有:

 val =(?1)0×2124?127×(1+0.375)=0.171875

現(xiàn)在我們可以回答最初的問題:float 的表示方式包含指數(shù)位,導(dǎo)致其取值范圍遠(yuǎn)大于 int 。根據(jù)以上計算,float 可表示的最大正數(shù)為 2^(254?127)×(2?2^(?23))≈3.4×10^38 ,切換符號位便可得到最小負(fù)數(shù)。

盡管浮點數(shù) float 擴展了取值范圍,但其副作用是犧牲了精度。整數(shù)類型 int 將全部 32 位用于表示數(shù)字,數(shù)字是均勻分布的;而由于指數(shù)位的存在,浮點數(shù) float 的數(shù)值越大,相鄰兩個數(shù)字之間的差值就會趨向越大。

如表 3-2 所示,指數(shù)位 E=0 和 E=255 具有特殊含義,用于表示零、無窮大、NaN 等

表 3-2   指數(shù)位含義

指數(shù)位 E分?jǐn)?shù)位 N=0分?jǐn)?shù)位 N0計算公式
0±0次正規(guī)數(shù)(?1)S×2?126×(0.N)
1,2,,254正規(guī)數(shù)正規(guī)數(shù)(?1)S×2(E?127)×(1.N)
255±NaN

值得說明的是,次正規(guī)數(shù)顯著提升了浮點數(shù)的精度。最小正正規(guī)數(shù)為 2^(?126) ,最小正次正規(guī)數(shù)為 2^(?126)×2^(?23) 。

雙精度 double 也采用類似 float 的表示方法,在此不做贅述。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號