Arduinoのdtostrf()による四捨五入と浮動小数点の精度

Ardurinoで浮動小数点を表示するにはdtostrf()を使う(strprintf()で%fの書式指定は使えない)。このdtostrf()を使った場合、四捨五入は自分でやらなきゃダメかと思ったけど、それも面倒を見てくれるようだ。素晴らしい。しかし、ちょっといじってみたところ、小数点以下の桁数が多いと上手くいかないみたい。

以下、その検証。

void setup() {
	Serial.begin(9600);
}

void loop() {
	char	buf[16];
	int		i;
	float	f = 1.555555;

	dtostrf(f, 10, 8, buf);
	Serial.println(buf);

	dtostrf(f, 1, 0, buf);
	Serial.println(buf);

	for (i = 1; i <= 8; i++) {
		dtostrf(f, (i + 2), i, buf);
		Serial.println(buf);
	}

	while(1) {}
}

このコードを実行した結果がこれ。

1.55555500
2
1.6
1.56
1.556
1.5556
1.55555
1.555555
1.5555550
1.55555500

小数点以下四桁までは四捨五入してくれるけど、それ以上を表示しようとすると、そのまますり抜けてくる(切捨て)。

ちなみに、fを0.555555とするともっとややこしい結果。

0.55555499
1
0.6
0.56
0.556
0.5556
0.55555
0.555555
0.5555550
0.55555499

まず、0.555555は表現できないようで、0.55555499となる。それはとりあえず置いておいて、小数点以下四桁までは先ほどと同じように四捨五入してくれている。五桁はすり抜け、六桁は四捨五入。うーん、なんだ…?

そこで、浮動小数点の内部表現を確認する。

テストコード。まずは、f=1.555555。

void setup() {
	Serial.begin(9600);
}

void loop() {
	union {float f; unsigned char c[4];} x;
	int		i;
	char	buf[16];

	x.f =1.555555;
	
	dtostrf(x.f, 12, 10, buf);
	Serial.println(buf);

	for (i = 3; 0 <= i ; i--) {
		sprintf(buf, "%02x", x.c[i]);
		Serial.print(buf);
	}
	Serial.println("");

	while(1) {}
}

実行結果。

1.5555550000
3fc71c6d

ビットパターンは、0x3fc71c6dのようなので、これをFloating Point to Hex Converterで確認。

これによれば、仮数部は1.555554986000061とのこと(指数部は1なので考えなくてよい)。小数点以下五桁までは「5」が入っているので、四桁までは四捨五入で期待した値が得られる。六桁目は「4」だから「1.55555」になってしまったのね。なるほど。

ついでに、0.555555の場合も確認。

0.5555549900
3f0e38da

仮数部が1.111109972000122、指数部が0.5(2-1)。両者を掛けた結果は、0.555554986000061。なるほど、これを順番に一桁ずつ四捨五入の桁位置を下げていけば上の結果になるわ。

結局のところ、浮動小数点による表現には限界があるのでしょうがないね、という当たり前のことを確認することとなった。浮動小数点自体の話なので、Arduinoとかdtostrf()の問題というものではない。

精度をもっと上げたいならdoubleを使えばいいのだろうが、Arduino Unoでは使えない(コンパイルエラーにはならないけど、floatと同じ(かえってややこしい気もする))。

この記事のタイトルとURLをコピーする
スポンサーリンク
スポンサーリンク
スポンサーリンク
スポンサーリンク