算術シフトって初めて聞きました

 HSP3のビットシフトについて、知らなかった話を聴いたので覚書。

Re: Windows10 のテーマ色を取得するには?
http://hsp.tv/play/pforum.php?mode=all&num=92176

32ビット整数は一番左のビットが符号ビットで、
このビットが1だとマイナスとして扱われます。
そしてHSPの右ビットシフトは「算術シフト」と言って、
一番左のビットが1の場合、右ビットシフトで左にできたスペースは
すべて1で埋められます。

 そうだったのか!
HSP3のマニュアルにもこれについて説明する記述はなかったような気がします。 今までよく知らずに使ってましたが、たまたま不具合が出ていなかっただけのようです。良かった。…のか?

 とりあえず確認してみよう。

2進数表示

 HSP3には整数値を2進数表記して使用することは出来ます。

2進整数
%0~%111111...
0b0~0b111111...

引用元:HSP3「プログラミング・マニュアル 3.6.式」

 しかし一方で、整数値を2進数文字列にする機能はありません。詳しくは strf を参照。

 ビットシフトの動作を明確に目視できるようにするには、2進数表記が必要です。 仕方がないので、まずは整数値を 2 進数表記する機能を作ります。
HSP開発wiki 小ワザ/ビット操作
https://wiki.hsp.moe/%E5%B0%8F%E3%83%AF%E3%82%B6%EF%BC%8F%E3%83%93%E3%83%83%E3%83%88%E6%93%8D%E4%BD%9C.html
ここにいい感じのサンプルがあったので少し変えて使います。


#module
;値bitsを32桁の2進表記の文字列に変換するモジュール
#defcfunc strbit int bits
	s = ""
	repeat 32
		if bits & (1<<cnt) {
			s = "1" + s
		} else {
			s = "0" + s
		}
	loop
	return s
#global
    

 今回は符号フラグが見えないと確認にならないので、桁数は32の固定です。 適当な数値で動作を確認してみます。


mes  "%10987654321098765432109876543210"
flg = %10000000000000000000000000000000	; 2進数表記
mes "%" + strbit(flg)
mes "%" + strbit(1)
mes "%" + strbit(1<<10)
mes "%" + strbit(1<<20)
mes "%" + strbit(1<<30)
mes "%" + strbit(1<<31)
    

実行結果はこちら。

%10987654321098765432109876543210
%10000000000000000000000000000000
%00000000000000000000000000000001
%00000000000000000000010000000000
%00000000000100000000000000000000
%01000000000000000000000000000000
%10000000000000000000000000000000

良さそうです。見にくいようならs = "0" + ss = "_" + sに変えてください。私は変えておきます。

算術シフト

算術シフトでは、符号ビットを除いたビットパターンをずらし、符号ビットはずらさない。あふれたビットは切り捨てて、空いた部分に「0」を挿入する。

引用元:算術シフト - weblio辞書 IT用語辞典バイナリ

 ということらしいので、まずは「%0…01111011」を左へ 1 ビット シフトしてみます。


mes "左へ 1ビットパターン シフト"
mes  "%10987654321098765432109876543210"
flg = %00000000000000000000000001111011
mes "%" + strbit(flg)
mes "%" + strbit(flg<<1)
    

 結果はこうなります。一番右の1ビットが空になった後は、0で埋められています。これが普段使っているときの普通だと思っている動きです。

左へ 1ビットパターン シフト
%10987654321098765432109876543210
%_________________________1111_11
%________________________1111_11_

 次は、「%1011010…0」を右に1ビット シフトしてみます。一番左の符号ビットが 1 になっているデータです。


mes "右へ 1ビットパターン シフト"
mes "符号ビット 1"
mes  "%10987654321098765432109876543210"
flg = %10110100000000000000000000000000	; 符号ビット 1
mes "%" + strbit(flg)
mes "%" + strbit(flg>>1)
mes "%" + strbit(flg>>2)
    

 左に移動するときは空いたビットは 0 で埋められましたが、 右に移動すると一番左の空になったビット(符号ビット)が 1 の状態のまま変化していませんね。 2 ビットシフトした場合も、空きビットは符号ビットで埋められています。

右へ 1ビットパターン シフト
符号ビット 1
%10987654321098765432109876543210
%1_11_1__________________________
%11_11_1_________________________
%111_11_1________________________

 符号ビットを0にするとどうなるでしょうか。


mes "右へ 1ビットパターン シフト"
mes "符号ビット 0"
mes  "%10987654321098765432109876543210"
flg = %00110100000000000000000000000000	; 符号ビット 0
mes "%" + strbit(flg)
mes "%" + strbit(flg>>1)
    

 一番左のビット(符号ビット)は 0 のまま変化していません。空きビットは 0 (符号ビットと同じ値)で埋められています。

