C言語で2進数と10進数の相互変換ツールを作ってみる

2009 年 10 月 18 日 | カテゴリー: こっそり学ぶC言語

いよいよ「Ubuntu 9.10」のリリースが間近に迫ってきましたね!!

という事で、恒例行事(??)としてサイドバーにカウントダウンのスクリプトを貼り付けてみました。Japaneseチームのアナウンスによると、リリース後しばらくはBitTorrentによるダウンロードのみみたいですので、来週あたりにTransmissionの使い方(というよりBitTorrentそのもの)について学んでおかなくちゃなぁ、と徐々にテンションが上がってきています^^

さて、今回プログラミングを学ぶうえでしっかり理解しておかなくてはいけない「2進数」「8進数」「16進数」について勉強してみました。何となくしか理解していなかったのですが、以下のサイトにて非常に丁寧に解説されています。

2進数、16進数と10進数

こちらのサイト、Google検索でたどり着いたのですが、他のコンテンツもかなり充実していますので、「コンピュータの基礎」をしっかり学ぶのに活用させていただきたいと思います。先達の方々がこのように自身の知識を惜しげもなく公開してくださるところに、インターネットの素晴らしさの一旦が垣間見えますね。未だヘッポコではありますが、いつかはこういった「知識の共有・伝承」に貢献していきたいものです。

で、上記のサイトでそれぞれの変換時の計算方法まで解説されていますので、それを元に相互変換ツールを作ってみよう!!と思いたち、C言語で挑戦してみました。PHPやPythonを利用すれば関数一発で変換してくれますが、そこはそれ、学習という名目の車輪の再発明です><

本当は2/8/10/16進数それぞれの相互変換ツールを作りたかったのですが、コードを書いているうちにボリュームがどんどん増してきたので、まずは「2進数<->10進数」での変換が出来るようにしてみました。

相変わらずの駄コードを晒してみますが、いまだC言語の理解が中途半端なため、以下の文章は末尾を「~しているつもり」に読み替えていただけると幸いです^^

conveter.c

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <math.h>

# define PROGRAM_NAME "converter"
# define MAX_NUM 256
# define MAX_ARGS 3
# define BIN 2
# define DEC 10

char *option[] = {"-b", "-d"};

void usage(void);
int check_args(int argc, char **argv);
void exec_convert(char const *to, char const *input, char *result);
void convert_to_bin(char const *input, char *result);
void convert_to_dec(char const *input, char *result);

int main(int argc, char **argv)
{
    char result[MAX_NUM];

    if (! check_args(argc, argv)) {
        usage();
    }

    exec_convert(argv[1], argv[2], result);

    printf("%s\n", result);

    return 0;
}

void usage(void)
{
    fprintf(stderr, "Usage: %s [convert to] [binary or decimal numbers]\n", PROGRAM_NAME);
    fprintf(stderr, "\t-b: convert to binary\n");
    fprintf(stderr, "\t-d: convert to decimal\n");
    exit(1);
}

int check_args(int argc, char **argv)
{
    int i , result;

    i = result = 0;

    if (MAX_ARGS == argc) {
        while (1) {
            if (0 == strcmp(option[i], argv[1])) {
                result = 1;
                break;
            }
            i++;
        }
    }

    return result;
}

void exec_convert(char const *to, char const *input, char *result)
{
    if (0 == strcmp(option[0], to)) {
        convert_to_bin(input, result);
    } else if (0 == strcmp(option[1], to)) {
        convert_to_dec(input, result);
    } else {
        fprintf(stderr, "Error occured!!\n");
    }
}

void convert_to_bin(char const *input, char *result)
{
    char num[] = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
    };
    char tmp[MAX_NUM];
    int i, j, cnt, digit;

    /* Check input numbers */
    i = 0;
    cnt = strlen(input);
    for (; i < cnt; i++) {
        for (j = 0; j < DEC; j++) {
            if (num[j] == input[i]) {
                break;
            }
        }
        /* Invalid number */
        if (DEC == j) {
            strcpy(result, "Input number is invalid");
            return;
        }
    }

    /* Convert to binary */
    i = j = 0;
    digit = atoi(input);
    while (1) {
        tmp[i] = (digit % BIN) ? '1' : '0';
        digit = digit / BIN;
        i++;
        if (0 == digit) {
            tmp[i] == '\0';
            break;
        }
    }
    for (; i > 0; i--, j++) {
        result[j] = tmp[i -1];
    }
    result[j] = '\0';
}

