Perl入門ゼミ

テキスト処理、Linuxサーバー管理、Web開発
  1. Perl
  2. 進数変換

2進数、8進数、16進数の進数変換

Perlで「進数変換」を行う方法を解説します。

10進数から2進数、8進数、16進数への変換

10進数から2進数、8進数、16進数に変換してみましょう。sprintf関数でフォーマット指定を使います。

# 2進文字列に変換
my $bit_str = sprintf("%b", $num);
# 8進文字列に変換
my $oct_str = sprintf("%o", $num);
# 16進文字列に変換
my $hex_str = sprintf("%X", $num);

10進文字列を2進数に変換するには、sprintf関数でフォーマット指定で "%b"とします。8進数、16進数に変換するにはそれぞれ、"%o","%X"とします。

sprintf関数はprintf関数によくにた関数です。sprintfを使えば、printf関数で出力される文字列が戻り値として取得できます。

以下は2進数、8進数、16進数への変換のサンプルです。

use strict;
use warnings;

# 数値
my $num = 255;

# 2進文字列に変換
my $bit_str = sprintf("%b", $num);

# 8進文字列に変換
my $oct_str = sprintf("%o", $num);

# 16進文字列に変換
my $hex_str = sprintf("%X", $num);

print "(1)10進文字列から2,8,16進文字列への変換\n";
print "10進文字列: $num;\n";
print "2進文字列: $bit_str;\n";
print "8進文字列: $oct_str;\n";
print "16進文字列: $hex_str;\n";

2進数、8進数、16進数から10進数への変換

2進数、8進数、16進数から10進数への変換を解説します。

2進数から10進数への変換

2進数から10進数へ変換するにはoct関数を使用します。oct関数は本来は8進数を10進数に変換する関数ですが、0bが先頭についた文字列を引数として渡した場合は、2進数を10進数に変換します。

my $num = oct('0b' . $bit_str);

8進数から10進数への変換

8進数から10進数へ変換するにはoct関数を使用します。サンプルでは'17'を渡していますが'017'のように先頭に0がついていても正しく変換されます。

my $num = oct($oct_str);

16進数から10進数への変換

16進数から10進数へ変換するにはhex関数を使用します。サンプルでは'FF'を引数に渡していますが'0xFF'のように先頭に0xがついていても正しく変換されます。

my $hex_to_dec = hex($hex_str);

2進数、8進数、16進数を10進数へ変換するサンプルです。

use strict;
use warnings;

my $bit_str = '110';
my $oct_str = '17';
my $hex_str = 'FF';

# oct関数で、2進文字列から10進数へ変換する。
print "2進数から10進数への変換\n";
my $bit_to_dec = oct('0b' . $bit_str); 
print "$bit_str -> $bit_to_dec\n\n";

# oct関数で8進文字列から10進数へ変換する。
print "8進数から10進数への変換\n";
my $oct_to_dec = oct($oct_str);
print "$oct_str -> $oct_to_dec\n\n"; 

# hex関数で8進文字列から10進数へ変換する。
print "16進数から10進数への変換\n";
my $hex_to_dec = hex($hex_str);
print "$hex_str -> $hex_to_dec\n\n"; 

10進数からn進数へ変換するアルゴリズム

10進数からn進数に変換するサンプルです。引数の有効性のチェックもきちんと行ってみました。

use strict;
use warnings;

# 10進数からへn進数への変換
my $dec_num = -255;

# 変換すると-FF
print "10進数 $dec_num => 16進数 " . dec_to_n($dec_num, 16);

# 10進数をn進数に変換( 16進数まで対応 )
sub dec_to_n {
    # 10進数と変換後の進数
    my ($dec, $n) = @_;

    # $n の有効性のチェック
    return if !defined $n || $n !~ /^\d+$/ || $n < 2 || $n > 16;

    # $decの有効性のチェック
    return if !defined $dec;
    return if $dec !~  /^([+-]?)\d+$/;

    # 符号が存在する場合は保存しておく
    my $sign = $1 ? substr($dec, 0, 1, '') : ''; 

    # 0ならばそのまま返却
    return $dec if $dec == 0; 

    # 余りの配列
    my @rem_list; 
    
    # 10進数をn進数に変換する。
    while ($dec != 0) {
      # 余りを求めて保存しておく。
      unshift(@rem_list, $dec % $n);
      
      # 商を求める。
      $dec = int($dec / $n);
    }
    
    # 進数表現に変換するテーブル。
    my @dec_to_n = (0 .. 9, 'A' .. 'F');

    # 変換テーブルで変換して文字を結合
    return join('', $sign, map { $dec_to_n[$_] } @rem_list); 
}

