Main Content

ビット単位の演算

このトピックでは、MATLAB® でビット単位の演算を使用して数値のビットを操作する方法を説明します。ビットの演算は、ほとんどの新しい CPU で直接サポートされています。多くの場合、この方法で数値のビットを操作する方が、除算や乗算などの算術演算を実行するよりも高速です。

数値の表現

任意の数値はビット ("2 進数の桁" とも呼ばれる) で表現できます。2 進数 (基数 2) 形式の数値には 1 と 0 が含まれており、その数値に 2 のべき乗が存在することを示します。たとえば、8 ビットの 2 進数形式では、7 は次のようになります。

00000111

また、8 ビットの集まりは、1 "バイト" と呼ばれます。2 進数表現ではビット数は右から左へとカウントされるため、この表現では最初のビットが 1 になります。この数値が 7 を表すのは、以下に基づきます。

22+21+20=7.

MATLAB に数値を入力すると、数値は倍精度 (64 ビットの 2 進数表現) であると仮定されます。しかし、単精度数 (32 ビットの 2 進数表現) や整数 (符号付きまたは符号なし、8 ~ 64 ビット) を指定することもできます。たとえば、数値 7 を最も効率よくメモリに格納するには、8 ビット符号なし整数を使用します。

a = uint8(7)
a = uint8
    7

さらに、接頭辞 0b に続けて 2 進数の桁を使用し、2 進数形式を直接指定することもできます (詳細については、16 進数値と 2 進数値を参照)。MATLAB では、数値は最小のビット数の整数形式で保存されます。すべてのビットを指定するのではなく、最も左の 1 とその右側すべての桁のみを指定する必要があります。そのビットより左のビットが 0 であることは自明です。したがって、数値 7 は次のようになります。

b = 0b111
b = uint8
    7

MATLAB では、負の整数は 2 の補数を使用して保存されます。たとえば、8 ビットの符号付き整数の -8 について考えます。この数値について、2 の補数のビット パターンを求めるには次のようにします。

  1. まず、数値 8 が正の場合のビット パターン 00001000 を求めます。

  2. 次に、すべてのビットを反転し、11110111 にします。

  3. 最後に、結果に 1 を加算すると、11111000 になります。

結果の 11111000 が -8 のビット パターンです。

n = 0b11111000s8
n = int8
    -8

MATLAB では、2 進数形式の数値はネイティブに表示されません。これには、正の整数を表す 2 進数の文字ベクトルを返す関数dec2binを使用できます。この場合も、この関数は 0 ではないことが自明な桁のみを返します。

dec2bin(b)
ans = 
'111'

bin2decを使用して、2 つの形式を切り替えることができます。たとえば、次のコマンドを使用して 2 進数 10110101 を 10 進数形式に変換できます。

data = [1 0 1 1 0 1 0 1];
dec = bin2dec(num2str(data))
dec = 181

異なるデータ型間で切り替えるには、関数 cast と関数 typecast も便利です。これらの関数は似ていますが、根底にある数値のストレージの処理方法が異なります。

  • cast — 変数の基となるデータ型を変更します。

  • typecast — 基となるビットは変更せずにデータ型を変換します。

MATLAB では 2 進数の桁は直接表示されないため、ビット単位の演算を扱う場合はデータ型に注意しなければなりません。関数の中には、2 進数の桁を文字ベクトルで返すもの (dec2bin) や、10 進数で返すもの (bitand)、ビット自体のベクトルを返すもの (bitget) などがあります。

論理演算子を使ったビット マスク

MATLAB には、2 つの等しい長さの数値の 2 進数表現のビットに対する論理演算 ("ビット マスク" という) を実行可能にする関数がいくつかあります。

  • bitand"両方の" 桁が 1 であれば、結果の桁も 1 になります。それ以外の場合、結果の桁は 0 です。

  • bitor"いずれかの" 桁が 1 であれば、結果の桁も 1 になります。それ以外の場合、結果の桁は 0 です。

  • bitxor — 各桁が異なる場合、結果の桁は 1 になります。それ以外の場合、結果の桁は 0 です。

これらの関数のほか、bitcmpではビット単位の補数を使用できますが、これは単項演算で、一度に 1 つの数値でのみビットを反転します。

ビット マスクの使用法の 1 つは、特定のビットのステータスをクエリすることです。たとえば、ビット単位の AND 演算を 2 進数 00001000 で使用する場合、4 番目のビットのステータスをクエリできます。次に、そのビットを先頭位置にシフトし、MATLAB で 0 または 1 が返されるようにすることができます (ビット シフトの詳細については、次の節で説明)。

n = 0b10111001;
n4 = bitand(n,0b1000);
n4 = bitshift(n4,-3)
n4 = uint8
    1

ビット単位の演算には、意外な用途もあります。たとえば、数値 n=8 の 8 ビット 2 進数表現を考えます。

00001000

8 は 2 のべき乗であるため、その 2 進数表現には 1 が 1 つ含まれます。次に、数値 (n-1)=7 を考えます。

00000111

1 を減算することで、右端の 1 から始まるすべてのビットが反転されています。その結果、n が 2 のべき乗の場合、n(n-1) の対応する桁は常に異なり、ビット単位の AND は 0 を返します。

n = 0b1000;
bitand(n,n-1)
ans = uint8
    0

