ミケネコ研究所 / Perl / Perl の数値変換

Perl の数値変換

Last Updated: 2002/03/26

Perl で、16進文字列、10進整数、2進文字列、バイナリー文字列の相互変換を行うための、スクリプトメモです。

特に断っていない限り、Perl 5.001 以上で動作します。

目次

  1. 16進文字列からの変換
  2. 10進整数からの変換
  3. 2進文字列からの変換
  4. バイナリー文字列からの変換

16進文字列からの変換


16進文字列→10進整数への変換

hex() 関数は、16進文字列を整数値に、手軽に変換できます。

$num10 = hex("4A");     # $num10 には 74 が入る
$num10 = hex("FFFFFF"); # $num10 には 16777215 が入る

余談ですが、数値リテラル中では 0x に続けて 16 進数で記述することで、数値を表すことができます。

$num10 = 0x4A;     # 74
$num10 = 0xFFFFFF; # 16777215

16進文字列→2進文字列への変換

2進文字列はやっかいです。一度バイナリーにパックしてから、2進文字列に変換するというステップを踏みます。

$num2 = unpack("B8",  pack("H2", "7F"  )); # "01111111"
$num2 = unpack("B16", pack("H*", "7F03")); # "0111111100000011"

16進文字列→バイナリ文字列への変換

何?バイナリにパックしたい? そりゃ pack() でしょう。

$char = pack("H2", "41"  );       # "A"

$char = pack("H4", "454A");       # "EJ"
$char = pack("H*", "454A");       # "EJ"
$char = pack("H2H2", "45", "4A"); # "EJ"

CGI でよく使う、URI unescape。エンコードされた文字列 "%83%7E%83P%83l%83R" を "ミケネコ" にデコードしたいときも、上の例どおり pack() を使います。

$char = "%83%7E%83P%83l%83R";

# 上のお手本どおり
$char =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("H2", $1 )/eg;

# しかしどういうわけか、
$char =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C", hex($1) )/eg;
# こちらの方がポピュラーです。

pack("H2") を使えば一撃で変換できるのに、見るからに遅そうな hex() + pack() がよく使われているのはなぜでしょう?ベンチマークをとってみましょう。

Benchmark: timing 5000 iterations of a, b...
 a:  3 wallclock secs ( 3.24 usr +  0.00 sys =  3.24 CPU) <- pack()
 b:  6 wallclock secs ( 5.19 usr +  0.00 sys =  5.19 CPU) <- hex()+pack()

およそ2倍弱ほど違います。

ここでも余談を。変数展開が行われるようなケースでは、エスケープシーケンス \x に続けて 16 進数で記述することで、 1 バイトの文字列を表現することができます。

$char = "\x41";     # "A"
$char = "\x45\x4A"; # "EJ"
$char = "\x454A";   # "E4A" あれれ? \x は、続く 16 進文字を 2 つだけ認識

/[\x41-\x5A]/    #  /[A-Z]/ と同値

10進整数からの変換


10進整数→16進文字列への変換

一度 pack() して、unpack() することもできますが…

$num16 = unpack("H2", pack("C", 59)); # "3b"  しかし…

sprintf() の x フォーマットを使えば、より簡単に変換ができます。

$num16 = sprintf("%x",    59); # "3b"
$num16 = sprintf("%X",    59); # "3B"
$num16 = sprintf("%X", 65536); # "10000"

しかし 10 進整数がリストの場合、要素数だけ、%x%x%x.. とフォーマットを指定しなければならず、リストが大きくなると不便になってきます。この場合は、pack() と unpack() の方がスマートに記述できるでしょう。

$num16 = sprintf("%x%x",   59, 256);       # "3b100" 
$num16 = sprintf("%x%x",   59, 60);        # "3b3c" 
$num16 = unpack("H4", pack("C2", 59, 60)); # "3b3c"

$num16 = sprintf("%x%x%x", 59, 60, 61);        # "3b3c3d"
$num16 = unpack("H*", pack("C*", 59, 60, 61)); # "3b3c3d"  スマート?

10進整数→2進文字列への変換

pack() して、unpack()。やっかいですな。

$num2 = unpack("B8",  pack("C", 127));     # "01111111"
$num2 = unpack("b8",  pack("C", 127));     # "11111110"
$num2 = unpack("B16", pack("C*", 254, 3)); # "0111111100000011"

10進整数→バイナリー文字列への変換

1個だけなら chr() が手軽です。

$char = chr(65);       # "A"

いくつかあるなら素直に pack()。

$char = pack("C", 65);                  # "A"
$char = pack("C*", 65, 66, 67, 68, 69); # "ABCDE"

vec() は少し特殊です。左辺値として使い、整数 65 を A にパックし、$char の任意の位置に埋め込むことができます。

$char = "";      vec($char, 0, 8) = 65; # "A"
$char = "hello"; vec($char, 1, 8) = 65; # "hAllo"

