補碼與符號位取反
先來一個 C 語言的小例子:
#include <stdio.h>
#include <stdint.h>
int main(void)
{
int16_t n = -1;
n &= 0x7FFF; // 按位與
printf("%d", n); // 這里輸出什么?
return 0;
}
對於16位的整數 n ,按位與運行將最高位設置為0(符號位),得到的結果卻不是 1 ,結果是 32767 。
原因在於有符號整數的實現方式。
有符號整數,最容易想到的方式是在最高位加一個符號位,0表示整數,1表示負數,其它位不變(保留原始值),也即是原碼方式。但這個方式有一個問題,存在兩個0,正0和負0,在計算時需要先判斷符號位,然后才能決定用加法還是減法,機器計算不便。
另外一個方法是負數全部按位取反,也就是反碼方式。這個運算就相對簡單了,進行加法時,按位計算,0和0為0,0為1為1,1和1為0並產生進位,最高位有進位時,結果要加1,減法可處理為其負數的加法。但還是有點問題,還存在兩個0,正0和負0。
問題是出現負數上,那么把負數的反碼 + 1 ,不就把負0去掉了嗎?還真的是這樣,而同時負數比整數能多表示一個數(這是基於同余的)。
嚴格的表達為:
對於位數為 n 的整數,其補碼 [x]補 為:(2^n + x) mod 2^n ,
表示的范圍為 -2^(n-1) <= n < 2^(n-1) ,注意正數最大為 2^(n-1)-1
即:
當 0 <= x < 2^(n-1) 時,
[x]補 = x 的原碼
當 -2^(n-1) <= x < 0 時,
[x]補 = 2^n + x 的原碼。
而經驗上,可看作負數的補碼為其反碼加1(特殊數 -2^(n-1) ) 的反碼 :
x < 0 時:
[x]補 = [x]反 + 1
特殊數 [ -2^(n-1) ] = 100...0
它的加法處理非常簡單,符號位也可以運行,
[x+y]補 = (2^n + x + y) mod 2^n = ((2^n + x) + (2^n + y)) mod 2^n = [x]補 + [y]補
[x-y]補 = (2^n + x - y) mod 2^n = ... = [x]補 + [-y]補
現在回到原來的問題,對於16位 -1 ,其補碼為:
[-1]補 = [-1]反 + 1 = 0xFFFFF
按位與去掉符號位,得到的是 0x7FFF 也就是16位整數最大的正數( 2^15 - 1) 32767。