Perl - 2, 8, 10, 16 進法とかの変換

pack, unpack, oct, hex などいろいろ使えば、A 進法から B 進法に変換できる。これをまとめてやる Integer クラスを Perl で作ってみた。2-16 進法まで扱えて、例えば 12 進法の 4A7 は

use Integer;
my $int = Integer->new_as_base_of(12, "4A7");

で作れて、それを 5 進法表記で欲しいなら

print $int->to_base_of(5); # 10303

とすればおk。特に 2, 8, 10, 16 進法は bin, oct, dec, hex の名前の付く命令でアクセスできる。

実行例

#!/usr/bin/env perl
# Integer.pm と同じディレクトリに保存して実行
use strict;
use warnings;
use lib '.';
use Integer;

my $base     = "12";
my $expression = "A5B239";

# baseとそのbaseでの表現を引数としてオブジェクトをつくる
my $int1 = Integer->new_as_base_of($base, $expression);
printf "Base-%2d: %s can be expressed as follows.\n",
    $base, $expression;
for my $i (2 .. 16) {
    printf "Base-%2d: ", $i;
    print $int1->to_base_of($i), "\n";
}
print "\n";

# 2,8,10,16 は bin,oct,dec,hex の命令がある
my $int2 = Integer->new(123); # Integer->new_dec(123) でもいい
print "123 can be expressed as follows\n";
print "Binary     : ", $int2->to_bin, "\n";
print "Octal      : ", $int2->to_oct, "\n";
print "Decimal    : ", $int2->to_dec, "\n";
print "Hexadecimal: ", $int2->to_hex, "\n";

f:id:ks0608:20120127024711p:plain

Integer.pm は以下。

# Save as Integer.pm
package Integer;
use strict;
use warnings;
use overload '""' => "to_dec";

sub new {
    my ($class, $int) = @_;
    bless \$int, $class;
}

sub to_dec { ${shift()} }
sub to_hex { sprintf "%X", ${shift()} }
sub to_oct { sprintf "%o", ${shift()} }
sub to_bin { shift->to_base_of(2) }

sub new_dec { shift->new(shift) }
sub new_hex { shift->new(hex(shift)) }
sub new_oct { shift->new(oct("0" . shift)) }
sub new_bin { shift->new_as_base_of(2, shift) }

sub new_as_base_of {
    my ($class, $base) = @_;
    my @expression = _replace_to_number(split //, $_[2]);
    my $int;
    my $position = 0;
    for (reverse @expression) {
        $int += ($base ** $position++) * $_;
    }
    $class->new($int);
}

sub to_base_of {
    my ($self, $base) = @_;
    my $int = $$self;
    my @expression;
    do {
        unshift @expression, $int % $base;
        $int = int($int / $base);
    } while ($int);
    join('', _replace_to_char(@expression));
}

sub _replace_to_number {
    my @expression = @_;
    for (@expression) {
        s/A|a/10/; s/B|b/11/; s/C|c/12/;
        s/D|d/13/; s/E|e/14/; s/F|f/15/;
    }
    return @expression;
}
sub _replace_to_char {
    my @expression = @_;
    for (@expression) {
        s/10/A/; s/11/B/; s/12/C/;
        s/13/D/; s/14/E/; s/15/F/;
    }
    return @expression;
}

1;

今日の疑問

  • コードを少なくするため sub new_dec とかを shift を2つ使うやつで書いた。書いた自分がこれで shift の解釈の順が思った通りになるのか疑問だったがうまくいったのでそうした。これくらい常識なのか?