C++: doublures, précision, machines virtuelles et GCC
j'ai le code suivant:
#include <cstdio>
int main()
{
if ((1.0 + 0.1) != (1.0 + 0.1))
printf("not equaln");
else
printf("equaln");
return 0;
}
lorsqu'il est compilé avec O3 en utilisant gcc (4.4,4.5 et 4.6) et exécuté nativement (ubuntu 10.10), il imprime le résultat attendu de"égal".
cependant le même code lorsqu'il est compilé comme décrit ci - dessus et exécuté sur une machine virtuelle (ubuntu 10.10, image virtualbox), il sort" pas égal " - c'est le cas lorsque les drapeaux O3 et O2 sont positionnés mais pas O1 et ci-dessous. Lorsqu'il est compilé avec clang (O3 et O2) et exécuter sur la machine virtuelle j'obtiens le résultat correct.
je comprends 1.1 ne peut pas être correctement représenté à l'aide de double et j'ai lu " ce que tout informaticien devrait savoir sur L'arithmétique à virgule flottante " alors s'il vous plaît ne me pointez pas là, cela semble être une sorte d'optimisation que GCC fait qui, d'une certaine façon, ne semble pas fonctionner dans les machines virtuelles.
des idées?
Note: Le C++ standard dit que la promotion de type dans ces situations dépend de la mise en œuvre, se pourrait - il que GCC utilise une représentation interne plus précise que lorsque le test d'inégalité est appliqué est vrai-en raison de la précision supplémentaire?
UPDATE1: la modification suivante du morceau de code ci-dessus, résulte maintenant dans le résultat correct. Il semble qu'à un moment donné, pour quelque raison que ce soit, GCC éteint mot de contrôle de point flottant.
#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 equaln");
else
printf("equaln");
return 0;
}
UPDATE2: pour ceux qui s'interrogent sur la nature de const expression du code, je l'ai modifié comme suit, et échoue toujours lorsqu'il est compilé avec GCC. - mais je suppose que l'optimiseur peut transformer ce qui suit en une expression de const.
#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 equaln");
else
printf("equaln");
return 0;
}
UPDATE3 résolution: Upgrading virtualbox to version 4.1.8r75467 a résolu le problème. Cependant, le son reste une question, qui est: pourquoi le bruit de construire travailler.
4 réponses
mise à jour: Voir cet article comment gérer l'excès de précision dans les calculs à virgule flottante? Il aborde les questions de la précision étendue de la pointe flottante. J'avais oublié la précision étendue de x86. Je me souviens d'une simulation qui aurait dû être déterministe, mais qui a donné des résultats différents sur les processeurs Intel que sur les processeurs PowePC. Les causes en sont L'architecture de précision étendue D'Intel.
cette page Web explique comment lancer des processeurs Intel mode d'arrondi double précision: http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html .
Virtualbox garantit-elle que ses opérations flottantes sont identiques aux opérations flottantes du matériel? Je n'ai pas pu trouver une telle garantie avec une recherche rapide sur Google. Je n'ai pas non plus trouvé de promesse que vituralbox FP ops se conforme à IEEE 754.
les VMs sont des émulateurs qui essayent-- et réussissent le plus souvent-- émuler un ensemble d'instructions ou une architecture particulière. Ils ne sont toutefois que des émulateurs et sont soumis à leurs propres bizarreries de mise en œuvre ou problèmes de conception.
si vous ne l'avez pas déjà, postez la question forums.virtualbox.org et voir ce que la communauté en dit.
Oui c'est un comportement vraiment étrange, mais il peut en fait être facilement expliqué:
Sur x86 registres virgule flottante en interne, utiliser plus de précision (par exemple, 80 au lieu de 64). Cela signifie que le calcul 1.0 + 0.1
sera calculé avec plus de précision (et puisque 1.1 ne peut pas être représenté exactement en binaire à tous ces bits supplémentaires seront utilisés) dans les registres. Ce n'est qu'en stockant le résultat dans la mémoire qu'il sera tronqué.
ce que cela signifie est simple: si vous comparez une valeur chargée de mémoire avec une valeur nouvellement calculée dans les registres, vous obtiendrez une "non-égale" de retour, parce qu'une valeur a été tronquée tandis que l'autre ne l'était pas. Donc cela n'a rien à voir avec VM/no VM cela dépend juste du code que le compilateur génère qui peut facilement fluctuer comme nous le voyons là-bas.
ajoutez-le à la liste croissante de surprises en virgule flottante..
je peux confirmer le même comportement de votre code non-VM, mais comme je n'ai pas de VM, je n'ai pas testé la partie VM.
cependant, le compilateur, à la fois Clang et GCC évaluera l'expression constante au moment de la compilation. Voir la sortie d'assemblage ci-dessous (en utilisant 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 "151900920", %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
il semble que vous comprenez l'assemblée, mais il est clair qu'il n'y a que la chaîne" égal", il n'y a pas de"pas égal". Donc la comparaison n'est même pas fait à l'Heure de départ, il imprime juste "égal".
j'essaierais de coder le calcul et la comparaison en utilisant l'assemblage et voir si vous avez le même comportement. Si vous avez un comportement différent sur la VM, alors c'est la façon dont la VM fait le calcul.
mise à jour 1: (basé sur la" mise à jour 2 " dans la question originale). Vous trouverez ci-dessous l'assemblage de sortie gcc -O0 -S test.cpp
(pour une architecture 64 bits). Vous pouvez y voir la ligne movabsq 07182418800017408, %rax
à deux reprises. Ce sera pour les deux options de comparaison, Je n'ai pas vérifié, mais je présume que la valeur $ 4607182418800017408 est 1,1 en termes de virgule flottante. Il serait intéressant de compiler ceci sur la VM, si vous obtenez le même résultat (deux lignes similaires) alors la VM fera quelque chose de drôle à l'exécution, sinon c'est une combinaison de VM et de compilateur.
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq , %rsp
movabsq 07182418800017408, %rax
movq %rax, -16(%rbp)
movabsq 07182418800017408, %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
je vois que vous avez ajouté une autre question:
Note: la norme C++ dit que la promotion de type dans ces situations dépend de la mise en œuvre, se pourrait - il que GCC utilise une représentation interne plus précise que lorsque le test d'inégalité est appliqué est vraie-en raison de la précision supplémentaire?
La réponse à cette question est non. 1.1
n'est pas exactement représentable dans un format binaire, peu importe le nombre de bits de format. Vous pouvez vous rapprocher, mais pas avec un nombre infini de zéros après le .1
.
ou voulez-vous dire un format interne entièrement nouveau pour les décimales? Non, je refuse de croire que. Ce ne serait pas très compatible si c'était le cas.