System.Collections.BitArrayクラスはビット列を操作するためのクラスで、任意長かつ固定長のビット列を扱うことができます。 BitArrayクラスでは、個々のビット(桁)をbool
/Boolean
型として扱います。
BitArrayクラスは、bool型のコレクションの形でビット列を表現するクラスとも言えます。 BitArrayクラスには、ビット単位の操作のほか、AND演算・OR演算などのビット演算を提供するメソッドが用意されています。
BitArrayクラスは非ジェネリックインターフェスのみを持つコレクションクラスですが、扱う型がbool型に限定されているため、foreachによる列挙(IEnumerableインターフェイス)以外の操作はタイプセーフとなっているコレクションクラスです。
BitArray
BitArrayクラスは可変長のコレクションではなく、固定長のコレクションとして動作します。 つまり、固定長の桁数のビット列を扱います。 他のコレクションとは異なり、Addなどのメソッドによって要素(=ビット列の桁)を追加・削除したりコレクションのサイズ(=ビット列の桁数)を変更したりすることはできません。
BitArrayクラスでは、ビット列となるbyte型、int型、bool型のいずれかの配列をコンストラクタに指定してインスタンスを作成します。 int型・byte型の配列では、配列内の各要素の各ビットがBitArrayインスタンスに設定されます。 bool型配列では、各要素が1ビットを表すものとしてBitArrayインスタンスに設定されます。 このほかに、ビット列の桁数を指定してインスタンスを作成することもできます。
BitArrayクラスをforeach文で列挙すると、ビットの各桁がbool型の値(0=False
、1=True
)として列挙されます。 下位の桁から列挙されるため、ビット順としてはリトルエンディアンとなります。 Countプロパティを参照すれば、BitArrayの現在の要素数(=桁数)を取得することができます。
using System;
using System.Collections;
class Sample {
static void Main()
{
BitArray bits1 = new BitArray(new byte[] {0xff, 0x00, 0xcc, 0xaa}); // byte配列(8×4ビットのビット列)からBitArrayを生成
BitArray bits2 = new BitArray(new int[] {0x00ffccaa, 0x0000ffff}); // int配列(32×2ビットのビット列)からBitArrayを生成
BitArray bits3 = new BitArray(new bool[] {true, false, true, false}); // bool配列(1×4ビットのビット列)からBitArrayを生成
BitArray bits4 = new BitArray(32, true); // 32ビットのビット列を扱うBitArrayを生成 (すべてのビットを1にする)
BitArray bits5 = new BitArray(64, false); // 64ビットのビット列を扱うBitArrayを生成 (すべてのビットを0にする)
Print(bits1);
Print(bits2);
Print(bits3);
Print(bits4);
Print(bits5);
}
static void Print(BitArray bits)
{
Console.Write("{0} bits: ", bits.Count);
// foreach文によってBitArrayの各桁の内容を表示する
foreach (bool bit in bits) {
Console.Write(bit ? "1" : "0");
}
Console.WriteLine();
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
Dim bits1 As New BitArray(New Byte() {&hFF, &h00, &hCC, &hAA}) ' Byte配列(8×4ビットのビット列)からBitArrayを生成
Dim bits2 As New BitArray(New Integer() {&h00FFCCAA, &h0000FFFF}) ' Integer配列(32×2ビットのビット列)からBitArrayを生成
Dim bits3 As New BitArray(New Boolean() {True, False, True, False}) ' Boolean配列(1×4ビットのビット列)からBitArrayを生成
Dim bits4 As New BitArray(32, True) ' 32ビットのビット列を扱うBitArrayを生成 (すべてのビットを1にする)
Dim bits5 As New BitArray(64, False) ' 64ビットのビット列を扱うBitArrayを生成 (すべてのビットを0にする)
Print(bits1)
Print(bits2)
Print(bits3)
Print(bits4)
Print(bits5)
End Sub
Shared Sub Print(ByVal bits As BitArray)
Console.Write("{0} bits: ", bits.Count)
' foreach文によってBitArrayの各桁の内容を表示する
For Each bit As Boolean In bits
Console.Write(If(bit, "1", "0"))
Next
Console.WriteLine()
End Sub
End Class
32 bits: 11111111000000000011001101010101 64 bits: 0101010100110011111111110000000011111111111111110000000000000000 4 bits: 1010 32 bits: 11111111111111111111111111111111 64 bits: 0000000000000000000000000000000000000000000000000000000000000000
ビットの取得・設定
BitArrayクラスでは、他のコレクションと同様にインデクサによってインデックス(=桁)を指定してビット値を取得・設定することができます。
using System;
using System.Collections;
class Sample {
static void Main()
{
// すべてのビットがfalse(0)でサイズが8ビットのBitArrayを作成
BitArray bits = new BitArray(8, false);
bits[0] = true; // 0ビット目をtrue(1)にする
bits[2] = true; // 2ビット目をtrue(1)にする
// for文によってすべてのビットを表示する (リトルエンディアン)
for (int i = 0; i < bits.Count; i++) {
Console.Write(bits[i] ? 1 : 0);
}
Console.WriteLine();
// for文によってすべてのビットを表示する (ビッグエンディアン)
for (int i = bits.Count - 1; 0 <= i; i--) {
Console.Write(bits[i] ? 1 : 0);
}
Console.WriteLine();
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
' すべてのビットがfalse(0)でサイズが8ビットのBitArrayを作成
Dim bits As New BitArray(8, False)
bits(0) = True ' 0ビット目をTrue(1)にする
bits(2) = True ' 2ビット目をTrue(1)にする
' for文によってすべてのビットを表示する (リトルエンディアン)
For i As Integer = 0 To bits.Count - 1
Console.Write(If(bits(i), 1, 0))
Next
Console.WriteLine()
' for文によってすべてのビットを表示する (ビッグエンディアン)
For i As Integer = bits.Count - 1 To 0 Step -1
Console.Write(If(bits(i), 1, 0))
Next
Console.WriteLine()
End Sub
End Class
10100000 00000101
インデックスを指定した取得・設定以外にも、Getメソッド・Setメソッドによってもビット値を取得・設定することができます。 また、SetAllメソッドを使えば、BitArray内のすべてのビット値を同じ値にすることもできます。
using System;
using System.Collections;
class Sample {
static void Main()
{
// すべてのビットがfalse(0)でサイズが8ビットのBitArrayを作成
BitArray bits = new BitArray(8, false);
bits.Set(0, true); // 0ビット目をtrue(1)にする
bits.Set(2, true); // 2ビット目をtrue(1)にする
Console.WriteLine(bits.Get(0)); // 0ビット目のビット値を取得する
Console.WriteLine(bits.Get(1)); // 1ビット目のビット値を取得する
bits.SetAll(true); // すべてのビットをtrue(1)にする
foreach (bool bit in bits) {
Console.Write(bit ? 1 : 0);
}
Console.WriteLine();
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
' すべてのビットがfalse(0)でサイズが8ビットのBitArrayを作成
Dim bits As New BitArray(8, False)
bits.Set(0, True) ' 0ビット目をTrue(1)にする
bits.Set(2, True) ' 2ビット目をTrue(1)にする
Console.WriteLine(bits.Get(0)) ' 0ビット目のビット値を取得する
Console.WriteLine(bits.Get(1)) ' 1ビット目のビット値を取得する
bits.SetAll(True) ' すべてのビットをTrue(1)にする
For Each bit As Boolean in bits
Console.Write(If(bit, 1, 0))
Next
Console.WriteLine()
End Sub
End Class
True False 11111111
ビット演算
BitArrayクラスでは、各種ビット演算に対応する以下のメソッドが用意されています。 引数に他のビット列を表すBitArrayを指定することで、そのBitArrayとの演算結果を求めることができます。
メソッド | 動作 |
---|---|
Notメソッド | すべての桁のビットを反転する |
Orメソッド | 指定されたBitArrayとの論理和を求める |
Andメソッド | 指定されたBitArrayとの論理積を求める |
Xorメソッド | 指定されたBitArrayとの排他的論理和を求める |
上記のメソッドによる演算では、メソッドを呼び出したインスタンス自身に演算結果が反映され、さらにインスタンス自身を戻り値として返します。 以下は、上記メソッドを使ってビット演算を行う例です。
using System;
using System.Collections;
class sample {
static void Main()
{
// 000000001111111100000000のビット列を持つ24ビットのBitArrayを作成
BitArray bits = new BitArray(new byte[] {0x00, 0xff, 0x00});
Print(bits);
// 全ビットを反転
Print(bits.Not());
// 排他的論理和を求める
Print(bits.Xor(new BitArray(new byte[] {0xaa, 0xaa, 0xaa})));
}
static void Print(BitArray bits)
{
Console.Write("{0} bits: ", bits.Count);
foreach (bool bit in bits) {
Console.Write(bit ? 1 : 0);
}
Console.WriteLine();
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
' 000000001111111100000000のビット列を持つ24ビットのBitArrayを作成
Dim bits As New BitArray(New Byte() {&h00, &hFF, &h00})
Print(bits)
' 全ビットを反転
Print(bits.Not())
' 排他的論理和を求める
Print(bits.Xor(New BitArray(New Byte() {&hAA, &hAA, &hAA})))
End Sub
Shared Sub Print(ByVal bits As BitArray)
Console.Write("{0} bits: ", bits.Count)
For Each bit As Boolean In bits
Console.Write(If(bit, 1, 0))
Next
Console.WriteLine()
End Sub
End Class
24 bits: 000000001111111100000000 24 bits: 111111110000000011111111 24 bits: 101010100101010110101010
シフト演算
BitArrayクラスでは、ビットシフトを行うメソッドとして、以下の2つのメソッドが用意されています。 ただし、このメソッドは.NET Standard 2.1/.NET Core 2.0以降でのみ使用できます。
メソッド | 動作 |
---|---|
LeftShiftメソッド | 左シフト演算を行う |
RightShiftメソッド | 右シフト演算を行う |
いずれのメソッドも、シフト量を引数で指定することができます。 このメソッドでは、シフトによって欠ける桁には常に0
が補われる論理シフトとして動作します。 符号ビットは存在しないものとされ、考慮されません。
using System;
using System.Collections;
class sample {
static void Main()
{
// 11001100のビット列を持つ8ビットのBitArrayを作成
var bits = new BitArray(new byte[] {0xcc});
Print(bits);
// ビット列を左方向に1ビット分シフトする
bits.LeftShift(1);
Print(bits);
// ビット列を右方向に2ビット分シフトする
bits.RightShift(2);
Print(bits);
}
static void Print(BitArray bits)
{
Console.Write("{0} bits: ", bits.Count);
foreach (bool bit in bits) {
Console.Write(bit ? 1 : 0);
}
Console.WriteLine();
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
' 11001100のビット列を持つ8ビットのBitArrayを作成
Dim bits As New BitArray(New Byte() {&hCC})
Print(bits)
' ビット列を左方向に1ビット分シフトする
bits.LeftShift(1)
Print(bits)
' ビット列を右方向に2ビット分シフトする
bits.RightShift(2)
Print(bits)
End Sub
Shared Sub Print(ByVal bits As BitArray)
Console.Write("{0} bits: ", bits.Count)
For Each bit As Boolean In bits
Console.Write(If(bit, 1, 0))
Next
Console.WriteLine()
End Sub
End Class
8 bits: 00110011 8 bits: 00011001 8 bits: 01100100
要素の列挙順の都合により、シフト方向と実際にシフトされるビットの方向が逆になっています。
なお、回転シフト・循環シフトに相当する演算は用意されていません。 そのため、そのような操作を行う場合は独自に実装する必要があります。
using System;
using System.Collections;
class sample {
static void Main()
{
// 11001100のビット列を持つ8ビットのBitArrayを作成
BitArray bits = new BitArray(new byte[] {0xcc});
Print(bits);
// ビット列を右回転する
BitArray bits2 = new BitArray(bits.Count);
for (int i = 0; i < bits.Count; i++) {
bits2[(i + 1) % bits.Count] = bits[i];
}
Print(bits2);
}
static void Print(BitArray bits)
{
Console.Write("{0} bits: ", bits.Count);
foreach (bool bit in bits) {
Console.Write(bit ? 1 : 0);
}
Console.WriteLine();
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
' 11001100のビット列を持つ8ビットのBitArrayを作成
Dim bits As New BitArray(New Byte() {&hCC})
Print(bits)
' ビット列を右回転する
Dim bits2 As New BitArray(bits.Count)
For i As Integer = 0 To bits.Count - 1
bits2((i + 1) Mod bits.Count) = bits(i)
Next
Print(bits2)
End Sub
Shared Sub Print(ByVal bits As BitArray)
Console.Write("{0} bits: ", bits.Count)
For Each bit As Boolean In bits
Console.Write(If(bit, 1, 0))
Next
Console.WriteLine()
End Sub
End Class
8 bits: 00110011 8 bits: 10011001
その他のビット演算
先行する/後続する0のビット数 (LZCNT/TZCNT)
BitOperationsクラスでは、先行する・後続する0のビットの数を計上するLeadingZeroCountメソッド・TrailingZeroCountメソッドを使用することができます。
これと同様の演算を行うメソッドはBitArrayクラスには用意されていませんが、次のようにして実装することができます。
using System;
using System.Collections;
using System.Linq;
static class Sample {
static void Main()
{
var bitArray = new BitArray(new byte[] {0b_0000_0000, 0b_0000_0001, 0b_1000_0000, 0b_0000_0000});
int lzcnt = bitArray.Cast<bool>().TakeWhile(bit => bit == false).Count();
int tzcnt = bitArray.Cast<bool>().Reverse().TakeWhile(bit => bit == false).Count();
Console.WriteLine(lzcnt);
Console.WriteLine(tzcnt);
}
}
Imports System
Imports System.Collections
Imports System.Linq
Module Sample
Sub Main()
Dim bitArray As New BitArray(New Byte() {&B_0000_0000, &B_0000_0001, &B_1000_0000, &B_0000_0000})
Dim lzcnt As Integer = bitArray.Cast(Of Boolean)().TakeWhile(Function(bit As Boolean)
Return bit = False
End Function).Count()
Dim tzcnt As Integer = bitArray.Cast(Of Boolean)().Reverse().TakeWhile(Function(bit As Boolean)
Return bit = False
End Function).Count()
Console.WriteLine(lzcnt)
Console.WriteLine(tzcnt)
End Sub
End Module
立っているビットの数 (POPCNT)
BitOperationsクラスでは、立っているビットの数(1のビットの数)を計上するPopCountメソッドを使用することができます。
これと同様の演算を行うメソッドはBitArrayクラスには用意されていませんが、次のようにして実装することができます。
using System;
using System.Collections;
using System.Linq;
static class Sample {
static void Main()
{
var bitArray = new BitArray(new byte[] {0b_1111_0000, 0b_1010_0000, 0b_1000_0100, 0b_0010_0001});
int popcnt = bitArray.Cast<bool>().Count(bit => bit == true);
Console.WriteLine(popcnt);
}
}
Imports System
Imports System.Collections
Imports System.Linq
Module Sample
Sub Main()
Dim bitArray As New BitArray(New Byte() {&B_1111_0000, &B_1010_0000, &B_1000_0100, &B_0010_0001})
Dim popcnt As Integer = bitArray.Cast(Of Boolean)().Count(Function(bit As Boolean)
Return bit = True
End Function)
Console.WriteLine(popcnt)
End Sub
End Module
ビット列の配列へのコピー
CopyToメソッドを使うと、BitArrayのビット列を配列にコピーすることができます。 このメソッドはコンストラクタと逆の動作をします。 つまり、int[]
やbyte[]
をコピー先とした場合は、ビット列の内容がそのままint・byteのビット列としてコピーされます。 bool[]
の場合は、各ビットの値が一つのbool値として配列にコピーされます。 これ以外の配列をコピー先とすることはできず、そういった配列を指定した場合は例外ArgumentExceptionがスローされます。
コピー先の配列のほかに、CopyToメソッドではコピー先のインデックスを指定します。 0
を指定すれば、配列の先頭にコピーします。
using System;
using System.Collections;
using System.Linq;
class sample {
static void Main()
{
BitArray bits = new BitArray(new int[] {0x01234567, unchecked((int)0x89abcdef)}); // 32×2=64ビットのビット列
// int型配列にコピーする
int[] ints = new int[2]; // 32×2ビット分
bits.CopyTo(ints, 0);
// 配列の各要素を8桁の16進数として表示
Console.WriteLine("ints: {0}", string.Join(" ", ints.Select(i => i.ToString("X8"))));
// byte型配列にコピーする
byte[] bytes = new byte[8]; // 8×8ビット分
bits.CopyTo(bytes, 0);
// 配列の各要素を2桁の16進数として表示
Console.WriteLine("bytes: {0}", string.Join(" ", bytes.Select(by => by.ToString("X2"))));
// bool型配列にコピーする
bool[] bools = new bool[64]; // 1×64ビット分
bits.CopyTo(bools, 0);
// 配列の各要素を0または1で表示
Console.WriteLine("bools: {0}", string.Concat(bools.Select(b => b ? "1" : "0")));
}
}
Imports System
Imports System.Collections
Imports System.Linq
Class Sample
Shared Sub Main()
Dim bits As New BitArray(New Integer() {&H01234567, &H89ABCDEF}) ' 32×2=64ビットのビット列
' Integer型配列にコピーする
Dim ints(1) As Integer ' 32×2ビット分
bits.CopyTo(ints, 0)
' 配列の各要素を8桁の16進数として表示
Console.WriteLine("ints: {0}", String.Join(" ", ints.Select(Function(i) i.ToString("X8"))))
' Byte型配列にコピーする
Dim bytes(7) As Byte ' 8×8ビット分
bits.CopyTo(bytes, 0)
' 配列の各要素を2桁の16進数として表示
Console.WriteLine("bytes: {0}", String.Join(" ", bytes.Select(Function(by) by.ToString("X2"))))
' Boolean型配列にコピーする
Dim bools(63) As Boolean ' 1×64ビット分
bits.CopyTo(bools, 0)
' 配列の各要素を0または1で表示
Console.WriteLine("bools: {0}", String.Concat(bools.Select(Function(b) If(b, "1", "0"))))
End Sub
End Class
ints: 01234567 89ABCDEF bytes: 67 45 23 01 EF CD AB 89 bools: 1110011010100010110001001000000011110111101100111101010110010001
任意の桁数のビット列
BitArrayクラスでは、桁数を指定してビット列を作成できます。 このとき、桁数として2n以外や奇数を指定することもできます。 また、桁数に0を指定することもできます。 0を指定した場合でもArgumentOutOfRangeExceptionなどの例外はスローされません。
using System;
using System.Collections;
class sample {
static void Main()
{
BitArray bits3 = new BitArray(3); // 3桁のビット列
BitArray bits7 = new BitArray(7); // 7桁のビット列
BitArray bits24 = new BitArray(24); // 24桁のビット列
BitArray bits0 = new BitArray(0); // 0桁のビット列
}
}
Imports System
Imports System.Collections
Class Sample
Shared Sub Main()
Dim bits3 As New BitArray(3) ' 3桁のビット列
Dim bits7 As New BitArray(7) ' 7桁のビット列
Dim bits24 As New BitArray(24) ' 24桁のビット列
Dim bits0 As New BitArray(0) ' 0桁のビット列
End Sub
End Class
BitArrayとその他のクラス
BitArrayクラスでは、内部的には32ビット整数によって各ビットが管理されるようですが、直接整数型を使ってビット演算を行う場合に比べると若干のオーバーヘッドがあります。 ただ、例えば64ビット(long
)を超えるような長大な桁数のビット列や、2のべき乗でない桁数のビット演算を行いたい場合には多少の利点があります。
長大な桁数の数を扱いたい場合はBigInteger構造体を使うこともできます。 ただし、この構造体ではANDやORなどのビット演算を行うことはできません。 BigInteger構造体については多倍長整数型を参照してください。
数値や数値型配列のビット表現・バイト列を取得したい場合は、BufferクラスやBitConverterクラスを用いることができます。 詳しくはバイト列操作を参照してください。