コンピュータが認識するデータは0と1の羅列であるため、時には情報の最小単位としてビットで考える必要が出てきます。
通常1バイトというと8ビットに相当します。
8ビットのことをオクテットともいいます。


この8ビットというのは「0か1」のビットが8つ連なっていますので「ON」または「OFF」といった2種類のフラグで考えると、8つの情報を持つことができることになります。



■論理和と論理積

例えば1バイト内にゴミ捨て曜日を収めることにした場合、「00000000」と8つあるうちの先頭(左端)から「日曜」「月曜」...「土曜」「最後は予備」と割り当てるように決めて、「水曜」と「土曜」のビットを立てることを考えると


$gomi= bindec("00010000") | bindec("00000010"); // 「00010010」
と「|」を使って「論理和」で書くことができます。


ちなみにこの場合、立ってるビットがカブってないので
$gomi= bindec("00010000") + bindec("00000010"); // 「00010010」
と算術加算しても同じ値になります。


「| 論理和」では左側と右側のどちらかが1ならそのビット位置は1になります。
「& 論理積」を使うと、左側と右側の両方が1ならそのビット位置が1になるので上記の場合だと「00000000」になります。
$gomi= bindec("00010000") & bindec("00000010") // 「00000000」


「月曜」「水曜」「金曜」に変更になった場合は
$gomi= bindec("01000000") | bindec("00010000") | bindec("00000100"); // 「01010100」
このように、たった1バイトの中に一週間のゴミ出し曜日を収めることができました。
∩( ・ω・)∩ばんじゃーい



■排他的論理和
次に「排他的論理和」を使ってみます。
これは「論理和」というように「|」と似ていますが左右両側とも1だと打ち消し合って0になるという意味で「排他的」なわけです。
bindec("11110000") ^ bindec("10000000") は「01110000」になります。
10進数で書くと、240 ^ 128 が112ということになります。



■否定
次は「否定」を使ってみます。
否定の場合は左側と右側を演算するものではなくて、「~」直後の数値をビット反転させた値に変えます。
~bindec("10000000") なら「01111111」に
~bindec("00001111") なら「11110000」になります。


のつもりでしたが、実際に結果を出力させてみると


echo ~bindec("10000000") は「-129」、
echo ~bindec("00001111") は「-16」となってしまいます。


この値を2進数に直すと、


echo decbin(~bindec("10000000")) は「11111111111111111111111101111111」
echo decbin(~bindec("00001111")) は「11111111111111111111111111110000」


のように32ビットつまり4バイトとして表示されていたようです。


var_dump(bindec("10000000"));としてみると「int(128) 」の表示になります。


つまりPHPのint型は4バイトなのでビット反転では32ビットの上位24ビットがすべて反転されて思わぬ値になっていたようですね。


ということでこの場合は


~bindec("10000000") を ~bindec("10000000") & bindec("11111111") と書いて論理積でマスク(必要なビットだけ抜き出す)する必要があります。

確認してみます。


echo decbin(~bindec("10000000") & bindec("11111111")) は「1111111」という表示になりました。
∩( ・ω・)∩ばんじゃーい



■ビットシフト
さてお次は「ビットシフト」を使ってみます。


まずは右シフト「>>」

echo bindec("10000000") >> 1; これは128が64になります。
echo decbin(bindec("10000000") >> 1); 2進数で確認すると「1000000」のように1が右に1つずれていることが確認できます。


左シフト「<<」

echo bindec("10000000") << 1; これは128が256になります。
echo decbin(bindec("10000000") << 1); 2進数で確認すると「100000000」のように1が左に1つずれて9ビットになっていることが確認できます。



これまでに書いてきたビット演算子にはそれぞれビット演算と代入を同時にできる演算子が用意されています。


$a = $a & $b なら $a &= $b
$a = $a | $b なら $a |= $b
$a = $a ^ $b なら $a ^= $b
$a = $a << $b なら $a <<= $b
$a = $a >> $b なら $a >>= $b



もう何年も前からネットで「飲み物」と「米」を中心に注文して届けてもらっています。

普通に店で買って持ち帰ると重いのでネットで注文して届けてもらうのは非常に便利です。



しかも一定額以上の注文だと送料もかからないのでお得です。

飲み物も箱買いすると恐らく割安なのでこれまたお得です。



地域によってはまだこういうサービスが行われていないところもあるかも知れません。

私はものぐさなのでこのサービスが出来て以来、非常に重宝しております。



いい時代になったものですなぁ。。。



コンピュータで取り扱う情報は0と1の羅列、つまり2進数です。
ところがプログラミングでは2進数をそのまま定数として書くことはできません。
定数として書くことのできるのは8進数(0で始まる)、10進数、16進数(0xで始まる)の3種類です。


