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