2008-09-18 7 views
12

Les opérations en virgule flottante C#/.NET diffèrent-elles en précision entre le mode débogage et le mode déverrouillage?Float/double précision dans les modes débogage/déverrouillage

+0

Pourquoi pensez-vous qu'ils diffèrent? –

+0

Oui, je suis intéressé à découvrir votre processus de pensée aussi. –

+0

La question concerne la différence entre le débogage et la libération. On pourrait penser que la version de sortie utiliserait des registres plutôt que de la RAM qui serait plus précise: FPU = 80bit, double = 64bit, float = 32bit. – Skizz

Répondre

21

Ils peuvent en effet être différent. Selon la spécification ECMA CLR:

emplacements de stockage pour virgule flottante nombres (statique, éléments de tableau, et domaines de classes) sont de taille fixe. Les tailles de stockage prises en charge sont float32 et float64. Partout ailleurs (sur la pile d'évaluation, en tant qu'arguments , en tant que types de retour, et en tant que variables locales ) les nombres en virgule flottante sont représentés en utilisant un type à virgule flottante interne . Dans chaque exemple , le type nominal de la variable ou expression est R4 ou R8, mais sa valeur peut être représentée en interne avec plage supplémentaire et/ou précision. La taille de la représentation interne à virgule flottante dépend de l'implémentation, peut varier, et doit avoir une précision au moins égale à comme celle de l'expression variable ou étant représentée. Une conversion d'élargissement implicite à la représentation interne de float32 ou float64 est effectuée lorsque ces types sont chargés depuis le stockage. La représentation interne est généralement la taille native pour le matériel, ou comme requis pour la mise en œuvre efficace d'une opération.

Ce que cela signifie essentiellement est que la comparaison suivante peut ou peut ne pas être égale:

class Foo 
{ 
    double _v = ...; 

    void Bar() 
    { 
    double v = _v; 

    if(v == _v) 
    { 
     // Code may or may not execute here. 
     // _v is 64-bit. 
     // v could be either 64-bit (debug) or 80-bit (release) or something else (future?). 
    } 
    } 
} 

message à emporter: ne jamais vérifier les valeurs flottantes pour l'égalité.

+0

Cela n'a rien à voir avec les configurations de construction DEBUG vs RELEASE ... –

+2

L'IL généré sera le même ... mais le JITter est moins agressif lorsqu'il s'agit d'un assemblage marqué comme débogage. Les versions release ont tendance à déplacer plus de valeurs flottantes dans des registres de 80 bits; Les versions de débogage ont tendance à lire directement à partir du stockage de mémoire 64 bits. – stusmith

+0

L'IL générée peut ne pas être la même chose. Le mode débogage insère des sauts dans des endroits pour s'assurer qu'un point d'arrêt est possible, il peut également délibérément maintenir une variable temporaire que le mode de libération juge inutile. – ShuggyCoUk

2

En fait, ils peuvent différer si le mode de débogage utilise la FPU x87 et le mode de libération utilise SSE pour les opérations flottantes.

+1

Avez-vous une référence ou une démonstration faisant autorité? –

0

En réponse à la demande de Frank Krueger ci-dessus (dans les commentaires) pour une démonstration d'une différence:

