c++ - funciona - opciones de compilacion gcc
¿Resultado de coma flotante diferente con la optimización habilitada? ¿Error del compilador? (6)
El siguiente código funciona en Visual Studio 2008 con y sin optimización. Pero solo funciona en g ++ sin optimización (O0).
#include <cstdlib>
#include <iostream>
#include <cmath>
double round(double v, double digit)
{
double pow = std::pow(10.0, digit);
double t = v * pow;
//std::cout << "t:" << t << std::endl;
double r = std::floor(t + 0.5);
//std::cout << "r:" << r << std::endl;
return r / pow;
}
int main(int argc, char *argv[])
{
std::cout << round(4.45, 1) << std::endl;
std::cout << round(4.55, 1) << std::endl;
}
El resultado debería ser:
4.5
4.6
Pero g ++ con optimización ( O1
- O3
) dará como resultado:
4.5
4.5
Si agrego la palabra clave volatile
antes de t, funciona, ¿podría haber algún tipo de error de optimización?
Prueba en g ++ 4.1.2 y 4.4.4.
Aquí está el resultado en ideone: http://ideone.com/Rz937
Y la opción que pruebo en g ++ es simple:
g++ -O2 round.cpp
El resultado más interesante, incluso si /fp:fast
opción /fp:fast
en Visual Studio 2008, el resultado sigue siendo correcto.
Otra pregunta:
Me preguntaba, ¿debería activar siempre la opción -ffloat-store
?
Porque la versión de g ++ que probé se envía con CentOS / Red Hat Linux 5 y CentOS / Redhat 6 .
Recopilé muchos de mis programas bajo estas plataformas, y me preocupa que cause errores inesperados dentro de mis programas. Parece un poco difícil investigar todo mi código C ++ y las bibliotecas usadas si tienen tales problemas. ¿Cualquier sugerencia?
¿Alguien está interesado en por qué even /fp:fast
activó, Visual Studio 2008 aún funciona? Parece que Visual Studio 2008 es más confiable en este problema que g ++?
La salida debería ser: 4.5 4.6 Esa sería la salida si tuvieras una precisión infinita, o si estuvieras trabajando con un dispositivo que utilizara una representación de punto flotante basada en el decimal en lugar del binario. Pero, no lo eres. La mayoría de las computadoras usan el estándar de coma flotante IEEE binario.
Como ya mencionó Maxim Yegorushkin en su respuesta, parte del problema es que internamente su computadora está usando una representación de coma flotante de 80 bits. Esto es solo parte del problema, sin embargo. La base del problema es que cualquier número de la forma n.nn5 no tiene una representación flotante binaria exacta. Esos casos de esquina son siempre números inexactos.
Si realmente desea que su redondeo sea capaz de redondear de manera confiable estos casos de esquina, necesita un algoritmo de redondeo que resuelva el hecho de que n.n5, n.nn5 o n.nnn5, etc. (pero no n.5) es siempre inexacto. Encuentre el caso de esquina que determina si algún valor de entrada redondea hacia arriba o hacia abajo y devuelve el valor redondeado hacia arriba o redondeado en base a una comparación con este caso de esquina. Y debe tener cuidado de que un compilador de optimización no coloque la caja de esquina encontrada en un registro de precisión ampliado.
Consulte ¿Cómo redondea Excel con éxito los números flotantes aunque sean imprecisos? para tal algoritmo.
O simplemente puede vivir con el hecho de que los casos de las esquinas a veces se redondean erróneamente.
Para aquellos que no pueden reproducir el error: no eliminen los comentarios de las reglas de depuración, afectan el resultado.
Esto implica que el problema está relacionado con las instrucciones de depuración. Y parece que hay un error de redondeo causado al cargar los valores en los registros durante las declaraciones de salida, por lo que otros descubrieron que puede solucionarlo con -ffloat-store
Otra pregunta:
Me preguntaba, ¿debería encender siempre la opción de la
-ffloat-store
?
Para ser frívolo, debe haber una razón por la que algunos programadores no encienden -ffloat-store
, de lo contrario la opción no existiría (asimismo, debe haber una razón por la que algunos programadores activan -ffloat-store
). No recomendaría encenderlo o apagarlo siempre. Activarlo evita algunas optimizaciones, pero apagarlo permite el tipo de comportamiento que está recibiendo.
Pero, en general, existe una discrepancia entre los números de coma flotante binarios (como los usa la computadora) y los números de coma flotante decimal (con los que la gente está familiarizada), y esa falta de coincidencia puede causar un comportamiento similar a lo que obtienes (para ser claro, el comportamiento lo que estás obteniendo no es causado por esta falta de coincidencia, pero un comportamiento similar puede ser). La cuestión es que, dado que ya tiene cierta imprecisión al tratar con el punto flotante, no puedo decir que -ffloat-store
lo mejora o empeora.
En su lugar, es posible que desee buscar otras soluciones para el problema que está tratando de resolver (lamentablemente, Koenig no señala el documento real, y realmente no puedo encontrar un lugar "canónico" obvio para él, entonces Te enviaré a Google ).
Si no está redondeando para fines de salida, probablemente miraría std::modf()
(en cmath
) y std::numeric_limits<double>::epsilon()
(en limits
). Pensando en la función round()
original, creo que sería más limpio reemplazar la llamada a std::floor(d + .5)
con una llamada a esta función:
// this still has the same problems as the original rounding function
int round_up(double d)
{
// return value will be coerced to int, and truncated as expected
// you can then assign the int to a double, if desired
return d + 0.5;
}
Creo que sugiere la siguiente mejora:
// this won''t work for negative d ...
// this may still round some numbers up when they should be rounded down
int round_up(double d)
{
double floor;
d = std::modf(d, &floor);
return floor + (d + .5 + std::numeric_limits<double>::epsilon());
}
Una nota simple: std::numeric_limits<T>::epsilon()
se define como "el número más pequeño agregado a 1 que crea un número no igual a 1". Por lo general, necesita utilizar un épsilon relativo (es decir, epsilon de escala de alguna manera para tener en cuenta el hecho de que está trabajando con números que no sean "1"). La suma de d
, .5
y std::numeric_limits<double>::epsilon()
debe estar cerca de 1, por lo que agrupar esa suma significa que std::numeric_limits<double>::epsilon()
tendrá aproximadamente el tamaño correcto para lo que estamos haciendo En todo caso, std::numeric_limits<double>::epsilon()
será demasiado grande (cuando la suma de los tres es menor que uno) y puede hacer que redondeemos algunos números cuando no deberíamos.
Hoy en día, debes considerar std::nearbyint()
.
La respuesta aceptada es correcta si está compilando para un objetivo x86 que no incluye SSE2. Todos los procesadores x86 modernos admiten SSE2, por lo que si puede aprovecharlo, debe:
-mfpmath=sse -msse2 -ffp-contract=off
Vamos a romper esto.
-mfpmath=sse -msse2
. Esta ronda dentro de los registros SSE2, que es mucho más rápido que almacenar cada resultado intermedio en la memoria. Tenga en cuenta que esto ya es el predeterminado en GCC para x86-64. Desde la wiki de GCC :
En procesadores x86 más modernos que admiten SSE2, la especificación de las opciones del compilador
-mfpmath=sse -msse2
garantiza que todas las operaciones de flotación y doble se realicen en registros SSE y se-mfpmath=sse -msse2
correctamente. Estas opciones no afectan el ABI y, por lo tanto, deben usarse siempre que sea posible para obtener resultados numéricos predecibles.
-ffp-contract=off
. Sin embargo, controlar el redondeo no es suficiente para una coincidencia exacta. Las instrucciones FMA (fusted multiple-add) pueden cambiar el comportamiento de redondeo frente a sus contrapartidas no fusionadas, por lo que debemos deshabilitarlo. Este es el valor predeterminado en Clang, no en GCC. Como se explica en esta respuesta :
Una FMA tiene solo un redondeo (efectivamente mantiene una precisión infinita para el resultado de multiplicación temporal interna), mientras que un ADD + MUL tiene dos.
Al deshabilitar FMA, obtenemos resultados que coinciden exactamente con la depuración y la versión, a costa de algún rendimiento (y precisión). Todavía podemos aprovechar otros beneficios de rendimiento de SSE y AVX.
Los diferentes compiladores tienen diferentes configuraciones de optimización. Algunas de esas configuraciones de optimización más rápidas no mantienen reglas estrictas de punto flotante de acuerdo con IEEE 754 . Visual Studio tiene una configuración específica, /fp:strict
, /fp:precise
, /fp:fast
, donde /fp:fast
infringe el estándar sobre lo que se puede hacer. Puede encontrar que este indicador es lo que controla la optimización en dichos ajustes. También puede encontrar una configuración similar en GCC que cambia el comportamiento.
Si este es el caso, entonces, lo único que es diferente entre los compiladores es que GCC buscará el comportamiento de punto flotante más rápido de forma predeterminada en optimizaciones más altas, mientras que Visual Studio no cambia el comportamiento de punto flotante con niveles de optimización más altos. Por lo tanto, podría no ser necesariamente un error real, sino el comportamiento previsto de una opción que no sabía que estaba activando.
Los procesadores Intel x86 usan internamente una precisión extendida de 80 bits, mientras que el double
normalmente tiene una anchura de 64 bits. Diferentes niveles de optimización afectan la frecuencia con la que los valores de punto flotante de la CPU se guardan en la memoria y, por lo tanto, se redondean de precisión de 80 bits a precisión de 64 bits.
Use la -ffloat-store
gcc para obtener los mismos resultados de punto flotante con diferentes niveles de optimización.
Alternativamente, utilice el tipo long double
, que normalmente tiene 80 bits de ancho en gcc para evitar el redondeo de 80 bits a 64 bits de precisión.
man gcc
dice todo:
-ffloat-store
Do not store floating point variables in registers, and inhibit
other options that might change whether a floating point value is
taken from a register or memory.
This option prevents undesirable excess precision on machines such
as the 68000 where the floating registers (of the 68881) keep more
precision than a "double" is supposed to have. Similarly for the
x86 architecture. For most programs, the excess precision does
only good, but a few programs rely on the precise definition of
IEEE floating point. Use -ffloat-store for such programs, after
modifying them to store all pertinent intermediate computations
into variables.
Personalmente, he atacado el mismo problema yendo para otro lado, desde gcc hasta VS. En la mayoría de los casos, creo que es mejor evitar la optimización. La única vez que vale la pena es cuando se trata de métodos numéricos que involucran grandes matrices de datos de punto flotante. Incluso después de desmontar, a menudo me siento decepcionado por las elecciones de los compiladores. Muy a menudo es más fácil usar los intrínsecos del compilador o simplemente escribir el ensamble usted mismo.