掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板2

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧

No.30266

整数を文字列に変換
投稿者---べた(2007/06/02 18:32:01)


整数を文字列に変換しています。以下のように作ってみました。
一応、動いています。
ただ、整数から文字列に変換している処理をもう少しスマートと
いうか良いものにしたいのですが、良い方法はないでしょうか。

「ssprintf(s,"%d",val);」を使った手抜きもありますが、その他で。


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

void chg_itoa(int,char *);

main()
{
    char str[20];
    int num;

    printf("数値を入力してください:");
    scanf("%d",&num);

    chg_itoa(num,str);
    printf("%d => %s\n",num,str);
}

void chg_itoa(int val,char *s)
{
    char *t;
    char c;
    int mod;

    if(val < 0) {
        *s++ = '-';
        val = -val;
    }
    t = s;

    while(val) {
        mod = val % 10;
        *t++ = (char)mod + '0';
        val /= 10;
    }

    if(s == t)
        *t++ = '0';

    *t = '\0';

    while( *t != '\0' )
        t++;
    t--;

    while(t > s) {
        c = *s;
        *s = *t;
        *t = c;
        s++;
        t--;
    }
}




この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:整数を文字列に変換 30268 yoh2 2007/06/02 22:05:53
<子記事> Re:整数を文字列に変換 30269 Hermit 2007/06/02 22:13:27


No.30268

Re:整数を文字列に変換
投稿者---yoh2(2007/06/02 22:05:53)


まずは前置き。

>「ssprintf(s,"%d",val);」を使った手抜きもありますが、

とありますが、手抜きできる所はどんどん手抜きしていくべきです。
自前で実装しようとすると、かえってバグ込みのモノを作ってしまう危険性が
つきまといますので。
実際、べたさんの書いたコードでは、INT_MINの絶対値 > INT_MAX な処理系
(PCの処理系ではほぼすべて)では、INT_MINをうまく変換できませんし。

で、もうひとつ前置き。

スマート、というのは、感性に依存する部分が大きいので、人それぞれです。
場合によっては、二人が全く逆の感性を持っている場合があります。
(例: goto絶対ダメ派 vs 例外処理に使うとかえって見やすいじゃん派 -- ちなみに私は後者)
そのため、以下で私が書くことも、人によってはかえって受け入れ難いものである
可能性もあります。

さて、以上をふまえて。
べたさんの方法に手を加える場合と、趣向を変えて再帰処理で書き直す場合について
書いてみます。

まずはべたさんの方法に手を加える場合について。

その1:
> if(s == t)
>         *t++ = '0';
このifは、val == 0の時のみに成立します。
関数の冒頭で負の数かどうかチェックしているので、0かどうかのチェックも同じ
ところで行うのがよいのではないでしょうか。

その2:
>     *t = '\0';
>
>     while( *t != '\0' )
>         t++;
>     t--;
while分が全くの無駄です。消してしまいましょう。

その3:
chg_itoa() は、大きく分けて (1)数値を逆順の十進数字列にする、
(2)逆順になっている文字列を正順に直す、のふたつの部分からなっています。
これらをそれぞれ関数にまとめるとよいのではないでしょうか。

番外(重箱の隅):
これはコードの見易さ、という観点からは少し外れますが、
>         *t++ = (char)mod + '0';

(char)でキャストするのはよいのですが、その後'0'との加算を行っているため、
(char)mod + '0'全体の型は結局intになってしまいます。
char型への代入という意味でキャストを付けたいのなら、*t++ = (char)(mod + '0');
とすべきです。

べたさんの書き方を踏襲しつつ、これらをまとめて私なりに書き換えると、
以下のようになりました。
void chg_itoa(int val, char *s);
static char *chg_rev_itoa(int val, char *s);
static void reverse(char *begin, char *end);

...

void chg_itoa(int val,char *s)
{
    char *t;

    /* val == 0は特別扱い */
    if(val == 0){
        s[0] = '0';
        s[1] = '\0';
        return;
    }

    if(val < 0) {
        *s++ = '-';
        val = -val;
    }
    t = chg_rev_itoa(val, s) - 1;
    reverse(s, t);
}


/* val(正数)を十進文字列に変換した結果を逆順でsに格納する。
 *
 * 戻り値: 文字列の末尾('\0')を指すポインタ
 */
static char *chg_rev_itoa(int val, char *s)
{
    while(val) {
        int mod = val % 10;
        *s++ = (char)(mod + '0');
        val /= 10;
    }
    *s = '\0';
    return s;
}


