こんにちは､ そしてようこそ｡ ビット演算子について説明します｡
前回の講義で､ 代入演算子について少し触れましたが､ 代入演算子には､
加算などの演算を行い､ さらに代入することができる複合代入があるからです｡
これらの型演算子でも同じようなことができます｡
しかし､ これらをもっと詳しく､ 正確にお伝えしましょう｡
そこで､ cにはビット単位の論理演算子とシフト演算子が用意されています｡
それらの用語は､ まったくわからないかもしれません｡
論理演算子のように見えるが､ 全く違う｡ ブール演算のような評価はしない｡
真偽の評価はしない｡
これらはアンパサンドampersand､ 2つのorのように見えますが､
ブール演算のような評価をしているわけではありません｡
ビットを操作して､ 整数値で操作しているのです｡
そうすると､ 「なぜ､ こんなことをしようと思ったのか」と思うかもしれません｡ まあ､ ほとんどの番組で使われてないんですけどね｡
どちらかというと､ 高度な技術ですね｡
ビギナークラスとはいえ それらはオペレーターの一部であり､ オペレーターは初心者の概念である｡
だから､ それらに対応するつもりですが､ 必ずしもそれにこだわるつもりはありません｡
いくつかの例を挙げ､ どのように使えるかを紹介します｡
しかし､ この講義を受けたからといって､ その専門家になれるわけではありません｡
でも､ なぜ便利で､ 実際に使いたいかというと､ かなりの量のデータを保存することができるからです｡
ビット単位のオペランドには､ アンド､ アンパサンド､ そしてビット単位のオアがあり､
これは平行棒を1本並べただけのものです｡
また､ これらを論理演算子と混同しないようにしてください｡
論理演算子は2つあり､ アンパサンドampersand､ これはandのことです｡
ビット演算子にはシングルがあるだけです｡ だから､ アンパサンドだけ､ あるいはorだけでいいんです｡
そして､ これから行うのは､ 整数変数の中の個々のビットをテストして設定することです｡
ビットとは何ですか？
ビットはデータの測定方法であることは分かっています｡
しかし､ ビットは本来､ 0か1の値でしかない｡
そして､ そのうちの8個で1バイトを構成しています｡ つまり､ ビットとは0か1かのことです｡
そして､ あなたができることは､ 整数､ 覚えておいてください､
あなたが実行しているシステムによっては､ 8バイトになる可能性があることです｡
それで､ 各ビットのオン・オフができるようになったんですね｡
また､ ON/OFFというのは､ 1か0になるようにすればいいということです｡
そして､ そのビットを使って何かを伝えることができるのです｡
つまり､ ビットに何らかのデータを表現させることで､ 多くのデータを保存することができるのです｡
だからこそ､ コンピュータが1と0のビット､ 2進数を使って演算をする理由のひとつは､
たくさんのデータを保存できるからなのです｡
では､
具体的にどういうことかというと､ 次のような例です｡ 単一の整数型変数を使用することもできます｡
繰り返しになりますが､ 整数は数字です｡ 整数は4〜8バイトを表し､
実行しているシステムによって異なります｡
つまり､ 4バイトなら32ビットということです｡
0か1の値が32個あります｡
だから､ そのすべてのビットを使って何かを表現することができる｡
例えば､ 人物のキャラクターを個別に保存する場合､ 1ビットだけで､
その人物が男性か女性かを､ 1が男性､
0が女性というように表現することができます｡
スイッチを入れるか､ 切るかだけです｡
そうすると､ 3ビットでフランス語､ ドイツ語､ イタリア語のいずれかを話せるかどうかを指定することができます｡
つまり､ 1ビットはフランス語を表すことができるのです｡ 1 0 でないなら､ フランス語が話せないということです｡
ドイツ語が0なら､ ドイツ語を話さないということです｡
1の場合は､ ドイツ語が話せるということです｡ つまり､ これらのビットをオフにしてオンにして､ 1〜0にすることで､
多くのデータを表現することができるのです｡
その人の給料が5万円以上かどうかを記録するビットをもう一つ用意してもいいのではないでしょうか｡
1なら本当です､ はい､ そうです｡ 0 falseです｡
だから､ 基本的にはここで指定しただけです｡
4ビットと書いてありますが､ 実際には5ビットのデータを指定しました｡
32ビットでも64ビットでもいいのですが､ 5ビットだけを使って､
人が女性か男性か､ フランス語､ ドイツ語､ イタリア語の3つ全部か､ どれか一つかを表現することができるのです｡
そして､ 5万ドル以上稼いでいるかどうか｡
そのデータをすべてビットで表現することができるのです｡
そして､ ここで便利なのがビット演算子です｡
さて､ ビットを使ってさまざまなデータを保存する方法がわかりました｡
その2進数の話をしよう｡
2進数とは､ 1と0を含むだけの数であることは分かっています｡
だからこそ､ 使うのに有利なのです｡ 整数のビット演算子で､ 数値は任意の長さにすることができます｡
以下は､ 0と1だけの2進数の例です｡
つまり､ この0 1 0 1 0 1 1 0 1 0 1 0 1 0を見たんですね｡
そして､ 1バイトは8ビットであることを忘れないでください｡
整数は多くのプラットフォームで通常4バイトである｡
アーキテクチャによって異なりますが､ ほとんどが32ビットです｡
つまり､ オンとオフを示す1と0で記憶できる値を32個持っているわけです｡
2進数は､ やはりすべての2進数に10進数の値が対応しているので､
2進数を10進数に変換する方法を理解する必要があります｡
ここでは例として､ 1は1に等しい､ 1 0は2､ 1 1は3､
これらは単純な変換である｡
この場合も､ 通常は1 0 1 0 1 1 1というように､ より多くのビットを持つ｡
2進数から10進数への変換は､
バイトの各位置と対応するビットを調べ､
2のべき乗を利用します｡
そこで､ 各桁ごとに､ その桁の位置の値を2の累乗で掛け合わせます｡
そして､ プロジェクト全体を足し算して､ 最終的な結果を得るのです｡
つまり､ 位置値と2進数は2の累乗となる｡
つまり､ 最初の位置は値2 0､ つまり1ということになる｡
そして､ 2番目の位置は2の1乗の値である2､ 2の2乗は4､
2の3乗は8､ 2の4乗は16となるのである｡
そして､
それを何度も何度も繰り返している｡ それで､ 素敵な例を見ていただくと分かりやすいと思います｡
そして､ ここでは､ 0 1 1 0 1 0 0 1という2進数になっています｡
10進数で表すと105になります｡
変換方法は､ 各ビットの右辺から始めて､ ビットが0か1かによって2の累乗にします｡
だから､ ずっと右から始めると､ 1がある｡
だから､ 1×1と言って､ その1のままにしておくのです｡ そして､ 次の1､ 2番目の位置は0回2
0です｡ 次に､ 4の0倍､ 8の1倍､
16の0倍､ 32の1倍､ 64の1倍､
28の0倍となります｡
そして､ それらをすべて足すと105という結果になります｡
それがコンバージョンの1つの方法です｡ 実際には2倍して､ 次のビットが何であるかによって1を足したり0を足したりすれば､
左から右に変換することもできます｡ 例えば､ 左から右へ行く場合も､
0は関係ないので､ 1からスタートします｡
つまり､ 左から2番目のビットが1になっているわけです｡
3ビット目は､ 1×2＋1だから､
3ですね｡
そしてまた､ 2を掛けるので､ 3×2＋0＝6となる｡
6×2＋1で13となります｡
13×2は26です｡
26×2は52×2であり､
104＋1である｡
そこで､ 再び2を掛けて1を足す｡ もう一つの例ですが､ やはり右から左は通常もう少し簡単です｡
ここで､ 1 0 0 1 1 0 0｡
そして､ それが156に変換されるのは､ また0×1､
0×2､ 1×4､ 1×8､ 1×16などと言い､
それらを全部足すと4 8 12
16 28＋128で156になるからです｡
そしてまた､ 左から右へ進むと､ 2を掛けて1を加える｡
そこで､ 1に向けてスタートしました｡ 1 2 4 9とすると､
4×2＋1､ 9×2は18＋1＝19×2､
38＋1は39×2＝78×2＝156となります｡
というわけで､ 2進数から10進数への変換の簡単なデモンストレーションです｡
デシベルを2進数に変換する場合､
私は通常､ ウェブ上の変換を使用するだけです｡
しかし､
これらの2進数からビット演算を行うことができます｡ そこで､ ビット演算について見ていきましょう｡
そこで､ ビット演算子についてまとめてみました｡ またまた､ ネットで入手した素敵な表ですが､
論理演算子と似ていますね｡ しかし､ 繰り返しになりますが､ 混同してはいけません｡
つまり､ アンパサンド単体では二項演算子となるわけです｡
つまり､ 両方のオペランドにビットが存在する場合､ そのビットを結果にコピーしているのです｡
だから､
両方が本当なら､ ちょっとコピーすることになる｡
つまり､ aとbが12に等しい場合､
0 0 0 1 1 0 0という2進数表現になるわけです｡
つまり､ aとbがその数字になるわけです｡
いくつかの例を挙げて説明します｡
バイナリまたはバイナリは､ ビットがオペランドのどちらかに存在する場合､
そのビットをコピーします｡
つまり､
どちらかのオペランドが1になれば､ ビットをコピーすることになります｡
そして､ 2進数のxor､ これは排他的論理和である｡
これは､ ビットが1つのオペランドに設定されている場合にコピーされますが､ 両方には設定されません｡
つまり､ 「true」が1つしかない場合､
キーボードの「にんじん」でこれを表現すると､ ビットがコピーされて「true」､ 「1sの補数」というバイナリになります｡
つまり､ ここでもビットの話なので､ バイナリデータを取得することになります｡ だからうまくいけば､
1の補数｡
Operatorは単項で､ ビットを反転させる効果があり､
基本的にはネゲートしてモニターをはずすだけです｡
例えば､ チルダaの場合､ 1sの補数演算子を適用することになります｡
また､ マイナス61に等しい場合は､ 1 1 0 0 0 1となります｡
そこで､ 実際に2進数を10進数に変換すると､
負の数になり､ 最初の数字が符号になります｡
1はサインだと言ってるわけだが｡
そして､ 1 2 4 8 16 33と言い､ 61と言うわけで､
そういう表現になるわけです｡
そこで､ より具体的な例を挙げて説明します｡
しかし､ 基本的には､ 2進数､ aが最初は0 0 1 1
1 0 0で60であり､ 反転を与えると､
1 0 0 0 0 0 0 0 0 0 1 1で､
マイナス61になります｡
つまり､ これは実際に設定された変数の結果なのです｡ つまり､ 繰り返しになりますが､
aはある2進法表現を持ち､ bはある2進法表現を持つと仮定しています｡
つまり､ aが60でbが13の場合､
これらをビット演算で足すと0000 11
00となり､ 12となります｡
もう一度､ これらの例を見ていきますが､ これは､ これらの異なる二項演算子について少し説明するためのものです｡
less than小の記号は､ ビットを左にシフトすることになります｡
そのため､ 左オペランドの値は､ 白オペランドで指定されたビット数だけ左に移動します｡
例えば､ aが2進数の0
0 1 1 1 0
0に等しい場合､ バイトを2つシフトすることになります｡
つまり､ 0を取ると1 1 1 1となり､ その0は末尾に行くわけですね｡
また､ 右シフト演算子も同様です｡ 左とは逆に右にずらしてるだけだろ｡
ここでは､ 大なり記号を使用します｡
というわけで､ これらをちょっとずつ見ていきたいと思います｡
ビット演算子で理解すべきもうひとつの重要な点は､ これらのaやbの値をどのように評価するかということです｡
では､
アンパサンドなら1と1はどう評価されるのでしょうか｡
or
1と1であれば､ 同様に1と評価されます｡
しかし､ 1と1と0は､ アンパサンドとアンドを使用する場合､
0偽と評価されます｡ そこで､ anとan､ またはexclusive
orを使用した場合の値を示す､ 小さな真理値表をここに用意しました｡
つまり､ p0とq0があれば､
両方とも偽で､ andを適用すると偽になるのです｡
そして､ 3行目に表示されているのがそれです｡
両方が真ならpとqは1に評価される､ 真だ｡
両方が真でなければ､ それは常に偽です｡
Pかqのどちらか1つだけが真である必要があります｡
そのため､ 1行目と2行目が1になり､ 3行目が1になるわけです｡
虚偽であるのは､ 両方が虚偽であるときだけです｡
そして､ 排他的論理和は､ そのうちの少なくとも1つが真であり､
1つだけが真であることです｡
つまり､ pがfalseでqがtrueの場合､ trueを返す｡
そして､ pが真でqが偽の場合は1を返す｡
つまり､ これが評価の目安になるわけです｡
またまた数学的な話になってしまいますが､ 2進数の話といえばそうですよね｡
では､ 具体的な例とコードブロックを見てみましょう｡ 少しでも参考になれば幸いです｡
そこで､ この機能を紹介します｡ もう2つほど変数を作ってみます｡
デフォルトでそうなっているのに､
無署名にする｡ つまり､ unsigned int aを60に代入すると､
実際には2進数で0 0 1 1､ そして11 00と表現されることになります｡
別の符号なし変数を作成してint bという値を13に代入すると､ 実際には2進数で0
0 00 1 1 0 1と表現されることになります｡
そこで変換をすればいいのです｡
しかし､ ここで示したいのは､ これらの2進数を設定するとき､ 異なるビット演算子を使えば､
ビットをオフにすることができるということです｡
それでは､ int型の結果を作成して､ 0に等しくなるようにします｡
Eはビット演算の結果を保持することになる｡
の操作を行うことができます｡ ここで､ cとaを等しくして､
ビット単位のオペランドとaとbを適用すると､ aが1でbが1なら､
そのビットがオンになるはずなんです｡
では､ このVの結果はどうなるのでしょうか｡
さて､ aの最初のビットとbの最初のビットを見ると､ どちらも0になっていますね｡
つまり､ aでtrue､ bでbとやると､
1であるためには両方がtrueでなければならないので､ 結果は0になるのです｡
だから､ 次も0になるんです｡ 次が1､ 3ビット目が0です｡
つまり､ 0､ 1､ 0はまだ0なんです｡ そして､ 今は1と1があります｡
これでようやく真に迫ったわけです｡ 他にも1､ 1があります｡ 私たちは､ 真の
そして､ 0と0が0になり､ 0と1が0になるのです｡
つまり､ cを出力しようとすると､ その結果は10進数で出力されることになる｡
というようなことをすると､
その2進数の10進数相当の数値が出力されるのですが､ それは0 0 0 0に相当するものでしょう｡
そして､ これはすべて2進数から10進数への変換ですので､ この例を理解するためにはその方法を理解する必要があります｡
しかし､
0 0 0 1 1 0 0は､ 実際には1 3 7で､ 値は12､
1 3 6 12である｡
つまり､ これは12をプリントアウトするのであって､
cをプリントアウトするのであれば､
resultの方が良いわけです｡
そこで､ これを実行すると､ 値が12であることがわかると思います｡
実際にアンパサンドの比較に基づいてすべてのビットをオフにしたりオンにしたりしました｡
実行すると､
ビットのオフとオンが繰り返されるので､
値はd12になり､ 実際には0
0 0 1 1 0 0と表現されることがわかります｡
もしこれを変更して､ orを実行するようにしても､ orはenterのすぐ隣にある平行棒で､
今はどちらか1が真である場合にのみビットをオンにするようにしています｡
だから00､
どちらも嘘です｡ ここでも､ 上と下で比較しているんです｡
このビットを16と13で比較しているんです｡
16は2進数0 0 1 1 1 0 0で表されます｡
13は2進数で表すと0 0 0 1 1 0は1です｡
その数字のひとつひとつがビットを表しています｡
つまり､ 60の最初のビットは0､ 13の最初のビットは0です｡
それを0と0と並べると､ やはり0になるんです｡ これが最初のビットの結果なんですね｡
2番目のビット00を再び｡ 3ビット目は60で1､ 13で0になります｡
つまり､ 1が真でなければならないので､ これは1になります｡
ビット番号4と16､ 13も同様です｡ だから今､ また1があるんです｡
そして､ 1と1､ どちらも本当なんです｡ だからまた1､ 必要なのは1真､
もう1真です｡
両方とも偽なので､ それはないでしょう｡ 0なら覚えていない､ 1なら覚えている､
0なら覚えている 11. だから今､ 私は0 0 1 1 1 0 1を手に入れた｡
この値を10進数で表示すると､ 2進数から10進数への変換方法を理解する必要がありますが､
基本的には1 3 7 15 30 61となります｡
これで61をプリントアウトするわけです｡
このプログラムを終了し､ 再構築することで､
61の値が見えてくる｡
つまり､ ビット演算子ですね｡ また､ より高度なプログラムですが､
アンパサンドなどを使って､ ビットをオン・オフすることで､
多くのデータを表現する良い方法です｡
だから､
これで遊べるんです｡ 専用とどっちがニンジンかやってもいいんじゃない？
ニンジンはキーボードの6､ 数字の6の上にあります｡
違う結果が見えてくるはずです｡ チルダを付けると､ 基本的にすべてオフになります｡
つまり､ aとaが60に等しい場合､ 0 0 1 1
1 0 0 0 1 1にはならないんです｡
二項演算子でできるもうひとつのクールなことは､
左右にシフトすることです｡ 左右のシフトが必要な時にわかるポイントがいくつかあるんです｡
まず､ unsigned intは正の値しか取れないということがわかります｡
ネガティブではダメなんです｡
だから､ そこにマイナスの値を格納していると､ コンパイルで変換されて長くなるし､ サイズも大きくなってしまうんです｡
つまり､ 符号なしintは正の値しか持てないということを覚えておいてください｡
符号付き整数とは､ 負にも正にもなりうるということです｡
しかし､ もしシフトしたいのなら､
左にシフトするために覚えておいてください｡ 今､ result
equals と言って､ aを､ 例えば2だけシフトしたいとしましょう｡
演算子でサイズを求めることができます｡
つまり､ 整数は4が32ビットなので､
実際にはこのような数字になります｡
16の何かは､
4 8 12 16 20 24の何かです｡
だから､ あなたはおそらくそのような何かと､ そのような何か｡
本当にこの数字なんです｡ 残りは0なので､ 最後の8ビットだけ表示しました｡
しかし､
これを4バイトの32ビット整数として見ると､ 2進数ではこのようになり､
4 0s 4 0s 4 0s 4 0s 4 0s､ そして0 0 1 1 1 0 0があります｡
そのため､ 左右にシフトする際には､ サイズを把握する必要があります｡
2以上シフトすると､ クリアになりませんから｡
落ちるとかそういうことはないです｡
もう一度確認しますと､ 48 12 16 20 24
28 32と､ 13も同様です｡
つまり､ 2より大きい値を左にずらすと､
1も左にずれてしまうのです｡
その結果､
先行するすべての0はそのままに､
1 1 1 1 0 0 0 0が発生することになります｡
そしてそれは､ 240と同じになるのです｡
そして､ ざっと変換してみると､ 頭の中では1
3 7 15 30 60 122 40とやっていますが､
ビルドして実行してみると､ 結果が240であることが確認できます｡
そして､ その結果が240であることがわかります｡
では､ 実際に2以上シフトしたい場合､ 例えば4シフトしたい場合､
32ビット数であることを理解することが重要になります｡
ですから､ ここでは実際に0をいくつか使う必要があります｡
ですから､ もし4つシフトしたいのであれば､ そのようなものを用意すればいいのです｡
そして､ 4でグループ化したい場合は､
そのようなものになります｡
つまり､
ここでも頭の中で計算すると､ 1 3 7 15
30 60 120 240 480となり､ 次に960となるわけです｡
うまくいくといいのですが｡ もう一度実行すると､ 960と表示されるので､
左にシフトしていることがわかります｡
右にずらしたい場合は､
大なり記号を使う｡
そこで､ 二項演算子である右シフト演算子を右にずらすと､
大なり小なりとなる｡ 二項演算子（左シフト演算子）､ less thanをするんですね｡
つまりここでは､ 右にシフトするのであれば､ 右の4に移動したいわけです｡
そして､ ここにエンドがあるとどうなるかというと､ 元の60があれば､
4に行こうとするわけです｡ ただでさえ落とされる桁があるんだから｡
そのため､ この最後の4つは本来は60あるのですが､ それを持たずに､
60にしました｡ つまり､ 0 0 0 1
1 1 0 0 となり､ 4の上に移動する必要があります｡
だから､ 結局は2ビット分､ 落ちてしまうわけです｡
そして､ 最終的に出来上がるのは､
このような0 0です｡
そのため､
右にシフトしすぎてビットが失われ､ 3という結果になります｡
ですから､
左右にシフトするときは､ システムにもよりますが､ 32ビット4バイトを使うように注意する必要があります｡
ビット演算子について簡単にまとめたものです｡
さらに高度になると､ 特定のビットが1か0かを実際に確認する方法を理解する必要があります｡
それはとてもシンプルなことです｡ その上でアンドをやるだけです｡
でも､ もう少しコードを書かないといけない｡
この講義は､ ビット演算子について網羅的に説明することを意図したものではありません｡
概要を説明することで､ より良いスタートを切りたかったのです｡
しかし､
これらの演算子をより理解するためには､ かなり勉強して使いこなし､ 実践することが必要です｡
というわけで､ 参考になったでしょうか｡ 繰り返しになりますが､ 少しわかりにくいので､
フォローして遊びたいところです｡ しかし､ やはり､ コードを書き､ 例を学びながら､
学ぶのが一番です｡ ありがとうございます｡