見出し画像

ビット演算子とは with Python #437

プログラミング学習をしていくなかでビット演算子という言葉に出会い、それが何かわからなかったのでまとめてみました。ビット演算子を扱う方法は色々あると思いますが、ここではPythonで扱う方法を例にしています。


ビットとは

ご存知の通り、コンピュータでは全てのデータは「ビット」という最小単位で表されます。

ビットは0か1の値を持ちますが、これはコンピュータの電圧で判定されています。直流電圧0V(V=ボルト)で数値の0,5Vで数値の1を表します。

具体的には、例えば数値8はバイナリ(2進数)で「1000」と表されます。ここで各々の「1」や「0」がビットです。

ビット演算子とは

ビット演算子は、これらのビットを操作するためのツールです。通常の数学演算(足し算、引き算など)とは異なり、ビット演算子はビット単位で動作します。

詳細は後述しますが、以下の種類があります。

  • AND (&)

  • OR (|)

  • XOR (^)

  • NOT (~)

  • 左シフト (<<)

  • 右シフト (>>)

ビット演算の利点

ビット演算子を使用する利点には、以下のようなものがあります

効率性

ビット演算は非常に高速です。これはコンピュータがビットレベルで動作するため、ビット単位での操作はプロセッサにとって自然で、直接的な操作であるためです。

メモリの節約

ビット演算を使うとデータをコンパクトに表現できます。これにより、メモリの使用量を減らすことができます。

直接的なデータ操作

ビット演算を使用すると、データの低レベルの構造にアクセスし、変更できます。これは特定の種類のプログラミングタスク、特にハードウェアレベルのプログラミングや暗号化技術において重要です。

ビット演算子を使用するシステムの例

ビット演算子は特に低レベルのデータ操作や効率的な処理が求められる分野で重宝されます。以下に、ビット演算子が使われる一般的な例を挙げます。

1. ハードウェア制御と組み込みシステム

ハードウェアデバイスの制御や組み込みシステムのプログラミングでは、特定のビットやビットフィールドを操作して、ハードウェアの特定の機能をオン・オフしたり、設定を変更したりします。

2. グラフィックス処理

グラフィックス関連のプログラミング(特にゲーム開発や画像処理)では、色情報の操作(例えばRGB値の操作)にビット演算子が用いられます。これにより、色の合成、変更、抽出などが効率的に行えます。

3. 暗号化とセキュリティ

暗号化アルゴリズムやセキュリティ関連のプログラムでは、ビット演算子を使ってデータの変換や暗号化、復号を行います。ビットレベルでの操作は、暗号化技術において不可欠です。

4. ネットワークプロトコル

インターネットや通信プロトコルでは、アドレス計算(例えばIPアドレスのサブネットマスク計算)やデータパケットの処理にビット演算が使われます。

5. オペレーティングシステム

オペレーティングシステムでは、メモリ管理、プロセス管理、ファイルシステムなどの低レベルの操作にビット演算子が利用されます。

6. 最適化とパフォーマンス改善

高度な最適化が必要なプログラム(例えば、高速なアルゴリズムやデータ圧縮)では、ビット演算子を使用して処理速度を向上させたり、メモリ使用量を削減したりします。

各ビット演算子の使い方

各演算子の使い方を整理します。
通常は8ビットですが、ここでは簡単のため各数字を4ビットで表します。

まず分かりやすい左シフトと右シフトから記載します。
左シフトは基本的に値を2倍することに相当し、右シフトは値を半分にすることに相当します(ただし、整数の範囲内で)。

左シフト (<<)

左シフトは、ビット列を左に指定された数だけシフト(移動)します。例えば、1 << 1の場合、次のようになります:

  1. 数字1は2進数で0001と表されます。

  2. 1 << 1は、これらのビットを左に1ビット分シフトすることを意味します。

  3. シフト後、0001(1)は0010になります。これは2進数で「2」を意味します。

図解すると以下のようになります:

初期状態: 0001  (1 in decimal)
左シフト後: 0010  (2 in decimal)

右シフト (>>)

右シフトは、ビット列を右に指定された数だけシフトします。例えば、4 >> 1の場合、次のようになります:

  1. 数字4は2進数で0100と表されます。

  2. 4 >> 1は、これらのビットを右に1ビット分シフトすることを意味します。

  3. シフト後、0100(4)は0010になります。これは2進数で「2」を意味します。

初期状態: 0100  (4 in decimal)
右シフト後: 0010  (2 in decimal)

AND演算子 (&)

AND演算子は、両方のビットが1である場合に1を返し、それ以外の場合は0を返します。AND演算の例(5 & 3)では、以下のように計算されます:

    0101  (5)
AND 0011  (3)
  --------
    0001  (1)

この例では、右から2番目のビットだけが両方の数で1です。それゆえ、結果の同じ位置のビットが1になり、他のビットは0です。

OR演算子 (|)

OR演算子は、少なくとも一方のビットが1の場合に1を返し、両方が0の場合に0を返します。OR演算の例(5 | 3)では、以下のように計算されます:

    0101  (5)
 OR 0011  (3)
  --------
    0111  (7)

この例では、少なくとも一方の数の各ビットが1なので、結果として得られるビット列はほとんどの位置で1になります。

XOR演算子 (^)

XOR演算子は、2つのビットが異なる場合に1を返し、同じ場合は0を返します。XOR演算の例(5 ^ 3)では、以下のように計算されます:

     0101  (5)
 XOR 0011  (3)
  --------
    0110  (6)

この例では、右から数えて2番目と3番目のビットが異なります。そのため、これらの位置のビットが1になります。

NOT演算子 (~)

NOT演算子は、ビットを反転させます。

  1. 5 は2進数で 0101 です。

  2. これを反転させます。

     0101  (5)
 NOT
  --------
     1010  (反転結果)

ただし、Pythonではこの結果が -6 となります。これはPythonがビット反転を行う際、数値を補数形式で扱うためらしいです。
この辺は私もあまり理解できませんでした。

ビット演算子の速度検証

左シフトとPythonの累乗演算子を使って、速度を検証してみました。

使用するのは、同じ計算結果を得られる以下2つの計算式です。

5 * 2**1000  [普段の乗算演算子と累乗演算子]

5 << 1000    [ビット演算子:左シフト]

これを1,000,000回ループして処理時間を比較してみます。

まずはPythonの通常の演算子で実行します。
約0.7秒でした。

import time

time_start = time.time()
for i in range(1000000):
    b = 5 * 2**1000
time_end = time.time()
processing_time = time_end - time_start

print(f'>>>>>>> {processing_time}')
[出力]
0.6703360080718994

次にPythonのビット演算子で実行してみます。
約0.04秒まで縮まりました。

import time

time_start = time.time()
for i in range(1000000):
    a = 5 << 1000
time_end = time.time()
processing_time = time_end - time_start

print(f'>>>>>>> {processing_time}')
[出力]
>>>>>>> 0.04402494430541992

なんとビット演算子だと、通常の7%程度の時間で完了しました。
これは確かに恐ろしいほど高速ですね。

ここまでお読みいただきありがとうございました!


参考


この記事が気に入ったらサポートをしてみませんか?