调试/发布模式下的浮点/双精度

时间:2020-03-06 14:20:18  来源:igfitidea点击:

在调试模式和发布模式之间,C#/。NET浮点运算的精度是否有所不同?

解决方案

它们应该是相同的。浮点数基于IEEE_754标准。

实际上,如果调试模式使用x87 FPU,而释放模式使用SSE进行浮点运算,则它们可能会有所不同。

为了回应弗兰克·克鲁格(Frank Krueger)的上述要求(在评论中),以证明两者之间的区别:

在没有优化和-mfpmath = 387的情况下在gcc中编译此代码(我没有理由认为它不能在其他编译器上运行,但是我没有尝试过。)
然后,不进行任何优化和-msse -mfpmath = sse进行编译。

输出将有所不同。

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

谢谢大家,我发现了几篇文章,这些文章说在发行模式下浮动的行为会有所不同

http://blogs.msdn.com/davidnotario/archive/2005/08/08/449092.aspx

它们确实可以有所不同。根据CLR ECMA规范:

Storage locations for floating-point
  numbers (statics, array elements, and
  fields of classes) are of fixed size.
  The supported storage sizes are
  float32 and float64. Everywhere else
  (on the evaluation stack, as
  arguments, as return types, and as
  local variables) floating-point
  numbers are represented using an
  internal floating-point type. In each
  such instance, the nominal type of the
  variable or expression is either R4 or
  R8, but its value can be represented
  internally with additional range
  and/or precision.  The size of the
  internal floating-point representation
  is implementation-dependent, can vary,
  and shall have precision at least as
  great as that of the variable or
  expression being represented. An
  implicit widening conversion to the
  internal representation from float32
  or float64 is performed when those
  types are loaded from storage. The
  internal representation is typically
  the native size for the hardware, or
  as required for efficient
  implementation of an operation.

这基本上意味着以下比较可能相等或者不相等:

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

提示:永远不要检查浮点值是否相等。

这是一个有趣的问题,所以我做了一些实验。我使用了以下代码:

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

使用DevStudio 2005和.Net2. 我将其编译为调试和发布版本,并检查了编译器的输出:

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              
                                                              }

上面显示的是,调试和发行版的浮点代码都是相同的,编译器选择一致性而不是优化。尽管程序产生错误的结果(a * a不小于b),但无论调试/释放模式如何,它都是相同的。

现在,Intel IA32 FPU具有八个浮点寄存器,我们会认为编译器在优化时将使用寄存器来存储值,而不是写入内存,从而提高了性能,大致如下:

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

但这将与调试版本执行不同(在这种情况下,显示正确的结果)。但是,根据构建选项更改程序的行为是一件很糟糕的事情。

FPU代码是手工编写代码可以大大胜过编译器的一个领域,但是我们确实需要掌握FPU的工作方式。