echo 65,"<br />\n"; // これは10進数
echo 0101,"<br />\n"; // 65を8進数で
echo 0x41,"<br />\n"; // 65を16進数で


上記の出力はすべて「65」になります。そうです、何進数で書いてもそのままで表示する分には人間の読みやすい10進数になります。


PHPには人間の扱いやすい10進数を他の進数に変換するための関数が用意されています。
2進数(binary)8進数(octal)10進数(decimal)16進数(hexadecimal)から直感的にわかり易い関数名になっています。


10進数を他に変換するdecbin() decoct() dechex()
他から10進数変換するbindec() octdec() hexdec()


それではまず10進数を他の進数に変換していきます。



■10進数を他進数へ変換

$d= 65;
echo decbin($d),"<br />\n"; // 2進数へ
echo decoct($d),"<br />\n"; // 8進数へ
echo dechex($d),"<br />\n"; // 16進数へ
echo "<br />\n";

echo sprintf("%b", $d),"<br />\n"; // 2進数へ
echo sprintf("%o", $d),"<br />\n"; // 8進数へ
echo sprintf("%x", $d),"<br />\n"; // 16進数へ
echo "<br />\n";

echo base_convert($d, 10, 2),"<br />\n"; // 2進数へ
echo base_convert($d, 10, 8),"<br />\n"; // 8進数へ
echo base_convert($d, 10, 16),"<br />\n"; // 16進数へ



[出力結果]
1000001
101
41

1000001
101
41


1000001
101
41



上記出力では何進数かピンとこないのでフォーマットしてみます。


$d= 65;
echo sprintf("%08b", $d),"<br />\n"; // 2進数へ
echo sprintf("0%o", $d),"<br />\n"; // 8進数へ
echo sprintf("0x%x", $d),"<br />\n"; // 16進数へ



[出力結果]

01000001
0101
0x41



別のフォーマット方法もあります。

sprintf("%08d", decbin($d)); // これは「01000001」
substr("00000000" . decbin($d), -8); // これは「01000001」



10進数から16進数への変換はunpack()を使う方法もあります。

$hex_array= unpack("H2", chr($d)); // 配列の$hex_array[1]の要素にセットされる



10進数以外の進数同士の変換なら10進数変換が必ず用意されていることから、10進数経由で変換することができます。

decoct(bindec($bin)); // 2進数⇒8進数
dechex(bindec($bin)); // 2進数⇒16進数
decbin(octdec($oct)); // 8進数⇒2進数
dechex(octdec($oct)); // 8進数⇒16進数
decbin(hexdec($hex)); // 16進数⇒2進数
decoct(hexdec($hex)); // 16進数⇒8進数




■2進数を他進数へ変換

$b= '01000001';
echo decoct(bindec($b)),"<br />\n"; // 2進数⇒8進数
echo base_convert($b, 2, 8),"<br />\n"; // 2進数⇒8進数
echo bindec($b),"<br />\n"; // 2進数⇒10進数
echo intval($b, 2),"<br />\n"; // 2進数⇒10進数
echo base_convert($b, 2, 10),"<br />\n"; // 2進数⇒10進数
echo dechex(bindec($b)),"<br />\n"; // 2進数⇒16進数
echo base_convert($b, 2, 16),"<br />\n"; // 2進数⇒16進数



[出力結果]

101
101
65
65
65
41
41




■8進数を他進数へ変換

$o= '0101';
echo decbin(octdec($o)),"<br />\n"; // 8進数⇒2進数
echo base_convert($o, 8, 2),"<br />\n"; // 8進数⇒2進数
echo octdec($o),"<br />\n"; // 8進数⇒10進数
echo intval($o, 8),"<br />\n"; // 8進数⇒10進数
echo base_convert($o, 8, 10),"<br />\n"; // 8進数⇒10進数
echo dechex(octdec($o)),"<br />\n"; // 8進数⇒16進数
echo base_convert($o, 8, 16),"<br />\n"; // 8進数⇒16進数



[出力結果]

1000001
1000001
65
65
65
41
41




■16進数を他進数へ変換

$h= '0x41';
echo decbin(hexdec($h)),"<br />\n"; // 16進数⇒2進数
echo base_convert($h, 16, 2),"<br />\n"; // 16進数⇒2進数
echo decoct(hexdec($h)),"<br />\n"; // 16進数⇒8進数
echo base_convert($h, 16, 8),"<br />\n"; // 16進数⇒8進数
echo hexdec($h),"<br />\n"; // 16進数⇒10進数
echo intval($h, 16),"<br />\n"; // 16進数⇒10進数
echo base_convert($h, 16, 10),"<br />\n"; // 16進数⇒10進数



[出力結果]

1000001
1000001
101
101
65
65
65