2010-03-23 18 views
8

Tengo el siguiente código, sin embargo, al compilarlo con GCC 4.4 con varios indicadores de optimización obtengo algunos resultados inesperados cuando se ejecuta.Problema de GCC con comparaciones de tipo doble sin formato

#include <iostream> 

int main() 
{ 
    const unsigned int cnt = 10; 
    double lst[cnt] = { 0.0 }; 
    const double v[4] = { 131.313, 737.373, 979.797, 731.137 }; 

    for(unsigned int i = 0; i < cnt; ++i) { 
     lst[i] = v[i % 4] * i; 
    } 

    for(unsigned int i = 0; i < cnt; ++i) { 
     double d = v[i % 4] * i; 
     if(lst[i] != d) { 
     std::cout << "error @ : " << i << std::endl; 
     return 1; 
     } 
    } 
    return 0; 
} 
  • cuando se compila con: "g ++ -pedantic -Wall -Werror -O1 -o test.cpp test" consigo la salida siguiente: "error @: 3"

  • cuando se compila con: "g ++ -pedantic -Wall -Werror -O2 -o test.cpp test" consigo la salida siguiente: "error @: 3"

  • cuando se compila con: "test.cpp g ++ -pedantic -Wall -Werror -O3 -o prueba" me sale ningún error

  • cuando se compila con: "g ++ -pedantic -Wall - werror -o test.cpp prueba" me sale ningún error

no creo que esto sea un problema relacionado con el redondeo, o diferencia épsilon en la comparación. Intenté esto con Intel v10 y MSVC 9.0 y todos parecen funcionar como se esperaba. Creo que esto no debería ser más que una comparación de bits.

Si se sustituye la sentencia if con lo siguiente: if (static_cast<long long int>(lst[i]) != static_cast<long long int>(d)), y añadir "-Wno-largo tiempo" me sale ningún error en cualquiera de los modos de optimización cuando se ejecuta.

Si añado std::cout << d << std::endl; antes de que el "retorno 1", me sale ningún error en cualquiera de los modos de optimización cuando se ejecuta.

¿Esto es un error en mi código, o hay algo mal con GCC y la forma en que maneja el tipo doble?

Nota: Acabo de probar esto con las versiones 4.3 y 3.3 de gcc, el error no se exhibe.

Resolución: Mike Dinsdale en cuenta lo siguiente informe de error: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 Parece que el equipo GCC no son completamente seguros sobre la naturaleza del problema.

Como se sugiere en el informe de error, una posible solución es usar la opción ffloat-store. He intentado esto y funciona, sin embargo, los resultados desde el punto de vista del rendimiento no son tan buenos, aunque mmm.

+1

La comparación de 'dobles' se realiza mejor usando un pequeño valor delta para tener en cuenta las imprecisiones en coma flotante. Intente imprimir los valores de 'lst [i]' y 'd' justo antes de la comparación y díganos qué tan diferentes son. – dirkgently

+3

Este no es un escenario donde tengo valores generados a partir de diferentes fuentes y quiero ver si son iguales entre sí dentro de una tolerancia, estos son valores idénticos (bit a bit), deben ser verdaderos. El hecho de que el compilador no genere un binario que proporcione un conjunto coherente de resultados en cualquier sentido es lo que más preocupa. –

+0

@Monomer: considere cambiar el ensamblaje cuando cambie el programa para usar una comparación bit a bit de la FPU. Parece básico, pero esto significa que no usará los valores en la FPU, que pueden estar apagados, y en realidad cargarlos y truncarlos. En pocas palabras, su prueba no es concluyente. – GManNickG

Respuesta

4

El problema es probablemente el resultado de perder un poco de precisión para el almacenamiento del resultado de una expresión versus el compilador no hacerlo en un local como una optimización:

double d = v[i % 4] * i; // the result, `d`, might be kept in a register 
          // instead of storing it in a memory location, 
          // keeping full precision 

