組込み C > sprintf(): おこられた | dtostrf(): 実装した v0.1, v0.2

```txt:動作環境
MSP430
```

### sprintf() > 怒られた

組込みでFlashメモリが小さい環境でsprintf()を安易に使おうとして怒られた。

> Error [E3141]: ウサギ小屋に象を連れてこないでください。鼻でつっかえます。

### dtostrf() v0.1

[Arduino > sprintf() > %fを使えない? > dtostrf()が用意されている](https://qiita.com/7of9/items/e8e50156f687d026cbec#comment-ee0d007908b8610e04ab)

@mt08 さんの情報によると、Arduinoにはdtostrf()という便利な実装があるようだ。

[dtostrf() @ avr-libc 2.0.0](http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3ce8771d42)

実装してみた。

https://ideone.com/O4cpyT

```c
#include <stdio.h>

/*
v0.1 Mar. 12, 2018
  - add main()
  - add [MAX_DIGIT_WHOLE]
  - add calc_digits_wholenumber()
  - add dtostrf()
*/

#define MAX_DIGIT_WHOLE (10)  // limit for digits of whole number part

static int calc_digits_wholenumber(double val)
{
    int used = 0;
    int wrk;

    if (val < 0) {
        used += 1;
        val *= -1;
    }
    wrk = (int)val;

    while(wrk > 0) {
        used++;
        wrk /= 10;
    }
    return used;
}

char *dtostrf(double val, signed char width, unsigned char prec, char *dstPtr)
{
    // static: to reduce stack memory usage
    static char szbuf[MAX_DIGIT_WHOLE + 1] = { 0 };
    int posbuf = 0; // position of szbuf[]
    //
    int idx, loop;
    int wrk;
    int dgtWhl; // digits of whole number part
    char *start; // for function return

    start = dstPtr;

    if (dstPtr == NULL) {
        return start; // error
    }

    // 0. prefix spaces
    if (width > 0) {
        dgtWhl = calc_digits_wholenumber(val);
        wrk = width - dgtWhl - 1 - prec;  // 1: "decimal point"
        for (loop = 0; loop < wrk; loop++) {
            *dstPtr =' ';
            dstPtr++;
        }
    }

    // 1. whole number part
    if (val < 0) {
        *dstPtr = '-';
        dstPtr++;
        val *= -1;
    }
    wrk = (int)val;

    while (wrk > 0) {
        if (posbuf >= MAX_DIGIT_WHOLE) {
            return start; // error
        }
        szbuf[posbuf++] = (wrk % 10) + '0';
        wrk /= 10;
    }
    for (idx = posbuf-1; idx>=0; idx--) {
        *dstPtr = szbuf[idx];
        dstPtr++;
    }
    *dstPtr = '.';
    dstPtr++;

    // 2. fractional part
    for (loop=0; loop < prec; loop++){
        val *= 10;
        wrk = (int)val;
        *dstPtr = (wrk % 10) + '0';
        dstPtr++;
    }

    // 3. postfix spaces
    if (width < 0) {
        dgtWhl = calc_digits_wholenumber(val);
        wrk = MAX_DIGIT_WHOLE - dgtWhl - 1 - prec;  // 1: "decimal point"
        for (loop = 0; loop < wrk; loop++) {
            *dstPtr =' ';
            dstPtr++;
        }
    }

    *dstPtr = 0x00;
    dstPtr++;
    return start;
}

int main(void) {
    double pi = 3.1415926535;
    char szbuf[15] = { 0 };
    char *szwrk;

    dtostrf(pi * 10000, 10, 3, szbuf);
    printf("%s\n", szbuf);  // " 31415.926"
    dtostrf(pi * 10000, -10, 3, szbuf);
    printf("%s\n", szbuf);  // "31415.926 "

    dtostrf(-pi * 10, 8, 3, szbuf);
    printf("%s\n", szbuf);  // " -31.415"

    dtostrf(-pi * 10, -8, 3, szbuf);
    printf("%s\n", szbuf);  // "-31.415 "

    return 0;
}
```

```txt:run
 31415.926
31415.926
 -31.415
-31.415 
```

一応希望の文字列は取れそう。

細かいバグはあるだろう。必要な時にそのバグは対処するかもしれない。

整数部分はいったんバッファに入れてから戻す、という安易な方法で実装している。

`char *`型の戻り値がある部分は注意が必要かもしれない。

引数(val)の値を-1するのは、行儀がよくないだろう。

### 参考

- [Arduino IDE(dtostrf関数の使い方)](https://blogs.yahoo.co.jp/nobita_rx7/27630336.html) @ NOBのArduino日記 by NOBさん

情報感謝です。

### dtostrf() v0.2

- width引数がnegativeの時の処理に間違いがあった
- intの変数で10倍を繰り返す間違いをしていた
    - longを使うようにした
- > 引数(val)の値を-1するのは、行儀がよくないだろう。
    - srcvalとcpyvalに分けた

```c
#include <stdio.h>

/*
v0.2 Mar. 12, 2018
  - fix bug: return [31415.9265)))] for larger [prec]
    + use [long] instead of [int]
  - fix bug: for negative [width], one right space was missing
    + fix dtostrf()
v0.1 Mar. 12, 2018
  - add main()
  - add [MAX_DIGIT_WHOLE]
  - add calc_digits_wholenumber()
  - add dtostrf()
*/

#define MAX_DIGIT_WHOLE (10)  // limit for digits of whole number part

static int calc_digits_wholenumber(double val)
{
    int used = 0;
    int wrk;

    if (val < 0) {
        used += 1;
        val *= -1;
    }
    wrk = (int)val;

    while(wrk > 0) {
        used++;
        wrk /= 10;
    }
    return used;
}

char *dtostrf(double srcval, signed char width, unsigned char prec, char *dstPtr)
{
    // static: to reduce stack memory usage
    static char szbuf[MAX_DIGIT_WHOLE + 1] = { 0 };
    int posbuf = 0; // position of szbuf[]
    //
    int idx, loop;
    long wrk;
    int dgtWhl; // digits of whole number part
    char *start; // for function return
    double cpyval;

    start = dstPtr;
    cpyval = srcval;

    if (dstPtr == NULL) {
        return start; // error
    }

    // 0. prefix spaces
    if (width > 0) {
        dgtWhl = calc_digits_wholenumber(srcval);
        wrk = width - dgtWhl - 1 - prec;  // 1: "decimal point"
        for (loop = 0; loop < wrk; loop++) {
            *dstPtr =' ';
            dstPtr++;
        }
    }

    // 1. whole number part
    if (cpyval < 0) {
        *dstPtr = '-';
        dstPtr++;
        cpyval *= -1;
    }
    wrk = (int)cpyval;

    while (wrk > 0) {
        if (posbuf >= MAX_DIGIT_WHOLE) {
            return start; // error
        }
        szbuf[posbuf++] = (wrk % 10) + '0';
        wrk /= 10;
    }
    for (idx = posbuf-1; idx>=0; idx--) {
        *dstPtr = szbuf[idx];
        dstPtr++;
    }
    *dstPtr = '.';
    dstPtr++;

    // 2. fractional part
    for (loop=0; loop < prec; loop++){
        cpyval *= 10;
        wrk = (long)cpyval;
        *dstPtr = (wrk % 10) + '0';
        dstPtr++;
    }

    // 3. postfix spaces
    if (width < 0) {
        dgtWhl = calc_digits_wholenumber(srcval);
        wrk = (-width) - dgtWhl - 1 - prec;  // 1: "decimal point"
        for (loop = 0; loop < wrk; loop++) {
            *dstPtr =' ';
            dstPtr++;
        }
    }

    *dstPtr = 0x00;
    dstPtr++;
    return start;
}

int main(void) {
    double pi = 3.141592653589;
    char szbuf[15] = { 0 };
    char *szwrk;

    dtostrf(pi * 10000, 10, 3, szbuf);
    printf("[%s]\n", szbuf);  // 1. "[ 31415.926]"
    dtostrf(pi * 10000, -10, 3, szbuf);
    printf("[%s]\n", szbuf);  // 2. "[31415.926 ]"

    dtostrf(-pi * 10, 8, 3, szbuf);
    printf("[%s]\n", szbuf);  // 3. "[ -31.415]"

    dtostrf(-pi * 10, -8, 3, szbuf);
    printf("[%s]\n", szbuf);  // 4. "[-31.415 ]"

    dtostrf(-pi * 10, -10, 3, szbuf);
    printf("[%s]\n", szbuf);  // 5. "[-31.415    ]"

    dtostrf(pi * 10000, 10, 7, szbuf);
    printf("[%s]\n", szbuf);  // 6. "[31415.9265358]"

    return 0;
}
```

```txt:run
[ 31415.926]
[31415.926 ]
[ -31.415]
[-31.415 ]
[-31.415   ]
[31415.9265358]
```

デバッグ用のArduinoはしばらくの間お休みしている ([Arduino Leonardo > Lチカ入門したら文鎮化した](https://qiita.com/7of9/items/ef8ea911edf4515d9af8))。

ESP8266は40秒で使える状況にはない。

そのため、avr-libc 2.0.0と同じ結果かは確認していない。