単一バージョンのコンパイラで問題を再現することができました。
私のはMinGWg++4.6.2です。
プログラムをとしてコンパイルするとg++ -g -O2 bugflt.cpp -o bugflt.exe
、が得られ720720
ます。
これはの分解ですmain()
:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
call ___main
movl $720720, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl %eax, (%esp)
call __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
xorl %eax, %eax
leave
ret
ご覧のとおり、値はコンパイル時に計算されます。
としてコンパイルするとg++ -g -O2 -fno-inline bugflt.cpp -o bugflt.exe
、が得られ720719
ます。
これはの分解ですmain()
:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
...
LC1:
.long 1196986368 // 55440.0 exactly
exp()
の呼び出しを次のように13.0の読み込みに置き換えると、次のようになります。
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
// call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fildl (%esp)
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
取得し720720
ます。
exp()
次のfistpl 4(%esp)
ような命令の場合と同じ期間、x87FPU制御ワードの丸めと精度の制御フィールドを設定すると次のようになります。
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $1, 4(%esp)
movl $13, (%esp)
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
call __ZSt3powIiiEN9__gnu_cxx11__promote_2INS0_11__enable_ifIXaasrSt15__is_arithmeticIT_E7__valuesrS3_IT0_E7__valueES4_E6__typeES6_E6__typeES4_S6_
fldcw 30(%esp)
fmuls LC1
fnstcw 30(%esp)
movw 30(%esp), %ax
movb $12, %ah
movw %ax, 28(%esp)
fldcw 28(%esp)
fistpl 4(%esp)
fldcw 30(%esp)
movl $__ZSt4cout, (%esp)
call __ZNSolsEi
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
movl %eax, (%esp)
call __ZNSolsEPFRSoS_E
xorl %eax, %eax
leave
ret
私も得720720
ます。
このことから、 131を13.0として正確にexp()
計算していないと結論付けることしかできません。
そのソースコードを見て__gnu_cxx::__promote_2<__gnu_cxx::__enable_if<(std::__is_arithmetic<int>::__value)&&(std::__is_arithmetic<int>::__value), int>::__type, int>::__type std::pow<int, int>(int, int)
、整数でべき乗を台無しにする方法を正確に確認することは価値があるかもしれません(Cとは異なり、exp()
2ints
ではなく2を必要としますdoubles
)。
しかし、私はそれを責めませんexp()
。C ++ 11は、Cに加えてを定義float pow(float, float)
し long double pow(long double, long double)
ますdouble pow(double, double)
。しかしdouble pow(int, int)
、標準にはありません。
コンパイラが整数引数のバージョンを提供しているという事実は、結果の精度について追加の保証をするものではありません。abを次のようにexp()
計算する場合
a b = 2 b * log 2(a)
またはとして
a b = e b * ln(a)
浮動小数点値の場合、プロセスで丸め誤差が発生する可能性があります。
の「整数」バージョンがexp()
同様のことを行い、丸め誤差のために同様の精度の低下が発生した場合でも、それは正しく機能します。また、精度の低下が、通常の丸め誤差ではなく、いくつかのばかげたバグによるものであっても、それを実行します。
この振る舞いは意外に思われるかもしれませんが、それは正しいことです。またはそう私は間違っていることが証明されるまで信じています。