Compile ce code dans gcc sans optimisations et -mfpmath = 387 (je n'ai aucune raison de penser que ce ne serait pas travailler sur d'autres compilateurs, mais je ne l'ai pas essayé.) Puis le compiler sans optimisations et -msse -mfpmath = sse.

La sortie sera différente.

#include <stdio.h> 

int main() 
{ 
    float e = 0.000000001; 
    float f[3] = {33810340466158.90625,276553805316035.1875,10413022032824338432.0}; 
    f[0] = pow(f[0],2-e); f[1] = pow(f[1],2+e); f[2] = pow(f[2],-2-e); 
    printf("%s\n",f); 
    return 0; 
} 
+1

La question portait sur C# /. Net; Votre exemple est pour C++/code natif. –

+1

D'une manière ou d'une autre, je doute que la précision de SSE vs x87 FPU diffère en fonction de la langue que vous l'appelez! –

+0

Une traduction directe en C# présente également des résultats différents pour les x86 et x64 dans Visual Studio 2013. Notez que le CLR x86 utilise x87 FPU alors que le CLR x64 utilise SSE. – Asik

11

C'est une question intéressante, j'ai donc fait un peu d'expérimentation.J'ai utilisé ce code:

static void Main (string [] args) 
{ 
    float 
    a = float.MaxValue/3.0f, 
    b = a * a; 

    if (a * a < b) 
    { 
    Console.WriteLine ("Less"); 
    } 
    else 
    { 
    Console.WriteLine ("GreaterEqual"); 
    } 
} 

utilisant DevStudio 2005 et .Net 2. Je compilé à la fois comme le débogage et la libération et la sortie du examinai du compilateur:

Release             Debug 

    static void Main (string [] args)      static void Main (string [] args) 
    {              { 
                 00000000 push  ebp 
                 00000001 mov   ebp,esp 
                 00000003 push  edi 
                 00000004 push  esi 
                 00000005 push  ebx 
                 00000006 sub   esp,3Ch 
                 00000009 xor   eax,eax 
                 0000000b mov   dword ptr [ebp-10h],eax 
                 0000000e xor   eax,eax 
                 00000010 mov   dword ptr [ebp-1Ch],eax 
                 00000013 mov   dword ptr [ebp-3Ch],ecx 
                 00000016 cmp   dword ptr ds:[00A2853Ch],0 
                 0000001d je   00000024 
                 0000001f call  793B716F 
                 00000024 fldz    
                 00000026 fstp  dword ptr [ebp-40h] 
                 00000029 fldz    
                 0000002b fstp  dword ptr [ebp-44h] 
                 0000002e xor   esi,esi 
                 00000030 nop    
     float              float 
     a = float.MaxValue/3.0f,        a = float.MaxValue/3.0f, 
00000000 sub   esp,0Ch       00000031 mov   dword ptr [ebp-40h],7EAAAAAAh 
00000003 mov   dword ptr [esp],ecx     
00000006 cmp   dword ptr ds:[00A2853Ch],0   
0000000d je   00000014        
0000000f call  793B716F        
00000014 fldz            
00000016 fstp  dword ptr [esp+4]      
0000001a fldz            
0000001c fstp  dword ptr [esp+8]      
00000020 mov   dword ptr [esp+4],7EAAAAAAh   
     b = a * a;            b = a * a; 
00000028 fld   dword ptr [esp+4]     00000038 fld   dword ptr [ebp-40h] 
0000002c fmul  st,st(0)       0000003b fmul  st,st(0) 
0000002e fstp  dword ptr [esp+8]     0000003d fstp  dword ptr [ebp-44h] 

     if (a * a < b)           if (a * a < b) 
00000032 fld   dword ptr [esp+4]     00000040 fld   dword ptr [ebp-40h] 
00000036 fmul  st,st(0)       00000043 fmul  st,st(0) 
00000038 fld   dword ptr [esp+8]     00000045 fld   dword ptr [ebp-44h] 
0000003c fcomip  st,st(1)       00000048 fcomip  st,st(1) 
0000003e fstp  st(0)        0000004a fstp  st(0) 
00000040 jp   00000054       0000004c jp   00000052 
00000042 jbe   00000054       0000004e ja   00000056 
                 00000050 jmp   00000052 
                 00000052 xor   eax,eax 
                 00000054 jmp   0000005B 
                 00000056 mov   eax,1 
                 0000005b test  eax,eax 
                 0000005d sete  al 
                 00000060 movzx  eax,al 
                 00000063 mov   esi,eax 
                 00000065 test  esi,esi 
                 00000067 jne   0000007A 
     {               { 
     Console.WriteLine ("Less");      00000069 nop    
00000044 mov   ecx,dword ptr ds:[0239307Ch]    Console.WriteLine ("Less"); 
0000004a call  78678B7C       0000006a mov   ecx,dword ptr ds:[0239307Ch] 
0000004f nop           00000070 call  78678B7C 
00000050 add   esp,0Ch       00000075 nop    
00000053 ret             } 
     }             00000076 nop    
     else            00000077 nop    
     {             00000078 jmp   00000088 
     Console.WriteLine ("GreaterEqual");      else 
00000054 mov   ecx,dword ptr ds:[02393080h]    { 
0000005a call  78678B7C       0000007a nop    
     }               Console.WriteLine ("GreaterEqual"); 
    }             0000007b mov   ecx,dword ptr ds:[02393080h] 
                 00000081 call  78678B7C 
                 00000086 nop    
                   } 

Ce que les spectacles ci-dessus est que le flottant Le code de point est le même pour le débogage et la publication, le compilateur choisit la cohérence plutôt que l'optimisation. Bien que le programme produise le mauvais résultat (a * a n'est pas inférieur à b), il est le même quel que soit le mode de débogage/relâchement.

Maintenant, Intel IA32 FPU dispose de huit registres à virgule flottante, on pourrait penser que le compilateur utiliserait les registres pour stocker des valeurs lors de l'optimisation plutôt que d'écrire à la mémoire, améliorant ainsi la performance, quelque chose le long des lignes de:

fld   dword ptr [a] ; precomputed value stored in ram == float.MaxValue/3.0f 
fmul  st,st(0) ; b = a * a 
; no store to ram, keep b in FPU 
fld   dword ptr [a] 
fmul  st,st(0) 
fcomi  st,st(0) ; a*a compared to b 

mais cela s'exécuterait différemment à la version de débogage (dans ce cas, afficher le résultat correct). Cependant, changer le comportement du programme en fonction des options de compilation est une très mauvaise chose.

Le code FPU est l'un des domaines où l'élaboration manuelle du code peut surpasser de manière significative le compilateur, mais vous avez besoin de comprendre le fonctionnement du FPU.

Questions connexes