web-dev-qa-db-ja.com

ビットシフト(bit-shift)演算子とは何ですか?またそれらはどのように機能しますか?

私は暇なときにCを学ぼうとしていました、そして他の言語(C#、Javaなど)も同じ概念を持っています(そしてしばしば同じ演算子を使用します)...

私が思っているのは、コアレベルでは、ビットシフト(<<>>>>>)は何をするのか、それはどのような問題を解決するのに役立つのでしょうか?言い換えれば、そのすべての長所を少し変えるための絶対的な初心者向けガイドです。

1294
John Rudy

ビットシフト演算子は、その名前が意味するとおりに動作します。ビットをシフトします。これは、さまざまなシフト演算子についての簡単な説明です。

オペレータ

  • >>は算術(または符号付き)右シフト演算子です。
  • >>>は、論理(または符号なし)右シフト演算子です。
  • <<は左シフト演算子で、論理シフトと算術シフトの両方のニーズを満たします。

これらの演算子はすべて整数値に適用できます(intlong、場合によってはshortおよびbyteまたはchar)。一部の言語では、intより小さいデータ型にシフト演算子を適用すると、オペランドのサイズが自動的にintに変更されます。

<<<は冗長であるため、演算子ではありません。また、CとC++は右シフト演算子を区別しません。これらは>>演算子のみを提供し、右シフトの振る舞いは符号付き型に対して定義された実装です。


左シフト(<<)

整数は一連のビットとしてメモリに格納されます。たとえば、32ビットのintとして格納されている数値6は次のようになります。

00000000 00000000 00000000 00000110

このビットパターンを1つ左の位置(6 << 1)にシフトすると、数値12になります。

00000000 00000000 00000000 00001100

ご覧のとおり、数字は1桁左にシフトし、右側の最後の数字はゼロで埋められています。また、左へのシフトは2のべき乗での乗算に相当します。したがって、6 << 16 * 2に相当し、6 << 36 * 8と等価です。最適化コンパイラとしては、可能であれば乗算をシフトに置き換えます。

非円形シフト

これらはnot循環シフトです。この値を1桁左にシフトする(3,758,096,384 << 1):

11100000 00000000 00000000 00000000

3,221,225,472の結果:

11000000 00000000 00000000 00000000

「最後まで」シフトした数字は失われます。それは回りません。


論理右シフト(>>>)

論理的な右シフトは、左シフトの逆です。ビットを左に移動するのではなく、単に右に移動します。たとえば、数字12をシフトすると、

00000000 00000000 00000000 00001100

1つ右に移動すると(12 >>> 1)、元の6に戻ります。

00000000 00000000 00000000 00000110

そのため、右にシフトすると2のべき乗で除算するのと同じことがわかります。

失われたビットは消えています

ただし、シフトで「失われた」ビットを取り戻すことはできません。たとえば、このパターンをシフトしたとします。

00111000 00000000 00000000 00000110

左の4桁(939,524,102 << 4)に行くと、2,147,483,744となります。

10000000 00000000 00000000 01100000

それから((939,524,102 << 4) >>> 4)シフトバックすると、134,217,734が得られます。

00001000 00000000 00000000 00000110

ビットを失うと元の値を取り戻すことはできません。


算術右シフト(>>)

算術右シフトは、ゼロでパディングする代わりに最上位ビットでパディングすることを除いて、論理右シフトとまったく同じです。これは、最上位ビットがsignビット、つまり正数と負数を区別するビットであるためです。最上位ビットを埋め込むことによって、算術右シフトは符号を保存します。

たとえば、このビットパターンを負の数として解釈すると、

10000000 00000000 00000000 01100000

-2,147,483,552です。算術シフト(-2,147,483,552 >> 4)でこれを右の4つの位置にシフトすると、次のようになります。

11111000 00000000 00000000 00000110

または-134,217,722の数。

したがって、論理右シフトではなく算術右シフトを使用して、負数の符号を保持していることがわかります。またしても、2のべき乗による除算を行っていることがわかります。

1619
Derek Park

単一のバイトがあるとしましょう:

0110110

単一の左ビットシフトを適用すると、次のようになります。

1101100

左端のゼロがバイトからシフトアウトされ、バイトの右端に新しいゼロが追加されました。

ビットはロールオーバーしません。それらは破棄されます。つまり、1101100を左にシフトしてから右にシフトすると、同じ結果は得られません。

Nだけ左にシフトすることは、2を乗算することと同じです。N

Nだけ右にシフトすることは( one'scomplement を使用している場合)は2で除算することと同等ですN ゼロに丸めます。

2のべき乗で作業している場合、ビットシフトは非常に高速な乗算と除算に使用できます。ほとんどすべての低レベルグラフィックルーチンはビットシフトを使用します。

たとえば、昔のことですが、ゲームにはモード13h(320x200 256色)を使用していました。モード13hでは、ビデオメモリはピクセルごとに順番にレイアウトされました。これは、ピクセルの位置を計算することを意味し、次の数学を使用します。

memoryOffset = (row * 320) + column

さて、その日と時代に戻って、速度が重要だったので、ビットシフトを使用してこの操作を行いました。

ただし、320は2のべき乗ではないため、これを回避するには、加算された2のべき乗とは何かを調べる必要があります。

(row * 320) = (row * 256) + (row * 64)

これを左シフトに変換できます:

(row * 320) = (row << 8) + (row << 6)

最終結果:

memoryOffset = ((row << 8) + (row << 6)) + column

今度は以前と同じオフセットを取得しますが、高価な乗算演算の代わりに2つのビットシフトを使用します...いくつかの間違いと32ビットの例を追加)):

