[解決!Python]not演算子で整数の0と1を反転、~演算子でビット単位に0と1を反転させるには解決!Python

not演算子を使って整数の0と1を反転する方法と、~演算子を使ってビット幅で0と1を反転する方法を紹介する。

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

連載目次

# 整数の0と1を反転する
def invert_0_and_1(x):
    return int(not x)

result = invert_0_and_1(0)
print(result)  # 1

result = invert_0_and_1(1)
print(result)  # 0

result = invert_0_and_1(2# 0以外の値は0になる
print(result)  # 0

# ビット単位に0と1を反転させる
from math import log2, ceil

def invert_bitwise(x):
    if x == 0:
        d = 1
    else:
        d = ceil(log2(abs(x))) + 1  # ビット幅を求める
    mask = (1 << d) - 1
    tmp = ~x
    orig_repr = f'{x & mask:0= {d+1}b}'
    inverted_repr = f'{tmp & mask:0= {d+1}b}'
    #print(f'{orig_repr}\n{inverted_repr}')
    return orig_repr, inverted_repr

o, i = invert_bitwise(-128)
print(f'{o}\n{i}')
# 出力結果:
# 10000000
# 01111111

o, i = invert_bitwise(0)
print(f'{o}\n{i}')
# 出力結果:
# 0
# 1

o, i = invert_bitwise(5)
print(f'{o}\n{i}')
# 出力結果:
# 0101
# 1010


整数の0と1を反転する

 整数の0と1を反転するには幾つかの方法が考えられる。例えば、if文を使ってもよいだろうし、ビット演算子「^」を使って1と排他的論理和を取る方法も考えられる(「0 ^ 1」は「1」に、「1 ^ 1」は0になる)。あるいはPythonでは0は偽値として1は真値として扱われる(逆に偽値は0として真値は1として扱われる)特性を利用して、以下のようなコードを書くことも考えられる。

def invert_0_and_1(x):
    return int(not x)


 この例ではinvert_0_and_1関数は受け取った値を論理値として扱い、真偽を反転させ、それを整数値に変換している。以下は実行例だ。

result = invert_0_and_1(0)
print(result)  # 1

result = invert_0_and_1(1)
print(result)  # 0


 このように0と1が反転していることが分かる。なお、Pythonでは0以外の値は真値として扱われるので、以下のように0以外の整数をこの関数に渡すと0が返される。

result = invert_0_and_1(2# 0以外の値は0になる
print(result)  # 0


ビット単位に0と1を反転させる

 整数の0と1を反転させるのではなく、ビット単位に0と1を反転させるにはビット演算子の「~」が使用できる。

x = 0b1101  # 10進数の13
print(x)  # 13
y = ~x


 これによりビット単位で0と1が反転される。ただし、その値を単純にbin関数で調べてもビット単位で反転しているようには見えない。これはbin関数に負値を渡しても、その絶対値が2進数として表記され、符号はその前に置かれるようになっているからだ。

print(y)  # -14
print(bin(y))  # -0b1110


 変数xの値である0b1101(10進数で13)のビットを反転させると、0b0010のようになってほしいがそうはなっていないように見えてしまう(Pythonのドキュメントではビット反転では「x のビット単位反転は、-(x+1) として定義されて」いると述べられている。このため、13をビット反転した値は-14となっている)。

 だが、このyの値と2進数のマスク0b11111111とANDを取ってみると(-14の2進表現で各ビットが1になっている桁だけが1となる)、次のような結果が得られる。

print(f'{y & 0b11111111:08b}'# # 11110010


 この結果である「11110010」の下位の4桁に注目すると、元の「0b1101」(10進数)の各ビットがちゃんと反転されていることが確認できる。

 なお、「11110010」は-14を2の補数表現で表記したものであり、その値を10進数に直すには1と0を反転させて1を加算する(符号は最上位ビットが1なのでマイナス)。0と1を反転させると「00001101」となり、そこに1を加算した値である「00001110」を10進数にすると「8+4+2」=14、符号は既に述べた通りマイナスなので、「11110010」は10進数では「-14」であると計算できる。

 このことを使用して、ここではビット単位に0と1を反転させた結果を得る次のような関数を定義した。

from math import log2, ceil

def invert_bitwise(x):
    if x == 0:
        d = 1
    else:
        d = ceil(log2(abs(x))) + 1  # ビット幅を求める
    mask = (1 << d) - 1
    tmp = ~x
    orig_repr = f'{x & mask:0= {d+1}b}'
    inverted_repr = f'{tmp & mask:0= {d+1}b}'
    print(f'{orig_repr}\n{inverted_repr}')
    return orig_repr, inverted_repr


 最初にあるのは、関数が受け取った整数値を2進数で表現するのに必要な桁数を計算して変数dに代入するコードだ。変数maskには上で見たようなAND演算を行うためのマスク(全てのビットが1で桁数は上で計算したもの)を設定している。その後、~演算子でビットを反転した結果を得た後に、元の値とビット反転した値のビットパターンを文字列化している。

 実行例を以下に示す。

o, i = invert_bitwise(-128)
print(f'{o}\n{i}')
# 出力結果:
# 10000000
# 01111111

o, i = invert_bitwise(0)
print(f'{o}\n{i}')
# 出力結果:
# 0
# 1

o, i = invert_bitwise(5)
print(f'{o}\n{i}')
# 出力結果:
# 0101
# 1010


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

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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