[解決!Python]ビット演算まとめ解決!Python

ビット単位のAND/OR/XOR/NOT演算とビットシフト演算について紹介し、それらによってビットパターンがどう変化するかを確認していく。

» 2024年06月11日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

# ビットパターンを表示するヘルパー関数の定義
def bitpat(x):
    l = x.bit_length() + 1  # 符号ビットを含めるため+1
    if l < 8# 最低でも8桁のビットパターンとする
        l = 8
    if l % 4 != 0# 4桁ごとの表示にするため、4の倍数にする
        l += 4 - l % 4
    c = l // 4 - 1  # セパレーターの数
    if x >= 0:
        pattern = f'{x:0{l+c}_b}'
    else:
        mask = (1 << l) - 1
        pattern = f'{x & mask:0{l+c}_b}'
    return pattern

print(bitpat(127))  # 0111_1111
print(bitpat(-127))  # 1000_0001
print(bitpat(32766))  # 0111_1111_1111_1110
print(bitpat(-32766))  # 1000_0000_0000_0010

# ビット演算の結果を表示するヘルパー関数の定義
def show_bitpat_0(x, y, res, op):
    sep_len = len(bitpat(x)) + 11
    print(f'   {bitpat(x)} = {x:5d}',
          f' {op} {bitpat(y)} = {y:5d}',
          f'{"-" * sep_len}',
          f'   {bitpat(res)} = {res:5d}', sep='\n')

show_bitpat_0(58, 92, 58 & 92, '&')
# 出力結果:
#   0011_1010 =    58
# & 0101_1100 =    92
#--------------------
#   0001_1000 =    24

def show_bitpat_1(x, res, op):
    x_bitpat = bitpat(x)
    res_bitpat = bitpat(res)
    print(f'{' ' * (2 + len(op))}{x_bitpat} = {x:5d}',
          f' {op} {res_bitpat} = {res:5d}', sep='\n')

show_bitpat_1(58, ~58, '~')
# 出力結果:
#   0011_1010 =    58
# ~ 1100_0101 =   -59

