1. 程式人生 > 實用技巧 >Ch2資訊的表示和處理——caspp深入理解計算機系統

Ch2資訊的表示和處理——caspp深入理解計算機系統

目錄

第2章 資訊的表示和處理

2.1 資訊儲存

一些基礎概念引入:

  • 虛擬儲存器:機器級程式將儲存器視為一個非常大的位元組陣列
  • 地址:儲存器的每個位元組都由唯一的數字來標識
  • 虛擬地址空間:所有可能地址的集合

2.1.1 十六進位制

一、表示法

一個位元組:(二進位制)00000000 ~ 11111111;(十六進位制)00 ~ FF

二、加減

不轉換為十進位制或二進位制,直接相加減,超過16則進位或取位即可。

三、進位制轉換

  • 16 and 2

    • 16 to 2 :分位轉換

      十六進位制 1 7 3 A 4 C
      二進位制 0001 0111 0011 1010 0100 1100
    • 2 to 16:四位四位的轉

      二進位制 11 1100 1010 1101 1011 0011
      十六進位制 3 C A D B 3
  • 16 and 10

    • 16 to 10:3CA = 3 * 16^2 + 12 * 16^1 + 10
    • 10 to 16:累除取餘數

2.1.2 字

字長:指明整數和指標資料的標稱大小。對於一個字長為 w 位的機器而言,虛擬地址的範圍為 $0 $ ~ \(2^{w-1}\) ,程式最多訪問 \(2^w\)

個位元組。

2.1.3 資料大小

C宣告 32位機器 64位機器
char 1 1
short int 2 2
int 4 4
long int 4 8
long long int 8 8
**char *** 4 8
float 4 4
double 8 8

2.1.4 位元組順序與表示

對於跨越多位元組的程式物件,兩個規則:這個物件的地址是什麼,在儲存器中如何排列這些位元組。在幾乎所有機器上,多位元組物件被儲存為連續的位元組序列,地址為所使用位元組最小的地址。

一、位元組的排列規則

假設 x 型別為 int,位於地址 0x100 處,十六進位制值為 0x01234567

  • 小端法:按照從最低有效位元組到最高有效位元組的順序儲存物件,大多 Intel 相容機採用

  • 大端法:按照從最高有效位元組到最低有效位元組的順序儲存物件,大多 IBM 的機器採用

二、列印位元組

  • 實現程式
#include<bits/stdc++.h>
using namespace std;
typedef unsigned char* byte_pointer;

/* show_bytes 為位元組列印函式 */
void show_bytes(byte_pointer start, int len){ 
    int i;
    for(i = 0; i < len; i++)
        printf(" %.2x",start[i]);
    printf("\n");
}
void show_int(int x){
    show_bytes((byte_pointer) &x, sizeof(int));
}
void show_pointer(void* x){
    show_bytes((byte_pointer) x, sizeof(void*));
}
int main()
{
    int a = 100;
    show_int(a);
    return 0;
}
/* 
 * 輸出結果為 64 00 00 00
 * 標誌著編譯的該機器為小端法
*/
  • 結果比較

    • int & float:除了順序,值是相同的,且可以看出只有 Sun 處理器是大端法
    • 指標:不同機器配置使用不同的儲存分配規則,其中注意只有 Linux 64 指標是 8 位元組

三、字串的位元組表示

C 語言中字串被編碼為一個以 null (其值為0)字元字元的字元陣列。

/* 利用程式碼輸出s的位元組表示*/
char s[] = "12345";
show_bytes((byte_pointer)s,strlen(s)+1); //注意要加 1
/* 結果如下 */
// 31 32 33 34 35 00 可以看到最後null的值為0,即'\0'

四、程式碼表示

同一個C語言檔案在不同機器編譯時,由於規則方式不同,會產生不同的機器程式碼。因此二進位制程式碼是不相容的,很少能在不同機器和作業系統組合之間移植。

2.1.5 位級預算

一、布林運算

  • 編碼:true & false 編碼為二進位制 1 和 0;

  • 運算:非、與、或、異或

  • 表示式運算:

    確定一個位級表示式的結果最好的方法,就是將十六進位制的引數擴充套件成二進位制表示並執行二進位制運算,然後再轉換成十六進位制。

