【C#】 ビット演算子ガイド:&, |, ^, ~, <<, >> の基本と使い方

目次

ビット演算とは

C#における演算子には、+-といった算術演算子、&&||といった論理演算子(bool値に作用)があります。それらとは別に、数値を2進数のビット(0と1)の並びとして直接操作するための演算子群があり、これらを「ビット演算子」と呼びます。

ビット演算は、パフォーマンスが非常に重視される場面、ハードウェアの直接制御、フラグ管理(複数のON/OFF状態を一つの数値で管理する手法)などで使用されます。

この記事では、主要なビット演算子(&, |, ^, ~)とビットシフト演算子(<<, >>)の動作について、具体的なコード例と共に解説します。


準備:2進数表示ヘルパー

ビット演算の結果を理解するため、数値を8桁(byte型)の2進数文字列(0000 0000)に変換するヘルパーメソッドを用意します。

using System;
using System.Security.Cryptography; // RandomNumberGenerator のため

/// <summary>
/// byte型(8ビット)の値を、0埋めした2進数文字列に変換します。
/// </summary>
/// <param name="value">変換するbyte値</param>
/// <returns>8桁の2進数文字列 (例: "00101100")</returns>
static string ToBinaryString(byte value)
{
    return Convert.ToString(value, 2).PadLeft(8, '0');
}

ビット論理演算子 (&, |, ^, ~)

ビット論理演算子は、2つの数値の同じ位置にあるビット同士を比較して、新しい数値を生成します。

ここでは、例として以下の2つのbyte(8ビット符号なし整数)値を使用します。

byte configFlags = 0b_1101_0010; // (Decimal: 210)
byte permissions = 0b_0111_0100; // (Decimal: 116)

Console.WriteLine($"設定フラグ: {ToBinaryString(configFlags)}");
Console.WriteLine($"権限マスク: {ToBinaryString(permissions)}");

& (AND – 論理積)

&演算子は、両方のビットが1の場合にのみ、結果のビットが1になります。

これは「マスク処理」として、特定のビットが立っているか(1か)を抽出するのによく使われます。

/*
  1101 0010  (configFlags)
& 0111 0100  (permissions)
------------------
  0101 0000  (両方とも1の部分だけが残る)
*/
byte commonBits = (byte)(configFlags & permissions);
Console.WriteLine($"AND (&): {ToBinaryString(commonBits)} (Decimal: {commonBits})");
// 出力: AND (&): 01010000 (Decimal: 80)

| (OR – 論理和)

|演算子は、どちらか一方(または両方)のビットが1であれば、結果のビットが1になります。

これは「フラグの設定」として、特定のビットを1(ON)にするためによく使われます。

/*
  1101 0010  (configFlags)
| 0111 0100  (permissions)
------------------
  1111 0110  (どちらかが1の部分が1になる)
*/
byte combinedFlags = (byte)(configFlags | permissions);
Console.WriteLine($"OR (|) : {ToBinaryString(combinedFlags)} (Decimal: {combinedFlags})");
// 出力: OR (|) : 11110110 (Decimal: 246)

^ (XOR – 排他的論理和)

^演算子は、2つのビットが異なる場合(一方が1で他方が0)にのみ、結果のビットが1になります。

これは「フラグのトグル(反転)」によく使われます。同じ値で2回XORすると、元の値に戻る特性があります。

/*
  1101 0010  (configFlags)
^ 0111 0100  (permissions)
------------------
  1010 0110  (ビットが異なる部分だけが1になる)
*/
byte exclusiveFlags = (byte)(configFlags ^ permissions);
Console.WriteLine($"XOR (^) : {ToBinaryString(exclusiveFlags)} (Decimal: {exclusiveFlags})");
// 出力: XOR (^) : 10100110 (Decimal: 166)

~ (NOT – ビット反転)

~演算子は、単一の数値のすべてのビットを反転させます(01に、10に)。

/*
~ 1101 0010  (configFlags)
------------------
  0010 1101  (すべてのビットが反転する)
*/
byte invertedFlags = (byte)(~configFlags);
Console.WriteLine($"NOT (~) : {ToBinaryString(invertedFlags)} (Decimal: {invertedFlags})");
// 出力: NOT (~) : 00101101 (Decimal: 45)

(注意: intなどの符号付き整数に~を使用すると、2の補数表現のため、~5-6になるなど、直感的でない結果になることがありますが、byteのような符号なし整数では単純な反転となります。)


ビットシフト演算子 (<<, >>)

ビットシフト演算子は、数値のビット全体を左または右に指定した数だけ移動させます。

<< (Left Shift – 左シフト)

<<演算子は、ビット列を左に移動させ、空いた右側のビットには0を挿入します。左側からはみ出したビットは破棄されます。

nビット左にシフトする操作は、元の値に $2^n$(2のn乗)を掛ける計算とほぼ同等です。

