ECMAScript位演算子

ビット演算子は数字の下層(つまり数字を表す32桁)で操作を行います。

整数を再確認

ECMAScriptの整数には2種類のタイプがあり、それは符号付き整数(正数と負数を使用できます)と無符号整数(正数のみを使用できます)です。ECMAScriptでは、すべての整数文字列はデフォルトで符号付き整数として扱われ、これは何を意味するのでしょうか?

符号付き整数は31桁で整数の値を表し、第32桁で整数の符号を表します。0は正数を表し、1は負数を表します。値の範囲は-2147483648から2147483647です。

符号付き整数は2種類の異なる方法で二進数形式で保存できます。1つは正数を保存するために使用され、もう1つは負数を保存するために使用されます。正数は真二進数形式で保存され、前31桁の各桁は2の冪を表し、第1桁(ビット0)から始まり、2を表します0第2桁(ビット1)は2を表します1使用されていない桁は0で埋められ、無視されます。例えば、以下の図は数18の表示法を示しています。

32ビット符号整数の表現

18の二進数バージョンは前5桁のみを使用しており、これらがこの数字の有効桁です。数字を二進数文字列に変換すると、有効桁が見えます:

var iNum = 18;
alert(iNum.toString(2));	//「10010」を出力

このコードは「10010」を出力するだけで、18の32ビット表現ではなく、他の桁は重要ではありません。なぜなら、この10進数値を決定するのに前5桁だけで十分だからです。以下の図のように示されています:

5ビットの整数18

負数も二進数コードとして格納されますが、二進数補码の形式を取ります。数字の二進数補码を計算する手順は3ステップあります。

  1. その数字の非負バージョンの二進数表現を決定します(例えば、-18の二進数補码を計算するには、まず18の二進数表現を決定する必要があります)
  2. 二進数反码を求めます。これは0を1に置き換え、1を0に置き換えることです
  3. 二進数反码に1を加えます

-18の二進数表現を決定するには、まず18の二進数表現を得なければなりません。以下の通りです:

0000 0000 0000 0000 0000 0000 0001 0010

次に、二進数反码を計算します。以下の通りです:

1111 1111 1111 1111 1111 1111 1110 1101

最後に、二進数反码に1を加えます。以下の通りです:

1111 1111 1111 1111 1111 1111 1110 1101
                                      1
---------------------------------------
1111 1111 1111 1111 1111 1111 1110 1110

したがって、-18の二進数表現は1111 1111 1111 1111 1111 1111 1110 1110です。記憶に留めておいてください、符号付き整数を処理する際には、開発者は31ビットにアクセスできません。

面白いことに、負の整数を二進数文字列に変換すると、ECMAScriptは二進数補码の形式で表示せず、負号を付ける標準の二進数コードの絶対値の前に出力します。例えば:

var iNum = -18;
alert(iNum.toString(2));	//「-10010」を出力

このコードは「-10010」として出力されますが、二進数補码ではなく、ビット31にアクセスを避けるために、ECMAScriptは単純な方法で整数を処理し、開発者がその使用法を心配しなくても済みます。

一方、無符号整数は最後のビットを別の数位として処理します。このモードでは、32ビット目は符号を表すのではなく、値2を表します。31この追加のビットにより、無符号整数の値の範囲は0から4294967295です。2147483647未満の整数の場合、無符号整数は符号付き整数と同じように見えますが、2147483647以上の整数の場合は、符号付き整数の31ビット(このビットは常に0)を使用します。

無符号整数を文字列に変換すると、その有効ビットのみが返されます。

注意:すべての整数リテラルはデフォルトで符号付き整数として格納されます。ECMAScriptのビット演算子のみが無符号整数を作成できます。

ビット演算 NOT

ビット演算 NOT は否定記号(~)で表され、ECMAScript で二進数算術に関連する少数の演算子の1つです。

位演算NOTは3つの処理プロセスから成り立っています:

  1. 演算数を32ビットの数字に変換
  2. 二進数をその二進反転符号に変換
  3. 二進数を浮動小数点数に変換

例えば:

var iNum1 = 25;		// 25は00000000000000000000000000011001に等しい
var iNum2 = ~iNum1;	// 11111111111111111111111111100110に変換
alert(iNum2);		//「-26」と表示

位演算NOTは数字を負にし、その後1を引くことで実現されます。したがって、25は-26になります。以下の方法でも同じ結果が得られます:

var iNum1 = 25;
var iNum2 = -iNum1 -1;
alert(iNum2);	//「-26」と表示

位演算AND

位演算ANDは和号(&)で表され、直接数字のバイナリーフォームに対して演算を行います。各数字の桁を対齐し、同一位置の桁に対して以下のルールでAND演算を行います:

一番目の数字のビット 二番目の数字のビット 結果
1 1 1
1 0 0
0 1 0
0 0 0

例えば、数字25と3のAND演算を行う場合、以下のようになります:

var iResult = 25 & 3;
alert(iResult);	//「1」と表示

25と3のAND演算の結果は1です。なぜでしょうか?以下に分析します:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

25と3の中で、1が含まれている数位(桁0)は1つだけです。したがって、他の数位は0が生成され、結果は1となります。

位演算OR

位演算ORは記号(|)で表され、直接数字のバイナリーフォームに対して演算を行います。各桁の計算では、OR演算子は以下のルールを使用します:

一番目の数字のビット 二番目の数字のビット 結果
1 1 1
1 0 1
0 1 1
0 0 0

AND演算子を使用した例を引き続き使用して、25と3のOR演算を行います。以下はそのコードです:

var iResult = 25 | 3;
alert(iResult);	//「27」と表示

25と3のOR演算の結果は27です:

25 = 0000 0000 0000 0000 0000 0000 0001 1001
 3 = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

2つの数字の中で、1が含まれている数位は4つあり、これらの数位が結果に伝達されます。バイナリーコード11011は27に等しいです。

ビット演算 XOR

ビット演算 XORはシグネチャー(^)で表されます。もちろん、二進数形式で直接演算されます。XORはORとは異なり、1つのビットが1であれば1を返します。真値表は以下の通りです:

一番目の数字のビット 二番目の数字のビット 結果
1 1 0
1 0 1
0 1 1
0 0 0

25と3に対してXOR演算を行うコードは以下の通りです:

var iResult = 25 ^ 3;
alert(iResult);	// "26"を出力

25と3がXOR演算を行う結果は26です:

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

2つの数字の中で、4つのビットが1で埋まっています。これらのビットが結果に伝搬されます。二進数コード11010は26に相当します。

リグトシフト演算

リグトシフト演算は二つの小さい矢印で表されます(<<)。指定された数のビットがすべて左に移動します。たとえば、数字2(二進数での10)を5ビットリグトシフトすると、64(二進数での1000000)になります:

var iOld = 2;		// 10(二進数)に相当します
var iNew = iOld << 5;	// 1000000(二進数)の十進数64に相当します

注意:リグトシフトする際に、数字の右側に5つの空位ができます。リグトシフト演算ではこれらの空位を0で埋めますが、結果は完全な32ビットの数字になります。

数字2の左シフト演算

注意:リグトシフト演算は符号ビットを保持します。たとえば、-2を5ビットリグトシフトすると、-64になりますが、64ではありません。「符号は第32ビットにまだ保存されていますか?」と尋ねられるかもしれません。もちろん、ECMAScriptのバックエンドで行われますが、開発者は第32ビットに直接アクセスすることはできません。二進数文字列形式で負数を出力すると、負号形式で表示されます(たとえば、-2は-10として表示されます。)

シグネチャーレフトシフト演算

シグネチャーレフトシフト演算子は二つの大きい矢印で表されます(>>)。それにより32ビットの数字のすべてのビットが一斉に右にシフトされ、その数の符号(正号または負号)も同時に保持されます。シグネチャーレフトシフト演算子はリグトシフト演算の逆です。たとえば、64を5ビットシフトすると、2に変わります:

var iOld = 64; // 二進数 1000000
var iNew = iOld >> 5; // 二進数 10 十進数 2

同様に、ビットを移動すると空位が生じます。この場合、空位は数字の左側にあり、符号位の後ろに位置します。ECMAScriptは符号位の値でこれらの空位を埋め、完全な数字を作成します。以下の図のように:

数字64の符号右シフト演算

無符号右シフト演算

無符号右シフト演算子は3つの>>>で表され、無符号32ビット数のすべてのビットを一括で右シフトします。正数の場合、無符号右シフト演算の結果は符号付き右シフト演算の結果と同じです。

符号付き右シフト演算の例として、64を5ビット右シフトすると、2になります:

var iOld = 64; // 二進数 1000000
var iNew = iOld >>> 5; // 二進数 10 十進数 2

負数の場合、状況は異なります。

無符号右シフト演算はすべての空位に0を埋めます。正数の場合、これは符号付き右シフト演算の操作と同じですが、負数の場合は符号付きとして処理されます。

無符号右シフト演算の結果は32ビットの正数になるため、負数の無符号右シフト演算の結果は常に非常に大きな数字になります。例えば、-64を5ビット右シフトすると、134217726になります。この結果はどうやって得られるのでしょうか?

これを実現するために、この数字を無符号の等価形式に変換する必要があります(この数字自体は符号付きです)、以下のコードを使用してこの形式を取得できます:

var iUnsigned64 = -64 >>> 0;

それから、NumberクラスのtoString()を使って、実際のビット表現を取得し、基数として2を使用します:

alert(iUnsigned64.toString(2));

これにより、11111111111111111111111111000000が生成され、符号付き整数-64の二進数補码表示となりますが、無符号整数4294967232と等しいです。

このため、無符号右シフト演算子の使用には注意が必要です。