二、掩碼運算

掩碼是一個位模式,表示從一個字中選出的位的集合。

比如,掩碼 0xFF(最低的8位為1)表示一個字的低位位元組。x&0xFF生成一個由x的最低有效位元組組成的值****,其他的位元組設定為0.x=0x89abcdef,則將得到 0x000000EF.

三、移位運算

  • 運算

    • 左移

      x << k 表示 x 的位元組值向左移動 k 位,並在右端補 k 個 0

    • 右移

      • 邏輯右移:在左側補 k 個 0
      • 算數右移:在左側補 k 個最高有效位的值(有符號整數的運算)
  • 當 k 超出位數時

    對於一個由 w 位組成的資料型別,若移動 k >= w 位時,C語言標準規定移動 k mod w

  • 優先順序

    移位運算的優先順序低於加減法(不確定時加上括號就完事)


2.2 整數表示

描述兩種不同的方式:無符號和有符號

2.2.1 整型資料型別

需要注意的是,取值範圍不是對稱的,負數的範圍比整數的範圍大 1

  • 32 位機器

  • 64位機器

2.2.2 編碼方式

一、無符號編碼

假設一個整數資料型別有w,函式B2U (Binary to Unsigned):

\[B2U_w(x)=\sum_{i=0}^{w-1}x_i2^i \]

其實就是將十進位制轉換為二進位制。

二、補碼編碼(有符號)

最高有效位為符號位,且|TMin|=|TMax|+1

B2T(Binary to Two's-complement):

\[B2T_W(x)=-x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}x_i2^i \]

  • 負數的補碼:其絕對值的補碼,取反後加一
  • 非負數的補碼:與無符號編碼一致,需注意其他位(尤其是最高位)必是 0

三、有符號數的其他表示方法

  • 反碼:除了最高有效位的權是\(-(x^{w-1}-1)\) 而不是\(-2^{w-1}\),其它與補碼一致

    \[B2O_W(x)=-x_{w-1}(2^{w-1}-1)+\sum_{i=0}^{w-2}x_i2^i \]

  • 原碼:最高有效位是符號位,用來確定剩下的位應該取負權還是正權

三、有無符號數的轉換

強制型別轉換的結果保持位值不變,只是改變了解釋這些位的方式。

  • T2U

    \[T2U_w(x)=\begin{cases}x+2^w,&x<0\\x,&x\geq0\end{cases} \]

    證明:

    計算B2U - B2T 的差,從0到w-2 的位的加權和抵消,剩下 \(B2U(x)-B2T(x)=x_{w-1}(2^{w-1}--2^{w-1})=x_{w-1}2^w\),因此可得\(T2U(x)=B2U(T2B(x))=x_{w-1}2^w+x\)。且在x 的補碼錶示中,\(x_{w-1}\)決定了x 是否為負,故得上式。

  • U2T

    \[U2T(u)=\begin{cases}u,&u<2^{w-1}\\u-2^w,&u\geq 2^{w-1}\end{cases} \]

    證明跟上面差不多,自己推推。

四、位的擴充套件

將一個較小的資料型別轉換到一個較大的型別

  • 零擴充套件:將一個無符號數轉換位一個更大的資料型別,在位元組位表示的開頭新增 0
  • 符號擴充套件:將一個補碼數字轉換為一個更大的資料型別,在表示中新增最高有效位值的副本

五、數的截斷

將一個較大的資料型別轉換到一個較小的型別

先看例子:

int   x = 53191;
short sx = (short) x; /* 將32位的int階段為16位的short,截斷後位模式為-12345的補碼 */
int   y = sx;		  /* 符號擴充套件上一行的-12345,得到-12345的32位補碼錶示 */

將一個 w 位的數截斷為k 位數字時,丟棄高w-k 位,可能會改變它的值(overflow)。下面探究截斷前後的數值變化的規律。

對於一個無符號數字x ,截斷它到k 位的結果就相當於計算 x mod 2^k,綜合上面T2UU2T,不難推理出:

  • 無符號

