ビット単位のシフト(ビットシフト)演算子とは何ですか?


1382

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

私は思ったんだけどは、コアレベルでは、何が(ビットシフトんされ<<>>>>>)、それが解決する何の問題を助けることができる、そしてどのような落とし穴は、曲げの周りに潜んでいるのですか?言い換えれば、そのすべての良さをビットシフトするための絶対初心者向けガイドです。


2
3GLでビットシフトを使用する機能的または非機能的なケースはほとんどありません。
Troy DeMonbreun 2008

15
:これらの答えを読んだ後、あなたはこれらのリンクを見てみたいことがあり graphics.stanford.edu/~seander/bithacks.htmljjj.de/bitwizardry/bitwizardrypage.html

1
コンピュータにとってビットシフトは非常に簡単で高速であることに注意することが重要です。プログラムでビットシフトを使用する方法を見つけることにより、メモリの使用量と実行時間を大幅に削減できます。
Hoytman

@Hoytman:ただし、優れたコンパイラーはこれらのトリックの多くをすでに知っており、通常、意味のある場所を認識するのに優れています。
セバスチャンマッハ

回答:


1713

ビットシフト演算子は、その名前が示すとおりに動作します。彼らはビットをシフトします。以下は、さまざまなシフト演算子の簡単な(またはそれほど簡単ではない)紹介です。

オペレーター

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

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

<<<は冗長になるため、は演算子ではありません。

また、CおよびC ++は右シフト演算子を区別しないことに注意してください。これらは>>演算子のみを提供し、右シフト動作は符号付きの型に対して定義された実装です。残りの回答では、C#/ Java演算子を使用しています。

(GCCおよびClang / LLVMを含むすべての主流のCおよびC ++実装では>>、符号付きの型は算術です。一部のコードはこれを前提としていますが、これは標準で保証されているものではありません。ただし、undefinedではありません。しかし、負の符号付き数値の左シフト未定義の動作です(符号付き整数オーバーフロー)。したがって、算術右シフトが必要でない限り、通常は符号なしの型でビットシフトを行うことをお勧めします。)


左シフト(<<)

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

00000000 00000000 00000000 00000110

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

00000000 00000000 00000000 00001100

ご覧のとおり、数字は左に1桁シフトしており、右側の最後の数字はゼロで埋められています。左シフトは2のべき乗による乗算と同等であることに注意してください。したがって、と6 << 1同等であり6 * 2、と6 << 3同等6 * 8です。優れた最適化コンパイラは、可能であれば乗算をシフトで置き換えます。

非循環シフト

これらは循環シフトではないことに注意してください。この値を左に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

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


算術右シフト(>>)

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

たとえば、このビットパターンを負の数として解釈すると、次のようになります。

10000000 00000000 00000000 01100000

番号は-2,147,483,552です。これを算術シフト(-2,147,483,552 >> 4)で右4ポジションにシフトすると、次のようになります。

11111000 00000000 00000000 00000110

または番号-134,217,722。

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


304
答えは、これがJava固有の答えであることをより明確にするはずです。C / C ++またはC#には>>>演算子はなく、>>が符号を伝播するかどうかは、C / C ++で定義された実装(主要な潜在的な問題)
Michael Burr

56
C言語のコンテキストでは、答えは完全に正しくありません。Cでは「算術」シフトと「論理」シフトに意味のある分割はありません。Cでは、シフトは符号なしの値と正の符号付きの値で期待どおりに機能します。ビットをシフトするだけです。負の値の場合、右シフトは実装で定義され(つまり、一般に何が行われるかについては何も言えません)、左シフトは単純に禁止されます-未定義の動作を生成します。
AnT

10
オードリー、算術と論理右シフトの間には確かに違いがあります。Cは単に選択の実装を定義したままにします。負の値の左シフトは絶対に禁止されていません。0xff000000を1ビット左にシフトすると、0xfe000000が得られます。
デレクパーク、

