C#・VBでは言語組み込みのビット演算として、AND・ORなどの論理演算やシフトなどの演算子が用意されています。 (§.ビット演算子、§.シフト演算子)
このほかに、.NETでは言語組み込みの演算子では直接サポートされない回転シフトなどのようなビット演算を行うためのクラスBitOperationsが用意されています。 (§.BitOperationsクラス)
ここではこれらのビット演算について解説します。 あわせて、ビット演算に際してよく用いられる基数を指定して数値⇄文字列の変換を行う方法についても解説します。 2進数・16進数で数値(整数リテラル)を記述する方法についてはリテラルとサフィックス §.整数リテラルを参照してください。
なお、この文章では、n進法での数値表記であることを強調する場合、下付きの(n)を使用します。 10進数での数値31は31(10)、他の基数の場合も同様に11111(2) = 31(10) = 1F(16)のように表記します。
ビット演算子
C#・VBでは言語組み込みのビットごとの演算として、以下のビット演算子が用意されています。 整数型に対して使用することで、ビットごとの演算を行うことができます。 &=
などのように、ビット演算子に=
を後置できる演算子では、代入先を左オペランド(左項)とする複合代入演算子となります。
ビット演算 | 演算子 | |
---|---|---|
C# | VB | |
論理否定(NOT)・1の補数 |
~
|
Not
|
論理積(AND) |
& , &= |
And
|
論理和(OR) |
| , |= |
Or
|
排他的論理和(XOR) |
^ , ^= |
Xor
|
C#におけるビット演算子は、32ビットまたは64ビットの整数型(int
, uint
, long
, uint
)に対してのみ定義されています。 8ビット・16ビット整数型に対してビットごとの演算を行う場合、値は自動的にint
へ拡大変換された上で演算が行われます。 つまり、sbyte
, byte
, char
, short
, ushort
の値に対して演算を行う場合、結果はint
となります。 複合代入演算子を使う場合は、自動的に代入先の型にキャストされます。
VBにおけるビット演算子は、Char
を除く各整数型に対して使用できます。 演算の結果は、左右のオペランド(項)が同じ型の場合はその型、異なる型の場合はより大きい型の方に拡大変換されます。
上記の演算子をbool
・Boolean
に対して使用すると、ビットごとの演算ではなく単一のブール値に対する演算を行う論理演算子となります。 ただしVBでは、ブール値に対して論理積・論理和を求める場合は通常AndAlso
・OrElse
演算子を使います 詳細は論理演算子 §.AndAlso演算子・OrElse演算子を参照してください。
ビット演算子はFlags属性を持つ列挙体に対しても使用することができます。 (列挙体とフラグ)
シフト演算子
C#、およびVB(7.1以降)では、言語組み込みのシフト演算として、以下のシフト演算子が用意されています。 代入先を左オペランド(左項)とする複合代入演算子も用意されています。
シフト演算 | 演算子 | |
---|---|---|
C# | VB(7.1以降) | |
左シフト |
<< , <<= |
<< , <<= |
右シフト |
>> , >>= |
>> , >>= |
右シフト演算子は、左オペランド(左項・シフトされる値)が符号付き整数の場合は算術シフト(負数の場合は上位側ビットに1
、正数の場合は0
が補われる)となり、符号無し整数の場合は論理シフト(上位側ビットに常に0
が補われる)となります。 言い換えると、右シフト演算子では常に最上位ビット(符号ビット)と同じ値が補われます。 左シフト演算子では、常に0
が補われます。
C#におけるシフト演算子は、32ビットまたは64ビットの整数型(int
, uint
, long
, uint
)に対してのみ定義されています。 8ビット・16ビット整数型に対してシフト演算を行う場合、値は自動的にint
へ拡大変換された上でシフト演算が行われます。 つまり、sbyte
, byte
, char
, short
, ushort
の値に対してシフト演算を行う場合、結果はint
となります。 複合代入演算子を使う場合は、自動的に代入先の型にキャストされます。
VBにおけるシフト演算子は、Char
を除く各整数型に対して使用できます。 シフト演算の結果は、常に左オペランド(シフトされる値)と同じ型となります。
右オペランド(右項・シフトする量、amount)は、常に左オペランドの型のビット数未満となるようにマスクされます。 例として32ビット整数をシフトしようとする場合、右オペランドの値は31(10)=11111(2)で自動的にマスク(AND演算)されます。 値が32ビット整数でシフト量が33の場合、33(10) AND 31(10) = 1(10)
つまりシフト量1のシフト演算となります。
このため、シフト量が型のビット数を超える値となる場合や、特に逆方向へのシフトを意図して負の値を指定してもそのとおりの動作とはならない点には注意が必要です。
回転シフト・循環シフトを行う場合はBitOperations.RotateLeftメソッド・RotateRightメソッドを使用します。 このメソッドでは、シフト量に負の値を指定すると逆方向への回転シフトとなります。
BitOperationsクラス
BitOperationsクラスは、特定CPUに固有な命令に対応するビット演算や、それに類似するビット演算を行うためのクラスです。 CPUがサポートしている場合はベクトル演算が使用されるVector<T>構造体と同様に、JITコンパイラによってメソッド呼び出しが固有のCPU命令、あるいはそれを利用した実装に展開されます。 これにより、BitOperationsクラスにより提供される演算を独自に実装する必要がなくなり、また多用するような場面ではパフォーマンス向上が期待できます。
BitOperationsクラスのメソッドでは、対応するCPU命令がサポートされていない場合でも例外とはならず、同じ結果を返すフォールバック実装(汎用のビット演算・算術演算による実装)が実行されます。 このため、CPUがサポートしているかどうかを呼び出し前に調べる必要はありません。 また、各メソッドはMethodImpl属性でMethodImplOptions.AggressiveInliningが指定されているため、メソッド呼び出しはフォールバック実装となる場合でも積極的にインライン展開されるようになっています。
BitOperationsクラスは.NET Core 3.0/.NET 5以降でサポートされます。 .NET Standardでは使用できません。 BitOperationsクラスでは、Mathクラスと同様に静的メソッドとして用意されているメソッドを呼び出します。 インスタンスの作成は不要です。 また、使用に際しては名前空間System.Numerics
をインポートします。
左回転シフト・右回転シフト (RotateLeft・RotateRight)
RotateLeftメソッド・RotateRightメソッドは、回転シフト(循環シフト、circular shift)を行います。 x86 ROL/ROR命令に類似する演算を行います。
RotateLeft・RotateRightメソッドは、uint
/UInteger
, ulong
/ULong
の整数型のみに対して定義されています。 シフト量(引数offset)は、値の型のビット数(32または64)を超える数を指定できるほか、負数を指定することもできます。 シフト量が負の場合は、逆方向への回転シフトになります。
先行する/後続する0のビット数 (LeadingZeroCount・TrailingZeroCount)
LeadingZeroCountメソッド・TrailingZeroCountメソッドは、1のビットに先行/後続する0のビットの数(最上位/最下位ビットから見て0の桁が連続する数)を計上します。 x86 LZCNT/TZCNT命令に類似する演算を行います。
LeadingZeroCount・TrailingZeroCountメソッドは、uint
/UInteger
, ulong
/ULong
の整数型のみに対して定義されています。 なお、結果はint
/Integer
で返されます。
立っているビットの数 (PopCount)
PopCountメソッドは、立っている(1
である)ビットの数(population count)を計上します。 x86 POPCNT命令に類似する演算を行います。
PopCountメソッドは、uint
/UInteger
, ulong
/ULong
の整数型のみに対して定義されています。 なお、結果はint
/Integer
で返されます。
2進対数・lb n (Log2)
Log2メソッドは、2を底とした対数(2進対数、⌊log2n⌋・⌊lb n⌋)を求めます。
Math.Logメソッドによっても2進対数を求めることはできますが、LeadingZeroCountメソッド相当のビット演算によって計算される点、これに従い戻り値が整数(⌊lb n⌋)となる点が異なります。 たとえば、値が100(2)から111(2)の場合、Log2メソッドの結果はすべて2(10)となります。 Mathクラスを使った場合にMath.Floor(Math.Log(2.0, n))
とすることで得られる値が、Log2メソッドの結果となります。
Log2メソッドは、uint
/UInteger
, ulong
/ULong
の整数型のみに対して定義されています。 結果はint
/Integer
で返されます。
数値・文字列間での基数変換
ここでは、ビット演算に関連する事項として、2進・16進の数値・文字列を相互に変換する方法について解説します。
基数を指定した数値から文字列への変換
基数を指定して数値を文字列化するには、Convert.ToStringメソッドを使用します。 引数toBaseに基数を指定すると、その基数で数値を文字列化することができます。 基数には2, 8, 10, 16のいずれかを指定できます。
このメソッドでは0埋め・桁揃えをすることはできないので、そういった目的にはString.PadLeftメソッド等と組み合わせて使用します。 また、基数に16を指定した場合は、0
〜9
と小文字a
〜f
が用いられるため、16進数を大文字で表記したい場合はさらにString.ToUpperメソッドを組み合わせる必要があります。
10進数・16進数に限れば、ToStringメソッドやConsole.WriteLineメソッドなどの書式を指定できる文字列化メソッドを使うことでも文字列化することができます。 これらのメソッドで10進形式(D
)・16進形式(X
)を表す書式指定文字列を指定すれば、その書式に基づいて文字列化されます。 これらに加えて、.NET 8以降であれば2進形式(B
)の書式指定文字列も使用できます。
また、複合書式指定を使用することで、0埋め・桁揃えも同時に行うことができます。 複合書式指定は、Consoleクラス・StreamWriterクラス・StringBuilderクラスのWriteLine等のメソッドでサポートされます。 (書式指定子 §.書式化に対応したメソッドと書式指定子)
複合書式指定において、alignmentに正の値を指定すると右揃え、負の値を指定すると左揃えとなります。
複合書式指定は、文字列補間($"..."
形式の文字列リテラル内)でも使用することができます。 文字列補間は、C# 6.0/VB 14以降で使用できます。
基数を指定した文字列から数値への変換
基数を指定して文字列を数値化するには、Convert.ToInt32等のメソッドを使用することができます。 引数fromBaseに基数を指定すると、その基数で表記されている数値として文字列を数値化することができます。 基数には2, 8, 10, 16のいずれかを指定できます。
基数が10の場合において、例えば文字列2147483648
をToInt32メソッドで変換する場合は、値が型(Int32)の扱える範囲を超えるため、例外OverflowExceptionがスローされます。 一方、基数が10以外の場合、文字列はその型におけるメモリ上での表現であるものとして変換されます。 そのため、例えば文字列80000000
を基数16としてToInt32メソッドで数値に変換する場合は、オーバーフローとはならず、値-2147483648として変換されます。 ただしこの場合でも、値が型の扱える範囲を超えるような場合はOverflowExceptionがスローされます。
基数が10の場合に限り、符号+
および-
が前置された文字列の場合でも例外はスローされません。 それ以外の基数で符号が前置されている場合は、FormatExceptionとなります。 0埋めされた文字列(数値に桁揃えの0が先行する)場合でも、そのまま変換できます。 一方、前後に空白等が含まれる場合はFormatExceptionとなります。
このほか、0x
や&H
などのプレフィックスや、UL
などのサフィックスが含まれる文字列を数値化することはできません。 この場合FormatExceptionがスローされます。 また、桁区切り記号_
を含む文字列の場合も同様です。 プレフィックス・サフィックス・桁区切り文字が含まれる文字列を数値化したい場合は、事前に取り除いておく必要があります。
16進表記の場合はa
〜f
およびA
〜F
は大文字小文字が混在していても数値化することができます。
Convert.ToInt32メソッドは文字列をint
/Integer
に変換します。 このほかにも、変換先・値の型に合わせて以下のメソッドを使用することができます。
メソッド | 変換先の型 | |
---|---|---|
C# | VB | |
Convert.ToSByte |
sbyte
|
SByte
|
Convert.ToByte |
byte
|
Byte
|
Convert.ToInt16 |
short
|
Short
|
Convert.ToUInt16 |
ushort
|
UShort
|
Convert.ToInt32 |
int
|
Integer
|
Convert.ToUInt32 |
uint
|
UInteger
|
Convert.ToInt64 |
long
|
Long
|
Convert.ToUInt64 |
ulong
|
ULong
|
このほか、10進数に限れば、int.Parse/Integer.Parseメソッド(Int32.Parse)や、int.TryParse/Integer.TryParseメソッド(Int32.TryParse)を使うことで文字列から数値への変換ができます。 詳しくは基本型の型変換 §.文字列からの変換 (Parse, TryParse)を参照してください。
関連事項
任意長のビット列に対する演算
ビット演算子・BitOperationsクラスでは、固定長の整数型に対する演算のみが定義されています。 任意長のビット列に対してビット演算を扱いたい場合は、使用できる演算はNOT・AND等の基礎的な論理演算等に限られるものの、BitArrayクラスやBigInteger構造体を使用することができます。 詳しくは以下のページを参照してください。
集合演算
集合同士の和・積・差・対称差などの演算や包含関係の演算などを行いたい場合は、HashSet・SortedSetを使うことができます。 詳しくは以下のページを参照してください。