02-信息的表示和處理
二進制&十進制&十六進制
二進制轉十六進制(分組轉換)
四位二進制可表示一位十六進制,那么對于一個0000 1011,轉換后的結果為0x0B,只需要記住關鍵的十六進制和二進制對應關系即可,關系表如下:

對某個二進制如0010 0000 0000,可將其拆分為:\(2^n\) 中 \(n = i + 4j\),即\(2^9\)進行上述拆分結果為: \(9 = 1 + 4\times2\),我們需要知道\(i = 0,1,2,3\)分別對應十六進制\(1,2,4,8\),那么 \(2^9\) 對應0x200,j有多少個決定了后面多少0,i為幾,其對應的十六進制值就是幾。
再舉例\(2^7\)拆分為\(3+4\times1\),所以十六進制為0x80;\(2^{15}\)拆分為\(3+4\times3\),十六進制為0x8000。
十進制轉十六進制(輾轉相除法)

除以16得到的余數逆序轉為16進制數,余數由高位到低位排列,314156的十六進制就是0x4CB2C。
十六進制轉十進制(直接算)
比如 \(\text{0x7AF} = \text{F}\times 16^0 + \text{A}\times 16^1 + 7\times 16^2 = 1967\)
虛擬地址
虛擬地址實際上是通過內存映射的方式將磁盤物理地址轉換得到的地址,在32位字長主機上每個進程能夠訪問的最大地址空間(虛擬地址空間)大小為4GB,即\(2^{32}\)。
16位字長機器的地址范圍:0~65535(FFFF)
32位字長機器的地址范圍:0~4294967296(FFFFFFFF,4GB)
64位字長機器的地址范圍:0~18446744073709551616(1999999999999998,16EB)
32位編譯指令:gcc -m32 main.c
64位編譯指令:gcc -m64 main.c
為了避免由于依賴“典型”大小和不同編譯器設置帶來的奇怪行為,ISOC99引入了類數據類型,其數據大小是固定的,不隨編譯器和機器設置而變化。其中就有數據類型int32_t和int64_t,它們分別為4個字節和8個字節。使用確定大小的整數類型是程序員準確控制數據表示的最佳途徑。
大端小端地址


#include <stdio.h>
int main() {
unsigned int x = 0x12345678;
unsigned char *p = (unsigned char *)&x;
printf("%0x %0x %0x %0x\n", p[0], p[1], p[2], p[3]); //78 56 34 12
return 0;
}
以上為我的主機測試結果,可以看到低位存儲至低地址,這是一個小端的存儲方式。
那么為什么要劃分大端還是小端地址呢?
我們知道在不同的主機上,會有不同的結果,但是可以明確知道的一點就是,如果兩個主機在網絡中通信,那么一定是大端方式進行的,這是為了統一字節序的排列而規定的。我們稱當前自己的設備上不論大端存儲還是小端存儲的序列都稱為主機字節序,而向網絡發送字節序列,我們稱之為網絡字節序,那么在網絡通信中我們需要將主機字節序轉換為大端的網絡字節序,然后發送到對端接收時再轉為其機器的存儲方式,即由網絡字節序再轉為主機字節序,主機字節序有可能是大端或小端,但網絡字節序一定是大端存儲,這樣做的好處是,統一了不同設備存儲方式在網絡中的字節序列(大端)。
位運算符&邏輯運算符
位運算有:& | ~ ^。邏輯運算(布爾運算)有:&& || !。特別要注意~和!的區別,看下面例子:

“!”邏輯非運算符,邏輯操作符一般將其操作數視為條件表達式,返回結果為Bool類型:“!true”表示條件為真(true)。“!false ”表示條件為假(false)。"~"位運算符,代表位的取反,對于整形變量,對每一個二進制位進行取反,0變1,1變0。
關于異或^,只需要知道相同為0,相異為1即可。比如0^0=0 0^1=1。
左移&右移
左移直接右側低位補0,右移的話因為高位可能是符號位的原因,要區分邏輯右移還是算術右移,邏輯右移至的是高位補0,但算術右移高位補符號位。
在計算機中對二進制的數值表示方式有:原碼、反碼、補碼、移碼等。原碼表示很簡單,符號為正則最高位補0,符號為負則最高位補1,我們發現對于0,其原碼表示有兩種,符號位既可以是1也可是0。原碼的表示更易于人為的理解,但計算機對原碼的理解就非常困難,特別是計算機進行加減法運算時效率很低,比如兩個異號數相加或兩個同號數相減時,就要做減法運算,對于ALU運算單元,加法運算時要快于減法運算的,為了減法運算器復雜性,提高運算速度,我們需要把減法運算轉換為加法運算,因此人們設計出了反碼和補碼。
對于原碼的表示,若為一個正數,其反碼表示不發生改變,若原碼數是一個負數,則反碼表示需在保持符號位不變的基礎上,對其他位按位取反。比如一個正數[X原]=01010110=[X反],那如果是負數[X原]=10110101,那它的反碼就是[X反]=11001010。這種表示有個問題,比如對于0,它在原碼中既可以是+0,也可以是-0,即00000000或10000000,那對其進行反碼運算,可以看到為兩個值00000000或11111111,那對于八位二進制而言我們豈不是表示不了128這個數了,因為0占了兩個位置。所以原碼和反碼的表示范圍為:-127~127,為了解決這種情況,才產生了補碼。我們將全1的八位二進制作為-128,這個時候0的正確表示就是00000000了,所以補碼的表示范圍為-128~127。那么以上說的均是對于有符號數而言。
計算機中的運算均是以補碼的形式進行的,正數補碼與原碼一致,與反碼也一致。負數的補碼需要對原碼進行符號位不變,其他位按位取反并+1?;蛘哒f我們將最高位作為符號位的同時也作為數值位,就比如如果按照原始方法計算-5,那么其原碼為1101,對其求反+1得到結果:1011,所以-5的二進制補碼表達為1011,同樣我們可以不計算原碼而直接得出想要的結果,那最高位1不僅僅是符號,還代表8,所以最高位代表-8,那要得到-5,只需要其他位加起來是3即可,所以-5的補碼就是1011,其中第三位011代表3。
二進制補碼范圍如下圖所示,便于理解:

數據類型轉換:
-
較小數據類型轉較大數據類型(符號擴展):
當有符號位數從一個較小的數據類型轉換為較大數據類型時,進行符號位擴展可保持數值不變。 -
較大數據類型轉較小數據類型:
高位丟棄,丟棄截斷后原值可能會發生改變。
整數計算
無符號數加法:

如,a = 255, b = 1:

可以看到255+1=0,因為發生了溢出,所以發生溢出的計算公式為\(x+y-2^{\omega}\),若要預防溢出的情況,一般在代碼中實現為:a + b < a && a + b < b,也就是說溢出后的值是比求和前任一值要小的。
有符號數加法(即補碼加法):

我們發現其可能出現正溢出或負溢出,對于正溢出:
若char x=127,char y=1,則計算如下:

當x=127,y=1時,x+y后高位符號位為1,1000 0000是補碼形式,計算結果高位可以是符號位同時也是數值位,所以1000 0000的值為\(-2^7 = -128\),發生正溢出。
對于負溢出:
若char x=-128,char y=-1,則計算如下:

當x=-128,y=-1時,溢出最高位,然后最高位變為0,整體變為正值127,發生了負溢出。
綜上可知:
當\(x \geqslant 0, y \geqslant 0, x+y < 0\),發生正溢出
當\(x \leqslant 0, y \leqslant 0, x+y > 0\),發生負溢出
對于乘法的相關計算,和加法一樣,分為無符號數乘法和有符號數乘法(補碼乘法):

無符號數乘法結果要對其進行取模操作,取模相當于是截斷只保留低位,比如下圖中3位無符號數x和y,二者的乘積可能需要6位二進制數來表示,那么在C語言中定義了無符號數乘法所產生的結果是3位,因此運行下圖得到的\(x\times y\)結果會截取5位中的低3位。截斷采用取模的方式,因此運行結果等于x與y乘積并對\(2^3\)取模。