/* [s, t]の範囲を逆順に並べかえる。 */
static void reverse(char *s, char *t)
{
    while(t > s) {
        char c = *s;
        *s = *t;
        *t = c;
        s++;
        t--;
    }
}
で、次に再帰を使う方法について。こっちはプロトタイプだけ合わせて一から
書いてみました。環境によってはINT_MINを正しく変換できない問題も健在です。

割と短くまとまりますが、スタック消費量の点でやや不利です。
とはいえ、再帰回数は高々十回程度(intが32ビット幅の場合)ですし、あまりシビアな
環境でなければ十分許容範囲ではないかと。
また、私は関数型言語の経験もあるためか、再帰処理が分かり易いと感じるのですが、
イジワルな謎コードに見えてしまう人も結構な割合で存在するような気がします。
void chg_itoa(int val, char *s);
static char *chg_itoa_body(int val, char *s);

...

void chg_itoa(int val, char *s)
{
    if(val == 0){
        /* 0は特別扱いしておく */
        *s++ = '0';
    }
    else{
        if(val < 0){
            *s++ = '-';
            val = -val;
        }
        s = chg_itoa_body(val, s);
    }

    *s = '\0';
}

/* val(正数)を十進文字列に変換した結果をsに格納する。
 * ただし、'\0'で終端されない。
 *
 * 戻り値: 変換した文字列の最後の文字のひとつ後ろを指すポインタ
 */
static char *chg_itoa_body(int val, char *s)
{
    if(val == 0){
        return s;
    }
    else{
        s = chg_itoa_body(val / 10, s);
        *s = val % 10 + '0';
        return s + 1;
    }
}



この投稿にコメントする

削除パスワード

No.30269

Re:整数を文字列に変換
投稿者---Hermit(2007/06/02 22:13:27)


