公開

シフト演算・ビットマスクとは?左シフトとAND・OR・XORを図解

文:野澤 嘉孝 (ソフトウェアエンジニア/プログラミングスクール Sandbox 代表)

シフト演算・ビットマスクとは?左シフトとAND・OR・XORを図解

この記事の概要

ビットを左にずらすだけで2倍になる。必要な桁だけを取り出せる。そんなことが、本当にできるのでしょうか? シフト演算はビット列を左右へずらす操作、ビットマスクは操作する桁を指定するためのビット列です。 文化祭の写真加工で起きた事件から、仕組みを見ていきましょう。

ビットを左にずらすだけで2倍になる。必要な桁だけを取り出せる。そんなことが、本当にできるのでしょうか?

シフト演算はビット列を左右へずらす操作、ビットマスクは操作する桁を指定するためのビット列です。

文化祭の写真加工で起きた事件から、仕組みを見ていきましょう。

先に結論:「位置を変える」と「桁を選ぶ」を分けよう

  • 左シフト=桁の位置を左へ動かす。 あふれなければ、1桁で2倍、2桁で4倍になる。
  • 論理右シフト=右へ動かし、左を0で埋める。 非負の整数なら、2のべき乗で割った整数の商が残る。
  • マスクとのAND=必要な桁を残す。 残したい位置を1、それ以外を0にする。
  • マスクとのOR=指定した桁を1にする。 指定しない位置はそのまま。
  • マスクとのXOR=指定した桁を反転する。 同じマスクで2回行うと元に戻る。

基本は 符号なしの8ビット整数(0〜255) をモデルにします。後半の画像の例だけは、見やすくするため4ビット(0〜15)で考えます。

写真を明るくしたら、一部が黒くなった

放課後の部室。ユウキは文化祭のポスターに使う写真を、明るくしようとしていました。

画素の値を2倍にするプログラムを、テッパン先輩が左シフトを使う形に書き換えます。物語では処理が速くなったものの、結果を8ビットに収める際にはみ出した桁を捨てたため、写真の一部がまだらに暗くなってしまいました。

ユウキ「先輩、何を変えたんですか」

テッパン先輩「掛け算を、やめた」

Sandy「左に1つずらしたんや。せやけどな、はみ出した桁まで面倒を見たわけやないねん」

なぜ、ずらすだけで数が変わるのでしょう。まずは2進数の桁の重みから確認します。

※ここで扱うのは画素値を2倍にする簡略化した加工です。人が感じる明るさまで正確に2倍になる、という意味ではありません。

左シフトは、なぜ2倍になるのか

2進数の各桁の重みは、右から1・2・4・8・16・32・64・128と、2倍ずつ増えます。00000101は、重み4と1の桁が1なので、値は5です。

これを左に1桁ずらし、右端へ0を入れると00001010。今度は重み8と2が使われるため、合計は10になります。

1が置かれた位置の重みがすべて2倍になるので、値も2倍になるのです。

8ビットの5を左シフトして10、論理右シフトして2にする桁の重みとビット列

左へ1桁で重みが2倍になり、右へ1桁では右端の情報が落ちる。

操作

ビット列

10進数

元の値

00000101

5

左へ1桁

00001010

10

左へ2桁

00010100

20

左へn桁ずらすと、あふれない範囲では値が2のn乗倍になります。1桁なら2倍、2桁なら4倍、3桁なら8倍です。シフト1回だけで、すべての値を3倍にすることはできません。

ただし、シフトと足し算を組み合わせれば、(x << 1) + x で3倍を作れます。固定幅の場合は、この計算でも桁あふれに注意が必要です。

Sandy「同じ1でも、席が変わったら担当する値が変わるんや。左の席は、お隣の2倍やからな」

右シフトは半分。ただし、2.5は出てこない

右へ1桁ずらすと、各桁の重みは半分になります。ここでは左端を0で埋める論理右シフトを考えます。

00001010の10を右シフトすると00000101の5。さらに右シフトすると、右端の1が落ちて00000010の2です。

操作

ビット列

10進数

元の値

00001010

10

右へ1桁

00000101

5

さらに右へ1桁

00000010

2

5÷2の結果2.5を小数として保存するのではなく、整数の商である2が残ります。 右へn桁なら、非負の整数を2のn乗で割った商に対応します。

落ちた情報は、逆にずらしても戻りません。5→右シフト→2→左シフト→4となり、元の5には戻らないのです。

なお、符号付きの負の数では、左を0で埋める論理シフトと、符号ビットを複製する算術シフトを区別します。「半分・端数切り捨て」という説明は、ここでは非負の整数についてのものです。

桁あふれ:130を2倍にしても、260が入らない

8ビットの符号なし整数で表せる範囲は0〜255です。画素値130を2倍にすると260で、8ビットには収まりません。

