1、原碼、反碼、補碼,正數減法轉補碼加法 js 在進行二進位運算時,使用32 位元二進位整數,由於js 的整數都是有符號數,最高位元0表示正數,1表示負數,因此,js 二進位運算中使用的整數表達範圍是
-Math.pow(2,31) ~ Math.pow(2,31)-1 // -2147483648 ~ 2147483647
原碼:最高位元0 表示正,1表示負,其餘31 位元是該數的絕對值(真值的絕對值)的二進位形式
反碼:正數反碼與原碼相同,負數反碼是原碼符號位元不變,其餘31位元取反(0變1,1變0)
補碼:正數補碼與原碼相同,負數補碼為反碼加1 (符號位元參與運算,其實只有求-0 的補碼才涉及最高位進位,因此不用擔心在反碼加1時由於符號位參與運算進位而使- 變)。
0 的反碼:32個0 ,以正數處理,原碼、反碼、補碼都是0。
-0 的反碼:最高位1,其餘位由0 原碼取反,得到32 個1
-0 的補碼:其反碼是32 個1 加1,最高位溢出被捨棄,得到32 個0
因此,正負0 的補碼都是0.
由負數的補碼求他的絕對值補碼:負二進制數的絕對值,只要各位(包括符號位)取反,再加1,就得到其絕對值。
電腦在處理加減運算時,使用補碼進行運算,減法被視為加上一個負數,在處理負數時,用負數的補碼進行加法可以即可得到正確運算結果,補碼是為了統一加減運算而生的。
正數減法轉補碼加法的原理是32 位數溢位:
對於32 位元二進位正整數來說,其模為
Math.pow(2,32) = 4294967296
32 位元正整數最大表達範圍是4296 1 ,達到4294967296 這個值就要進位到33位,33 位是溢出位被丟棄,只得到32 個0(這個道理跟錶盤上0 點和12 點的時針指在同一個位置是一樣的,錶盤以12 為模),因此,一個數逐漸增大,一旦超出4294967296-1 的數M 就可以表示為MB94967296
而負數-M (M為絕對值)可以表示為一個正數: 4294967296 - M(這個正數就是負數的補碼對應的二進制正整數,負數的補碼按32位二進制數,與他的原碼相加剛好等於模),道理跟錶盤一樣,11點和負1點指在同一個位置。
以-3 為例:
程式碼如下:
(Array( 32).join("0") (3).toString(2)).slice(-32); // |-3| 的二進位數,即原碼
原碼= 0000000000000000000000000000011
碼= 11111111111111111111111111111100; //原碼符號位為1,其餘位取反
補碼= 11111111111111111111111111111101111111111111111111111111011111111011111111011111111010111111101111111101011111110111111110010碼,因此取碼的單碼。兩個數的低31位元全都是1,加上反碼符號位1,得到32 個1
那麼,有
補碼原碼= (反碼1) 原碼
= (反碼原碼) 1
= 1 (32位元全是1 的二進制數) //因為反碼由正數形式的原碼的低31位取反加上符號位1得到,因此這兩個數的和的低31位元全都是1,加上反碼符號位1,得到32 個1
= Math.pow(2,32)
= 4294967296 //這正是32位元二進位數的模, 跟|-1| 11 = 12 原理一樣
這就是: 正數減法-> 負數加法-> 補碼加法的過程。
2、位元運算
複製程式碼
複製程式碼 程式碼如下: (-123).toString(2) ;// "-1111011"
按位取反(~): 一元運算, 1 變0,0變1 ,如
~123 ; //-124
可以驗證一下這個過程:正數取反,符號位為負,所以結果是負數,根據Math.pow(2,32) - M 可以表示成-M,可以用下面方法計算
parseInt((Array(32).join(0) (123).toString(2)).slice(-32).replace(/d /g,function(v) {
return (v*1 1)%2;
}),2)-Math.pow(2,32) // -124 ,如果是負數減Math.pow (2,32)
要注意的是, javascript 位運算都是有符號的,因此達到32 位,其最高位將作為符號位,取反時應得到正數(取模Math.pow(2,32) 的補數--兩個數相加得到模,稱這兩個數互為補數)。
對整數M 位元取反可以這樣算:
((-1*M-1) Math.pow(2,32))%Math.pow(2,32)
位元與(&):兩個數的相同位,都是1 回傳1 ,否則回傳0
複製程式碼
= 106
"00000000000000000000000001111011"
"000000000000000000000000011101010" "00000000000000000000000001101010"
複製碼代碼如下:
位元異或(^):兩個數的相同位,一個是1 另一個是0 則回傳1,否則回傳0
複製代碼
代碼如下:
異或運算的一些特性:
複製程式碼
程式碼如下:
a ^ a = 0
a ^ b = b = b = b a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
a ^ b ^ a = b
d = a ^ b ^ c 可以推出a = d ^ b ^ c //這個特性被用在一些加密演算法
//如果運算數在適當範圍內(不溢出), 如a=1,b=2,交換兩個變數的值
a = [b ,b=a][0]
//或
a = a^b
b = a^b
a= a^b 利用位的異或運算使用一個數字記錄多個資訊: 有幾個狀態值分別是1、2、8、16 ..... 這些值的規律是,他們的二進制只有一位是1 ,其餘都是0, 因此, 他們中的任幾個的按位異或運算的結果都不會出現兩個數的某一位都是1 的情況,並且運算的值都是唯一確定的,也就是,知道運算的結果,就知道是哪幾個數的組合,這樣可以用一個數字記錄多個資訊。
複製程式碼
程式碼如下:
00000001 00000001 00001 00001000 00010000
1^2^4 = 7 // "00000111"
因此,如果我們知道結果是 7 ,就知道他們是由 1 、2、4 組合而成。
如果我們要設定一個參數,使其包含幾個狀態值,就可以用位元或運算,
這樣的例子可以參考PHP 中關於圖片類型的幾個常數,和PHP 錯誤等級定義的幾個常量。
這樣的例子,也許有用十進制數來描述的,例如: 個位數的數字表示某個屬性的狀態,十位數的數字表示另一個屬性的狀態,這樣的話,每個狀態可以有10個值,只用一個數字就可以描述的組合非常多。
左移位(如果不溢出, 左移位的效果是乘以 2。
右移位(>>): 一個數的二進位所有位元向右移動,符號位不動,高位補0,低位丟棄
右移位操作的效果是除以2 並向下取整。
帶符號右移(>>>):移位時符號位跟隨移動,符號位也作為數值看待,所以,該操作的結果是32 位元無符號整數,因此負數的帶符號右移將產生正整數,正數的帶符號右移與無符號右移相同,這是唯一可以操作符號位的運算。
-123>>>1 ;//2147483586
一些要注意的地方:
位元運算必須是整數,如果運算元不是可用的整數,將取0 作為運算元
~NaN; // 會執行~為-1
~'x'; // -1
'hello'|0; // 0
({})|0 ; //0
位移運算無法移動超過31位,若試圖移動超過31位,將位數對32取模後再移位
123>>32 //實際是123>>0 (322 = 0)
123> >33 //實際是123>>1
32位帶符號整數表達範圍是-Math.pow(2,31) ~ Math.pow(2,31)-1 即-2147483648~2147483647,而js 數字的精度是雙精度,64位,如果一個超過2147483647 的整數參與位運算的時候就需要注意,其二進制溢出了,截取32位後,如果第32位是1將被解讀為負數(補碼)。
>>0; //-214748364
>0; //0
>>0; //-1
>>0; //1