if(lst[i] != d) {   // the value stored in lst[i] may have lost some 
          // precision since it had to be stored in memory, 
          // which might not be able to hold the full 
          // precision that the expression generated 

El estándar C99 dice en 6.3.1 .8/2 "conversiones aritméticas habituales":

Los valores de operandos flotantes y de los resultados de expresiones flotantes pueden ser representado en mayor precisión y rango que el requerido por el tipo; los tipos no son cambiados por eso.

+1

Este error no se ve cuando no está habilitada la optimización según la cuarta opción, también en el mismo sistema (ubuntu) y en Windows usando Intel v10, el error no se ve. –

+3

@Monomer: es muy probable que el efecto de este comportamiento esté fuertemente influenciado por la implementación, la plataforma y la configuración de optimización. Este tipo de cosas es una de las razones por las que estoy bastante contento de que rara vez tenga que lidiar con el punto flotante. –

+1

Estoy de acuerdo, los problemas con los puntos flotantes han sido la perdición de mi existencia en los últimos años, crees que lo has visto todo y de repente aparece algo más. –

3

El ancho de los registros de coma flotante en x86 es diferente del ancho de double en RAM. Por lo tanto, las comparaciones pueden tener éxito o fallar dependiendo completamente de cómo el compilador decida optimizar las cargas de los valores de coma flotante.

+0

que es correcto, ¿no debería el compilador generar el código correcto para la expresión dada? –

+0

Genera el código correcto. ¿Cómo no es correcto? En un ejemplo decimal, 3.333 no es igual a 3.333333. Así es como lo está viendo la FPU, solo en formato binario en lugar de decimal. –

+2

El punto que trato de hacer es que los valores en ambos lados de "==" se generan de la misma manera. Entiendo que para dobles a == b y b == a no puede producir el mismo resultado. Pero, estos valores son idénticos a los bits, si no fuera así, el enfoque de comparación larga no funcionaría. Además, ¿cómo se explica que todo funcione, si tengo una declaración extra impresa? no huele un poco a pescado? –

6

El hecho de que el resultado dependa de la configuración de optimización sugiere que podría tratarse de una ampliación de precisión x87 complicando las cosas (como dice Michael Burr).

Aquí hay un código que utilizo (con gcc en procesadores x86) para cambiar la precisión extendida fuera:

static const unsigned int PRECISION_BIT_MASK = 0x300; 
///< bitmask to mask out all non-precision bits in the fpu control word \cite{INTEL}. 
static const unsigned int EXTENDED_PRECISION_BITS = 0x300; 
///< add to the fpu control word (after zeroing precision bits) to turn on extended precision \cite{INTEL}. 
static const unsigned int STANDARD_PRECISION_BITS = 0x200; 
///< add to the fpu control word (after zeroing precision bits) to turn off extended precision \cite{INTEL}. 

void set_fpu_control_word(unsigned int mode) 
{ 
    asm ("fldcw %0" : : "m" (*&mode)); 
} 

unsigned int get_fpu_control_word() 
{ 
    volatile unsigned int mode = 0; 
    asm ("fstcw %0" : "=m" (*&mode)); 
    return mode; 
} 

bool fpu_set_extended_precision_is_on(bool state) 
{ 
    unsigned int old_cw = get_fpu_control_word(); 
    unsigned int masked = old_cw & ~PRECISION_BIT_MASK; 
    unsigned int new_cw; 
    if(state) 
    new_cw = masked + EXTENDED_PRECISION_BITS; 
    else 
    new_cw = masked + STANDARD_PRECISION_BITS; 
    set_fpu_control_word(new_cw); 
    return true; 
} 

bool fpu_get_extended_precision_is_on() 
{ 
    unsigned int old_cw = get_fpu_control_word(); 
    return ((old_cw & PRECISION_BIT_MASK) == 0x300); 
} 

o simplemente puede ejecutar el código con valgrind, que no simula los registros de 80 bits ¡y probablemente sea más fácil para un programa corto como este!

+0

@Mike: la palabra de control está activada, creo que esto es un problema de que los puntos flotantes de 64 bits se degraden a puntos flotantes de 32 bits. –

+3

A 32 bits? ¿Has comprobado la diferencia y es realmente tan grande? Si es así, parece un error en gcc. Si no, apostaría a que es este truncamiento de 80 bits -> 64 bits causado por guardar los registros FPU en la memoria. ¿Tiene valgrind instalado? Si es así, verificaría si el problema desaparece al ejecutar tu código a través de valgrind. –

+0

@Mike: hace un error relacionado con guardar el estado de fpu, ya que si accedo una vez más al valor "d" (también conocido como una declaración de impresión para d), el problema desaparece. –

Cuestiones relacionadas