0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

数値の文字化を高速にしてみた

Last updated at Posted at 2020-02-16

#sprintf()からの限界へ挑戦
数値データを文字列にする機会が、JSONフォーマットをレスポンスするAPIで増えた気がします。数値の書式フォーマット付き文字列化といえば、sprintf()を使うでしょう。通常ならば、それで全く問題ありません。

しかし、sprintf() は、フォーマット文字列を複雑に定義できる反面、そのパースに処理も必要なので、単純なパターンであれば、ちょっとオーバースペックすぎるところです。そこで、sprintf(s,"%.1f",fv) に限って処理する関数で置き換えてみることにしました。

実装方針

  • 書式指定文字列は使用しない。いさぎよく "%.1f" 相当だけ。
  • 小数点以下第二位は切り捨てとする(sprintf()の実装では四捨五入される)

というかんじで、書いてみたのが以下のコードです。

fv2s.c

void fv2s(char *s, double fv)
{
    int iv,i,zf;

    if ((fv >= 1000000) || (fv <= -1000000)){ // overflow error, fallback                                                                                            
        sprintf(s,"%.1f",fv);
        return;
    }

    iv = fv * 10;
    if (iv < 0){
        *s++ = '-';
        iv = -iv;
    }

    zf = 0;
    i = ((iv % 10000000) / 1000000);  if (zf || (i != 0)){*s++ = i + '0'; zf = 1;}
    i = ((iv %  1000000) /  100000);  if (zf || (i != 0)){*s++ = i + '0'; zf = 1;}
    i = ((iv %   100000) /   10000);  if (zf || (i != 0)){*s++ = i + '0'; zf = 1;}
    i = ((iv %    10000) /    1000);  if (zf || (i != 0)){*s++ = i + '0'; zf = 1;}
    i = ((iv %     1000) /     100);  if (zf || (i != 0)){*s++ = i + '0'; zf = 1;}
    i = ((iv %      100) /      10);  *s++ = i + '0';
    *s++ = '.';
    i = ((iv %      10)         );    *s++ = i + '0';
    *s = '\0';
}

コード解説

  • 100万以上、-100万以下の場合は、無理せず素直に本家 sprintf() に処理を依頼しています。(今回のターゲットしている値の扱う範囲でざっくり適当に決めています)
  • 今回は、小数点以下第一位までを使用しますので、10倍して整数化してしまいます。
  • 負の数の場合は、符号を記述します。
  • 高位の桁から順に1桁ずつみていき、文字化して追記していきます。
  • C言語の場合は、文字化するには '0' (0x30) を加算するのが手っ取り早いです。
  • 先頭の桁で0の場合はスキップしますが、一旦0以外の数値が出たあとは0でも出力します。
  • 前項によらず、一の位は0でも必ず出力します。
  • 小数以下第一位も、同様に必ず出力します。

#実行結果

% gcc -O2 a.c
% time ./a.out 1
fv2s optimized version
0.117u 0.003s 0:00.12 91.6%	0+0k 0+0io 0pf+0w

% time ./a.out 2
sprintf native version
3.063u 0.000s 0:03.06 100.0%	0+0k 0+0io 0pf+0w

25倍速くなりました。 めでたしめでたし。
参考までに、上記のテストに使ったコードはこちら

a.c
int random_check()
{
    char s1[1023];
    char s2[1023];
    int i;
    float fv;

    srand(time(NULL));
    for (i = 0; i < 10000000; i++) {
        fv = (((double)rand() / RAND_MAX) - 0.5) * 100000000;
        fv = (((int)(fv * 10)) / 10);
        sprintf(s1,"%.1f",fv);
        fv2s(s2,fv);
        if (strcmp(s1,s2) != 0){
            printf("error %.1f, %s, %s\n", fv, s1, s2);
        }
    }
}

int speed_test_1()
{
    char s[1023];
    int i;
    printf("fv2s optimized version\n");
    for (i = 0; i < 10000000; i++) {
        fv2s(s, 23.4);
    }
}

int speed_test_2()
{
    char s[1023];
    int i;
    printf("sprintf native version\n");
    for (i = 0; i < 10000000; i++) {
        sprintf(s,"%.1f", 23.4);
    }
}

int main(int argc, char **argv)
{
    int opt = 0;
    if (argc >= 2){
        opt = atoi(argv[1]);
    }
    switch (opt){
    case 0: random_check(); break;
    case 1: speed_test_1(); break;
    case 2: speed_test_2(); break;
    }
    return 0;
}

かなり、用途が限定されるので、どれほどお役に立つかわかりませんが、まあ、こういうチャレンジは、やってて楽しいですよね。

0
0
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?