10進数からn進数に変換するサブルーチンの仕様

dec_to_n($dec, $n);
dec_to_n(255 , 16);

第1引数は10進数、第2引数は10進数から何進数に変換するのかを数字で指定します。戻り値は変換できた場合は対応するn進数が、変換できなかった場合はundefが返ります。$nは、2進数から16進数までに対応しています。

$n の有効性のチェック

return if !defined $n || $n !~ /^\d+$/ || $n < 2 || $n > 16;

まず$nが引数として渡されている必要があります。次に2以上16以下の整数である必要があります。この条件を1行で表現したのが上の文です。

もし「$nが定義されていない」あるいは「$nが数字列でない」あるいは「$nが2より小さい」あるいは「$nが16より大きい」場合は、エラーとみなして単独のreturnを返します。

単独のreturnはスカラーコンテキストの場合はundefを、リストコンテキストの場合は空リストを返します。

$decの有効性のチェックと符号の取り出し

return if !defined $dec;
return if $dec !~  /^([+-]?)\d+$/;
my $sign = $1 ? substr($dec, 0, 1, '') : '';

$decは引数として渡されている必要があります。次に、10進数の整数である必要があります。10進整数のをあらわす正規表現

/^([+-]?)\d+$/

になります。(「先頭に+か-があってもなくてもよくて、その後に数字がひとつ以上続いて終わる」と読む)

my $sign = $1 ? substr( $dec, 0, 1, '' ) : '';

符号がついていた場合は$1に符号が代入されますので、$1が定義されているかどうかで符号のありなしを判定します。ここでは、条件分岐に3項演算子を用いています。

符号があれば、substr関数で、先頭の一文字を空文字に置換することで削除します。substr関数の戻り値には、削除された文字すなわち+か-が返却されます。

$1が未定義の場合は符号がないので''空文字列を代入しておきます。

10進数をn進数に変換するアルゴリズム

return $dec if $dec == 0;
    
my @rem_list;
    
# 10進数をn進数に変換する。
while ($dec != 0) {
  unshift(@rem_list, $dec % $n);
  $dec = int($dec / $n);
}

$decが0の場合は、戻り値が必ず0になるので0を返却しておきます。whileループの中で、10進数をn進数に変換するのですが、0の場合は例外的な記述を行わないといけないので先に処理しておきました。

10進数をn進数に変換するアルゴリズムは以下のようになります。

10進数をnで割り算することを商が0になるまで繰り返す。繰り返しの割り算を行った余りを逆さから記述するとn進数に変換できる。

もう少し数字を使ってわかりやすく書いてみます。

10進数の11を2進数に変換する。

11 ÷ 2 = 5 余り 1
 5 ÷ 2 = 2 余り 1
 2 ÷ 2 = 1 余り 0
 1 ÷ 2 = 0 余り 1

余りを逆さから書くと 1011 でこれが答え。

unshift関数を使って、配列の先頭に余りを挿入していくことで、この順番に並びます。

配列に代入されている数値をn進文字に変換して結合

my @dec_to_n = (0 .. 9, 'A' .. 'F');
return join('', $sign, map {$dec_to_n[$_]} @rem_list); 

たとえば16進数などの場合は、11~15をA~Fに変換する必要があります。@dec_to_nは変換のための配列です。

@rem_list の各要素をmap関数で、 $dec_to_n[ $_ ] に変換します。

$dec_to_n[1] → 1
$dec_to_n[10] → A
$dec_to_n[15] → F

のように変換されます。

変換された配列と$signに保存してある符号をjoin関数で結びつけてひとつの文字列にします。

n進数から10進数へ変換を行うアルゴリズム

n進数から10進数へ変換するサンプルです。

n進数から10進数へ変換するアルゴリズムは簡単なので、引数の有効性のチェックもきちんと行ってみました。

このサンプルでは、引数の有効性のチェックは本処理の3倍くらいありますが、本処理よりエラー処理の記述が多くなることはよくあることです。

use strict;
use warnings;

# n進数から10進数への変換
my $hex_num = "FF"; # 10進数では255 (15*16 + 15)
print "16進数 $hex_num => 10進数 " . n_to_dec($hex_num , 16) . "\n";

