C++のビット反転とは、整数を構成している各ビットを、0なら1に、1なら0に反転する操作のことです。
この操作には ~ 演算子 を使います。
たとえば、ある値のビット列が並んでいるとき、~ を適用すると、その値を構成する各ビットがすべて逆になります。
そのため、~ は「値そのものを単純にマイナスにする演算」ではなく、内部のビット表現に直接作用する演算だと考えるのが大切です。
まず理解しておきたい基本
ビット反転の説明では、「0が1になる」「1が0になる」という理解でまず問題ありません。
ただし、C++では実際の式の評価で型変換が関わるため、単純に「見えている型のビットだけがそのまま反転する」と考えると、途中で混乱しやすくなります。
特に重要なのは、C++では ~ を適用する前に、対象の値に対して整数昇格が行われることがある、という点です。
つまり、小さい整数型に対して ~ を使っても、その小さい型のまま演算されるとは限りません。
多くの場合、まず int などに変換されてから反転が行われます。
この性質を知らないと、「8ビットの値を反転したつもりなのに、結果が予想より大きな値になった」「負の数になってしまった」といった疑問が起こります。
なぜ反転すると負の値になることがあるのか
C++で ~ を使ったとき、多くの人が最初に驚くのが、正の数を反転したら負の数になることがある点です。
これは ~ が「負数に変える演算」だからではありません。
そうではなく、反転後のビット列が、符号付き整数として解釈された結果、負の値に見えるからです。
たとえば、一般的な環境では整数は2の補数表現で扱われています。
そのため、ある正の整数に ~ を適用すると、反転後のビット列が負数として読まれることがよくあります。
つまり、~ の本質はあくまでビット列の反転であり、その結果が正になるか負になるかは、どの型としてそのビット列を解釈するかに左右されます。
符号付き整数と符号なし整数の違い
ビット反転を理解するうえでは、符号付き整数と符号なし整数の違いがとても重要です。
符号付き整数では、反転後のビット列が負数として解釈されることがあります。
一方、符号なし整数では、同じビット列でも負数にはならず、単に非常に大きな正の値として扱われます。
このため、ビット操作をわかりやすく扱いたいときは、符号付き型よりも符号なし型を使うほうが意図が明確になります。
特に、数値としての意味よりも「どのビットが立っているか」を重視する処理では、符号なし型のほうが混乱が少なくなります。
よくある誤解
元の型のビット幅だけがそのまま反転されると思ってしまう
これはよくある誤解です。
実際には、C++では ~ の前に整数昇格が行われることがあるため、小さい型でもそのままの幅で演算されるとは限りません。
そのため、「8ビットの変数だから8ビットだけ反転される」と単純に思い込むのは危険です。
保存される型が8ビットであっても、式の評価途中ではより大きい型で計算される場合があります。
~ は ! と同じようなものだと思ってしまう
これも非常に多い混同です。
~ はビット単位の反転です。
一方で ! は論理否定です。
つまり、~ は数値の内部のビット列に対して働きますが、! はその値を「真か偽か」で判断して結果を返します。
見た目は似ていますが、役割はまったく違います。
~ は一部のビットだけを反転するのに向いていると思ってしまう
~ は全ビットをまとめて反転する演算です。
そのため、「特定の位置だけを反転したい」という用途にはそのままでは向いていません。
特定のビットだけを反転したい場合は、通常はXORを使います。
一方で ~ は、全体を反転したり、マスクを反転させて別のビット演算と組み合わせたりするときに使われます。
ビットフラグ操作でよく使われる理由
~ は、単独で「全部反転する」ためだけに使われるわけではありません。
実際には、ビットフラグ操作の中で非常によく登場します。
代表的なのは、「ある特定のフラグだけを落としたい」ときです。
このような場面では、対象のビットだけが1になっているマスクを用意し、それを ~ で反転してから AND を取ることで、必要なビットだけを0にできます。
この使い方は実務でも頻出です。
そのため ~ は単独で覚えるだけでなく、AND や XOR と組み合わせて使うものとして理解すると、実践的な感覚がつかみやすくなります。
小さい整数型を扱うときの注意
char や unsigned char、あるいは uint8_t のような小さい整数型を使うと、「ビット幅が明確だから安心」と思いがちです。
しかし、ここにも注意が必要です。
確かに、こうした型を使えば保存する値の幅はわかりやすくなります。
ただし、~ を使った式の評価そのものまで、その小さい幅で固定されるとは限りません。
C++では小さい整数型が整数昇格されるため、式の途中では int などとして扱われることがあります。
そのため、小さい型を使っていても、演算結果の見え方や比較のしかたには注意が必要です。
つまり、小さい型を使うことには意味がありますが、それだけで「ビット反転の挙動が単純になる」とまでは言えません。
保存する型と演算中の型は分けて考えることが大切です。
ビット反転を理解するための見方
C++のビット反転は、次の順番で考えると理解しやすくなります。
まず、~ は各ビットを反転する演算です。
次に、その演算はC++の型変換規則の影響を受けます。
そして最後に、反転後のビット列が、その型では正の数に見えるのか負の数に見えるのかが決まります。
つまり、ビット反転では単に「数値がどう変わるか」だけを見るのではなく、
- どの型で評価されるのか
- その型に符号があるのか
- 反転後のビット列がどう解釈されるのか
この3点をセットで考える必要があります。
まとめ
C++のビット反転は、~ を使って各ビットを0と1で反転する操作です。
ただし、実際の評価では整数昇格が関わるため、特に小さい整数型では「見た目どおりのビット幅でそのまま計算される」とは限りません。
また、反転後の結果が負の値になることがあるのは、~ が負数化の演算だからではなく、反転したビット列が符号付き整数として解釈された結果です。
このため、ビット操作では型の幅と符号の有無を強く意識することが重要です。
さらに、~ は全ビット反転、! は論理否定、特定ビットだけを反転したいときは XOR、というように役割を区別して理解すると混乱しにくくなります。
以上、C++のビット反転についてでした。
最後までお読みいただき、ありがとうございました。