16
A good optimizing compiler will substitute shifts for multiplications when possible. 何?Bitshiftsは、それがCPUの低レベルの操作にダウンしていたときに、優れた最適化コンパイラはどうしたら速く桁違いに正確なビットシフトに2のべき乗で、通常の乗算を回し、ある反対を。
Mahn

55
@マーン、あなたは私の意図からそれを逆に読んでいます。XをYに置き換えるとは、XをYに置き換えることを意味します。YはXの代わりです。したがって、シフトは乗算の代わりになります。
デレクパーク

209

シングルバイトがあるとしましょう:

0110110

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

1101100

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

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

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

Nだけ右シフトすることは(1の補数を使用している場合)、2 Nで除算してゼロに丸めることと同じです。

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

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

memoryOffset = (row * 320) + column

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

ただし、320は2の累乗ではないため、これを回避するには、2の累乗を加算すると320になります。

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

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

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

最終結果:

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

これで、以前と同じオフセットが得られますが、高価な乗算演算の代わりに、2つのビットシフトを使用します... x86では、次のようなものになります(注、私が組み立てを行って以来ずっとずっと続いています(編集者注:修正済み)。いくつかのミスと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サイクル。

VR

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.

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、およびMicrosoft Visual C ++がすべてshift + leareturn 320*row + col;をどのように使用するかを確認してください。

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


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

// Byte1: 11110000
// Byte2: 00001111

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

// value = 000011111110000;

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


8
これを拡張すると、Intelプロセッサ(および他の多くのプロセッサ)では、これを実行する方が高速です:int c、d; c = d << 2; これより:c = 4 * d; 時々、 "c = d << 2 + d << 1"でも "c = 6 * d"より速い!! 私はこれらのトリックをDOS時代のグラフィック機能に幅広く使用しましたが、もうそれほど便利ではないと思います...
Joe Pineda '26

5
@James:不正解です。現在では、CPUではなくGPUによって実行されるのは、このようなコードを含むビデオカードのファームウェアです。したがって、理論的には、グラフィック関数にこのようなコード(またはカーマックのブラックマジック逆ルート関数のような)を実装する必要はありません:-)
Joe Pineda

3
@JoePineda @jamesコンパイラ作成者は間違いなくそれらを使用しています。あなたが書けc=4*dばあなたはシフトを得るでしょう。あなたが書く場合k = (n<0)、それもシフトで行われるかもしれません:k = (n>>31)&1分岐を避けるため。結論として、このコンパイラの巧妙さの向上は、Cコードでこれらのトリックを使用する必要がなくなり、読みやすさと移植性が損なわれることを意味します。たとえばSSEベクトルコードを記述している場合は、それらを知ることは非常に良いことです。または、それを高速に必要とし、コンパイラーが使用していないトリック(GPUコードなど)がある状況。
greggo 2014年

2
もう一つの良い例:非常に一般的なものがされif(x >= 1 && x <= 9)て行うことができif( (unsigned)(x-1) <=(unsigned)(9-1)) 、大きな速度の利点することができ一から二条件テストを変更します。特に、ブランチの代わりに述語実行を許可する場合。10年前にコンパイラがオプティマイザでこの変換を開始したことに気づくまで、私はこれを何年も(正当化された場合)使用し、その後停止しました。コンパイラはあなたのために変換を行うことができない同様の状況があるので、まだ知っておくと良いでしょう。または、コンパイラに取り組んでいる場合。
greggo 2014年

3
「バイト」が7ビットしかない理由はありますか?
メイソンワトモフ2016年

104

ビットシフトを含むビット単位の演算は、低レベルのハードウェアまたは組み込みプログラミングの基本です。デバイスの仕様または一部のバイナリファイル形式を読み取ると、バイト、ワード、および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で、これはバイナリでは0000011111100000(10進数では2016)です。

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

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

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