\[B2U_k([x_{k-1},x_{k-2},\cdots,x_0])=B2U_w([x_{w-1},w_{w-2},\cdots,x_0]) mod\space2^k \]

  • 補碼數字

    \[B2T_k([x_{k-1},\cdots,x_0])=U2T_k(B2U_w([x_{w-1},..,x_0])mod\space2^k) \]

上面表述是課本表述,簡單點說其實就是,先將 x 無符號化,然後對 2^k 取餘,即可得到無符號型別的截斷結果,若要得到有符號型別,直接運用U2T 即可。

2.2.3 C語言中的有無符號數

宣告一個像 12345 的常量時,這個值被認為是有符號的。要建立無符號常量,須加上字尾'u';用 printf 輸出時,%d、%u和%x 分別表示以十進位制、無符號十進位制和十六進位制格式輸出

C語言允許無符號數與有符號數的轉換,服從T2UU2T ,其中w 表示資料型別的位數。

/* 顯式的強制型別轉換 */
int tx, ty;
unsigned ux, uy;
tx = (int) ux;
uy = (unsigned) ty;
/* 不同型別變數賦值也會發生隱式型別轉換 */

需要注意的是,當一個運算式中同時出現有符號和無符號數時,C語言會隱式的將有符號轉換為無符號。

另外這裡補充一下C TMin 的寫法

#define INT_MAX 2147483647
#define INT_MIN (- INT_MAX - 1) //這樣表示,因為limits.h標頭檔案寫法如此

2.3 整數運算

2.3.1 加法

一、無符號加法

  • 無符號數字相加運算

    考慮兩個非負整數xy,滿足 \(2\leq x,y\leq 2^w-1\)。實際上就是兩者位數為w

    無符號運算可以視為一種模運算形式。無符號加法等價於計算和模上\(2^w\)在數位上,可以通過簡單的丟棄x+yw+1 位表示的最高位,來計算這個數值。

\[x+_w^uy=\begin{cases}x+y,&x+y\leq 2^w\\x+y-2^w,&2^w\leq x+y\leq 2^ {w+1}\end{cases} \]

  • 判斷無符號相加是否溢位

    當且僅當 s<x(或者等價地*s<y)時,發生了溢位。

    • 證明:
      • 若沒有溢位,\(x+y\geq x\),則必然\(s\geq x\)
      • 若溢位了,有\(s=x+y-2^w\),又由於 \(y<2^w\),則 \(y-2^w<0\),因此 \(s=x+(y-2^w)<x\)
  • 加法逆元

    對於每個值x,必然有某個值\(-_w^ux\)滿足\(-_w^ux+_w^ux=0\),則該值稱為x 的加法逆元。

    \[-_w^ux=\begin{cases}x,&x=0\\2^w-x,&x>0\end{cases} \]

    • 證明:
      • x =0時,加法逆元顯然是0
      • x >0時,考慮 \(2^w-x\)\((x+2^w-x)\space mod\space 2^w=0\),故該值即為x 的逆元

