# CSAPP chp2 研讀 第二章標題是 Representing and Manipulating Information。也就是在講述計算機如何表示與操作數據。重點聚焦在整數與浮點數之二進制表示。以下為此章的架構 : * 信息存儲 (2.1): * 核心概念:計算機以位(bits)存儲信息,數據的解釋依賴上下文(例如,同一組位可以表示整數、字符或指令)。 * 小節: * 十六進制表示(2.1.1):便於閱讀二進制數據。 * 數據大小(2.1.2):不同數據類型(int、long、float等)在 32 位和 64 位系統中的大小。 * 尋址與字節序(2.1.3):大端法(big-endian)與小端法(little-endian)。 * 字符串(2.1.4) * 代碼(2.1.5 ) * 布爾代數(2.1.6)。 * C 語言的位操作(2.1.7):如 &、|、^、~。 * C 語言的邏輯操作(2.1.8):如 &&、||。 * C 語言的移位操作(2.1.9):左移 << 和右移 >>。 :::success 疑問1(2.1.1) : 為啥計算機要用二進制表示不是十進制 ? - 二進制被選用為計算機所讀取與處理,當中必源自於二進制有他獨有的優點。以下為二進制的優點 : - 硬件實現簡單: 二進制只需要兩種狀態(0 和 1),對應電路的開(高電壓)和關(低電壓)。這可以用簡單的電子元件(如晶體管)實現,設計和製造成本低。 十進制需要表示 0 到 9 的十種狀態,電路設計複雜(需要多級電壓或模擬信號),增加硬件成本和功耗,且控制精度要求更高。 - 可靠性高: 二進制系統容錯性強。電路只需區分兩種狀態,抗噪聲能力強,誤差率低。 十進制系統需要精確區分十種狀態,容易受電壓波動或噪聲干擾,導致錯誤。這好比將一個數值10,本來切倆等分改切成十等份,假設 noise 值為1.1,則使用十進制時的輸出會產生誤差,本來正確值為7,加入noise後變成8.1輸出變成8。而二進制依舊是high。 - 邏輯運算高效: 二進制與布爾代數直接對應,邏輯運算(如 AND、OR、NOT)簡單且快速,用於實現加法、乘法等算術運算。 十進制運算需要更複雜的邏輯電路,運算速度慢,效率低。 - 存儲與傳輸優勢: 二進制數據存儲在磁盤、內存或傳輸線路上只需表示兩種狀態,設計統一且穩定。 十進制存儲或傳輸需要更多位元(例如,BCD 編碼用 4 位表示一個十進制數字),浪費空間且增加複雜性。 - 歷史與標準化: 早期計算機(如真空管、繼電器時代)採用二進制,因其技術實現最可行。現代計算機沿襲這一標準,確保兼容性和一致性。 十進制計算機(如 ENIAC 的部分功能)曾被嘗試,但因複雜性和成本高被淘汰。 疑問2(2.1.1) : 為啥有二進制還需要十六進制 ? - 最大的原因是為了讓人方便閱讀。二進制若數字稍大時,所擁有的位數常常非常冗長,一時之間很難辨識出是為何值。但有了十六進制時就能將冗長的二進制長度縮短四倍。 疑問3 : 計算機能表示的數據極值是?為啥這樣的極值足夠? - 由 CSAPP 可得知以下數據類型大小 - 無符號整數(Unsigned Integer): - 最大值:$2^{64} - 1$ = 18,446,744,073,709,551,615(約 $1.84 × 10^{19}$)。 - 最小值:0。 - 有符號整數(Signed Integer,二進制補碼) - 最大值:$2^{63} - 1$ = 9,223,372,036,854,775,807(約 $9.22 × 10^{18}$)。 - 最小值:$-2^{63}$ = -9,223,372,036,854,775,808(約 $- 9.22 × 10^{18}$)。 - 單精度(32 位,float): - 最大值:約 $3.4 × 10^{38}$(正規化數,$2^{126} × (2 - 2^{-23})$)。 - 最小正值(非零):約 $1.18 × 10^{-38}$(非正規化數,$2^{-126}$)。 - 特殊值:±∞(無窮大),NaN(非數)。 - 雙精度(64 位,double): - 最大值:約 $1.79 × 10^{308}$(正規化數,$2^{1022} × (2 - 2^{-52})$)。 - 最小正值(非零):約 $2.23 × 10^{-308}$(非正規化數,$2^{-1022}$)。 - 特殊值:±∞,NaN。 由以上可知計算機能處理之數據極值為 double ,值為約 $1.79 × 10^{308}$ ,有趣的是此值也解釋了工程模擬軟體 matlab 中所能模擬出最大響應值。當將發散的響應放大來看會發現值的級數為 $10^{308}$ 。 再來探討這個數值是否足夠,首先極值角度來說,宇宙級尺度(如可觀測宇宙直徑 ~$8.8 × 10^{26}$ m)或微觀尺度(如普朗克長度 ~$1.6 × 10^{-35}$ m), double 都能涵蓋且依舊有餘裕。而若從精度角度來說,52 位尾數(mantissa)提供約 15-17 位十進制精度,已足夠滿足工程、物理和機器學習的精度需求。 若將來真的需要更大的數值處理,也可以用更改軟體方式(多精度庫如 GMP、MPFR)實現,而不用改變硬體標準。目前也已有一些架構(如 ARMv8-A)已支持 128 位整數或浮點數(範圍達 $2^{128}$ 或 $10^{6144}$)。 數據類型的設定通常需與 "硬體規格" 與 "ISA" 相關聯。從硬體規格來說,CPU寄存器大小決定了數據類型基本單位的定義。整數類型(如 int、long)通常與寄存器大小對齊。以下是CPU寄存器大小相關例子 : 1. 例如,在 x86-64 中,int 通常為 32 位,long 為 64 位,匹配通用寄存器(RAX、RBX 等)的寬度。 2. 64 位寄存器允許高效處理 64 位整數(uint64_t)或雙精度浮點數(double),而 32 位系統(如 x86)處理 64 位數據需要多指令模擬,效率較低。 3. 例:在《CS:APP》2.1.2(頁 75),描述了不同架構下數據類型的大小,例如 64 位系統中 long 為 8 字節(64 位),而 32 位系統中為 4 字節(32 位)。 #### 硬體規格 此外硬體方面,內存系統也要求數據按特定邊界對齊(alignment),以優化訪問速度。例如,x86-64 要求 64 位數據(如 double)按 8 字節對齊,否則可能引發性能損失或硬體異常。以下是 Memory 大小影響數據類型相關例子 : 1. 數據類型的大小和對齊規則(如 int 4 字節、double 8 字節)由硬體內存控制器和匯編指令決定。例如,movq 指令假設 64 位數據對齊到 8 字節邊界。 2. 若一個 32 位整數未按 4 字節對齊,CPU 可能需要多次內存訪問,降低效率。 再來是硬體方面之浮點數硬體。現代 CPU 包含專用浮點單元(FPU)或向量寄存器(如 x86-64 的 XMM/YMM 寄存器),支持 IEEE 754 浮點數標準(32 位 float 和 64 位 double)。浮點數類型的大小和格式直接對應硬體浮點寄存器。例如,XMM 寄存器(128 位)可存儲 4 個 float 或 2 個 double,YMM 寄存器(256 位)支持 SIMD(單指令多數據)運算。 #### ISA 而在來到 ISA 方面。首先,ISA決定 CPU 能直接處理什麼數據。舉例來說,x86-64(常見的 PC 處理器架構)可以直接處理 8 位(像 char)、16 位、32 位(像 int)、64 位(像 long)的整數,還能處理 32 位(float)和 64 位(double)的小數。如果你用一個 ISA 不直接支持的數據類型(比如 128 位整數),CPU 得用好幾個指令“拼湊”出結果,就像你不會做一道菜,得先查食譜再慢慢試,費時又費力。 而ISA會影響數據處理的速度。ISA 裡的指令是為特定數據類型量身定做的。比如:x86-64 有個指令 addq,可以一次加兩個 64 位整數,超快! 但如果你用一個怪怪的數據類型(像 80 位浮點數),ISA 沒有一條指令能直接搞定,編譯器得把這操作拆成好幾步,CPU 跑起來就慢。 有些ISA中有特殊指令也會影響數據類型之選擇。比如 x86-64 的 AVX 指令,可以一次處理 8 個 32 位小數(float),特別適合圖形或 AI 計算。這會讓使用者傾向選用 ISA 支援得好的數據類型。例如,寫遊戲程式時,你可能選 float 而不是 double,因為 AVX 指令能讓 float 跑得飛快。 最嚴重的情況就是 ISA 與數據類型不合而影響程式正確性。比如: x86-64 假設 int 是 32 位。如果你硬要當它是 64 位,CPU 可能只讀一半數據,算出錯的結果。浮點數得符合 IEEE 754 標準(x86-64 支援的格式),不然 CPU 可能不認,程式直接崩。 疑問3 : 討論 bit ops、logical ops、shift ops、arithmetic ops 開銷 ? - bit ops 在 c 中有 `&`、`|`、`^`、`~`等等。此些運算由 cpu logic gate integrated circuit 之中的 and gate、or gate、xor gate、not gate 實現。無論 operand 是 8 位、32 位還是 64 位都能在一個 clock cycle 完成。 - logical ops 在 c 中有 `&&`、`||`、`!` 等等。邏輯運算處理 boolean value(true/false) 。分兩種情況,第一是 not `!`。僅需一個 clock cycle 即可完成。而第二種是 `&&` 與 `||`。此種涉及短路求值與預測錯誤。短路求值指當檢查第一個 operand 即能決定結果時,即可花一個 clock cycle 就完成運作。但若是 - shift ops - arithmetic ops ::: * 整數表示 (2.2): * 核心概念:整數的兩種主要表示方式——無符號(unsigned)和二進制補碼(two's-complement)。 * 小節: * 整數數據類型(2.2.1):有符號與無符號類型的區別。 * 無符號編碼(2.2.2):直接二進制表示,範圍為 0 到 2^n-1。 * 二進制補碼編碼(2.2.3):用於有符號整數,範圍為 -2^(n-1) 到 2^(n-1)-1。 * 有符號與無符號轉換(2.2.4):轉換規則及潛在問題。 * C 語言中的有符號與無符號(2.2.5):隱式轉換的陷阱。 * 位擴展(2.2.6):零擴展(unsigned)與符號擴展(signed)。 * 截斷(2.2.7):位數減少時的影響。 * 有符號與無符號建議(2.2.8):避免混淆的最佳實踐。 :::success 疑問 : ::: * 整數運算 (2.3): * 核心概念:整數運算的數學屬性,特別是溢出和模運算。 * 小節: * 無符號加法(2.3.1):模 2^n 運算。 * 二進制補碼加法(2.3.2):處理正負數,注意溢出。 * 二進制補碼取負(2.3.3):-x = ~x + 1。 * 無符號乘法(2.3.4):模 2^n 運算。 * 二進制補碼乘法(2.3.5):類似無符號,但考慮符號。 * 常數乘法(2.3.6):編譯器優化(如用移位代替乘法)。 * 除以 2 的冪(2.3.7):右移操作的細節。 * 整數運算總結(2.3.8):溢出和精度的注意事項。 :::success 疑問 : ::: * 浮點數 (2.4): * 核心概念:IEEE 754 浮點數標準,用於表示實數。 * 小節: * 分數二進制數(2.4.1):類似十進制小數的二進制表示。 * IEEE 浮點表示(2.4.2):符號、指數、尾數(sign, exponent, mantissa)。 * 示例數值(2.4.3):如何表示 0、∞、NaN 等特殊值。 * 捨入(2.4.4):四捨五入、向偶數捨入等。 * 浮點運算(2.4.5):加法、乘法的數學屬性(非關聯性)。 * C 語言中的浮點數(2.4.6):float 與 double 的使用。 :::success 疑問 : :::