web-dev-qa-db-ja.com

ビット単位の「not1」が-2に等しいのはなぜですか?

1があり、基数2のこの数値は次のとおりであるとします。

00000000000000000000000000000001

次に、すべてのビットを反転して、次の結果を取得します。

11111111111111111111111111111110

私の知る限り、解決策は~(ビット単位のNOT演算子)を使用してすべてのビットを反転することですが、~1の結果は-2です。

console.log(~1); //-2
console.log((~1).toString(2)); //-10 (binary representation)

なぜこの奇妙な結果が得られるのですか?

28

1-2の間には2つの整数があります:0-1

バイナリの100000000000000000000000000000001です
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; }
59
Cerbrus
_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  /
_
20
D-side

コンピュータサイエンスでは、解釈がすべてです。コンピュータにとって、すべては多くの方法で解釈できるビットのシーケンスです。たとえば、_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_に変わることに気付いたかもしれません。例:

  • 0否定== _~00000000_ == _11111111_ == -1
  • 1否定== _~00000001_ == _11111110_ == -2
  • 127否定== _~01111111_ == _10000000_ == -128
  • 128否定== _~10000000_ == _01111111_ == 127

これはまさにあなたが観察したことです。JSは2の補数を使用しているふりをしています。では、なぜparseInt('11111111111111111111111111111110', 2)が4294967294なのですか?まあ、それはふりをしているだけだからです。

内部的には、JSは常に浮動小数点数表現を使用します。 2の補数とはまったく異なる方法で機能し、ビット単位の否定はほとんど役に立たないため、JSは数値が2の補数であると偽って、ビットを否定し、浮動小数点表現に変換し直します。これはparseIntでは発生しないため、バイナリ値は一見同じように見えますが、4294967294になります。

15
gronostaj

2の補数の32ビット符号付き整数(Javascriptは、32ビット符号付き整数に使用される形式であると主張しています)は、-2を11111111111111111111111111111110として格納します。

だからすべて期待通り。

6
Bathsheba

2の補数演算です。これは「テープカウンター」演算に相当します。テープレコーダーにはカウンターが取り付けられている傾向がありました(マシンの追加はさらに良い例えですが、2の補数がヒップになったときにすでに廃止されていました)。

000から2ステップ後方に巻くと、998に到達します。したがって、998は、テープカウンターの-2の10の補数の算術表現です。つまり、前方に2ステップ巻いて、再び000に到達します。

2の補数はそのようなものです。 1111111111111111から1を前方に巻くと、0000000000000000に到達するため、1111111111111111は-1を表します。代わりに、そこからさらに1を巻き戻すと、-11111111111111110が得られます。これは、-2の表現です。

4
user5112071

これは予想される動作です。 mdn:bitwise-not によると。

おそらく理解できない部分は、符号付き整数として表現された場合、[11111111111111111111111111111110]₂ = [10]₂¹です。先頭の1sはいくつでも構いませんが、符号なし整数/小数の先頭の0sと同様に、同じ数です。

¹[10]₂は、10を基数2(バイナリ)として解釈する必要があることを指定します

4
Dodekeract

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_を生成します。

3
Salman A