はじめに

ビット演算を利用するケースでは unsigned を期待することが多いと思うのですが JavaScript ではその期待は捨てたほうが良いです。(ただし >>> 演算子を除く) ここではその具体例と対策、簡単な説明をしていきたいと思います。

具体例

ではさっそく、例として

0x12345678 ^ 0xFFFFFFFF

を見てみましょう。 (なぜ 32-bit かというと JavaScript のビット演算は 32-bit で行われるからです。)

0x12345678 は 2 進数表記にすると 0001 0010 0011 0100 0101 0110 0111 1000 になります。 これを XOR 0xFFFFFFFF で反転させるのですから、 下記のように計算して期待する値は 0xEDCBA987(=3,989,547,399) となるはずです。

   0001 0010 0011 0100 0101 0110 0111 1000
 ^ 1111 1111 1111 1111 1111 1111 1111 1111
 -----------------------------------------
   1110 1101 1100 1011 1010 1001 1000 0111

しかし、実際に実行してみると

-305419897
という値になります。そうです。signed なんです。 それだけならまだ分かりにくいだけで良いかも知れません。

しかし、以下の例コードを実行してみてください。

 (0x12345678 ^ 0xFFFFFFFF) === 0xEDCBA987 // false

これが通らないのはおかしいと思いませんか? (とは言っても、上記を展開すると -305419897 === 3989547399 となるので false となるのが当然なんですが。)

対策

上記の例を、あまり変更せずに通るようにするにはどうしたらよいかというと

((0x12345678 ^ 0xFFFFFFFF) >>> 0) === 0xEDCBA987 // true

とするだけです。 >>> 演算子は結果を unsigned で返すため、簡単な型変換に利用することができます。

説明

ここまで読んでも (0x12345678 ^ 0xFFFFFFFF) === 0xEDCBA987 は両方とも 16 進表記にすると 0xEDCBA987 なのに何故一致しないのか疑問に思う人もいるかもしれません。

ECMA-262 5.1 によると、ビット演算子は以下のような型変換を行うとされています。

演算子 参照した章 結果の型
~ 11.4.8 Bitwise NOT Operator ( ~ ) signed 32-bit integer
<< 11.7.1 The Left Shift Operator ( << ) signed 32-bit integer
>> 11.7.2 The Signed Right Shift Operator ( >> ) signed 32-bit integer
>>> 11.7.3 The Unsigned Right Shift Operator ( >>> ) unsigned 32-bit integer
&, ^, | 11.10 Binary Bitwise Operators signed 32 bit integer

また、JavaScript の数値は IEEE 754 であるとされています。( "4.3.19 Number value" より)

つまり、0x12345678 ^ 0xFFFFFFFF-305419897 という値は 32-bit integer ではなく、 ビット演算子によって signed 32-bit integer に変換されたのち IEEE 754 形式にされるため、 0xEDCBA987 = 3989547399 の IEEE 754 形式と一致しないというわけです。