概要
fenv.h を使うときは、FENV_ACCESSの指定も忘れないようにしましょう。
fenv.h
浮動小数演算の丸めモードには、4つあって、
- -∞ 方向への丸め(FE_DOWNWARD)
- +∞ 方向への丸め(FE_UPWARD)
- 0 方向への丸め(FE_TOWARDZERO)
- 近い値への丸め(FE_TONEAREST)
fesetround()で設定できます。
しかし、fesetroundだけでは、完全にこの挙動を指定できるわけではないです。
というのは、コンパイラは、コンパイル時に定数の演算を終わらせてしまう最適化をする可能性があるためです。
#include <fenv.h>
#include <stdio.h>
double get1() {
return 1.0;
}
double get3() {
return 3.0;
}
double f(void) {
return get1() / get3();
}
int main() {
fesetround(FE_UPWARD);
printf("%a\n", f());
fesetround(FE_DOWNWARD);
printf("%a\n", f());
fesetround(FE_TOWARDZERO);
printf("%a\n", f());
fesetround(FE_TONEAREST);
printf("%a\n", f());
}
というようなコードを考えると、f()関数中の、1/3 という演算は本来なら丸めモードを参照しないといけないわけですが、コンパイル時に、1/3 の演算を済ませてしまうと、丸めモードを指定した場合の結果と変わってしまいます。
上のプログラムだと、-O0 と -O2 で結果が変わるのが確認できると思います。
$ clang -std=c11 -O0 fenv.c -Wall -lm
$ ./a.out
0x1.5555555555556p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
$ clang -std=c11 -O2 fenv.c -Wall -lm
$ ./a.out
0x1.5555555555555p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
$ gcc -std=c11 -O0 fenv.c -Wall -lm
$ ./a.out
0x1.5555555555556p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
$ gcc -std=c11 -O2 fenv.c -Wall -lm
$ ./a.out
0x1.5555555555555p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
0x1.5555555555555p-2
というわけで、fesetroundを使った丸めモード指定を正しく使うなら、コンパイラによる最適化を抑制する必要があります。これを指定するのが、
#pragma STDC FENV_ACCESS ON
というプラグマです。
上のプログラムをこのFENV_ACCESSを使うように書きかえると、
double f(void) {
#pragma STDC FENV_ACCESS ON
return get1() / get3();
}
$ gcc -std=c11 -O2 fenv.c -Wall -lm
fenv.c: In function ‘f’:
fenv.c:13:0: warning: ignoring #pragma STDC FENV_ACCESS [-Wunknown-pragmas]
#pragma STDC FENV_ACCESS ON
^
$ clang -std=c11 -O2 fenv.c -Wall -lm
fenv.c:13:14: warning: pragma STDC FENV_ACCESS ON is not supported, ignoring pragma [-Wunknown-pragmas]
#pragma STDC FENV_ACCESS ON
^
1 warning generated.
GCC, clangではまだサポートされていないことが確認できますね。(完)