# n進数を10進数に変換( 16進数まで対応 )
sub n_to_dec {
  # 数字列と進数
  my ($num_str, $n) = @_;

  # $n の有効性のチェック
  return if !defined $n || $n !~ /^\d+$/ || $n < 2 || $n > 16;
    
  # $num_strの有効性のチェック( $nとの整合性のチェックを含む )
  return if !defined $num_str;

  # 個々の数字に分解
  my @nums = split(//, $num_str);
    
  # 許容するのは1~9とA~F
  my @valid = (0 .. 9, 'A' .. 'F');
  my %valid;
  for (my $i = 0; $i < @valid; $i++) {
    # 変換テーブルを作成( $valid{E} = 15
    # $valid{F} = 16 のようなテーブル )
    $valid{$valid[$i]} = $i; 
  }
    
  # @nums の個々の数字が変換テーブルで正しく変換できない場合
  # あるいは、変換後の数字に$n以上の数が含まれていた場合はundefを返す。
  for my $num (@nums) {
    # 小文字の場合は大文字に変換
    $num = $valid{ uc $num };
    return if !defined $num || $num >= $n;
  }
    
  # ここからが進数変換の本処理
  my $dec = 0;
  for my $i (@nums) {
    # $nを掛けることで繰上げを行う
    $dec = $dec * $n + $i;
  }
  return $dec;
}

n進数から10進数に変換するサブルーチンの仕様

n_to_dec($num_str, $n);
n_to_dec('FE16' , 16);

第1引数はn進文字列、第2引数は何進数から10進数に変換するのかを数字で指定します。戻り値は変換できた場合は対応する10進数が、変換できなかった場合はundefが返ります。$nは、2進数から16進数までに対応しています。

$n の有効性のチェック

return if !defined $n || $n !~ /^\d+$/ || $n < 2 || $n > 16;

まず$nが引数として渡されている必要があります。次に2以上16以下の整数である必要があります。この条件を1行で表現したのが上の文です。

もし「$nが定義されていない」あるいは「$nが数字列でない」あるいは「$nが2より小さい」あるいは「$nが16より大きい」場合は、エラーとみなして単独のreturnを返します。

単独のreturnはスカラーコンテキストの場合はundefを、リストコンテキストの場合は空リストを返します。

$num_strの有効性のチェック

$num_strが有効である場合は、「$num_str が定義されていて」、「$num_strが数字あるいはA~F,a~fで構成されている」場合です。

また$num_str の有効性は $nに依存します。$n がたとえば8ならば$num_strに9という数字が出てきてはいけません。8進数の文字列に9という数字が出てきた場合はエラーとします。

個々の文字に分解

my @nums = split(//, $num_str);

"1010"という2進数文字列を10進数に直す場合は、8*1 + 4 * 0 + 2*1 + 1*0 という計算をすることになるので、split関数を使って個々の文字に分解しておきます。

有効な文字をキーとして持つハッシュの作成

my @valid = (0 .. 9 , 'A' .. 'F');
my %valid;
for (my $i = 0; $i < @valid; $i++){
  $valid{$valid[$i]} = $i;
}

有効だとみなしたいのは、0~9,A~F,a~fです。また進数変換の計算に備えて、Aという文字列は10に、Fという文字は15にといったような変換を行うハッシュを作っておきます。

このハッシュテーブルは便利にできていて、有効な文字ならば変換された文字が返却され、そうでなければundefが返ります。このようなハッシュテーブルを作れば、変換と変換可能かどうかの判定を同時に行うことができます。

「$num_strの有効性のチェック」と「$nとの整合性のチェック」

for my $num (@nums) {
  $num = $valid{uc $num};
  return if !defined $num || $num >= $n;
}

@numsには、分解されたひとつつひとつの文字が入っています。これを%validハッシュを使って、変換できる場合は数値に変換します。

uc関数は大文字に変換する関数です。'f'といった小文字の場合でも、'F'に変換することで有効な文字と判断されるようにします。

%validを使って変換できなかった場合は、$numにはundefが代入されます。$numがundefだった場合は有効な$str_numで無かったと判断しreturnします。

また $num が $n( 引数での進数の指定 )以上の場合も有効な$str_numでないとみなして、returnします。

n進数から10進数への変換

my $dec = 0;
for my $i (@nums) { 
  $dec = $dec * $n + $i;
}

n進数から10進数の進数変換のアルゴリズムは以下のようになります。8進数の'254'を10進数に変換するには以下のように演算を行っていきます。

0×8 + 2 => 2
2×8 + 5 => 21
21×8 + 4 => 172

計算過程をそのまま残すと以下のようになります。

0×8 + 2
(0×8 + 2)×8 + 5
{(0×8 + 2)×8 + 5}×8 + 4 
# つまり上記は
2×8×8 + 5×8 + 4 となっています。