Pergunta

Do C # /. Operações NET ponto flutuante diferiram em precisão entre o modo de depuração e modo de versão?

Foi útil?

Solução

Eles podem realmente ser diferente. De acordo com a especificação CLR ECMA:

locais de armazenamento de ponto flutuante números (estática, os elementos da matriz, e campos de classes) são de tamanho fixo. Os tamanhos de armazenamento são suportados float32 e float64. Em qualquer outro lugar (Sobre a pilha de avaliação, quanto argumentos, como tipos de retorno, e como variáveis ??locais) de ponto flutuante os números são representados utilizando um interno tipo de ponto flutuante. Em cada como exemplo, o tipo nominal do variável ou expressão é qualquer um ou R4 R8, mas seu valor pode ser representado internamente com faixa adicional e / ou precisão. O tamanho do representação interna de ponto flutuante é dependente da aplicação, podem variar, e terá precisão pelo menos tão grande como o da variável ou expressão a ser representada. A conversão alargando implícita para o representação interna do float32 ou float64 é realizada quando aqueles tipos são carregados a partir do armazenamento. o representação interna é tipicamente o tamanho nativo para o hardware, ou conforme necessário para eficiente implementação de uma operação.

O que isto significa, basicamente, é que a seguinte comparação pode ou não ser igual:

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?).
    }
  }
}

Tome-home mensagem:. Nunca verificar valores flutuante para a igualdade

Outras dicas

Esta é uma pergunta interessante, então eu fiz um pouco de experimentação. Eu usei esse código:

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");
  }
}

usando DevStudio 2005 e .Net 2. Eu compilado como ambos os de depuração e liberação e examinou a saída do compilador:

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              
                                                              }

O que os shows acima é que o código de ponto flutuante é o mesmo para ambos os de depuração e liberação, o compilador é escolher consistência ao longo de otimização. Embora o programa produz o resultado errado (a * a não ser inferior a b) é o mesmo, independentemente do modo de depuração / release.

Agora, a Intel IA32 FPU tem oito registradores de ponto flutuante, você poderia pensar que o compilador iria usar os registros para armazenar valores em termos de optimização, em vez de escrever para a memória, melhorando assim o desempenho, algo ao longo das linhas 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

mas isso iria executar de forma diferente para a versão de depuração (neste caso, apresentar o resultado correto). No entanto, alterar o comportamento do programa, dependendo das opções de compilação é uma coisa muito ruim.

código FPU é uma área onde a mão elaborar o código pode significativamente realizar-se o compilador, mas você precisa para obter a sua cabeça em torno da forma como funciona o FPU.

Na verdade, eles podem ser diferentes se o modo de depuração usa o x87 FPU e modo de versão usa SSE para bóia-ops.

Em resposta ao pedido de Frank Krueger acima (nos comentários) para uma demonstração de uma diferença:

Compilar este código no gcc sem otimizações e -mfpmath = 387 (não tenho qualquer razão para pensar que não iria funcionar em outros compiladores, mas eu não tentei.) Em seguida, compilá-lo sem otimizações e -msse -mfpmath = sse.

A saída será diferente.

#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;
}

Aqui está um exemplo simples onde os resultados não só diferem entre depuração e modo de liberação, mas a maneira pela qual eles o fazem depender se alguém usa x86 ou x84 como plataforma:

Single f1 = 0.00000000002f;
Single f2 = 1 / f1;
Double d = f2;
Console.WriteLine(d);

Este escreve os seguintes resultados:

            Debug       Release
x86   49999998976   50000000199,7901
x64   49999998976   49999998976

Um rápido olhar para a desmontagem (Debug -> Windows -> Desmontagem no Visual Studio) dá algumas dicas sobre o que está acontecendo aqui. Para o caso x86:

Debug                                       Release
mov         dword ptr [ebp-40h],2DAFEBFFh | mov         dword ptr [ebp-4],2DAFEBFFh  
fld         dword ptr [ebp-40h]           | fld         dword ptr [ebp-4]   
fld1                                      | fld1
fdivrp      st(1),st                      | fdivrp      st(1),st
fstp        dword ptr [ebp-44h]           |
fld         dword ptr [ebp-44h]           |
fstp        qword ptr [ebp-4Ch]           |
fld         qword ptr [ebp-4Ch]           |
sub         esp,8                         | sub         esp,8 
fstp        qword ptr [esp]               | fstp        qword ptr [esp]
call        6B9783BC                      | call        6B9783BC

Em particular, vemos que um monte de aparentemente redundante "armazenar o valor do registrador de ponto flutuante na memória, então imediatamente carregá-lo de volta a partir da memória para o registro de ponto flutuante" foram otimizadas no modo de versão. No entanto, as duas instruções

fstp        dword ptr [ebp-44h]  
fld         dword ptr [ebp-44h]

são suficientes para alterar o valor no registo x87 de + 5.0000000199790138e + 0010 para + 4.9999998976000000e + 0010 como se pode verificar por percorrendo a desmontagem e investigar os valores dos registros relevantes (Debug -> Windows -> Registros , em seguida, clique direito e verificar "ponto flutuante").

A história de x64 é totalmente diferente. Ainda vemos a mesma otimização removendo algumas instruções, mas desta vez, tudo depende de SSE com seus registradores de 128 bits e conjunto de instruções dedicado:

Debug                                        Release
vmovss      xmm0,dword ptr [7FF7D0E104F8h] | vmovss      xmm0,dword ptr [7FF7D0E304C8h]  
vmovss      dword ptr [rbp+34h],xmm0       | vmovss      dword ptr [rbp-4],xmm0 
vmovss      xmm0,dword ptr [7FF7D0E104FCh] | vmovss      xmm0,dword ptr [7FF7D0E304CCh]
vdivss      xmm0,xmm0,dword ptr [rbp+34h]  | vdivss      xmm0,xmm0,dword ptr [rbp-4]
vmovss      dword ptr [rbp+30h],xmm0       |
vcvtss2sd   xmm0,xmm0,dword ptr [rbp+30h]  | vcvtss2sd   xmm0,xmm0,xmm0 
vmovsd      qword ptr [rbp+28h],xmm0       |
vmovsd      xmm0,qword ptr [rbp+28h]       |
call        00007FF81C9343F0               | call        00007FF81C9343F0 

Aqui, porque a unidade SSE evita usar maior precisão do que precisão simples internamente (enquanto a unidade x87 faz), acabamos com o resultado "single precisão-ish" do caso x86 independentemente de otimizações. Na verdade, encontra-se (depois de ativar os registradores SSE na visão geral do Visual Studio Registros) que depois vdivss, XMM0 contém 0000000000000000-00000000513A43B7 que é exatamente o 49999998976 de antes.

Ambos os discrepâncias me mordeu na prática. Além de ilustrar que nunca se deve comparar a igualdade de pontos flutuantes, a exemplo também mostra que ainda há espaço para a montagem de depuração em uma linguagem de alto nível, como C #, no momento em pontos flutuantes aparecer.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top