對于無符號的乘2和除以2,我們可以對其進行<<和>>操作執行,左移代表乘以2,右移代表除以2。一般在我們的計算機中,CPU中的ALU只支持加減法運算和移位運算,因此我們不能使用乘法運算法則去進行運算,計算機會將其拆解開來,比如x*14,就可以拆解成如下移位和加法的組合:

對于有符號乘法(補碼乘法):


計算機的有符號數用補碼表示,因此有符號數乘法就是補碼乘法。無論是無符號數乘法還是有符號數乘法,運算結果的位級表示都是一樣的,只不過補碼乘法比無符號數乘法多一步,需要將無符號數轉換成補碼(有符號數)。雖然完整的乘積結果的位級表示可能會不同,但是截斷后的位級表示都是相同的。
除法運算,我們除以2的冪為例進行討論補碼乘除的運算規則:

浮點數
理解浮點數的第一步是考慮含有小數值的二進制數。具體關于二進制權重的理解可以看一下這張圖:

這種定點表示的方法表示出的二進制對應十進制的值關系如下圖:

但是我們發現,這樣的表示方法非常復雜并且無法表示非常大的數,對于浮點型,我們有另一種IEEE的表示方法,將浮點數分為多個比特位,分別用于表示符號,階碼和尾數等。IEEE關于浮點數的表示公式為:
其中,s位符號位,s的正負決定了浮點數的正負,E為階碼,尾數為M。以32位float4字節浮點數為例,其占有32個比特位,那么它的符號位、exp、尾數分別劃分占用1位、8位和23位,這里需要說明,中間的多個比特位構成的exp并非階碼,想到得到階碼是需要對exp進行相關運算的,后續會說,先看單精度float的表示吧,如下圖:

對于double雙精度而言,符號位、exp、尾數分別劃分占用1位、11位、52位:



浮點數分為規格化和非規格化的,由階碼決定,拿32尾單精度含8位exp的浮點數來舉例,若exp不為0或255,即不全0或不全1,表示的是規格化的數,當exp全0則為非規格化,當exp全1則為特殊值,特殊值分為無窮大和無窮小。


規格化
如上圖,對于規格化值,exp的最小值為1,最大值為254,為了方便表述,用e等價于上面圖中的exp,來表示8位二進制數,前面我們提到,階碼的運算是需要通過e來得到的,階碼E需要e減去一個偏執量。


非規格化
以上說了規格化數的階碼運算以及尾數表示,接下來再來說說非規格化,非規格化的exp為全0,IEEE的表示法中,非規格化表示有兩個用途:
- 提供0值的正負表示。s=0,exp為全0,尾數全0,表示+0;s=1, exp為全0,尾數全0,表示-0。IEEE認為正0和負0在某些方面不同。
- 非規格化的數可以表示趨于0的數。當exp全0,階碼\(E=1-\text{bias}\)。表示趨于0時的表示方法與規格化表示0的方式不同,區別如下,主要在計算階碼和尾數上:

特殊值(無窮大、NaN等)
無窮大的表示分為正負無窮大,對于無窮大,exp全1,尾數部分全為0,正負無窮大的區分在于符號位的正負,s=0表示正無窮大,s=1表示負無窮大。

整型轉單精度浮點型
接下來感受一下整型和浮點型之間的轉換,以整型轉單精度浮點型為例。比如現在將整型12345轉換成浮點型12345.0。12345的32位比特位為 :0000 0000 0000 0000 0011 0000 0011 1001,因為高18位均為0,我們只看低14位:11 0000 0011 1001。根據規格化的表示規則,可以將12345表示成如下結果:
根據IEEE浮點數編碼規則M=1+f,所以f的值要對M-1才能得到,小數點左邊的1丟棄,由于單精度的小 數字段長度為 23,我們還需要在末端增加10個零:1 0000 0011 1001 0000 0000 00,這樣我們就得到浮點數的小數字段了。
接下來因為階碼E=13,由于32位單精度bias=127,所以根據E=e-bias得,e的值為140,二進制表示為:1000 1100,再加上符號位0表示正數,那么最終的結果如下圖:

以上則為整型轉單精度浮點型的過程實例。

浙公網安備 33010602011771號