2進文字列からの変換


2進文字列→10進整数への変換

pack() して、unpack()。

$num10 = unpack("C",  pack("B8",   "01111111"           )); # 127
@num10 = unpack("C*", pack("B16",  "0000000101111111"   )); # (1, 127)
@num10 = unpack("C*", pack("B8B8", "00000001","01111111")); # (1, 127)

pack のテンプレートを小文字の b にすると、反転したビットを扱います。

$num10 = unpack("C", pack("b8", "01111111")); # 254

Perl 5.6 以上からは、2進数の定数を 0b で表現できるようになりました。

$num10 = 0b01111111; # 127   Perl5.6 以上限定なんだって

2進文字列→16進文字列への変換

pack() して、unpack()。

$num16 = unpack("H2", pack("B8",   "01111111"            )); # "7f"
$num16 = unpack("H*", pack("B16",  "0000000101111111"    )); # "017f"
$num16 = unpack("H*", pack("B8B8", "00000001", "01111111")); # "017f"

2進文字列→バイナリ文字列への変換

バイナリパックは、もちろん pack()。pack()は必ずスカラーを返します。

$char = pack("B8",  "01000001");            #  "A"
$char = pack("B16", "0100000101000010");    # "AB"
$char = pack("B8B8","01000001","01000010"); # "AB"

2進ビットリスト→バイナリ文字列への変換

vec() を使い、$char の任意の位置のビットを書き換える例です。

@bit = qw( 1 0 0 0 0 0 1 0   0 1 0 0 0 0 1 0 );
$char = ""; $i = 0;
foreach (@bit){
	vec($char, $i++, 1) = $_;
}

# $char は、"AB" になる

バイナリ文字列からの変換


バイナリ文字列→16進文字列への変換

これは簡単。unpack() で一撃です。unpack() はリストを返すことに注意して下さい。

$num16 = unpack("H2",   "A");        # "41"
$num16 = unpack("H*",   "AB");       # "4142"
$num16 = unpack("H*",   "\x41\x42"); # "4142"
@num16 = unpack("H2H2", "\x41\x42"); # ("41", "42")

バイナリ文字列→10進整数への変換

unpack() を使います。固定した文字列に対して、数箇所のバイトを抽出するときは、vec() が便利です。

$num10 = unpack("C", "A" );     # 69
$num10 = unpack("C", "\x45" );  # 69

$num10 = vec("A",        0, 8); # 69
$num10 = vec("\x45",     0, 8); # 69
$num10 = vec("\x45\x46", 1, 8); # 70  "\x45" はスキップされた

$num10 = ord("A");              # 69  ord() って関数もあったね

長いバイナリを変換する例。

@num10 = unpack("C*", "\x47\x48\x49\x4A"); # (71, 72, 73, 74)
@num10 = unpack("C*", "GHIJ");             # (71, 72, 73, 74)

$num10 = vec("\x00\x00\x00\x01", 0 , 32);  # 1
$num10 = vec("\x00\x00\xFF\xFF", 0 , 32);  # 65535
$num10 = vec("\x47\x48\x49\x4A", 0 , 32);  # 1195919690
$num10 = vec("GHIJ",             0 , 32);  # 1195919690

バイナリ文字列→2進文字列への変換

unpack() で一撃です。

$num2 = unpack("B8",   "\x45");     # "01000101"
$num2 = unpack("B8",   "E"   );     # "01000101"
$num2 = unpack("B16",  "EF");       # "0100010101000110"
$num2 = unpack("B*",   "\x45\x46"); # "0100010101000110"
@num2 = unpack("B8B8", "\x45\x46"); # ("01000101", "01000110")

バイナリ文字列→2進ビットリストへの変換

unpack() が返したスカラー1個を split() で分解し、ビット列のリストにして返します。

@bit = split(//, unpack("B8", "E"));  # qw(0 1 0 0 0 1 0 1)
@bit = split(//, unpack("B16","EJ")); # qw(0 1 0 0 0 1 0 1  0 1 0 0 0 1 1 0)

また、上と同じことをしようとして、次のように書いてもうまく動きません。

@bit = unpack("BBBBBBBB", "E")); # qw(0 1 0 0 0 1 0 1) じゃないの?

# 実際には、qw(0 undef undef undef undef undef undef undef) が返る。

最後に

これらの変換が大量に必要になったとき、どうして1回で変換できる関数が Perl に用意されていないのか、どうしてこんなにややこしくて目の疲れるフォーマットで記述しなければならないのか、と憤慨するかもしれません。しかしその前に、自分は Perl に苦手な仕事をさせようとしていないか、ちょっと考えてみて下さい。

一般に、Perl はこういった仕事をたくさんこなすには、不向きなのです。


http://mikeneko.creator.club.ne.jp/~lab/perl/numerical_transform/
ミケネコ研究所
お問い合わせ <lab@mikeneko.ne.jp>