x = 0b0011_1010
y = 0b0101_1100
print(f'{x=}, {y=}'# x=58, y=92

# ビット単位AND
z = x & y
show_bitpat_0(x, y, z, '&')
# 出力結果:
#   0011_1010 =    58
# & 0101_1100 =    92
#--------------------
#   0001_1000 =    24

# ビット単位OR
z = x | y
show_bitpat_0(x, y, z, '|')
# 出力結果:
#   0011_1010 =    58
# | 0101_1100 =    92
#--------------------
#   0111_1110 =   126

# ビット単位XOR
z = x ^ y
show_bitpat_0(x, y, z, '^')
# 出力結果:
#   0011_1010 =    58
# ^ 0101_1100 =    92
#--------------------
#   0110_0110 =   102

# ビット単位NOT
z = ~x
show_bitpat_1(x, z, '~')
# 出力結果:
#   0011_1010 =    58
# ~ 1100_0101 =   -59

# 左シフト
x = 0b0001_0100
print(f'{x=}'# x=20
z = x << 2  # 2桁左にシフト(2 ** 2=4で乗算)
show_bitpat_1(x, z, '<<')
# 出力結果:
#    0001_0100 =    20
# << 0101_0000 =    80

# 右シフト
z = x >> 2  # 2桁右にシフト(2 ** 2=4で除算)
show_bitpat_1(x, z, '>>')
# 出力結果:
#    0001_0100 =    20
# >> 0000_0101 =     5


ビット演算とは

 ビット演算とは整数値を2進表記したときに現れる0と1の列に対する演算である。演算対象となる被演算子が2つあるときには、それらの被演算子の同じ桁にあるビット同士の演算の対象となる。なお、Pythonの演算子全般については『Python入門』にある「Pythonの演算子まとめ」を参照のこと。

演算の種類 演算子 説明
ビット単位AND & 2項演算。2つの被演算子の同じ桁にあるビットの両者が1なら演算結果は1になり、それ以外の場合は0になる
ビット単位OR | 2項演算。2つの被演算子の同じ桁にあるビットが1つでも1なら演算結果は1になり、それ以外(つまり2つのビットの値が0)なら演算結果は0になる
ビット単位XOR ^ 2項演算。2つの被演算子の同じ桁にあるビットの一方が1でもう一方が0なら演算結果は1になり、それ以外(つまり2つのビットが両方とも1か、両方とも0)なら演算結果は0になる
ビット単位NOT ~ 単項演算子。被演算子の0と1からなるビットパターンを反転させる。つまり、0だったビットの値は1に、1だったビットの値は0になる
左シフト << 2項演算。演算子の左側に置かれた被演算子のビットパターンを、右側に置かれた被演算子の値だけ左方向にシフトする。シフトして空いた下位ビットには0が挿入される。左側の演算子の値を2のべき乗で乗算する処理に等しい(2倍、4倍など)
右シフト >> 2項演算。演算子の左側に置かれた被演算子のビットパターンを、右側に置かれた被演算子の値だけ右方向にシフトする。シフトして空いた上位ビットには正数なら0が、負数なら1が挿入される。左側の演算子の値を2のべき乗で除算する処理に等しい(2で除算、4で除算など)
ビット演算

 ここでは以下に示す3つのヘルパー関数を定義して、表に示したビット演算でビットがどのように変化するかを確認していく。

# ビットパターンを表示するヘルパー関数の定義
def bitpat(x):
    l = x.bit_length() + 1  # 符号ビットを含めるため+1
    if l < 8# 最低でも8桁のビットパターンとする
        l = 8
    if l % 4 != 0# 4桁ごとの表示にするため、4の倍数にする
        l += 4 - l % 4
    c = l // 4 - 1  # セパレーターの数
    if x >= 0:
        pattern = f'{x:0{l+c}_b}'
    else:
        mask = (1 << l) - 1
        pattern = f'{x & mask:0{l+c}_b}'
    return pattern

print(bitpat(127))  # 0111_1111
print(bitpat(-127))  # 1000_0001
print(bitpat(32766))  # 0111_1111_1111_1110
print(bitpat(-32766))  # 1000_0000_0000_0010

# ビット演算の結果を表示するヘルパー関数の定義
def show_bitpat_0(x, y, res, op):
    sep_len = len(bitpat(x)) + 11
    print(f'   {bitpat(x)} = {x:5d}',
          f' {op} {bitpat(y)} = {y:5d}',
          f'{"-" * sep_len}',
          f'   {bitpat(res)} = {res:5d}', sep='\n')

show_bitpat_0(58, 92, 58 & 92, '&')
# 出力結果:
#   0011_1010 =    58
# & 0101_1100 =    92
#--------------------
#   0001_1000 =    24

def show_bitpat_1(x, res, op):
    x_bitpat = bitpat(x)
    res_bitpat = bitpat(res)
    print(f'{' ' * (2 + len(op))}{x_bitpat} = {x:5d}',
          f' {op} {res_bitpat} = {res:5d}', sep='\n')

show_bitpat_1(58, ~58, '~')
# 出力結果:
#   0011_1010 =    58
# ~ 1100_0101 =   -59


 bitpat関数は引数に受け取った整数値を2進表記した結果を文字列として戻す。負数については2の補数表現を戻すのが、bin関数や書式指定文字列で2進表記を指定した場合と異なる点である。

 show_bitpat_0関数とshow_bitpat_1関数はbitpat関数を内部で使用して、被演算子と演算結果のビットパターンを表示する。前者はビット単位AND、ビット単位OR、ビット単位XORの説明で使用する。後者はビット単位NOTとシフト演算の説明で使用する。

ビット単位AND

 ビット単位ANDは&演算子で表される。2つの被演算子の同じ桁にあるビットが共に1なら演算結果は1に、それ以外なら演算結果は0になる。

被演算子0 被演算子1 演算結果
0 0 0
0 1 0
1 0 0
1 1 1
ビット単位AND

 以降では次の2つの変数xとyを使ってビット単位AND、ビット単位OR、ビット単位XOR、ビット単位NOTの演算結果を確認していくことにする。

x = 0b0011_1010
y = 0b0101_1100
print(f'{x=}, {y=}'# x=58, y=92


 2つの値のビット単位ANDを求めるには以下のように&演算子を使用する。

z = x & y


 xとyの値は上で見た通り、58と92だ。そして58は32+16+8+2であるからその2進表記(ビットパターン)は「00111010」となる。92は64+16+8+4なのでその2進表記(ビットパターン)は「01011100」となる。ビットANDでは同じ桁にあるビットが両方とも1なら演算結果が1になり、それ以外の場合は0となるので、この場合の演算結果は次のように「00011000」(=24)になる。

show_bitpat_0(x, y, z, '&')
# 出力結果:
#   0011_1010 =    58
# & 0101_1100 =    92
#--------------------
#   0001_1000 =    24


 出力結果で両方のビットが1となっているところの演算結果が1に、それ以外のところは0になっていることを確認されたい。

ビット単位OR

 2つの値のビット単位ORは|演算子で求められる。ビット単位ORでは2つの被演算子の同じ桁にあるビットが1つでも1なら演算結果は1に、そうでなければ(両方とも0なら)演算結果は0になる。

被演算子0 被演算子1 演算結果
0 0 0
0 1 1
1 0 1
1 1 1
ビット単位OR

 以下では上で定義した変数xとyのビット単位ORを求めている。

z = x | y


 このときの2つの被演算子とその演算結果のビットパターンを以下に示す。

show_bitpat_0(x, y, z, '|')
# 出力結果:
#   0011_1010 =    58
# | 0101_1100 =    92
#--------------------
#   0111_1110 =   126


 出力結果で両方のビットのうち1つでも1となっている桁の演算結果が1に、両方とも0の桁は0になっていることを確認しよう。

ビット単位XOR

 2つの値のビット単位XORは^演算子で求められる。ビット単位XORでは2つの被演算子の同じ桁にあるビットの一方が1でもう一方が0なら演算結果は1に、そうでなければ(両方とも1、または両方とも0なら)演算結果は0になる。

被演算子0 被演算子1 演算結果
0 0 0
0 1 1
1 0 1
1 1 0
ビット単位XOR

 以下では上で定義した変数xとyのビット単位XORを求めている。

z = x ^ y


 これまでと同様、2つの被演算子と演算結果のビットパターンを以下に示す。

show_bitpat_0(x, y, z, '^')
# 出力結果:
#   0011_1010 =    58
# ^ 0101_1100 =    92
#--------------------
#   0110_0110 =   102


 同じ桁にあるビットの一方が1でもう一方が0のときにはその桁の演算結果が1に、そうでなければ0になっていることが分かるはずだ。

ビット単位NOT

 ビット単位NOTは~演算子で表現され、被演算子である整数値のビットパターンを反転させる(0を1に、1を0にする)。例えば、上で定義した変数xの値のビット単位NOTを得るには次のようにする。

z = ~x


 変数xの値である58のビットパターンは「00111010」だった。ビット単位NOTでは、この各ビットの0と1を反転させるので結果は「11000101」となる。以下で結果を確認されたい。

show_bitpat_1(x, z, '~')
# 出力結果:
#   0011_1010 =    58
# ~ 1100_0101 =   -59


 得られた「11000101」は最上位ビットが1であることから負数であり、その絶対値は全ビットを反転して1を加算したもの、つまり、2進数の「00111010」+「00000001」=「00111011」=「59」となる。これに符号を付加した「-59」が58のビット単位NOTを取った結果となる。これはPythonのドキュメントに「xのビット反転は-(x+1)として定義されている」とあることにも一致している。

ビットシフト

 ビットシフト演算には次の2種類がある。

  • 左シフト:<<演算子で表される。右側の被演算子に指定されたビット数だけ、左側の被演算子を左にシフトする(ビットパターンを左にずらす)
  • 右シフト:>>演算子で表される。右側の被演算子に指定されたビット数だけ、左側の被演算子を右にシフトする(ビットパターンを右にずらす)

 「x << n」は実際にはxの2n倍を計算し、「x >> n」はxの-n倍を計算する。つまり、nが2なら、「x << n」はxの4倍を計算して、「x >> n」はxを4で除算した結果を求めることになる。

 以下に例を示す。ここでは変数xの値を20にしている。

x = 0b0001_0100
print(f'{x=}'# x=20
z = x << 2  # 2桁左にシフト(2 ** 2=4を乗算)
show_bitpat_1(x, z, '<<')
# 出力結果:
#    0001_0100 =    20
# << 0101_0000 =    80

# 右シフト
z = x >> 2  # 2桁右にシフト(2 ** 2=4で除算)
show_bitpat_1(x, z, '>>')
# 出力結果:
#    0001_0100 =    20
# >> 0000_0101 =     5


 サンプルコードでは左シフトでも右シフトでも、シフトするビット数に2を指定している。そのため、左シフトの演算結果は20の4倍である80となり、右シフトの演算結果は20を4で除算した結果である5になっている。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。