前の書き込み、ちょっと変更しようと思って削除してる間に、
yoh2 さんが書いてますね。
一応、書いておきます。
INT_MIN に確実に対応しているかどうか、( % のマイナス時の仕様がようわからん(^^;)

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

char *chg_itoa(int val, char *s) {
    /*static*/ char buff[16];
    char *ptr = &buff[sizeof(buff)-1];
    int minus = val < 0 ? -1 : 1;
    *ptr = '\0';
    if (val == 0)
        *--ptr = '0';
    while (val) {
        *--ptr = val % 10 * minus + '0';
        val /= 10;
    }
    if (minus == -1)
        *--ptr = '-';
    return strcpy(s,ptr);
}
main()
{
    int num = INT_MIN;
    char str[20];
    printf("数値を入力してください:");
    scanf("%d",&num);
    printf("%d => %s\n",num,chg_itoa(num,str));
    return 0;
}





この投稿にコメントする

削除パスワード

No.30270

Re:整数を文字列に変換
投稿者---yoh2(2007/06/02 22:30:55)


>INT_MIN に確実に対応しているかどうか、( % のマイナス時の仕様がようわからん(^^;)

C99より前の規格だと、%のオペランドに負数が現れた場合の挙動は処理系定義なんですよね。
C++98も含めて。
この辺の説明が面倒なのと、ただでさえ長い書き込みを、対処法込みのコードで
さらに長くすることに気が引けたので前の書き込みではあえて触れませんでした。

ちなみにC99なら、商がゼロ方向への切り捨て、余りが
商 * 除数 + 余り == 被除数
となる値(当然か……)、と決まっています。これ、余りの符号が被除数と同じものになります。
この条件ではHermitさんのコードが正しいものになっていますね。


この投稿にコメントする

削除パスワード

No.30273

Re:整数を文字列に変換
投稿者---べた(2007/06/04 12:48:22)


yoh2さん、Hermitさん
ありがとうございます。

詳しく、解説、説明して頂きありがとうございます。
確認しながら試してみます。



この投稿にコメントする

削除パスワード

No.30274

Re:整数を文字列に変換
投稿者---Hermit(2007/06/04 19:44:19)


ふと、0 のときの処理が冗長だなって思ったので、もうひとつ、書いてみます。

INT_MIN の処理は、ちょっと遊んで変えてみました。
#include <stdio.h>
#include <string.h>
#include <limits.h>

char *chg_itoa(int val, char *s) {
    char buff[12];
    char *ptr = &buff[sizeof(buff)-1];
    int minus = val < 0;
    if (minus) {
        val = -(val);
        if (val < 0)
            return strcpy(s,"-2147483648");
    }
    *ptr = '\0';
    do {
        *--ptr = val % 10 + '0';
        val /= 10;
    } while (val);
    if (minus)
        *--ptr = '-';
    return strcpy(s,ptr);
}
int main()
{
    int num = INT_MIN;
    char str[20];
    printf("数値を入力してください:");
    scanf("%d",&num);
    printf("%d => %s\n",num,chg_itoa(num,str));
    return 0;
}



この投稿にコメントする

削除パスワード

No.30275

Re:整数を文字列に変換
投稿者---yoh2(2007/06/04 23:20:31)


遊びということですので、あまり神経質になることもないとは思い
ますが、ちょいと突っ込みをば。
分かった上でやっているとしたら申し訳ないです。適当にスルーして
やって下さい。

細かいことを言うと、計算結果が符号付き整数の表現できる範囲を超えた
場合の挙動は処理系定義なので、+2147483648をint(32ビット幅と仮定します)で
表現しようすると負になるとは限りません。処理系によっては0除算で
よくあるようにシグナルが飛ぶかも。
あと、INT_MINが-2147483648とは限りませんね。分かりやすいところで、
intの幅が64ビットとか。他、32ビットだけど、INT_MINが-2147483647と
いうのも規格上は有り得ます。


この投稿にコメントする

削除パスワード

No.30278

Re:整数を文字列に変換
投稿者---Hermit(2007/06/05 17:41:38)


>遊びということですので、あまり神経質になることもないとは思い
>ますが、ちょいと突っ込みをば。
一応承知の上で書いてますが、
突っ込み期待で書いてますのでいっぱい突っ込んでください(^^;


この投稿にコメントする

削除パスワード

No.30276

Re:整数を文字列に変換
投稿者---かずま(2007/06/05 00:55:25)


> ふと、0 のときの処理が冗長だなって思ったので、もうひとつ、書いてみます。
>
> INT_MIN の処理は、ちょっと遊んで変えてみました。

INT_MIN を特別扱いしたくないですね。
void chg_itoa(int val, char *s)
{
    char b[24], *p = b;
    unsigned u = (val < 0) ? (*s++ = '-', -val) : val;
    do *p++ = u % 10 + '0'; while (u /= 10);
    do *s++ = *--p; while (p != b);
    *s = '\0';
}
val が INT_MIN のとき、-val がシグナルを出す処理系ではだめですが。


この投稿にコメントする

削除パスワード

No.30277

Re:整数を文字列に変換
投稿者---べた(2007/06/05 14:59:32)


yoh2さん、Hermitさん、かずまさん
ありがとうございます。

>val が INT_MIN のとき、-val がシグナルを出す処理系ではだめですが。
範囲を超えた場合は、何かしたいです。
シグナルを出すとか。




この投稿にコメントする

削除パスワード

No.30280

Re:整数を文字列に変換
投稿者---yoh2(2007/06/05 23:01:34)


>範囲を超えた場合は、何かしたいです。
>シグナルを出すとか。

整数→文字列の変換では、整数の幅が何ビットだとしても、変換をうまく
行いさえすれば範囲超えなんてことは起こりませんよ。
変換後の文字数が多すぎて格納しきれない、という事態は起こり
得ますが、それは別の問題。

別問題といっても、chg_itoa()を実用化しようと考えるのなら結構
切実な問題ですので、話は逸れますがちょっとヒントを。
文字数チェックもするなら、今回のchg_itoa()が取る引数だけでは
パラメータが足りずません。具体的には s に格納できる文字数を
パラメータに取る必要があります。
で、収まり切らない場合の仕様ですが、これは人によって結構方針が
バラバラ。共通しているのは間違っても許容範囲を超えた書き込みを
しない、ということくらい。
とりあえず、C99の標準関数 snprintf() と、Microsoftの独自拡張関数
_snprintf()の仕様を見比べてみると参考になるかと。
名前も利用方法もほとんど同じなのに、文字列数が許容範囲を超えた場合の
挙動が違います。

snprintf()の解説
http://www.linux.or.jp/JM/html/LDP_man-pages/man3/snprintf.3.html

_snprintf()の解説
http://msdn2.microsoft.com/ja-jp/library/2ts7cx93(VS.80).aspx


この投稿にコメントする

削除パスワード

No.30279

Re:整数を文字列に変換
投稿者---Hermit(2007/06/05 22:29:01)


>INT_MIN を特別扱いしたくないですね。
それは同感ですが・・・
Cの範囲内の解釈では、
-(INT_MIN) が、INT_MIN になって、
それを unsigned に変換したものが、たまたまその数値になっているだけみたいです。
void chg_itoa(int val, char *s)
{
    char b[24], *p = b;
    __int64 u = (val < 0) ? (*s++ = '-', -val) : val;
    do *p++ = u % 10 + '0'; while (u /= 10);
    do *s++ = *--p; while (p != b);
    *s = '\0';
}


まあ、私のみたいに 乗算使うよりはスマートでいいかもしれないけど。


この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