とあるバイナリデータがある。
そのデータは、1バイト(8ビット)を2ビットずつに区切って、全部で60個ある装置の状態をヘルスデータとして流してくる。データの大きさは 2ビット x 60装置 = 15バイト。
その 2ビットの内容は、
0(00)...NG1(01)...WRN(警告)2(10)...OK
という具合。(カッコ内は 2進数)
その 1バイトの中で、装置番号の若いものが下位ビットにセットされる。
だから、4台の装置の状態が、
No.1 10(OK)No.2 01(WRN)No.3 10(OK)No.4 00(NG)
となっていれば、'10011000'ではなく、下位から順にセットしていき '00100110' となる。16進であれば、0x26 だ。
この情報を装置の番号順に取っていきたい。つまり、15バイトのデータから1バイトずつ抜き出して、さらにその 1バイトの中の下位ビットから 2ビットずつ取っていきたい。
'00100110' というデータであれば、
1.下位 2ビットだけを残す論理積計算をおこなう。
('00000011' という 2進数と論理積を取れば、下位 2ビットの情報だけを抜き出せる)
2.論理積の計算後、次の処理のため 2ビット下位にシフト。
('00100110'を、2ビット右にシフトして'00001001'に)
この処理を、1バイト分(つまり 4回)繰り返し・・・となる。
$v1 = "\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x26\xAA\x00"; # データ 15byte(60装置分)$v2 = 3; # 00000011for ($i = 0; $i < 15; $i++) {# 1byte 抜き出すif ($v1 !~ /^(.{1})(.*)$/) {last; # データが無くなれば15byte未満でもループ終了}$v3 = $1; # 抜き出した 1byte をセット$v1 = $2; # 残りは次の処理に使う$w1 = hex(unpack "H*", $v3); # 10進数に変換# 抜き出した 1byte の下位から 2bitずつ見ていくfor ($j = 0; $j < 4; $j++) {$w2 = $w1; # 論理積計算用に退避$w2 = $w2 & $v2; # 論理積$w1 = $w1 >> 2; # 右(下位)に 2bit シフトprint "$i.$j $w2\n";}}
まあ、こんな感じで。
ポイントは、「シフトする値は、予め10進数に変換しておくこと」だな。
「整数」でないと駄目なので、16進のまま、
$v1 = "\x26";$v2 = "\x03";$v1>>2; # 右(下位に 2bit シフト)$v1 = $v1 & $v2;
なんてやってもまともな結果は出ない。
('00100110'をシフトして'00001001'になってほしいけど、何故か'01001000'になっちゃうんだよな)
そこに気づくのに時間がかかってハマってしまった(^^;
ビット単位でシフトするだけなんだから 16進のままでええやん・・・と思うのだが、俺の知らない問題が色々あるんだろうね。プログラミング言語の実装には。
ちなみに、上位ビットからセットしてくれれば、Perl なら「2進文字列」に unpack して、2文字ずつ頭から抜けばいいだけなので、上の例の処理部を差し替えるなら、
$w1 = unpack("B*", $v3); # 1バイト=8桁の2進数に変換@w = $w1 =~ /.{2}/g; # 2桁ずつに分解for ($j = 0; $j < 4; $j++) {$w[$j] = unpack("C", pack("B8", '000000' . $w[$j])); # 2桁の 2進数を10進変換print "$i.$j $w[$j]\n";}
こんな感じで、如何にも Perl っぽくて嬉しい。(いや、ちょっとでも「正規表現」が入ると Perl っぽいやん(笑)2桁ずつに分解してるところだけど(笑))