Perl で 2進数 実数 変換

Perl の基礎を学習する上で、2進数から実数に変換するプログラムを扱うことにしました。

ハードウェアを設計していて、算術演算回路の符号付きで小数点以下の数値を扱う場合、デジタルなので当然 2進数で演算するのですが、2進数から整数の変換をしてくれる電卓はあるものの、2進数から実数に変換してくれる電卓がなく、いちいち手計算で検証するのは大変です。

そこで、 Perl でプログラムを書いてみました。あとで解説しますが、まずは全体を載せておきます。

なお、このプログラムは以下のように実行します。

  % bin2real.pl - 1010.00110011

 第1引数:符号
 第2引数:小数点付きの2進数

すると、結果がこのように表示されます。

  -5.80078125


  1 #!/usr/bin/perl
  2
  3 sub print_input_error {
  4   print "\n";
  5   print " ##ERROR## Not enough argument\n";
  6   print " Usage:     % bin2real.pl {sign +/-} {binary value}\n";
  7   print " Example:   % bin2real.pl - 1010.0110011\n";
  8   print "\n";
  9 }
 10
 11 if (@ARGV == 2) {
 12   $input_sign = $ARGV[0];
 13   $input_bin = $ARGV[1];
 14 } else {
 15   &print_input_error;
 16   exit;
 17 }
 18
 19 ($integral_bin, $decimal_bin) = split /\./, $input_bin;
 20
 21 $integral = 0;
 22 $_ = $integral_bin;
 23 s/0/ 0/g;
 24 s/1/ 1/g;
 25 @tmp = split / +/, $_;
 26 $k = 0;
 27 for ($i = $#tmp; $i > 0; $i--) {
 28   $n = 2 ** $k;
 29   $integral = $integral + $tmp[$i] * $n;
 30   $k++;
 31 }
 32
 33 $max = 2 ** $#tmp;
 34
 35 $decimal = 0;
 36 $_ = $decimal_bin;
 37 s/0/ 0/g;
 38 s/1/ 1/g;
 39 @tmp = split / +/, $_;
 40 $k = (-1);
 41 for ($i = 1; $i <= $#tmp; $i++) {
 42   $n = 2 ** $k;
 43   $decimal = $decimal + $tmp[$i] * $n;
 44   $k--;
 45 }
 46
 47 $tmp_result = $integral + $decimal;
 48
 49 if ($input_sign =~ /-/) {
 50   $result = $max - $tmp_result;
 51 } else {
 52   $result = $tmp_result;
 53 }
 54
 55 print "$input_sign";
 56 print "$result";
 57 print "\n";
 58
 59 exit 0;

それでは1つずつ解説していきます。


実行属性の付け方

  1 #!/usr/bin/perl

1行目のこれ↑は 実行コマンドの perl がインストールされている path を指定しています。実行コマンドの perl がどの path にあるかは、以下のように which コマンドで確認します。

  % which perl
  /usr/bin/perl

#!/usr/bin/perl のように1行目に path を指定しておき、perl の script ファイル (ここでは bin2real.pl) のファイル属性に実行属性を付けておけば、script ファイル名を入力するだけで perl プログラムが実行できるようになります。

  % chmod +x bin2real.pl
  % ls -l bin2real.pl
  -rwxr-xr-x bin2real.pl

これで bin2real.pl という script は実行属性が付きましたので、以下のように実行させることができます。

  % bin2real.pl - 1010.00110011
  -5.80078125


サブルーチン

いきなりですが、3〜9行目にはサブルーチンが定義されています。これは引数も戻り値も持たない簡単なサブルーチンです。

  3 sub print_input_error {
  4   print "\n";
  5   print " ##ERROR## Not enough argument\n";
  6   print " Usage:     % bin2real.pl {sign +/-} {binary value}\n";
  7   print " Example:   % bin2real.pl - 1010.0110011\n";
  8   print "\n";
  9 }

このサブルーチンの目的は、外部引数が必要な bin2real.pl を実行させるとき、誤った引数が与えられたときに表示する syntax error メッセージであり、引数の与え方を忘れてしまったときに、あえて表示させる help メッセージの代わりとなっています。

サブルーチンの書き方として、引数の受け取りの仕方や戻り値の書き方の説明は、長くなるので別のところで説明したいと思います。


外部引数の受け取り

11〜17行目に書かれているのは、bin2real.pl に必要な引数の代入を行っています。また、引数として2つが必要なため、2つ以外の引数が与えられた場合はプログラムが終了する仕掛けになっています。

 11 if (@ARGV == 2) {
 12   $input_sign = $ARGV[0];
 13   $input_bin = $ARGV[1];
 14 } else {
 15   &print_input_error;
 16   exit;
 17 }

1つ目の引数は、符号です。
2つ目の引数は、実数に変換したい2進数です。

