質問: ``` p (~0b1010).to_s(2) ``` は ``` "-1011" ``` になりました。 【ビット反転】 p ~0b1100 #=> -13になる理由がわかりませ... - Yahoo!知恵袋 には > > ここで注意して頂きたいのが、負の数の管理方法です。一言で言うと、「その数にいくつ足すと0になるか」です。例えば~0b1100。2進数の部分に1101(2進数で13)を足すと > > (1) 000000000000000000000000000000 > > > とあります。 なにか計算によって`0`が並ぶような数値がビット反転の答えになると推測しました。 しかし`1010 + 1011` は `10101`になるので、理解できません。どうして `(~0b1010).to_s(2)`は`"-1011"`になるのでしょうか? 追記: コメントで頂いたとおり ``` p sprintf('%b', [0b1010].pack('L').unpack('L')[0]) p sprintf('%b', [~0b1010].pack('L').unpack('L')[0]) ``` がそれぞれ ``` "1010" "11111111111111111111111111110101" ``` となって、名前のとおり反転していることがわかりました。 ここで、 ``` p (~0b1010).to_s(10) p (~0b1010).to_s(2) ``` としてみたところ ``` "-11" "-1011" ``` となったので、 10進数で-11 2進数で-1011 になることがわかりました。 `2進数で-1011`は`10進数で-11`ですので、質問はどうして`~0b1010`は`10進数で-11`になるのか? とも言える気がしてきました。
回答: ご質問に記載された挙動をきちんと理解しようと思うと、整数を2進数で表す場合の2の補数表現について正しく理解しておく必要があります。 ### 【2の補数表現について】 例えば4桁の2進数では、10進表現で0〜15の16通り(=2^4 `^`はビット演算ではなくべき乗を表すものとする)の値を表現できます。 ``` 2進 10進 0000 0 0001 1 0010 2 0011 3 0100 4 0101 5 0110 6 0111 7 1000 8 1001 9 1010 10 1011 11 1100 12 1101 13 1110 14 1111 15 ``` コンピュータ上で負の整数を表すやり方にはいろいろあるのですが、**最上位ビットが1の場合が負数を表す**ことにして次のように値を割り振るのを「2の補数表現」と言います。 ``` 2進 10進 1000 -8 1001 -7 1010 -6 1011 -5 1100 -4 1101 -3 1110 -2 1111 -1 0000 0 0001 1 0010 2 0011 3 0100 4 0101 5 0110 6 0111 7 ``` 初めてみると、何か不自然な並びに見えるかもしれませんが、(2進)`1111`に`1`を足すと(2進)`10000`になります。計算処理が固定長でしかできないことにして、桁あふれを無視すると結果は(2進)`0000`つまり`0`になります。 つまり **`1`を足すと`0`になる数を`-1`として使う** という表現方法を取っていることになります。 ### 【Rubyの場合】 Rubyの場合は、無限多倍長整数が使えるので話がちょっとややこしくなるのですが、整数値の`0b1010`と言うのは正の数のはずですから最上位ビットは内部的には`0`でないといけません。つまり4ビットの2の補数表現で表せる範囲を超えているのです。Rubyの整数処理では必要なビット数をどんどん補ってくれるので、概念的に言うと`0b1010`は ``` (2進)...0000000000001010 ``` と言うような感じで、上位ビットに延々と`0`が連なる整数を表していると思わないといけません。**固定長の`1010`ではない** のです。 従って、それを反転した`~0b1010`は、こんな数を表していると言うことになります。 ``` (2進)...1111111111110101 ``` これは最上位ビット(?)が`1`ですから、負の数、値としては(2進)`...0000000000001010`(=`10`)を足すと`...1111111111111111`(=`-1`)になりますから、2の補数的には(10進)`-11`を表している、と考えられるのです。 (10進)`11`を2進法で表すと`1011`で、値が負なので、to_s(2)が作り出す「符号+絶対値表現」では`-1011`なんて表記になります。 `sprintf('%b', [~0b1010].pack('L').unpack('L')[0])`なんてことをやると、結果の桁数が32桁になったり4桁になったりしてわかりにくいですが、Rubyの整数の2進表現では **正の数の場合無限個の`0`が、負の数の場合無限個の`1`が前に連なっている** と思って下さい。(「概念的には」と言うことで、内部的には「符号を表す1ビットまで含めて32ビットに収まるときには32ビットで表す」なんてことをしています。) と言うわけでコメントに書かれていることをまとめ直しただけで、「どうして~0b1010は10進数で-11になるのか?」を説明したつもりなのですが、お分かりいただけましたでしょうか。何か分かりにくい部分があればコメントして下さい。