ただし、n が 2 のべき乗でない場合、右端の 1 は 20 ビットに使用されるため、n(n-1) では 20 ビット以外のビットがすべて同じになります。この場合、ビット単位の AND は非ゼロの数値を返します。

n = 0b101;
bitand(n,n-1)
ans = uint8
    4

この演算から、与えられた入力数値のビットに対して演算を行い、数値が 2 のべき乗かどうかをチェックする単純な関数が考えられます。

function tf = isPowerOfTwo(n)
  tf = n && ~bitand(n,n-1);
end

ショートサーキット AND 演算子 && のチェックを使用することで、n がゼロでないことを確認します。ゼロであれば、正しい答えが false であることを確認するために、関数で bitand(n,n-1) を計算する必要はありません。

ビットのシフト

ビット単位の論理演算では、2 つの数値の対応するビットが比較されるため、ビットを移動させて比較対象のビットを変更できると便利です。bitshiftを使用するとこの演算を実行できます。

  • bitshift(A,N)A のビットを "左"N 桁シフト。これは、A2N を "乗算する" ことと等価です。

  • bitshift(A,-N)A のビットを "右" に N 桁シフト。これは、A2N で "除算する" ことと等価です。

これらの演算は A<<N (左シフト) および A>>N (右シフト) と記述される場合がありますが、MATLAB では << および >> 演算子をこの目的では使用しません。

数値のビットがシフトされると、いくつかのビットは数値の端からはみ出し、新しく作成されたスペースは 0 または 1 で埋められます。ビットを左にシフトすると、右側のビットが埋められ、ビットを右にシフトすると、左側のビットが埋められます。

たとえば、数値 8 (2 進数: 1000) のビットを右に 1 桁シフトすると、4 (2 進数: 100) が得られます。

n = 0b1000;
bitshift(n,-1)
ans = uint8
    4

同様に、数値 15 (2 進数: 1111) を左に 2 桁シフトすると、60 (2 進数: 111100) が得られます。

n = 0b1111;
bitshift(15,2)
ans = 60

負の数値のビットをシフトする場合は、bitshift によって符号付きビットが保持されます。たとえば、符号付き整数 -3 (2 進数: 11111101) を右に 2 桁シフトすると、-1 (2 進数: 11111111) が得られます。このような場合、bitshift によって左側が 0 ではなく 1 で埋められます。

n = 0b11111101s8;
bitshift(n,-2)
ans = int8
    -1

ビットの書き込み

関数bitsetを使用して数値内のビットを変更できます。たとえば、数値 8 の最初のビットを 1 に変更します (これにより、数値に 1 が加算されます)。

bitset(8,1)
ans = 9

既定では、bitset はビットを "オン"、すなわち 1 に反転します。オプションで、3 番目の入力引数を使用してビット値を指定できます。

bitset で複数のビットが一度に変更されることはないため、複数のビットを変更するには for ループを使用する必要があります。そのため、変更するビットは連続であっても不連続であってもかまいません。たとえば、2 進数 1000 の最初の 2 ビットを変更します。

bits = [1 2];
c = 0b1000;
for k = 1:numel(bits)
    c = bitset(c,bits(k));
end
dec2bin(c)
ans = 
'1011'

bitset のもう 1 つの一般的用法は、2 進数のベクトルを 10 進数形式に変換することです。たとえば、ループを使用して整数 11001101 の個々のビットを設定します。

data = [1 1 0 0 1 1 0 1];
n = length(data);
dec = 0b0u8;
for k = 1:n
    dec = bitset(dec,n+1-k,data(k));
end
dec
dec = uint8
    205
dec2bin(dec)
ans = 
'11001101'

連続したビットの読み取り

ビット シフトのもう 1 つの使用法は、ビットの連続したセクションを分離することです。たとえば、16 ビット数値 0110000010100000 の最後の 4 ビットを読み取ります。最後の 4 ビットは、2 進数表現では "左" にあることを思い出してください。

n = 0b0110000010100000;
dec2bin(bitshift(n,-12))
ans = 
'110'

数値の途中にある連続ビットを分離するには、ビット シフトと論理マスクの用法を組み合わせることができます。たとえば、13 番目と 14 番目のビットを抽出するには、ビットを右に 12 シフトさせてから、結果の 4 ビットを 0011 でマスクします。bitand への入力は同じ整数データ型でなければならないため、0b11u16 を使って、0011 を符号なし 16 ビット整数として指定できます。-u16 接尾辞がない場合、MATLAB は数値を符号なし 8 ビット整数として保存します。

m = 0b11u16;
dec2bin(bitand(bitshift(n,-12),m))
ans = 
'10'

連続ビットを読み取るもう 1 つの方法は bitget を使用することで、これは指定したビットを数値から読み取ります。コロン表記法を使用し、読み取り対象とする複数の連続ビットを指定できます。たとえば、n の最後の 8 ビットを読み取ります。

bitget(n,16:-1:8)
ans = 1x9 uint16 row vector

   0   1   1   0   0   0   0   0   1

不連続のビットの読み取り

bitget を使用して、互いに隣り合っていないビットを数値から読み取ることもできます。たとえば、n から 5 番目、8 番目、14 番目のビットを読み取ります。

bits = [14 8 5];
bitget(n,bits)
ans = 1x3 uint16 row vector

   1   1   0

参考

| | | | | |

関連するトピック