見出し画像

遊月さんの『C言語-#8.atoi,atof』で遊んでみた

いつも的確なアドバイスをくださる遊月さんの記事が面白かったので、いろいろ弄って遊んでみた。

諸兄姉のコードを実際に動かしていろいろ試してみると、いろいろと気付かされたり、新たな知識を得たりしてたいへん面白い。

元となった記事はこちら。

シンプルで洗練されたなコードなのに、わたしは2箇所も誤解していた。
なので、遊月さんの記事に赤面もののコメントを残してしまった。

+の時だけs++するのは?単に読み飛ばしでも良さそうな気がするのですが、だめですか?

Akio van der Meer 2024年5月4日 11:58


switch文について

条件によって実行を制御するswitch文。

C言語以外のプログラミング言語でも似たような制御文があるんだけど、私が知っているVBAとかだと、条件が成立した時に実行するのは次の条件式が出現する直前までの実行文だけ。

でも、c言語だと成立した条件式以降、breakするまでの全ての実行分が処理される。

例えば、以下のコードだとcase '-'が成立すると、case '+'の前にbreakがないので、case '+'の後ろに記述されたs++まで実行される。

  // 符号チェック
  switch (*s) {
  case '-':
    sign = -1;
  // fall through
  case '+':
    s++;
    break;
  }


そういえばc言語のテキストにそんなことが書いてあった気がするけど、滅多にコーディングしないような、サンデードライバーならぬサンデープログラマーの私なんか、すっかり忘れてしまっている。やれやれ。

(こんな低レベルな質問に、真摯にご指導してくださる遊月さんに心より感謝です。)


while文について

与えられた条件式が成立する間、繰り返す制御文。

while (*s >= '0' && *s <= '9') {
    ret = ret * 10 + *s - '0';
    s++;
  }

ぱっと見、*sが'0'から'9'のときは、retの計算をするという部分だけしか認識してなかった。
なので、わざわざ冒頭の空白を読み飛ばしたり、符号記号(+/-)を読み飛ばす必要ないのではと思ってしまった。

全然ダメですねぇ。

この制御文だと、*sが'0'から'9'の範囲でなければ何もしてくれません。
c言語をマスターしておられる諸兄姉からみると、なんて低レベルなこと言っているとお思いだろうが、如何せんサンデープログラマーなもので、どうもすみません。やれやれ。


自分風に書き直してみる

サンプルコード

(わたしのcインタプリタ環境では、const修飾子が使えないので削除しています。)

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

int atoi(char *s) {
  int sign = 0;
  int ret = 0;
  int len = strlen(s);

  for (int i = 0; i < len ; i++) {

    // 符号チェック
    if (*s == '-') {
      sign = -1;
    }

    if (*s >= '0' && *s <= '9') {
      ret = ret * 10 + *s - '0';
    }
    
    s++;
    
  }

  return (sign != 0) ? -ret : ret;
}

double atof(char *s) {
  int sign = 0;
  int sw =0;
  int len = strlen(s);
  double ret = 0;
  double decimal = 1;

    for (int i = 0; i < len ; i++) {

    // 符号チェック
    if (*s == '-') {
      sign = -1;
    }

    // 小数点チェック
    if (*s == '.') {
      sw = 1;
    }
  
    if (*s >= '0' && *s <= '9') {
      ret = ret * 10 + *s - '0';
      if (sw != 0) {
        decimal *= 0.1;
      }
    }
    
    s++;
    
  }

  return ((sign != 0) ? -ret : ret) * decimal;
}

int main() {
    char *t1 = "€123";
    char *t2 = "-€456";
    char *t3 = "-€123.45";
    char *t4 = "€678.90";

    printf("atoi(\"%s\") + atoi(\"%s\") = %d\n", t1, t2, atoi(t1) + atoi(t2));
    printf("atof(\"%s\") + atof(\"%s\") = %5.2f\n", t3, t4, atof(t3) + atof(t4));

    return 0;
}


実行結果

atoi("€123") + atoi("-€456") = -333
atof("-€123.45") + atof("€678.90") = 555.45


所感

オリジナルのコードでは、引数は冒頭の空白と符号記号、それらに連続する数字の文字列しか受け付けない。符号記号の後に空白があったり、通貨記号が混じっていたりしたら数値に変換してくれない。
正しい引数を与えないと正しい結果を返さないという、当たり前だけど厳しさを感じる。

わたしのコードだと、符号記号と数字の文字列以外の文字があっても全て読み飛ばして数値に変換してしまう。

サンプルコードでは、 "-€456"という文字列を引数に与えて、-456という数値を得ている。

"10e2"といった累乗の表現の文字列を与えると102と誤解してしまうようなお茶目な一面(!)があるけれども、わたしにはこちらの緩い仕様の方がしっくりする。

私のコードには、プロから見たら看過できないリスクがいろいろあるかもしれない。もし、コメントいただけるようであれば幸いです。

  • 関数の仕様の考え方?

  • 実行時のレスポンス?

  • メンテナンサビリティ?

  • ライブラリの使い方?

  • ライブラリの互換性?

  • ポインタの使い方?(いまだに私の鬼門です)

  • オート変数が多過ぎ?(Ayumiさんに怒られそう)



(冒頭の写真は、北陸道魚津市付近からみた立山連峰。GW最終日にも拘らず雪を冠っていてびっくり。)

ここまで読んでいただき、ありがとうございました。

この記事が参加している募集

やってみた

これまでの収益は全て、それを必要としておられる方々へ、支援機関を通して寄付させていただきました。この活動は今後も継続したいと思っています。引き続きよろしくお願いいたします。