mov ax, 320; 2 cycles
mul Word [row]; 22 CPU Cycles
mov di,ax; 2 cycles
add di, [column]; 2 cycles
; di = [row]*320 + [column]

; 16-bit addressing mode limitations:
; [di] is a valid addressing mode, but [ax] isn't, otherwise we could skip the last mov

合計:古代のCPUがこれらのタイミングを持っていたとしても28サイクル。

Vrs

mov ax, [row]; 2 cycles
mov di, ax; 2
shl ax, 6;  2
shl di, 8;  2
add di, ax; 2    (320 = 256+64)
add di, [column]; 2
; di = [row]*(256+64) + [column]

同じ古代のCPUで12サイクル。

はい、16 CPUサイクルを削減するために一生懸命働きます。

32ビットモードまたは64ビットモードでは、両方のバージョンが大幅に短縮され、高速になります。 Intel Skylake( http://agner.org/optimize/ を参照)のような最新のアウトオブオーダー実行CPUは、非常に高速なハードウェア乗算(低レイテンシおよび高スループット)を備えているため、ゲインははるかに小さくなります。 AMD Bulldozerファミリは、特に64ビット乗算の場合、少し遅くなります。 Intel CPUおよびAMD Ryzenでは、2つのシフトはレイテンシがわずかに低くなりますが、乗算よりも命令が多くなります(スループットが低下する可能性があります)。

imul edi, [row], 320    ; 3 cycle latency from [row] being ready
add  edi, [column]      ; 1 cycle latency (from [column] and edi being ready).
; edi = [row]*(256+64) + [column],  in 4 cycles from [row] being ready.

vs.

mov edi, [row]
shl edi, 6               ; row*64.   1 cycle latency
lea edi, [edi + edi*4]   ; row*(64 + 64*4).  1 cycle latency
add edi, [column]        ; 1 cycle latency from edi and [column] both being ready
; edi = [row]*(256+64) + [column],  in 3 cycles from [row] being ready.

コンパイラがこれを行います。 gcc、clang、およびMSVCがすべてreturn 320*row + col; を最適化するときにshift + leaを使用する方法を参照してください。

ここで注意すべき最も興味深いことは、 x86にはshift-and-add命令(LEA があり、小さな左シフトを実行し、同時に追加できることです。 add命令。 ARMはさらに強力です。任意の命令の1つのオペランドを自由に左または右にシフトできます。したがって、2のべき乗であることが知られているコンパイル時定数によるスケーリングは、乗算よりもさらに効率的です。


わかりました、現代に戻って...今より便利なのは、ビットシフトを使用して2つの8ビット値を16ビット整数に格納することです。たとえば、C#の場合:

// Byte1: 11110000
// Byte2: 00001111

Int16 value = ((byte)(Byte1 >> 8) | Byte2));

// value = 000011111110000;

C++では、2つの8ビットメンバーでstructを使用した場合、コンパイラがこれを行う必要がありますが、実際には常にそうとは限りません。

197
FlySwat

ビットシフトを含むビット単位の操作は、低レベルのハードウェアまたは組み込みプログラミングにとって基本です。デバイスの仕様やバイナリファイル形式を読むと、バイト、ワード、およびdwordが、バイト単位で整列されていないビットフィールドに分割されています。読み書きのためにこれらのビットフィールドにアクセスするのが最も一般的な使い方です。

グラフィックプログラミングの簡単な実例は、16ビットピクセルが次のように表されることです。

  bit | 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1  | 0 |
      |       Blue        |         Green         |       Red          |

緑色の値を取得するには、次のようにします。

 #define GREEN_MASK  0x7E0
 #define GREEN_OFFSET  5

 // Read green
 uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;

説明

オフセット5で始まり10(つまり6ビット長)で終わる緑色の値のみを取得するには、(ビット)マスクを使用する必要があります。これを16ビットピクセル全体に適用すると、興味のある部分だけ.

#define GREEN_MASK  0x7E0

適切なマスクは0x7E0で、これは2進数で0000011111100000(10進数で2016)です。

uint16_t green = (pixel & GREEN_MASK) ...;

マスクを適用するには、AND演算子(&)を使います。

uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;

マスクを適用した後、そのMSBが11番目のビットにあるので、実際にはちょうど11ビットの数値である16ビットの数値になります。 Greenは実際には6ビット長なので、右シフト(11 - 6 = 5)を使用して縮小する必要があります。したがって、offset(#define GREEN_OFFSET 5)として5を使用します。

高速乗算と2のべき乗による除算にビットシフトを使用することも一般的です。

 i <<= x;  // i *= 2^x;
 i >>= y;  // i /= 2^y;
95
robottobor

ビットマスキングとシフト

ビットシフトは、低レベルのグラフィックプログラミングでよく使用されます。たとえば、32ビットのWordでエンコードされた特定のピクセルカラー値。

 Pixel-Color Value in Hex:    B9B9B900
 Pixel-Color Value in Binary: 10111001  10111001  10111001  00000000

よりよく理解するために、どのセクションでラベル付けされた同じ2進値は、どの色部分を表します。

                                 Red     Green     Blue       Alpha
 Pixel-Color Value in Binary: 10111001  10111001  10111001  00000000

たとえば、このピクセルカラーの緑色の値を取得したいとしましょう。その値は マスキングシフト で簡単に得ることができます。

私たちのマスク:

                  Red      Green      Blue      Alpha
 color :        10111001  10111001  10111001  00000000
 green_mask  :  00000000  11111111  00000000  00000000

 masked_color = color & green_mask

 masked_color:  00000000  10111001  00000000  00000000

論理的な&演算子は、マスクが1の値だけが保持されるようにします。今しなければならない最後のことは、それらのすべてのビットを16桁右にシフトすることによって正しい整数値を取得することです (論理右シフト)

 green_value = masked_color >>> 16

Etvoilá、ピクセルの色で緑の量を表す整数があります。

 Pixels-Green Value in Hex:     000000B9
 Pixels-Green Value in Binary:  00000000 00000000 00000000 10111001 
 Pixels-Green Value in Decimal: 185

これは、jpgpng...などの画像フォーマットのエンコードまたはデコードによく使用されます。

47
Basti Funck

1つの落とし穴は、次のものが実装に依存していることです(ANSI規格に準拠)。

char x = -1;
x >> 1;

xは127(01111111)または-1(11111111)のままです。

実際には、それは通常後者です。

27
AShelly

私はヒントやコツだけを書いていますが、テスト/試験に役立つことがあります。

  1. n = n*2n = n<<1
  2. n = n/2n = n>>1
  3. Nが2の累乗(1、2、4、8、...)であるかどうかをチェックする:!(n & (n-1))をチェックする
  4. 取得 x 番目 nの一部:n |= (1 << x)
  5. Xが偶数か奇数かをチェックする:x&1 == 0(even)
  6. n を切り替えます番目 xのビット:x ^ (1<<n)
15
Ravi Prakash

Javaの実装では、シフトするビット数はソースのサイズによって変更されます。

例えば:

(long) 4 >> 65

2に等しいです。ビットを65回右にシフトすると、すべてがゼロになると予想されるかもしれませんが、実際には次のようになります。

(long) 4 >> (65 % 64)

これは<<、>>、>>>にも当てはまります。他の言語で試したことはありません。

8

Pythonでの便利なビット操作/操作Pythonで@Ravi Prakash回答を実装しました。

# basic bit operations
# int to bin
print(bin(10))

# bin to int
print(int('1010',2))

# multiplying x with 2 .... x**2== x << 1
print(200<<1)

# dividing x with 2 .... x /2 == x >> 1
print(200>>1)

# modulo x with 2 .... x%2 == x&1
if 20&1==0:
    print("20 is a even number")

# check if n is power of 2 : check !(n & (n-1))
print(not(33 &(33-1)))

# getting xth bit of n : (n>>x)&1
print((10>>2)&1) # bin of 10==1010 and 2nd bit is 0

# toggle nth bit of x : x^(1<<n)
# take bin(10)==1010 and toggling 2nd bit in bin(10) we get 1110 === bin(14)
print(10^(1<<2)) 
0
Amin Ahmed