130は10000010。左へ1桁ずらすと、数学的な結果は9桁の100000100になります。ここで下位8ビットだけを残すと、00000100=4になってしまいます。

130の2倍260を下位8ビットで4にする場合と上限255で止める飽和処理の比較

下位8ビットを残す処理と、上限で止める処理は結果が違う。

元の画素値

数学的な2倍

下位8ビットを残した結果

100

200

200

130

260

4

200

400

144

ユウキ「2倍にしたかったのに、4……」

Sandy「計算結果を入れる箱が足りへんかったんや。はみ出した1を捨てたら、残る値が変わるやろ」

これが、物語で明るい部分が暗く崩れた理由です。原因はシフトそのものではなく、収まらない上位ビットを捨てたことにあります。掛け算でも、同じように下位8ビットだけを残せば130×2は4になります。

ただし、どの言語・画像処理でも自動的にこの結果になるわけではありません。ここでは、あふれた上位ビットを捨てるモデルに限定しています。

明るい部分を上限で止めたいなら、十分広い型で計算してから、255を超えた値を255にする「飽和処理」が使えます。あふれて4になった後に上限チェックをしても、元の260は分かりません。

Pythonの通常のintなら、次の違いを確認できます。

pixel = 130

print(pixel << 1)                  # 260
print((pixel << 1) & 0b11111111)    # 4:下位8ビットだけ残す
print(min(pixel * 2, 255))          # 255:上限で止める

Pythonのintは8ビット固定ではありません。130を左シフトしただけで4になる、と覚えないようにしましょう。

シフトに変えれば、必ず速くなる?

物語の「掛け算をやめたら速くなった」は、常に成立する性能の法則ではありません。実行時間はCPU、処理系、扱うデータなどによって変わります。

コンパイラが定数の掛け算をシフトなどへ置き換えることもあります。「×2」を「左シフト」に書き直しただけで、実際の命令や速度が変わるとは限りません。

まず大事なのは、同じ条件で正しい値が得られることです。速さの差は、実際の環境で測定して判断します。この記事では速度の比較測定はしていません。

ビットマスク:同じ位置の桁どうしを計算する

前回のAND・OR・XORを、ビット列の同じ位置どうしへ適用すると、ビットごとの演算になります。足し算と違い、桁上がりは起こりません。

その演算で、どの桁を残すか、1にするか、反転するかを指定するために使うのがビットマスクです。マスクは抽出だけでなく、設定や反転にも使えます。

マスクの各ビットが0か1かによるAND、OR、XORの動作をまとめた表

同じマスクでも、AND・OR・XORのどれを使うかで役割が変わる。

元の1ビットをxとすると、ルールは次の表で整理できます。

演算

マスクが0の位置

マスクが1の位置

AND

0にする

xをそのまま残す

OR

xをそのまま残す

1にする

XOR

xをそのまま残す

xを反転する

Sandy「型紙の1が、いつでも『残す』とは限らへんで。どの演算で使うかまでセットや」

ANDで下位4ビットを取り出す

11010110から下位4ビットだけを残したいなら、00001111とANDを取ります。

11010110 AND 00001111 = 00000110

マスクが0の上位4ビットは0になり、マスクが1の下位4ビットはそのまま残ります。

11010110と00001111のAND、OR、XORの結果を8桁で比較した図

ANDなら抽出、ORなら1を設定、XORなら指定した桁の反転になる。

同じ元の値とマスクでも、演算が変われば結果も変わります。

演算

結果

何が起きた?

AND 00001111

00000110

下位4ビットを残した

OR 00001111

11011111

上位は残り、下位をすべて1にした

XOR 00001111

11011001

上位は残り、下位を反転した

ORは「必要な部分だけを取り出す」操作にはなっていません。XORも、指定した位置を0に消すのではなく反転します。

PythonではANDが &、ORが |、XORが ^ です。条件を組み合わせる and・or とは別の演算子です。

value = 0b11010110
mask = 0b00001111

print(f&apos;{value & mask:08b}&apos;)  # 00000110:残す
print(f&apos;{value | mask:08b}&apos;)  # 11011111:1にする
print(f&apos;{value ^ mask:08b}&apos;)  # 11011001:反転する

0b は2進数で書く印です。08b は、結果を先頭の0も含めた8桁の2進数で表示する指定です。

画像にも使える:黒とのOR、白とのAND

ここでは4ビットのグレースケール画像を考えます。画素値は0〜15で、黒が0000、白が1111です。重ねる2枚の同じ位置の画素どうしを演算します。

  • 元の画素と黒0000をOR → 元の画素がそのまま残る。
  • 元の画素と白1111をAND → 元の画素がそのまま残る。
4ビット画像に黒背景の白い模様をOR、白背景の黒い模様をANDで重ねた結果

黒とのOR・白とのANDは、その位置の元画像の画素を変えない。