マスクを適用すると、MSBが11番目のビットにあるため、16ビットの数値になり、これは実際には11ビットの数値にすぎません。緑は実際には6ビット長しかないため、右シフト(11-6 = 5)を使用して縮小する必要があるため、オフセットとして5を使用します(#define GREEN_OFFSET 5)。

また、ビット乗算を使用して、2の累乗で乗算および除算を高速に行うことも一般的です。

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

1
0x7e0は、11111100000(10進数で2016)と同じです。
Saheed 2015年

50

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

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

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

よりよく理解するために、どのセクションでラベル付けされた同じバイナリ値は、どの色の部分を表すか

                                 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

ちなみに、ピクセルの色の緑の量を表す整数があります。

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

これは多くの場合のような画像フォーマットを符号化または復号のために使用されるjpgpng


オリジナル、たとえば32ビットのcl_uintをcl_uchar4のようにキャストして、必要なバイトに* .s2として直接アクセスする方が簡単ではありませんか?
David H Parry、

27

1つの落とし穴は、以下が(ANSI標準に従って)実装に依存することです。

char x = -1;
x >> 1;

xは127(01111111)でも、-1(11111111)でもかまいません。

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


4
私がそれを正しく思い出すと、ANSI C標準はこれが実装に依存していることを明示しているので、コードの符号付き整数を右シフトしたい場合は、コンパイラのドキュメントを調べてどのように実装されているかを確認する必要があります。
ジョー・ピネダ

はい、ANSI規格自体が強調しているように強調したかったのです。ベンダーが規格に従っていないだけの場合や、規格がこの特定のケースについて何も述べていない場合ではありません。
ジョー・ピネダ

22

ヒントとコツだけを書いています。テストや試験で役立つ場合があります。

  1. n = n*2n = n<<1
  2. n = n/2n = n>>1
  3. nが2の累乗(1,2,4,8、...)であるかどうかの確認:チェック !(n & (n-1))
  4. x 番目のビットを取得していますnn |= (1 << x)
  5. xが偶数か奇数かを確認する:(x&1 == 0偶数)
  6. xのn 番目のビットを切り替えます。x ^ (1<<n)

あなたが今までに知っているもういくつかあるに違いありませんか?
ライカー2017

@ryykerもう少し追加しました。私はそれを更新し続けるつもりです:)
Ravi Prakash

xとn 0はインデックス付けされていますか?
reggaeguitar

広告5 .:負の数の場合はどうなりますか?
Peter Mortensen、

それでは、2進数の2は10進数の10のようであると結論付けることができますか?そしてビットシフトは、10進数で別の数値の後ろにもう1つの数値を加算または減算するようなものですか?
ウィリーsatrio nugroho

8

Java実装では、シフトするビット数はソースのサイズによって変更されることに注意してください。

例えば:

(long) 4 >> 65

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

(long) 4 >> (65 % 64)

これは、<<、>>、および>>>に当てはまります。他の言語では試していません。


あ、面白い!Cでは、これは技術的に未定義の動作です。gcc 5.4.0警告を出しますが2、5 >> 65になります。同様に。
pizzapants184

2

Pythonでのいくつかの便利なビット操作/操作。

私は実装ラヴィ・プラカシュの答えを Pythonで。

# Basic bit operations
# Integer to binary
print(bin(10))

# Binary to integer
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 second bit is 0

# Toggle nth bit of x : x^(1 << n)
# take bin(10) == 1010 and toggling second bit in bin(10) we get 1110 === bin(14)
print(10^(1 << 2))

-3

Windowsプラットフォームでは32ビットバージョンのPHPしか使用できないことに注意してください。

次に、たとえば、<<または>>を31ビット以上シフトすると、予期しない結果になります。通常、ゼロではなく元の数値が返されますが、これは本当にトリッキーなバグになる可能性があります。

もちろん、64ビットバージョンのPHP(Unix)を使用している場合は、63ビットを超えるシフトは避けてください。ただし、たとえば、MySQLは64ビットのBIGINTを使用するため、互換性の問題はありません。

更新:PHP 7 Windowsから、PHPビルドは最終的に完全な64ビット整数を使用できるようになりました:整数 のサイズはプラットフォームに依存しますが、最大値は約20億が通常の値です(32ビットが署名されています)。64ビットプラットフォームの最大値は通常9E18ですが、PHP 7より前のWindowsでは常に32ビットでした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.