Я был в состоянии воспроизвести проблему с одной версии компилятора.
Шахта MinGW g ++ 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
.
Если установить одинаковые округления и контроля точности полей управляющего слова x87 FPU на время exp()
как для fistpl 4(%esp)
инструкции, как это:
_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
, а также.
Из этого можно сделать вывод, что exp()
не вычисляет 13 точно как 13.0.
Это может быть стоит посмотреть на исходный код этого __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()
она занимает два ints
вместо двух doubles
).
Но я бы не стал винить exp()
. C++ 11 определяет float pow(float, float)
и long double pow(long double, long double)
в дополнение к C's double pow(double, double)
. Но в стандарте нет double pow(int, int)
.
Тот факт, что компилятор предоставляет версию для целых аргументов, не дает дополнительной гарантии точности результата. Если exp()
вычисляет б как
в б = 2 б * войти (а)
или, как
б = e b * ln (a)
для значений с плавающей точкой, определенно могут быть ошибки округления в процессе.
Если «целочисленная» версия exp()
делает что-то подобное и несет аналогичную потерю точности из-за ошибок округления, она по-прежнему выполняет свою работу правильно. И он делает это, даже если потеря точности обусловлена некоторой глупой ошибкой, а не из-за обычных ошибок округления.
Как ни удивительно, это может показаться, это правильно. Или я считаю, что пока не доказано, что это неправильно.
Каковы результаты? И какие флагов компилятора? –
Кроме того, что такое версии gcc? Скорее всего, это ошибка компилятора. Я получаю правильный ответ (720720) с gcc 4.7.2 –
Зачем ты написал такой странный код? – Pubby