byte baseValue = 0b_0001_1001; // (Decimal: 25)

/*
  0001 1001  (baseValue)
<< 2
------------------
  0110 0100  (左に2つずれ、右に00が挿入された)
*/

// byte型は演算時にint型に昇格するため、(byte)キャストで戻す
byte leftShifted = (byte)(baseValue << 2); 
Console.WriteLine($"Left Shift (<< 2): {ToBinaryString(leftShifted)} (Decimal: {leftShifted})");
// 出力: Left Shift (<< 2): 01100100 (Decimal: 100)
// (25 * 2^2 = 25 * 4 = 100)

>> (Right Shift – 右シフト)

>>演算子は、ビット列を右に移動させます。右側からはみ出したビットは破棄されます。

nビット右にシフトする操作は、元の値に $2^n$(2のn乗)で割る(整数除算)計算とほぼ同等です。

(注意: byteのような符号なし整数の場合、空いた左側のビットには0が挿入されます(論理シフト)。intなどの符号付き整数の場合は、元の最上位ビット(符号ビット)がコピーされます(算術シフト)。)

byte baseValue = 0b_1110_1000; // (Decimal: 232)

/*
  1110 1000  (baseValue)
>> 3
------------------
  0001 1101  (右に3つずれ、左に000が挿入された)
*/
byte rightShifted = (byte)(baseValue >> 3);
Console.WriteLine($"Right Shift (>> 3): {ToBinaryString(rightShifted)} (Decimal: {rightShifted})");
// 出力: Right Shift (>> 3): 00011101 (Decimal: 29)
// (232 / 2^3 = 232 / 8 = 29)

C#コード全体

using System;

public class BitwiseOperationsExample
{
    /// <summary>
    /// byte型(8ビット)の値を、0埋めした2進数文字列に変換します。
    /// </summary>
    static string ToBinaryString(byte value)
    {
        return Convert.ToString(value, 2).PadLeft(8, '0');
    }

    public static void Main()
    {
        // --- 演算の対象となる値 ---
        byte configFlags = 0b_1101_0010; // (Decimal: 210)
        byte permissions = 0b_0111_0100; // (Decimal: 116)

        Console.WriteLine($"設定フラグ: {ToBinaryString(configFlags)} (Decimal: {configFlags})");
        Console.WriteLine($"権限マスク: {ToBinaryString(permissions)} (Decimal: {permissions})");
        Console.WriteLine("------------------------------------------");

        // --- ビット論理演算 ---
        // 11010010 & 01110100 = 01010000 (80)
        byte commonBits = (byte)(configFlags & permissions);
        Console.WriteLine($"AND (&): {ToBinaryString(commonBits)} (Decimal: {commonBits})");

        // 11010010 | 01110100 = 11110110 (246)
        byte combinedFlags = (byte)(configFlags | permissions);
        Console.WriteLine($"OR (|) : {ToBinaryString(combinedFlags)} (Decimal: {combinedFlags})");

        // 11010010 ^ 01110100 = 10100110 (166)
        byte exclusiveFlags = (byte)(configFlags ^ permissions);
        Console.WriteLine($"XOR (^) : {ToBinaryString(exclusiveFlags)} (Decimal: {exclusiveFlags})");

        // ~11010010 = 00101101 (45)
        byte invertedFlags = (byte)(~configFlags);
        Console.WriteLine($"NOT (~) : {ToBinaryString(invertedFlags)} (Decimal: {invertedFlags})");
        
        Console.WriteLine("------------------------------------------");

        // --- ビットシフト演算 ---
        byte baseValue = 0b_0001_1001; // (Decimal: 25)
        Console.WriteLine($"シフト元: {ToBinaryString(baseValue)} (Decimal: {baseValue})");

        // 00011001 << 2 = 01100100 (100)
        byte leftShifted = (byte)(baseValue << 2); 
        Console.WriteLine($"Left Shift (<< 2): {ToBinaryString(leftShifted)} (Decimal: {leftShifted})");

        byte baseValue2 = 0b_1110_1000; // (Decimal: 232)
        Console.WriteLine($"シフト元: {ToBinaryString(baseValue2)} (Decimal: {baseValue2})");
        
        // 11101000 >> 3 = 00011101 (29)
        byte rightShifted = (byte)(baseValue2 >> 3);
        Console.WriteLine($"Right Shift (>> 3): {ToBinaryString(rightShifted)} (Decimal: {rightShifted})");
    }
}

まとめ

ビット演算子とシフト演算子は、数値をビットレベルで細かく制御するための強力なツールです。&(AND)はビットの抽出(マスク)、|(OR)はビットの追加(設定)、^(XOR)はビットの反転(トグル)に主に使用されます。シフト演算子(<<, >>)は、2のべき乗での高速な乗算・除算として機能します。

これらの演算は、C#においてパフォーマンスの最適化や低レベルのデータ構造を扱う際に不可欠な知識となります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次