void convert_to_dec(char const *input, char *result)
{
    char num[] = {'0', '1'};
    int i, j, len, prog, tmp;

    len = strlen(input);

    /* Check input number */
    i = j = 0;
    for (; i < len; i++) {
        if (num[0] != input[i] && num[1] != input[i]) {
            /* Invalid number */
            strcpy(result, "Input number is invalid");
            return;
        }
    }

    /* Convert to decimal */
    j = len;
    prog = 0;
    for (i = 0; i < len; i++, j--) {
        tmp = (int)input[i] - 48;
        prog += tmp * pow(BIN, (j - 1));
    }
    sprintf(result, "%d", prog);
}

コンパイルして「./converter [オプション] [変換元の数値]」のように引数を与えると結果を表示します。「math.h」をincludeして累乗を求めるpow()関数を利用していますので、gccの場合「-lm」オプションが必要ですね。

手順としては、

  1. 引数が妥当かどうかをチェック
  2. 計算実行前に与えられた数値の妥当性をチェック
  3. 変換の実行
  4. 変換結果を表示

という感じです。

定義した関数を順に見ていくと、まずusage()関数で引数が妥当でない場合にメッセージを表示しています。基本的に、暇を見つけてはちょこちょこソースを読んでいる「coreutils」の手法を流用(パクリともいう)しています。今まではこういう場合printf()関数を利用していましたが、fprintf()関数を利用する事で出力先をstderrに出来るワケですね。

続いて、check_args()関数で引数の妥当性を検証しています。検証などとエラそうに言っても、引数の数とオプションで与えられた文字列をチェックしているのみです。結果によって処理を分岐出来るよう、intを返してみました。

次はexec_convert()関数ですが、これは名前と実態が一致していないという恐るべき実装ですが、オプションの文字列を見て「2進数<->10進数」の変換関数、「10進数<->2進数」の変換関数にそれぞれ入力データを渡しています。

convert_to_bin()関数で「10進数<->2進数」の変換を実行していますが、実行前に変換元の数値が10進数として妥当なもの(0~9のみ)かどうかをチェックしています。
その後、上記サイトで解説されていた方法そのまんまで10進数を2進数に変換しています。2進数をデータとして保持するにはどうすれば良いのか理解出来ていない為、文字列として保持する方法をとってみました。

最後のconvert_to_dec()関数は「10進数<->2進数」の変換です。convert_to_bin()関数もそうですが、与えられた数値に問題がある場合、結果保持用の変数にメッセージを代入して「return」で脱出しています。PHPなんかではこの方法をよく使うのですが、C言語では「return」のみはOKなのでしょうか??取りあえず動いているのでよしとしてしまいますが、その辺が気になるところであります。

「constの使い方ってこれで良いの??」とか「返り値voidの関数ってこんなに使うべきなの??」とか、頭の中の「?」マークは大量に残っておりますが、地道に習得していくしかないかな、と思います。

各処理のアルゴリズムに関しても、もっともっと改善の余地がありそうです。自分で晒しておきながら、よくもまぁこんなコードをwww空間に恥ずかしげもなく公開するもんだ、と恥ずかしい限りですが、自分の学習の歴史と割り切ってみました><

もっとスキルアップした暁には、自分で自分のコードをハックしてみたいものです。あとは、標準ライブラリにある関数をほとんど知らない(オイ!)ので、リファレンス本を購入しようかな、などと企んでいます。

しかし、PHP/Python/シェルスクリプトも十分に面白いのですが、C言語の楽しさは群を抜いていますね。スクリプト言語に比べて手間がかかるし、メモリを意識してコードを考えなくてはいけないので大変ですが、その分動いた時の喜びはひとしおです^^

身近にC言語を一緒に学習していける友達が欲しいなぁ、などと妄想する週末でした。

今日はここまで。

コメントはまだありません。