numpyでシフト演算を使って符号無し⇒符号有りに変換する


TL;DR

numpyの符号無しのndarrayをシフト演算を使って符号有りに変換する方法の紹介です.

numpyのシフト演算

numpyは要素毎にシフト演算することができる.
シフト演算も例外できる.

>>> import numpy as np
>>> val = np.arange(5)
>>> val
array([0, 1, 2, 3, 4])
>>> val << 4
array([ 0, 16, 32, 48, 64], dtype=int32)
>>> val >> 1
array([0, 0, 1, 1, 2], dtype=int32)

符号無し => 符号有りに変換

astype

astype関数を使って符号無しから符号有りに変換することはできる.
8bitであれば,uint8からint8に変換すれば問題ない(16bit/32bit/64bitも同様).

>>> import numpy as np
>>> val = np.asarray([255, 0, 127, 128], dtype=np.uint8)
>>> val
array([255,   0, 127, 128], dtype=uint8)
>>> val.astype(np.int8)
array([  -1,    0,  127, -128], dtype=int8)

しかし,4bitや12bitといったデータを扱う場合こうはいかない.
(4bitや12bitといった型は存在しないため)
要素数が少ない場合は2進変換->10進変換をfor文でループしたり,frompyfuncを使えば良いが,要素数が多いと時間がかかる(numpyの意味がない).
np.whereを使うとしても要素数が多いと時間はかかってくる(インデックスアクセスが遅くなる).

そこでシフト演算を使うことで解決を図る.

シフト演算

numpyでは型の上限のbitまで左シフトしてから右シフトすると符号有りに変換される.
最上位bit=符号bitに情報が残るようである.

符号有り8bitで詳しく説明する.

1を2進で表すと以下
1 → 00000001

これを7bit左シフト
000000001 << 7
> 10000000

10進で表すと
10000000 → -128

7bit左シフトした分を右シフト
10000000 >> 7
> 00000001 (理論的にはこっち)
> 10000001 (実際はこっち)

10進で表すと
00000001 → 1
10000001 → -1

つまり,4bitだと8bit,12bitだと16bitで情報を持つことになるので,4bit左シフトして右シフトすると符号有りに変換できる.
(24bitだと32bitなので8bitシフトになる)

>>> import numpy as np
>>> val = np.arange(pow(2, 4), dtype=np.int8)
>>> val
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15],
      dtype=int8)
>>> val << 4 >> 4
array([ 0,  1,  2,  3,  4,  5,  6,  7, -8, -7, -6, -5, -4, -3, -2, -1],
      dtype=int8)

まとめ

この特性を活かすことで,シフト演算を使って高速に4bitや12bitを符号無しから符号有りに変換できます.
(4bitや12bitといっていますが,何bitでもOKです)
ぜひ試してみてください.