たとえば、黒い背景に白い模様がある画像をORで重ねると、背景の黒い部分では元画像を保ち、白い模様の部分は白になります。逆に、白い背景に黒い模様がある画像をANDで重ねると、背景の白い部分では元画像を保ち、黒い模様の部分は黒になります。

元の画素

重ねる画素

演算

結果

0110(6)

0000(黒)

OR

0110(6)

0110(6)

1111(白)

AND

0110(6)

「背景が透ける」と見えるのは、その背景の値が相手を変えないためです。透明度の情報を作っているわけではありません。 中間色どうしなら、それぞれのビットに応じて値が変わります。たとえば0110 OR 1001は1111です。元の色をそのまま載せる通常の透過合成とは区別しましょう。

XORで反転し、同じマスクでもう一度戻す

4ビット全部を反転したいなら、1111とXORを取ります。

0110 XOR 1111 = 1001

もう一度、同じ1111とXORを取ると0110に戻ります。

0から15の濃淡を1111とのXORで反転し、もう一度同じマスクで元へ戻す図

同じマスクとのXORを2回適用すると元のビット列に戻る。

(x XOR m) XOR m = x。同じマスクmを2回使えば、反転した桁がもう一度反転するためです。途中で情報を失う処理をしなければ元に戻ります。

4ビットの全反転は、数値でいえば15−xと同じです。0は15、6は9、15は0になり、グレースケールの明暗が反転します。ビット幅を決めて全桁をNOTで反転した場合も同じ結果です。

Pythonで4ビットの反転を試すなら、0〜15のxに対して x ^ 0b1111 と書けます。たとえば6なら、6 ^ 0b1111 は9です。

~x だけでは4ビットに限定されません。Pythonでは ~6 は−7になります。4ビット分だけを残したければ、(~6) & 0b1111 とすると9になります。反転する桁の範囲まで決めるのがポイントです。

オリジナル学習例:上位4ビットを数として取り出すには?

この記事用のオリジナル学習例です。実際の共通テスト問題ではありません。

8ビットの11010110から、上位4ビットの1101を取り出し、数値13として使いたいとします。どうすればよいでしょうか?

まず11110000とANDを取ると、11010000になります。下位4ビットは消せましたが、上位4ビットは元の位置に残っており、数値は208です。

そこで、さらに右へ4桁ずらします。

11010000 → 00001101 = 13

ここで、マスクとシフトの役割がつながります。ANDで必要な桁を残し、右シフトで右端へ寄せるのです。ANDだけでは208、右へ4桁ずらして初めて13になります。

value = 0b11010110
upper = (value & 0b11110000) >> 4
print(upper)  # 13

0〜255の値から上位4ビットを取り出すなら、右へ4桁ずらすだけでも同じ結果になります。より一般的には、右シフトした後に必要な幅のマスクをかける方法も使えます。

まとめ:ずらす前に、残る桁を確かめよう

  • 左シフトは、範囲内なら2のべき乗倍。非負の整数の論理右シフトは、2のべき乗で割った商に対応する。
  • 固定幅からはみ出した桁や、右端から落ちた桁は、逆シフトだけでは戻らない。
  • ANDは必要な桁を残し、ORは指定した桁を1にし、XORは指定した桁を反転する。
  • 黒とのOR・白とのANDは、その位置の元の画素値を保つ。
  • 同じマスクでのXORは、2回で元に戻る。

ユウキは、計算結果が8ビットの範囲を超える前に確認し、上限で止めるように書き直しました。今度は、明るい部分が黒く崩れません。

Sandy「ずらすだけでも計算になる。でも、どの桁が残るかまで見とくんやで」

ほかのテーマは情報Ⅰサバイバルの記事一覧から読めます。仕組みを実際のプログラムや作品制作につなげたい方は、プログラミングスクールSandboxもご覧ください。

参考資料

あわせて読みたい記事

Sandboxのサポート内容 Sandboxの情報Ⅰ学習サポートを見る →
共通テスト情報Ⅰのまとめ 共通テスト「情報Ⅰ」対策シリーズの一覧を見る →
野澤 嘉孝

この記事を書いた人

野澤 嘉孝

ソフトウェアエンジニア/プログラミングスクール Sandbox 代表

ソフトウェアエンジニア。同志社大学理工学部を経て、京都大学大学院で核融合発電の基礎研究(プラズマ物理)に従事。高校生向け数学塾の講師、大学での物理実験の授業を通じて延べ500名以上の学生をサポート。現在は業界特化型SaaSの開発に携わりながら、小中高生向けプログラミングスクール Sandbox を運営。

プロフィールと執筆記事の一覧 →

「情報Ⅰ、うちの子はどう対策すればいい?」

情報Ⅰ対策について無料で相談する

志望校の配点を一緒に確認したい方は → 無料の学習相談へ

入会金無料・いつでも退会OK・退会後もいつでも再入会できます