右へ 1ビットパターン シフト
符号ビット 0
%10987654321098765432109876543210
%__11_1__________________________
%___11_1_________________________

 なるほど…ビットの動きは理解できました。

2 で割る

 算術シフトで右に1ビットシフトだと 2 で割った結果とおんなじになるんですね。これは便利。 HSP3の場合、整数は常に「符号付き整数」のみなので、符号ビットが残る算術シフトが常に使用されているのですね。


a = %000100
mes a
mes a>>1
mes (a / 2)

a = %10000000000000000000000000000000
mes a
mes a>>1
mes (a / 2)
    

結果

4
2
2
-2147483648
-1073741824
-1073741824

論理シフト

 ビットシフトして空いたビットは必ず 0 で埋められます。 この場合、右にビットシフトして空いた左側のビットは 0 で埋められます。 符号なし整数の場合は、この動きをするらしいです。

 HSP3だと関係ないのでここでは掘り下げません。

注意点と使い方

 HSP3ではビットシフトは常に算術シフトになります。 符号ビットまで使用するようなデータを取り扱う場合は、注意が必要になります。

 例えば、色データが0xAARRGGBB(※ わかりやすくするため16進数表記)で入っている変数を取り扱う場合です。 左から順に、0xAAが透明度、(0xRR, 0xGG, 0xBB)が(赤、緑、青)の情報として格納されています。 この場合、32bit全てがデータの格納に使用されています。一番左の符号ビット領域は、透明度情報の一部を記述するために利用されています。

 もしこのデータから値を取り出す際に、ビットシフトを論理シフトだと思っていた場合はスクリプトはこのように書いてしまうかもしれません。


mes "      0xAARRGGBB"
mes "flg = 0x81234567"
c = 0x81234567	; 0b10…0	; 2進数表記
mes "c     -> B = " + strf("0x%08X", c     & 0xFF )	; 0x81234567 & 0xFF
mes "c>>8  -> G = " + strf("0x%08X", c>>8  & 0xFF )	; 0x00812345 & 0xFF
mes "c>>16 -> R = " + strf("0x%08X", c>>16 & 0xFF )	; 0x00008123 & 0xFF
mes "c>>24 -> A = " + strf("0x%08X", c>>24        )	; 0x00000081 ←こうなると予想している…!
    

実際は実行するとこうなります。

      0xAARRGGBB
flg = 0x81234567
c     -> B = 0x00000067
c>>8  -> G = 0x00000045
c>>16 -> R = 0x00000023
c>>24 -> A = 0xFFFFFF81

 整数であるはずの透明度がマイナスに…どうして。意味わかんねーよ。試しに(c&0xFF000000)>>24などとやってみても同じ結果になります。 符号ビットで埋められてしまうのでこうなってしまいます。 恐ろしいことにテストするときだけ符号ビットがたまたま 0 だった場合は、論理シフトでも算術シフトでも同じ結果になってしまうので、不具合に気づかないまま使用してしまう可能性があります。

 というわけでHSP3の場合は算術シフトなので、正しくはこうする必要があります。


mes "      0xAARRGGBB"
mes "flg = 0x80000000"
c = 0x80000000	; 0b10…0	; 2進数表記
mes "c     -> B = " + strf("0x%08X", c     & 0xFF )	; 0x81234567 & 0xFF
mes "c>>8  -> G = " + strf("0x%08X", c>>8  & 0xFF )	; 0xFF812345 & 0xFF
mes "c>>16 -> R = " + strf("0x%08X", c>>16 & 0xFF )	; 0xFFFF8123 & 0xFF
mes "c>>24 -> A = " + strf("0x%08X", c>>24 & 0xFF )	; 0xFFFFFF81 & 0xFF ←実際はこうなっている
    
      0xAARRGGBB
flg = 0x80000000
c     -> B = 0x00000000
c>>8  -> G = 0x00000000
c>>16 -> R = 0x00000000
c>>24 -> A = 0x00000080

透明度がちゃんと取り出せました。よかったよかった。

右にビットシフトするような処理をする際は、気をつけたいですね。

結論

 過去にリリースしたモジュールについて、全部チェックしたほうがいいかもしれない。 …めんどくさい。「>>」を含んだテキストファイルをピックアップしてチェックするだけなのでスクリプトの修正は難しくありません。 しかし、一体いくつ検出されるのやら…あまり使っていなかったと思いますが。 そしてスクリプトを修正した後は、readme.txtも修正して zip に圧縮。その後サイトも修正して、ファイルをアップロード。 うん。手順多い。めんどくさい!!わーん!!!