Cortex-M4のFPUでは単精度浮動小数点数しかサポートされていません。そのためC言語のソース上では可能な限りdoubleを使わずにfloatのみで書くことが望まれます。しかし、C言語の言語仕様として浮動小数点数はデフォルトでdoubleなので、ちょっとしたミスで内部的にdoubleで演算してしまうということが発生してしまいます。
もしもsqrtf() をsqrt() と書いてしまったら
前回の記事 gccでCortex-M4のsqrtf()の呼び出しの代わりにFPU命令一発のコードを出力をさせる方法 でsqrtfを扱いましたが、もしこれをsqrtとタイプミスしたらどうなるかを見てみます。
#include <math.h>
float a(float x)
{
return sqrt(x); // typo of sqrtf !!
}
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Ofast wrong_sqrt.c
arm-eabi-objdump -d wrong_sqrt.o
このときコンパイラは何の警告メッセージも出してくれません!
00000000 <a>:
0: b508 push {r3, lr}
2: ee10 0a10 vmov r0, s0
6: f7ff fffe bl 0 <__aeabi_f2d>
a: ec41 0b10 vmov d0, r0, r1
e: f7ff fffe bl 0 <sqrt>
12: ec51 0b10 vmov r0, r1, d0
16: f7ff fffe bl 0 <__aeabi_d2f>
1a: ee00 0a10 vmov s0, r0
1e: bd08 pop {r3, pc}
ぎゃああ。
せっかく速いコードを生成しようとしたのに台無し。
floatからdoubleに変換して、doubleのsqrtを呼び出し、doubleからfloatに変換するというコードになっています。
しかもこれらはFPUでは処理できないので全てソフトウェアのライブラリで行っています。
演算結果としてはほぼ同じですが、処理速度はとても遅いはずです。
このように逆アセンブル表示しない限り発見のしにくいバグになりそうです。
役に立ちそうなgccの警告オプション
-Wfloat-conversion
doubleからfloatに暗黙で変換されたときに警告します。明示的にキャストした場合は大丈夫。
-Wdouble-promotion
内部演算でfloatからdoubleに暗黙の変換があったといに警告します。(しかしdoubleの関数の引数にfloatを渡したときには警告されないようです。)
-Wunsuffixed-float-constants
浮動小数点数の定数に型を示すサフィックスが無いときに警告します。
これらのオプションをつけてコンパイルしてみる
さきほどのwrong_sqrt.cでは
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Ofast -Wdouble-promotion -Wfloat-conversion -Wunsuffixed-float-constants wrong_sqrt.c
wrong_sqrt.c: In function ‘a’:
wrong_sqrt.c:5:9: warning: conversion to ‘float’ from ‘double’ may alter its value [-Wfloat-conversion]
return sqrt(x);
^~~~~~~
警告出ました。
さらに、gccのマニュアルに載っている例ですが、
float area(float radius)
{
return 3.14159 * radius * radius;
}
本来は 3.14159f と書くべきところでfを忘れています。そのために無駄なfloat<->doubleの変換が発生してしまっています。
arm-eabi-gcc -c -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Ofast -Wdouble-promotion -Wfloat-conversion -Wunsuffixed-float-constants f2.c
f2.c: In function ‘area’:
f2.c:3:4: warning: unsuffixed float constant [-Wunsuffixed-float-constants]
return 3.14159 * radius * radius;
^~~~~~
f2.c:3:19: warning: implicit conversion from ‘float’ to ‘double’ to match other operand of binary expression [-Wdouble-promotion]
return 3.14159 * radius * radius;
^
f2.c:3:28: warning: implicit conversion from ‘float’ to ‘double’ to match other operand of binary expression [-Wdouble-promotion]
return 3.14159 * radius * radius;
^
f2.c:3:28: warning: conversion to ‘float’ from ‘double’ may alter its value [-Wfloat-conversion]
return 3.14159 * radius * radius;
~~~~~~~~~~~~~~~~~^~~~~~~~
警告出ました。
gccの警告メッセージに関するコマンドラインオプションについてはこちらを参照してください。
https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Warning-Options.html#Warning-Options
#追記 (2018.1.12)
最適化オプションで -fsingle-precision-constant というものを見つけました。
これは浮動小数点数の定数のデフォルトの型をdoubleからfloatに変更します。
https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Optimize-Options.html#Optimize-Options
-Wunsuffixed-float-constants をつけたときに警告がたくさんありすぎて修正しきれないときに使うとよさそうです。