ビット演算とは
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 – ビット反転)
~演算子は、単一の数値のすべてのビットを反転させます(0は1に、1は0に)。
/*
~ 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#においてパフォーマンスの最適化や低レベルのデータ構造を扱う際に不可欠な知識となります。
