はじめに
今日はpython でbit 演算子の使い方を調べたのでメモします。バイナリメッセージを生成するときなどに使えると思います。
- 12bit で値を書いて、続けてuint12 で別の値を書く
- さらに続けて2bit + int38 を書く
- それらをつなげる
ことができればミッション達成です。つなげるのは bytes 型を接続するだけなので問題ない。最初の2つ、できるかな。
調査内容
12 bits + 12 bits
bytes 型で8bit 単位で扱うのが基本作戦。なので、ここでは 24bit = 3bytes のbytes を作ることを目標にします。
最初に固定された ID (1005 = 0011 1110 1101)があり、続いて指定された数字を 12bit で続く、というものです。
作戦は、二つの整数値を作って、合体させるです。具体的には、それぞれの位置にシフトさせて、or(論理和)をとる、というものです。
2進数表記は下記で確認できます。
In [2]: bin(1005)
Out[2]: '0b1111101101'
In [3]: 0b1111101101
Out[3]: 1005
In [5]: hex(1005)
Out[5]: '0x3ed'
In [6]: (1005).to_bytes(2,'big')
Out[6]: b'\x03\xed'
このままだと、最初の6bit は0 で2bit + 8bit で1005 を実現しています。0b11 が0x3です。
In [10]: 0b11*256+0b11101101
Out[10]: 1005
なので4bit シフトさせると、8bit (上位は0)+ 4 bit にできます。今は3byteにしたいので 12bit シフトさせます。
In [13]: bin(1005 << 4)
Out[13]: '0b11111011010000'
In [14]: bin(1005 << 12)
Out[14]: '0b1111101101000000000000'
In [15]: (1005 << 4).to_bytes(2, 'big')
Out[15]: b'>\xd0'
In [16]: (1005 << 12).to_bytes(3, 'big')
Out[16]: b'>\xd0\x00'
In [63]: [ f'{x:02X}' for x in int(1005 << 12).to_bytes(3,'big')]
Out[63]: ['3E', 'D0', '00']
指定したい番号nが12だとします。このあと、論理和(or)をとればOKです。なお、bytes 型の文字列表示の読み方が最初分かりませんでしたが、アスキーコードと16進数表示が混じっています。最初の1 bytes はアスキーコードです。下記を試すと分かり易い。b'>'は0x3Eのことです。
In [80]: f'{b">"[0]:X}'
Out[80]: '3E'
In [18]: bin(12)
Out[18]: '0b1100'
In [19]: bin(1005 << (4+8+8) | 12)
Out[19]: '0b1111101101000000001100'
In [20]: int(1005 << 12 | 12).to_bytes(3,'big')
Out[20]: b'>\xd0\x0c'
In [57]: [ f'{x:02X}' for x in int(1005 << 12 | 12).to_bytes(3,'big')]
Out[57]: ['3E', 'D0', '0C']
後半の12bit が 12 (0x0c) になっています。良さそうだ。
2 bits + 38 bits
次のお題は、2bit の後に、指定した数字(int38)を書いた bytes 型を作成する、というものです。
作戦としては、40 / 8 = 5bytes の型を作ります。
最初の2bit を仮に0b01 として、38 bit シフトさせます。
In [84]: a = 1
In [85]: bin(a)
Out[85]: '0b1'
In [86]: bin(a << 38)
Out[86]: '0b100000000000000000000000000000000000000'
書きたい値Xは、38 bit の符号付整数値です。5 bytes で書くのですが、bytes にはunsigned int からしか変換できない。
ので、負の数は2進法38bit の補数であることを考えて
In [214]: bin((x0 + 2**38) % (2**38))
Out[214]: '0b11011011001001010110001110111101001001'
としてみました。38bit あるしあってそう。
'0b11 0110 1100 1001 0101 1000 1110 1111 0100 1001'
ということで、二つの論理和をとるには、これで良いのかな。
In [249]: y = (a << 38 | ((x0 + 2**38) % (2**38)))
In [250]: [ f'{x:08b}' for x in y.to_bytes(5, 'big')]
Out[250]: ['01110110', '11001001', '01011000', '11101111', '01001001']
これでとりあえず、やりたかったことはできたっぽい。
まとめ
bit 演算子を使って、メッセージをencode するためのpython 実装方法を調査。int38 で負の数を扱うために2進数で補数を考えるところを復習してしまった。
明日から一週間、しんどいなー。とりあえず生き延びられるかな。
(2021/01/23)
リンク
- 素晴らしすぎる bit 演算子の解説
- 昔書いた bytes 型のメモ