2013-03-10 2 views
10

Недавно я написал небольшую программу и скомпилировал ее с помощью mingw32 (в Windows8) из двух разных версий. Удивительно, но я получил два разных повтора. Я попытался разобрать его, но ничего особенного не нашел. Может ли кто-нибудь мне помочь? Спасибо.Int to Float to Int conversion precision loss

ЕХЕ файлы: https://www.dropbox.com/s/69sq1ttjgwv1qm3/asm.7z

Результаты: 720720 (GCC версии 4.5.2), 720719 (GCC версии 4.7.0)

флаги компилятора: -lstdC++ -static

Код пропущено следующим образом:

#include <iostream> 
#include <cmath> 

using namespace std; 

int main() 
{ 
    int a = 55440, b = 13; 
    a *= pow(b, 1); 
    cout << a << endl; 
    return 0; 
} 

выход в сборе (4.5.2):

http://pastebin.com/EJAkVAaH

выход в сборе (4.7.0):

http://pastebin.com/kzbbFGs6

+0

Каковы результаты? И какие флагов компилятора? –

+0

Кроме того, что такое версии gcc? Скорее всего, это ошибка компилятора. Я получаю правильный ответ (720720) с gcc 4.7.2 –

+1

Зачем ты написал такой странный код? – Pubby

ответ

9

Я был в состоянии воспроизвести проблему с одной версии компилятора.

Шахта 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() делает что-то подобное и несет аналогичную потерю точности из-за ошибок округления, она по-прежнему выполняет свою работу правильно. И он делает это, даже если потеря точности обусловлена ​​некоторой глупой ошибкой, а не из-за обычных ошибок округления.

Как ни удивительно, это может показаться, это правильно. Или я считаю, что пока не доказано, что это неправильно.

+0

Большое спасибо! Мне это очень помогает. –