二、補碼加法

  • 補碼加法定義

    定義字長為w 的、運算數 xy 上的補碼加法(xy 滿足 \(-2^{w-1}\leq x,y\leq 2^{w-1}\),表示為 \(+_w^t\)

    \[x+_w^ty=U2T(T2U(x)+_w^uT2U(y))=U2T[(x+y)\space mod\space 2^w] \]

  • 溢位情況

    下面分四種情況分析:定義 z 為整數和 z=x+yz'\(z'=z\space mod\space 2^w\),而 z''\(z''=U2T(z')\),即 \(x+_w^ty\)

    • \(-2^w\leq z< -2^{w-1}\):負溢位。\(z'=z+2^w\),得出\(0\leq z'\leq 2^{w-1}\),則 \(z''=z'=x+y+2^w\)
    • \(-2^{w-1}\leq z<2^{w-1}\):易得 \(z''=x+y\)
    • \(2^{w-1}\leq z<2^w\):正溢位。\(z'=z\),得到 \(2^{w-1}\leq z'<2^w\),但在該範圍內,\(z''=z'-2^w=x+y-2^w\)

    綜上所述:

    \[x+_w^ty= \begin{cases} x+y-2^w,&2^{w-1}\leq x+y\\ x+y,&-2^{w-1}\leq x+y<2^{w-1}\\ x+y+2^w,&x+y<-2^{w-1} \end{cases} \]

    從上式也可判斷,負加負得正時負溢位,正加正得負時正溢位

  • 補碼加法例項

    下圖是4位補碼加法的例項:

  • 補碼的非(加法逆元)

    \[-_w^tx= \begin{cases} -2^{w-1},&x=-2^{w-1}\\ -x,&x>-2^{w-1} \end{cases} \]

2.3.2 乘法

一、無符號乘法

w 位無符號乘法運算 \(*_w^u\) 的結果為:

\[x*_w^uy=(x\cdot y)\space mod\space 2^w \]

二、補碼乘法

w 位的補碼乘法運算 \(*_w^t\) 的結果為:

\[x*_w^ty=U2T((x\cdot y)\space mod \space 2^w) \]

三、無符號與補碼乘法的關聯

給定長度為 w 的位向量 x,y,兩者無符號相乘和補碼相乘,截斷為 w 位的位級表示相同。

四、乘以常數

由於整數乘法指令相當慢,機器常使用移位和加法運算的組合來代替乘以常數因子的乘法。

  • 先考慮乘以 2 的冪

    對於一個位模式為 \([x_{w-1},x_{w-2},..,x_0]\)的補碼數 x ,以及範圍在 \(0\leq k<w\) 內的任意的 k ,位模式 \([x_{w-1},x_{w-2},..,x_0,0,..,0]\) 就是 \(x*_w^t2^k\) 的補碼錶示。(事實上無符號結果也是如此)

    因此對於有符號變數 x,C表示式 x<<k 等價於 \(x*2^k\)

    無論是無符號還是補碼運算,乘以2的冪都可能會導致溢位,但通過移位得到的結果是一樣的

  • 乘以一個任意常數

    先舉幾個例子:

    • x*14:利用等式 14=23+22+2^1,編譯器重寫為 (x<<3)+(x<<2)+(x<<1)
    • x*14:也可以利用 14=2^4-2 ^1,重寫為 (x<<4)-(x<<1)

    對於某個常數 K 的表示式 x * K 生成程式碼,編譯器會將 K 的二進位制表示表達為一組 0 和 1 交替的序列\([(0\cdots0)(1\cdots1)(0\cdots0)\cdots(1\cdots1)]\)

    考慮一組從位位置 n 到位位置 m 的連續的 1 ( n>=m)。用下列兩種形式中的一種來計算這些位對乘積的影響:

    • 形式 A:(x<<n) + (x<<n-1) + ... + (x<<m)
    • 形式 B:(x<<n+1) - (x<<m)

五、除以 2 的冪

整數除法總是舍入到零。對於 \(x\geq0\)\(y>0\) ,結果是 \([x/y]\),這裡對於任何實數 a,[a]定義為唯一的整數 a',使得 a' <= a < a'+1. 例如[3.14]=3, [-3.14]=-4.

記清楚,向零舍入,是我們做除法所需要的正確結果。

  • 無符號數(邏輯移位)

    對一個無符號數執行邏輯右移 k 位的效果,和除以 \(2^k\) 有一樣的效果。C 表示式 x >> k 等價於 x/pwr2k.

  • 有符號數(算術移位)

    可以發現,對 x<0 和 y>0,整數除法的結果應該是 [x/y],即將負的結果向上朝零舍入,當舍入發生時,將一個負數右移 k 位不等價於把它除以 2^k.

    可以在移位前“偏置”這個值,利用屬性 [x/y]=[(x+y-1)/y],這樣通過給 x 增加一個偏量 y-1,然後再將除法向下舍入,當 y 整除 x 時,得到 k,否則得到 k+1。因此在右移前,先將 x 加上 2^k-1,就可以得到正確舍入的結果。

    C表示式 (x<0? (x + (1<<k) - 1) : x) >> k 等價於 x/pwr2k

不幸的是,不能用除以 2 的冪的除法來表示除以任意常數 K 的除法

2.4 浮點數

2.4.1 浮點表示

一、二進位制小數

形如 \(b_mb_{m-1}\cdots b_1b_0.b_{-1}\cdots b_{-n}\) ,表示的數 b 定義如下:

\[b=\sum_{i=-n}^m2^i\times b_i \]

例如,101.11表示數字 \(1\times 2^2+0\times 2^1+1\times 2^0+1\times2^{-1}+1\times 2^{-2}=4+0+1+1/2+1/4=5\frac{3}{4}\)

不過我們並不能把 0.20 準確的表示為一個二進位制小數,只能近似地表示它,增加二進位制表示的長度可以提高表示的精度:

二、IEEE 浮點表示

  • 浮點表示定義

    IEEE 浮點標準用 \(V=(-1)^s\times M\times 2^E\) 的形式來表示一個數:

    • 符號s 決定這個數是負數(s=1)還是正數(s=0),對於數值 0 的符號位解釋作為特殊情況處理。
    • 尾數M 是一個二進位制小數,範圍是 1~2-e,或者是 0~1-e。
    • 階碼E 的作用是對浮點數加權,這個權重是 2 的 E 次冪(可能是負數)

    將浮點數的位表示劃分為三個欄位,分別對這些值進行編碼:

    • 一個單獨的符號位 s 直接編碼符號 s
    • k 位的階碼欄位 exp = \(e_{k-1}\cdots e_1e_0\) 編碼階碼 E
    • n 位小數字段 frac = \(f_{n-1}\cdots f_1f_0\) 編碼尾數 M,但是編碼出來的值也依賴於階碼欄位的值是否等於 0
  • 單精度與雙精度

    • 單精度:(float)s、exp、frac 欄位分別為 1 位、k = 8 位和 n = 23位
    • 雙精度:(double)s、exp、frac 欄位分別為 1 位、k = 11 位和 n = 52 位

  • 位表示的三種情況

    根據 exp 的值,被編碼的值可以分成三種不同情況(最後一種情況有兩個變種)

    下圖展示了對單精度格式的情況

    • 規格化的值

      最普遍。exp 的位模式不全為0,也不全為1(單精度數值為255,雙精度為2047)

      • 階碼:階碼欄位被解釋為以偏置形式表示的有符號整數,E = e - Bias ,其中e 為無符號數,Bias 是一個等於 \(2^{k-1}-1\)(單精度是127,雙精度是1023)的偏置值。由此產生指數的取值範圍,單精度是 -126 ~ +127,雙精度是 -1022 ~ +1023。
      • 尾數:小數字段 frac 為小數值f,範圍大於等於0小於1,其二進位制小數點在最高有效位的左邊。而尾數 \(M=1+f\)。(包含隱含開頭1)
    • 非規格化的值

      exp 的位模式全為 0。

      階碼值是 E = 1 - Bias ,尾數的值是 M = f ,也就是小數字段的值,不包含隱含開頭 1.

      用途是,1. 提供了一種表示數值 0 的方法;2. 表示那些非常接近於 0.0 的數

    • 特殊值

      exp 的位模式全為1。

      • 當小數域全為 0 時,得到的值表示無窮。s = 0 時為 +infty,s = 1 時為 -infty
      • 當小數域為非零時,結果值被稱為“NaN”(not a number)。一些運算結果不是實數或無窮時,會返回NaN,比如 \(\sqrt{-1}\)\(\infty - \infty\)

三、數字示例

  • 情況一

    假定 6 位格式,k=3 的階碼位和 n=2 的尾數位,偏置量是 \(2^{3-1}=3\).

    注意,規格化數的階碼無符號數最大值是 \(2^{k-1}-1\),因為不能全為1

    最大數量值的規格化數是 \(\pm14\),非規格化數聚集在 0 的附近。

  • 情況二

    假定 8 位格式,k=4 的階碼位和 n=3 的小數位,偏置量是 \(2^{4-1}-1=7\)

    這種格式的非規格化的數的 E = 1-7 = -6,得到權 \(2^E=\frac{1}{64}\),小數\(f\) 的範圍是 \(0,\frac{1}{8},..,\frac{7}{8}\),從而得到數 V 的範圍是 \(0\) ~ \(\frac{1}{64}\times\frac{7}{8}=\frac{7}{512}\)

  • 一般屬性

    • 值 +0.0 總有一個全為 0 的位表示。
    • 最小的正非規格化值的位表示:最低有效位為1其他位為0,小數值\(M=f=2^{-n}\)和階碼值\(E=-2^{k-1}+2\),因此它的數字值為 \(V=2^{-n-2^{k-1}+2}\)
    • 最大的非規格化值的位表示:位模式是由全為0的階碼欄位和全為1的小數字段組成
    • 最小的正規格化的位表示:階碼欄位的最低有效位為1,其它位全為0
    • 值 1.0 的位表示的階碼欄位除了最高有效位等於 0 以外,其它位都等於 1
    • 最大的規格化值的位表示的符號位為 0,階碼的最低有效位等於 0 ,其它位等於 1

  • 整數轉換成浮點形式的例子

    12345 的二進位制表示為 \([11000000111001]\),通過將二進位制小數點左移 13 位,建立了這個數的一個規格化表示,得到 \(12345 = 1.1000000111001_2\times 2^{13}\)。利用 IEEE 單精度形式來編碼,即逆過程:

    • 小數字段:丟棄開頭的 1,在末尾增加 10 個 0,得到\([10000001110010000000000]\)
    • 階碼欄位:13 + 127 = 140,其二進位制為 \([10001100]\)
    • 符號位:0

    然後拼在一起就是結果了,可以觀察到 12345 和 12345.0 在位表示上有一部分是重疊的。

2.4.2 舍入

因為表示方法限制了浮點數的範圍和精度,所以浮點運算只能近似地表示實數運算。因此對於值 x ,一般想用一種系統地方法,能夠找到“最接近的”匹配值 x',可以用期望的浮點式表示出來。即舍入。

IEEE 浮點格式定義了四種不同的舍入方式預設方法是找到最接近的匹配(向偶數舍入),其它三種可用於計算上界和下界。向偶數舍入,即預設試圖找到一個最接近的匹配值,當值處於兩個可能結果中間數值時,向偶數舍入(使得結果的最低有效數字是偶數)

向偶數舍入法,能運用於二進位制小數。(將最低有效位的值 0 認為是偶數,值 1 認為是奇數)

看例子:考慮舍入值到最近的四分之一,即二進位制小數點右邊 2 位

  • 非中間值:\(10.00011_2(2\frac{3}{32})\) 向下舍入到 \(10.00_2(2)\)\(10.00110_2(2\frac{3}{16})\) 向上舍入到 \(10.01_2(2\frac{1}{4})\)

  • 中間值:\(10.11100_2(2\frac{7}{8})\) 向上舍入到 \(11.00_2(3)\)\(10.10100_2(2\frac{5}{8})\) 向下舍入到 \(10.10_2(2\frac{1}{2})\)

    中間值湊偶,其實就是湊出有效位最後一位為 0

2.4.3 浮點運算

浮點運算的結果是對實際運算的精確結果進行舍入後的結果,記為\(Round(x+y)\)

一、加法

  • 定義:\(x+^fy = Round(x+y)\)
  • 性質:
    • 可交換:\(x+^fy=y+^fx\)
    • 不可結合:因為舍入,\((3.14+1e10)-1e10=0.0\),而\(3.14+(1e10-1e10)=3.14\)
    • 大多數值有加法逆元:\(x+^f-x=0\),無窮和NaN 是例外,對於任何x\(NaN+^fx=NaN\)
    • 單調性:如果 \(a\geq b\),那麼對於任何 a、b、x,除了 NaN ,都有 \(x+a\geq x+b\)。無符號或補碼加法不具有這個屬性。

二、乘法

  • 定義:\(x*^fy=Round(x\times y)\)
  • 性質:可交換,不可結合,在加法上不可分配,單調性

這裡關於單調性提一點,從浮點運算符合單調性可以看出,浮點不會因為溢位而改變符號

2.4.4 C語言中的浮點數

強制型別轉換

這個好像常考

  • int 2 float:數字不會溢位,但可能被舍入
  • int 或 float 2 double:double 有更大的範圍和更高的精度,所以能夠保留精確的數值
  • double 2 float:範圍減小,值可能溢位,精度降低,可能被舍入
  • float or double 2 int:值將會向零舍入,也可能會溢位。且,一個從浮點數到整數的轉換,如果不能為該浮點數找到一個合理的整數近似值,將會產生一個整數不確定值。