1. 程式人生 > >整數的加法和乘法運算

整數的加法和乘法運算

深入理解計算機系統第二章讀書筆記

    在程式設計入門的時候可能都知道兩個正數相加的結果可能為負數,還有一個更奇怪的現象就是:x < yx - y < 0兩個表示式可能會得出不一樣的結果,這些神奇的結果都和計算機整數的底層表示和運算有著密切的關係。

    C 語言中有無符號數與有符號數之分,而在 Java 中只有有符號數,下面的內容還是基於 C 語言進行說明,畢竟更 C 比 Java 更接近底層嘛。

加法運算

無符號加法

    假設我們使用 w 位來表示無符號數,那麼兩個加數取值範圍即為:0 ≤ x, y <2w,理論上它們的和的範圍為:0 ≤ sum < 2w+1,因為只有 w 位表示無符號數(要把和表示出來就需要 w+1 位),所以超過 zw

的部分就會造成溢位,如下圖所示。

image

    對於無符號數的溢位,計算機採用的處理方式是丟掉最高位,直觀的結果就是,當發生溢位了,就將採用取模運算(或者說是減去 2w),舉個例子。

    只用 4 為來表示無符號數,即 w = 4,現在有 x [1001] 和 y [1100] 相加,其結果應為:[10101] ,但是沒有 5 位用來表示,所以丟掉最高位的1,剩下的值為 5 [0101],也就是 21 mod 16 = 5。

    那麼如何檢測是否發生溢位呢?設求和結果為 s,對於加法有 x + y ≥ x 恆成立,即只要沒有發生溢位,肯定有 s ≥ x。另一方面,如果確實發生溢位了,就有 s = x + y - 2w

,又有 y - 2w < 0,因此 s = x + y - 2w < x。

補碼加法

    和前面一樣,對於兩個給定範圍的加數 - 2w-1 ≤ x, y ≤ 2w-1 - 1,它們的和的範圍就在 - 2w ≤ sum ≤ 2w - 2。要想把整個和的範圍表示出來,依舊需要 w+1 位才行,而現在只有 w 位,因此還是需要採用將溢位部分截斷。

image

    可以發現,當發生正溢位時,截斷的結果是從和數中減去了 2w;而當發生負溢位時,截斷結果是把和數加上 2w

    那麼對於補碼加法如何檢測溢位結果呢?通過分析可以發現,當且僅當 x > 0, y > 0,但和 s ≤ 0 時為正溢位;當且僅當 x < 0, y < 0,但 s ≥ 0 時發生負溢位。

乘法

無符號乘法

    有了前面的基礎,乘法就變得簡單一些了,對於溢位情況,計算機仍然採用的是求模,比如 0 ≤ x, y ≤ 2w - 1,它們乘積的範圍為 0 到 22w - 2w+1 + 1 之間,這可能需要 2w 位來表示,溢位部分直接截掉,如下所示。

image

補碼乘法

    對於補碼,兩個乘數的範圍為:- 2w-1 ≤ x, y ≤ 2w-1 + 1,那麼其乘積表示範圍就為 - 22w-2 + 2w-1 到 22w-2 之間,補碼乘法和無符號乘法基本是一樣的,只是在無符號基礎上多加了一步轉換,即將無符號數轉換為補碼。

image

乘以常數

    我們知道,計算機做加減法、位級運算的速度最快(1 個指令週期),而做乘除法很慢(10 個甚至更多指令週期),平時編寫的程式中常常會乘以一個常數,為了使程式執行的更快,編譯器可能會幫我們做一些處理。

    首先我們考慮常數是 2 的冪。x * 21 可以表示為 x << 1,x * 22 可以表示為 x << 2,依次類推即可。

    對於不是 2 的冪的常數,比如 x * 14 可以表示為:(x<<3) + (x<<2) + (x<<1),因為 14 = 23 + 22 + 21;聰明的你可能發現 14 還有另一種表示方法,即 14 = 24 - 21,這種表示比前一種表示方法又少了運算量,所以 x * 14 還可以表示為:(x<<4) - (x<<1)

    實際上,這裡有一個通用的解決方案,對於任何一個常數 K,其二進位制可以表示為一組 0 和 1 交替的序列:[(0…0)(1…1)(0…0)(1…1)],14可以表示為:[(0…0)(111)(0)],考慮一組從位位置 n 到位位置 m 的連續的 1 (n ≥ m),(對於 14 有 n = 3,m = 1)可以有兩種形式來計算位對乘積的影響。

image

    這個優化不是一定的,大多數編譯器只在需要少量移位、加減法就足夠的時候才使用這種優化。