ventajas - C++: dobles, precisión, máquinas virtuales y GCC.
tipos de maquinas virtuales (4)
Tengo el siguiente código:
#include <cstdio>
int main()
{
if ((1.0 + 0.1) != (1.0 + 0.1))
printf("not equal/n");
else
printf("equal/n");
return 0;
}
Cuando se compila con O3 usando gcc (4.4.4.5 y 4.6) y se ejecuta de forma nativa (ubuntu 10.10), imprime el resultado esperado de "igual".
Sin embargo, el mismo código cuando se compila como se describe anteriormente y se ejecuta en una máquina virtual (ubuntu 10.10, imagen de virtualbox), genera "no igual"; este es el caso para cuando los indicadores O3 y O2 se configuran, sin embargo, no son O1 e inferiores. Cuando se compila con clang (O3 y O2) y se ejecuta en la máquina virtual, obtengo el resultado correcto.
Entiendo que 1.1 no se puede representar correctamente utilizando el doble y leí "Lo que todo científico informático debería saber sobre la aritmética de punto flotante", así que, por favor, no me apunten, esto parece ser una especie de optimización de que GCC hace eso De alguna manera parece que no funciona en máquinas virtuales.
¿Algunas ideas?
Nota: El estándar de C ++ dice que la promoción de tipos en estas situaciones depende de la implementación, ¿podría ser que GCC esté usando una representación interna más precisa que cuando se aplica la prueba de desigualdad es verdadera, debido a la precisión adicional?
ACTUALIZACIÓN1: La siguiente modificación de la pieza de código anterior, ahora resulta en el resultado correcto. Parece que en algún momento, por el motivo que sea, GCC desactiva la palabra de control de punto flotante.
#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode));
int main()
{
set_dpfpu();
if ((1.0 + 0.1) != (1.0 + 0.1))
printf("not equal/n");
else
printf("equal/n");
return 0;
}
ACTUALIZACIÓN2: Para aquellos que preguntan acerca de la naturaleza de la expresión constante del código, lo cambié de la siguiente manera, y aún falla cuando se compila con GCC. - pero supongo que el optimizador puede convertir lo siguiente en una expresión constante también.
#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode));
int main()
{
//set_dpfpu(); uncomment to make it work.
double d1 = 1.0;
double d2 = 1.0;
if ((d1 + 0.1) != (d2 + 0.1))
printf("not equal/n");
else
printf("equal/n");
return 0;
}
Resolución de UPDATE3: la actualización de virtualbox a la versión 4.1.8r75467 resolvió el problema. Sin embargo, el problema sigue siendo uno de ellos, es decir, por qué funcionó el clang build.
ACTUALIZACIÓN: consulte esta publicación ¿Cómo lidiar con el exceso de precisión en los cálculos de punto flotante? Aborda los problemas de precisión extendida de punto flotante. Me olvidé de la precisión extendida en x86. Recuerdo una simulación que debería haber sido determinista, pero dio resultados diferentes en las CPU de Intel que en las CPU de PowePC. Las causas fue la arquitectura de precisión extendida de Intel.
Esta página web habla sobre cómo convertir las CPU de Intel en modo de redondeo de doble precisión: http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html .
¿Virtualbox garantiza que sus operaciones de punto flotante sean idénticas a las operaciones de punto flotante del hardware? No pude encontrar una garantía como esa con una rápida búsqueda en Google. Tampoco encontré una promesa de que las operaciones vituralbox FP se ajusten a IEEE 754.Las máquinas virtuales son emuladores que intentan, y en su mayoría tienen éxito, emular un conjunto de instrucciones o arquitectura en particular. Sin embargo, solo son emuladores y están sujetos a sus propias peculiaridades de implementación o problemas de diseño.
Si aún no lo ha hecho, publique la pregunta forums.virtualbox.org y vea lo que dice la comunidad al respecto.
Puedo confirmar el mismo comportamiento de su código de máquina virtual, pero como no tengo una máquina virtual, no he probado la pieza de máquina virtual.
Sin embargo, el compilador, tanto Clang como GCC evaluarán la expresión constante en el momento de la compilación. Vea la salida del ensamblaje a continuación (usando gcc -O0 test.cpp -S
):
.file "test.cpp"
.section .rodata
.LC0:
.string "equal"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
.section .note.GNU-stack,"",@progbits
Parece que entiendes el ensamblaje, pero está claro que solo existe la cadena "igual", no hay "no igual". Así que la comparación ni siquiera se realiza en tiempo de ejecución, simplemente imprime "igual".
Intentaría codificar el cálculo y la comparación utilizando el ensamblaje y ver si tiene el mismo comportamiento. Si tiene un comportamiento diferente en la VM, entonces es la forma en que la VM realiza el cálculo.
ACTUALIZACIÓN 1: (Basado en la "ACTUALIZACIÓN 2" en la pregunta original). A continuación se muestra el conjunto de salida gcc -O0 -S test.cpp
(para arquitectura de 64 bits). En ella puedes ver el movabsq $4607182418800017408, %rax
line dos veces. Esto será para los dos indicadores de comparación, no lo he verificado, pero supongo que el valor de $ 4607182418800017408 es 1.1 en términos de punto flotante. Sería interesante compilar esto en la máquina virtual, si obtienes el mismo resultado (dos líneas similares), entonces la máquina virtual hará algo gracioso en el tiempo de ejecución, de lo contrario es una combinación de máquina virtual y compilador.
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movabsq $4607182418800017408, %rax
movq %rax, -16(%rbp)
movabsq $4607182418800017408, %rax
movq %rax, -8(%rbp)
movsd -16(%rbp), %xmm1
movsd .LC1(%rip), %xmm0
addsd %xmm1, %xmm0
movsd -8(%rbp), %xmm2
movsd .LC1(%rip), %xmm1
addsd %xmm2, %xmm1
ucomisd %xmm1, %xmm0
jp .L6
ucomisd %xmm1, %xmm0
je .L7
Sí, ese es un comportamiento realmente extraño, pero en realidad se puede explicar fácilmente:
En los registros de coma flotante x86 internamente use más precisión (por ejemplo, 80 en lugar de 64). Esto significa que el cálculo 1.0 + 0.1
se computará con más precisión (y como 1.1 no puede representarse exactamente en binario en todos los bits adicionales que se usarán) en los registros. Solo cuando se almacena el resultado en la memoria se trunca.
Lo que esto significa es simple: si comparas un valor cargado desde la memoria con un valor recientemente calculado en los registros, obtendrás una devolución "no igual", porque un valor se truncó mientras que el otro no. Así que eso no tiene nada que ver con VM / no VM, solo depende del código que genere el compilador, que puede fluctuar fácilmente como vemos allí.
Agrégalo a la creciente lista de sorpresas de punto flotante.
Veo que añadiste otra pregunta:
Nota: El estándar de C ++ dice que la promoción de tipos en estas situaciones depende de la implementación, ¿podría ser que GCC esté usando una representación interna más precisa que cuando se aplica la prueba de desigualdad es verdadera, debido a la precisión adicional?
La respuesta a esa pregunta es no. 1.1
no es exactamente representable en un formato binario, sin importar cuántos bits tenga el formato. Puedes acercarte, pero no con un número infinito de ceros después del .1
.
¿O te refieres a un formato interno completamente nuevo para decimales? No, me niego a creer eso. No sería muy compatible si lo hiciera.