C言語関係掲示板

過去ログ

No.385.文字列→数値への変換

[戻る] [ホームページ]
No.2578

文字列→数値への変換
投稿者---R1000(2002/09/03 17:30:58)


こんにちは、よろしくお願いします。
後述のような処理が必要になり、現在関数を作成しています。
ですが、自分では何度作り直しても泥臭く、すごく長い関数になってしまいます。
できれば他の方が書いたコードを見て勉強させてもらいたいと思います。


・実数で表現された文字列を整数で取得したい。
・今のところ符号は必要ありません。
・floatやdouble、printfやscanf系は重いので使えません。

/* 考えてみたプロトタイプ */
short foo(const char */*元になる文字列*/, short /*少数第何位までか*/);

/* 使用例 */
n = foo("12.345", 2);

/* 変換例 */
"12" → 1200
"12." → 1200
"12.3" → 1230
"12.34" → 1234
"12.345" → 1235 四捨五入
"12.3456" → 1235 四捨五入
"12.34567" → 1235 四捨五入


No.2582

Re:文字列→数値への変換
投稿者---PSB(2002/09/03 18:54:10)


泥臭いです。ハイ。

short foo(const char *s/*元になる文字列*/, short i/*少数第何位までか*/)
{
        short   w=0;
        int             decimal=0;

        while(*s!='\0'){
                if(*s=='.'){
                        decimal=1;
                }else{
                        w*=10;
                        w+=*s-'0';
                        if(decimal==1){
                                i--;
                                if(i==0){
                                        if(*(s+1)){
                                                if(*(s+1)-'5'>=0)
                                                        w++;
                                                break;
                                        }
                                }
                        }
                }
                s++;
        }
        while(i--)w*=10;
        return w;
}


No.2583

Re:文字列→数値への変換
投稿者---Kaji(2002/09/03 19:12:14)


PSBさんとほとんど同じなのですが。

short foo(const char * src, short n)
{
        int i, e, decimal=0;
        short seisuu = 0;

        for(i=0;i<strlen(src);i++) {
                if(*(src+i) == '.') {
                        decimal = 1;
                }
                else {
                        if(decimal != 0) {
                                if(n == 0) {
                                        if(*(src+i) - '0' >= 5) seisuu++;
                                        break;
                                }
                                n--;
                        }
                        seisuu *= 10;
                        seisuu += *(src+i)-'0';
                }
        }
        for(e=0;e<n;e++) seisuu *= 10;

        return seisuu;
}



atofを使って良いのなら次のような感じです。


short foo(const char * src, short n)
{
        int pow_n=1, i;
        for(i=0;i<n;i++) pow_n = pow_n * 10;

        return (short)(atof(src) * pow_n + 0.5);
}



No.2587

Re:文字列→数値への変換
投稿者---かずま(2002/09/03 21:48:44)


short foo(const char *s, short n)
{
    int   v = 0;
    while (*s && *s++ != '.') v = 10*v + s[-1] - '0';
    while (*s &&  --n >=  0 ) v = 10*v + *s++  - '0';
    if    (*s &&  *s  >= '5') v++;
    while (       --n >=  0 ) v *= 10;
    return v;
}


No.2588

Re:文字列→数値への変換
投稿者---かずま(2002/09/03 21:54:58)


ちょっと修正。
short foo(const char *s, short n)
{
    int   v = 0;
    while (*s && *s++ != '.') v = 10*v + s[-1] - '0';
    while (*s &&  --n >=  0 ) v = 10*v + *s++  - '0';
    if    (*s  >= '5') v++;
    while (--n >=  0 ) v *= 10;
    return v;
}


No.2610

Re:文字列→数値への変換
投稿者---R1000(2002/09/05 15:05:18)


皆さんありがとうございました。

かずまさんのコードがインデントが浅くて非常に読み解きやすかったです。
整数部、小数点、小数部と順々に処理していけばいいんですね。
この方法なら符号に対応するのも簡単そうです。
新しい考え方が身につきました、ありがとうございます。

あの後、カンマ区切りのような文字列を処理しやすいように少しプロトタイプを変更しました。
あと、少数第何位までとするかはあまり変わることが無いので、関数のプロパティというような
イメージでグローバル変数にしました。


/*------------------------------------------------------
    実行結果:
        "1.2|3.456|78|.9,12.34" = 120 346 7800 90 0
------------------------------------------------------*/
#include <stdio.h>
#include <ctype.h>

unsigned char SmallNum = 2;

const char *AsciiRealToShort(short *dst, const char *src)
{
    short n = SmallNum;

    *dst = 0;
    while ( isdigit(*src) )
        *dst = (*dst * 10) + (*src++ - '0');
    if ( *src == '.' )
        src++;
    while ( isdigit(*src) && --n >= 0 )
        *dst = (*dst * 10) + (*src++ - '0');
    if ( isdigit(*src) && *src >= '5' )
        (*dst)++;
    while ( --n >= 0 )
        *dst *= 10;
    while ( isdigit(*src) )
        src++;

    return src;
}

int main(void)
{
    const char *str = "1.2|3.456|78|.9,12.34";
    const char *p;
    short num[5] = {0};
    short i;

    p = str;
    for ( i = 0; i < 5; i++ ) {
        p = AsciiRealToShort(&num[i], p);
        if ( *p++ != '|' ) break;
    }

    printf("\"%s\" =", str);
    for ( i = 0; i < 5; i++ )
        printf(" %d", num[i]);
    printf("\n");

    return 0;
}


No.2615

Re:文字列→数値への変換
投稿者---かずま(2002/09/05 22:07:22)


> ・floatやdouble、printfやscanf系は重いので使えません。

と言って、効率を重視するような発言をされていながら、ポインタを介して、
関数呼び出し元の short変数を頻繁にアクセスするような無駄なコードをなぜ
書くのでしょうか。次のように書けば、この関数は 3〜4倍早くなりますよ。
#define isDigit(c)     ((unsigned)((c) - '0') < 10)
#define isDigit5_9(c)  ((unsigned)((c) - '5') < 5)

const char *AsciiRealToShort(short *dst, const char *src)
{
    int v = 0, n = SmallNum;

    while (isDigit(*src))
        v = (v * 10) + (*src++ - '0');
    if (*src == '.')
        src++;
    while (isDigit(*src) && --n >= 0)
        v = (v * 10) + (*src++ - '0');
    if (isDigit5_9(*src))
        v++;
    while (--n >= 0)
        v *= 10;
    while (isDigit(*src))
        src++;
    *dst = v;
    return src;
}

そういえば、for(i=0;i<strlen(src);i++) { のような、眩暈がしそうな
コードも誰か書いていましたね。

No.2626

Re:文字列→数値への変換
投稿者---R1000(2002/09/06 17:37:46)


ポインタ参照が遅くなるというのは初耳でした。
興味があったので、鵜呑みにせずテストしてみました。
テスト環境はC++Builder5です。
前回投稿したコードのメイン関数を100万回のループで囲んでtimeGetTimeで計測しました。

結果は確かにかずまさんの示したコードの方が3倍速かったです。
しかし、どうもポインタ参照はボトルネックとは関係なさそうでした。
一番のネックはisdigit()だったようで、これをisDigit()に置き換えたらほぼ改善されました。
残りの少しは、nの宣言をshortからintに変更することによって改善されました。
しかし、速度には関係なくても読みやすさを考慮して *dst = v; の方法も使わせてもらいます。

isdigit()がこんなに遅いとは思いませんでした。
マクロで実装されてることをなまじ知っていたので速度的にはまったく心配せず使っていました。

では、ありがとうございました。