1
があり、基数2のこの数値は次のとおりであるとします。
00000000000000000000000000000001
次に、すべてのビットを反転して、次の結果を取得します。
11111111111111111111111111111110
私の知る限り、解決策は~
(ビット単位のNOT演算子)を使用してすべてのビットを反転することですが、~1
の結果は-2
です。
console.log(~1); //-2
console.log((~1).toString(2)); //-10 (binary representation)
なぜこの奇妙な結果が得られるのですか?
1
と-2
の間には2つの整数があります:0
と-1
バイナリの1
は00000000000000000000000000000001
です0
バイナリのは00000000000000000000000000000000
です-1
のバイナリは11111111111111111111111111111111
です。-2
のバイナリは11111111111111111111111111111110
です。
( "binary"は2の補数であり、ビット単位では~
ではありません)
ご覧のとおり、~1
は-2
に等しいので、~0
が-1
に等しいことはそれほど驚くことではありません。
@ Derek説明 のように、これらの ビット演算子 は、オペランドを32ビットのシーケンスとして扱います。一方、parseInt
はそうではありません。そのため、いくつかの異なる結果が得られます。
より完全なデモは次のとおりです。
for (var i = 5; i >= -5; i--) {
console.log('Decimal: ' + pad(i, 3, ' ') + ' | Binary: ' + bin(i));
if (i === 0)
console.log('Decimal: -0 | Binary: ' + bin(-0)); // There is no `-0`
}
function pad(num, length, char) {
var out = num.toString();
while (out.length < length)
out = char + out;
return out
}
function bin(bin) {
return pad((bin >>> 0).toString(2), 32, '0');
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
_100 -4
101 -3
110 -2
111 -1
000 0
001 1
010 2
011 3
_
2の補数表記がどのように機能するかを思い出す簡単な方法は、最後のビットが否定された同じ値に対応することを除いて、それが単なる通常のバイナリであると想像することです。私の考案した3ビット2の補数では、最初のビットは_1
_、2番目は_2
_、3番目は_-4
_です(マイナスに注意してください)。
ご覧のとおり、2の補数ではないビット単位は-(n + 1)
です。驚いたことに、それを数値に2回適用すると、同じ数値が得られます。
_-(-(n + 1) + 1) = (n + 1) - 1 = n
_
ビット単位で話すと明らかですが、算術効果はそれほど多くありません。
それがどのように機能するかを思い出すのを少し簡単にするいくつかの観察:
負の値がどのように上昇するかに注目してください。まったく同じルールですが、0と1だけが入れ替わっています。必要に応じて、ビット単位で表記します。
_100 -4 011 - I bitwise NOTted this half
101 -3 010
110 -2 001
111 -1 000
----------- - Note the symmetry of the last column
000 0 000
001 1 001
010 2 010
011 3 011 - This one's left as-is
_
バイナリのリストをそこにある数値の合計量の半分で循環させることにより、ゼロから始まる2進数の昇順の典型的なシーケンスを取得します。
_- 100 -4 \
- 101 -3 |
- 110 -2 |-\ - these are in effect in signed types
- 111 -1 / |
*************|
000 0 |
001 1 |
010 2 |
011 3 |
*************|
+ 100 4 \ |
+ 101 5 |-/ - these are in effect in unsigned types
+ 110 6 |
+ 111 7 /
_
コンピュータサイエンスでは、解釈がすべてです。コンピュータにとって、すべては多くの方法で解釈できるビットのシーケンスです。たとえば、_0100001
_は数値33または_!
_のいずれかになります(これが [〜#〜] ascii [〜#〜] このビットシーケンスをマップする方法です)。
数字、数字、文字、テキスト、Word文書、画面上のピクセル、表示された画像、またはハードドライブ上のJPGファイルとして表示されているかどうかに関係なく、すべてがコンピューターのビットシーケンスです。そのビットシーケンスを解釈する方法を知っていれば、それは人間にとって意味のあるものに変わるかもしれませんが、RAMとCPUにはビットしかありません。
したがって、コンピュータに数値を保存する場合は、エンコードする必要があります。負でない数の場合、それは非常に簡単です。2進表現を使用する必要があります。しかし、負の数はどうですか?
2の補数と呼ばれるエンコーディングを使用できます。このエンコーディングでは、各数値のビット数を決定する必要があります(たとえば、8ビット)。 最上位ビット は符号ビットとして予約されています。 _0
_の場合、数値は非負として解釈される必要があります。それ以外の場合は負です。他の7ビットには実際の数が含まれています。
_00000000
_は、符号なしの数値と同様に、ゼロを意味します。 _00000001
_は1つ、_00000010
_は2つというように続きます。 2の補数の8ビットに格納できる最大の正の数は127(_01111111
_)です。
次の2進数(_10000000
_)は-128です。奇妙に思えるかもしれませんが、すぐにそれが理にかなっている理由を説明します。 _10000001
_は-127、_10000010
_は-126などです。 _11111111
_は-1です。
なぜこのような奇妙なエンコーディングを使用するのですか?その興味深い特性のため。具体的には、加算と減算を実行している間、CPUはそれが2の補数として格納されている符号付き数値であることを知る必要はありません。両方の数値を符号なしとして解釈し、それらを合計すると、結果は正しくなります。
これを試してみましょう:-5 + 5. -5は_11111011
_、_5
_は_00000101
_です。
_ 11111011
+ 00000101
----------
000000000
_
結果は9ビット長です。最上位ビットがオーバーフローし、0である_00000000
_が残ります。動作しているようです。
別の例:23 + -7。 23は_00010111
_、-7は_11111001
_です。
_ 00010111
+ 11111001
----------
100010000
_
繰り返しますが、MSBは失われ、_00010000
_ == 16になります。動作します!
これが2の補数のしくみです。コンピュータはそれを内部的に使用して符号付き整数を格納します。
数値のビットを否定すると、2の補数でN
が_-N-1
_に変わることに気付いたかもしれません。例:
~00000000
_ == _11111111
_ == -1~00000001
_ == _11111110
_ == -2~01111111
_ == _10000000
_ == -128~10000000
_ == _01111111
_ == 127これはまさにあなたが観察したことです。JSは2の補数を使用しているふりをしています。では、なぜparseInt('11111111111111111111111111111110', 2)
が4294967294なのですか?まあ、それはふりをしているだけだからです。
内部的には、JSは常に浮動小数点数表現を使用します。 2の補数とはまったく異なる方法で機能し、ビット単位の否定はほとんど役に立たないため、JSは数値が2の補数であると偽って、ビットを否定し、浮動小数点表現に変換し直します。これはparseInt
では発生しないため、バイナリ値は一見同じように見えますが、4294967294になります。
2の補数の32ビット符号付き整数(Javascriptは、32ビット符号付き整数に使用される形式であると主張しています)は、-2を11111111111111111111111111111110
として格納します。
だからすべて期待通り。
2の補数演算です。これは「テープカウンター」演算に相当します。テープレコーダーにはカウンターが取り付けられている傾向がありました(マシンの追加はさらに良い例えですが、2の補数がヒップになったときにすでに廃止されていました)。
000から2ステップ後方に巻くと、998に到達します。したがって、998は、テープカウンターの-2の10の補数の算術表現です。つまり、前方に2ステップ巻いて、再び000に到達します。
2の補数はそのようなものです。 1111111111111111から1を前方に巻くと、0000000000000000に到達するため、1111111111111111は-1を表します。代わりに、そこからさらに1を巻き戻すと、-11111111111111110が得られます。これは、-2の表現です。
これは予想される動作です。 mdn:bitwise-not によると。
おそらく理解できない部分は、符号付き整数として表現された場合、[11111111111111111111111111111110]₂ = [10]₂
¹です。先頭の1
sはいくつでも構いませんが、符号なし整数/小数の先頭の0
sと同様に、同じ数です。
¹[10]₂
は、10
を基数2(バイナリ)として解釈する必要があることを指定します
JavaScriptの数値は浮動小数点数です 、IEEE754標準によって格納および表されます。
ただし、ビット演算の場合、オペランドは内部的に 2の補数形式で表される符号付き32ビット整数 :として扱われます。
すべてのビット演算子のオペランドは、2の補数形式の符号付き32ビット整数に変換されます。 2の補数形式とは、数値の負の対応物(5対-5など)がすべての数値のビットを反転したもの(ビット単位では数値ではなく、数値の1の補数)に1を加えたものであることを意味します。
負の数の正の対応物も同じ方法で計算されます。したがって、次のようになります。
_ 1 = 00000000000000000000000000000001b
~1 = 11111111111111111111111111111110b
11111111111111111111111111111110b = -2
_
Number.toString()
は、基数2の2の補数表現を返すことは想定されていないことに注意してください。
式_(-2).toString(2)
_は、マイナス記号(_-10
_)の後に_-
_(_2
_)の基数2表現が続く_10
_を生成します。