« hide_img.js:画像のクラス指定に順って閲覧を要クリック化する | トップページ | JRF Flag Semaphore for NES Emulator を作った »

2014年3月 5日 (水)

Perl で unpack_bits

Perl の unpack では、ビット表現つまり 2 進数にするための B や b が使える。が、…使えてねぇ!

例えば、

my ($mapper, $four_screen, $trainer, $sram, $vertical_mirror)
  =  unpack("B4BBBB", $ines_flags);


で、$ines_flags には 0x43 でも入ってるとしよう。すると、欲しい値は (4, 0, 0, 1, 1) だ。が、返ってくる値は ("0011", "0", undef, undef, undef) になる。
は?

驚くべきこと、その1。unpack("B") 系は、数値ではなく、ビットの「文字列」を返す。

驚くべきこと、その2。unpack("B") 系の一つの B は必ず 1 バイトにしか対応しない。0x43 == 67 なので、ord('6') == 0b00110110 の 0011 と ord('7') == 0b00110111 の 0 が返ってきたわけだ。

先に pack("C") をしなかったのは私のミスだ。それは認めよう。でも、そうして返ってくるのは ("0100", undef, undef, undef, undef) になる。そんなの欲しかったわけじゃない!

ということで、上でしたかったように書く正解は次になる。

my ($mapper, $four_screen, $trainer, $sram, $vertical_mirror)
  =  map {unpack("C", pack("b" . length($_), scalar reverse($_)))}
       unpack("a4aaaa", unpack("B8", pack("C", $ines_flags)))); # Crazy!


あかん。これはあかん。ということで最初に書いたみたいに動く関数 unpack_bits を書いた。これだと、↓で OK。

my ($mapper, $four_screen, $trainer, $sram, $vertical_mirror)
  = unpack_bits("B4BBBB", $ines_flags);


ソースは↓。もちろんパブリックドメインで。もっといい書き方があるかもしれないけど…。

  sub unpack_bits {
    my ($origspec, $origbits) = @_;
    my @r;
    my $c = "B";
    my $len = 0;
    my $spec = reverse($origspec);
    my $bits = $origbits;
    while ($spec ne "") {
      my $n = 1;
      if ($spec =~ s/^[01-9]+//) {
        $n = reverse($&);
      }
      $spec =~ s/^.//;
      if ($& eq "b") {
        $c = "b";
      }
      push(@r, $bits & ((1 << $n) - 1));
      $bits = $bits >> $n;
      $len += $n;
    }
    if ($c ne "b") {
      return reverse @r;
    }
    @r = ();
    $spec = $origspec;
    $bits = $origbits;
    while ($spec ne "") {
      my $n = 1;
      my $c = "B";
      if ($spec =~ s/^[^01-9]//) {
        $c = $&;
      }
      if ($spec =~ s/^[01-9]+//) {
        $n = $&;
      }
      if ($c eq "b") {
        push(@r, $bits & ((1 << $n) - 1));
        $bits = $bits >> $n;
      } else {
        push(@r, ($bits >> ($len - $n)) & ((1 << $n) - 1));
      }
      $len -= $n;
    }
    return @r;
  }

  sub pack_bits {
    my ($spec, @bits) = @_;
    my @r;
    my $len = 0;
    my $bits = 0;
    while (@bits) {
      my $b = shift(@bits);
      my $n = 1;
      my $c = "B";
      if ($spec =~ s/^[^01-9]//) {
        $c = $&;
      }
      if ($spec =~ s/^[01-9]+//) {
        $n = $&;
      }
      $b = $b & ((1 << $n) - 1);
      if ($c eq "b") {
        $bits = $bits | ($b << $len);
      } else {
        $bits = ($bits << $n) | $b;
      }
      $len += $n;
    }
    return $bits;
  }



参考

INES - Nesdev wiki》。ある意味広く普及した LLVM の一種としてファミコンエミュレータに注目。

Perl の数値変換》。>憤慨するかもしれません。しかしその前に、自分は Perl に苦手な仕事をさせようとしていないか、ちょっと考えてみて下さい。 <sed や awk の流れで perl を見ればそうなるけど…。もう Perl で文字列処理に限定される使われかたじゃないし…。

更新: 2014-03-05
初公開: 2014年03月05日 06:24:44
最新版: 2014年03月05日 06:24:44

2014-03-05 06:24:44 (JST) in Perl | | コメント (0) | トラックバック (0)

Perl」カテゴリ内の最近の記事

批評や挨拶のためのネットコミュニティ

  • はてなブックマーク(って何?) このエントリーをはてなブックマークに追加 このエントリーを含むはてなブックマーク このエントリーを含むはてなブックマーク
  • Twitter (って何?)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/93568/59239761

トラックバックのポリシー

他サイトなどからこの記事に自薦された関連記事(トラックバック)はまだありません。
» JRF のソフトウェア Tips:Perl で unpack_bits (この記事)

コメント

コメントを書く



(メールアドレス形式)


※匿名投稿を許可しています。ゆるめのコメント管理のポリシーを持っています。この記事にまったく関係のないコメントはこのリンク先で受け付けています。
※暗号化パスワードを設定すれば、後に「削除」、すなわち JavaScript で非表示に設定できます。暗号解読者を気にしないならメールアドレスでもかまいません。この設定は平文のメールで管理者に届きます。
※コメントを書くために<b>ボールド</b>、<pre>詩文やソースコード</pre>、<a href="">リンク</a>、その他のHTMLタグが使えます。また、漢字[かんじ]でルビが、[google: キーワード] で検索指定が使えます。


ランダムことわざ: 温故知新。

今日のネットの話題: アジアがすごいぜ。英語やろうぜ。