なお、2つ目の引数は、必ず小数点付きの2進数が入力されなければなりません。


整数部 と 小数部 に分離

19行目に書かれているのは、外部引数から与えられた小数点付きの2進数 $input_bin を整数部と小数部に分ける文です。

 19 ($integral_bin, $decimal_bin) = split /\./, $input_bin;

split は perl が持っている標準関数です。
split 引数1 , 引数2 ; という構文で、引数1 にどこで分割するか パターン を指定します。引数2 は分割される側の文字列を指定します。


整数部の10進数変換

21〜31行目で整数部の2進数から10進数への変換を行っています。

 21 $integral = 0;
 22 $_ = $integral_bin;
 23 s/0/ 0/g;
 24 s/1/ 1/g;
 25 @tmp = split / +/, $_;
 26 $k = 0;
 27 for ($i = $#tmp; $i > 0; $i--) {
 28   $n = 2 ** $k;
 29   $integral = $integral + $tmp[$i] * $n;
 30   $k++;
 31 }
 32
 33 $max = 2 ** $#tmp;

連続した0と1のパターンでは扱うのが大変ですので、まずは、1文字ずつに変換します。
標準変数 $_ を使うと s/1/1 /g の置換文を使うことが出来ます。

そうして、先ほどもあった split 関数を使って 1文字ずつに変換します。
ここでは、いくつに分割されるか想定することは出来ないので 配列 @tmp に代入しています。

配列化した後、その要素数 $#tmp 分の 10進数化 演算を for文 を使って行います。

また、33行目で 外部から与えられた 小数点付き2進数 が負の数である場合に備えて、その 2進数の桁数における最大値を $max として保存しておきます。


小数部の10進数変換

35〜45行目で小数部の2進数から10進数への変換を行っています。

 35 $decimal = 0;
 36 $_ = $decimal_bin;
 37 s/0/ 0/g;
 38 s/1/ 1/g;
 39 @tmp = split / +/, $_;
 40 $k = (-1);
 41 for ($i = 1; $i <= $#tmp; $i++) {
 42   $n = 2 ** $k;
 43   $decimal = $decimal + $tmp[$i] * $n;
 44   $k--;
 45 }

演算の仕方は、整数部と同様です。ただ 2の-1乗,-2乗,... と for文 で演算しています。


整数部と小数部の合算

47〜53行目で整数部と小数部を合算して、外部引数で与えられた 符号によって最終結果を出しています。

 47 $tmp_result = $integral + $decimal;
 48
 49 if ($input_sign =~ /-/) {
 50   $result = $max - $tmp_result;
 51 } else {
 52   $result = $tmp_result;
 53 }

外部引数1 $input_sign が - であった場合、外部引数2 で与えられた 2進数は 2の補数 であったことになるので、外部引数2 のbit幅で表すことの出来る最大値 $max から引き算します。

理由を以下に説明します。

コンピューターの中では、0 と 1 の値しかありませんから、負 (-) を表現するために一般的に使われているルールがあります。それが 2の補数を 負の数として扱うもので、分かりやすく説明するために整数を使って8bitで表現すると以下の数列のようになります。

   5 00000101
   4 00000100
   3 00000011
   2 00000010
   1 00000001
   0 00000000
  -1 11111111
  -2 11111110
  -3 11111101
  -4 11111100

2の補数というのは、もとの2進数を反転させて+1した数値のことです。
なぜ、2の補数が負の数として扱われるかと言うと、2の補数を使って足し算をすると、引き算をした結果を得ることができるからです。
以下に 5 - 3 の計算を例として示します。

3の2の補数は、3:00000011 を反転:11111100 して +1 した 11111101 です。
5:00000101 にこれを足し算すると以下のようになります。

    00000101
  + 11111101
  = 00000010

桁上がりで8bitの外に出てしまった1を無視すると、2:00000010 が得られました。
足して引き算の結果が得られるのだから、2の補数を負の数として扱おう、そうすれば加算器だけで引き算もできるので楽だ。と言うことになるのです。

さて、3の2の補数:11111101は、そのまま10進数の正の整数で表すと 253 です。これは、2の8乗である 256 を -3 した値です。2の補数がそういうものですから当然です。


最後に演算結果を表示してプログラムは終了です。

 55 print "$input_sign";
 56 print "$result";
 57 print "\n";
 58
 59 exit 0;


TITLE

自己紹介

50才になる半導体エンジニアです。
大学で電子電気工学を学び、1990年にその分野のまま就職。ASICやマイコンの設計を長く続けてきましたが20年も同じ分野にいると業態も衰退したり変化するもので退職し、今は外資のIT系会社に再就職して設計請負業をやっております。
お問い合わせは
nakata.xianzhi@outlook.com







Linux と 小ネタ

デジタル回路設計

海外駐在後記