2008-09-18 55 views
12

C#/ .NET浮点操作在调试模式和释放模式之间的精度有所不同吗?调试/释放模式下的浮点/双精度

+0

你为什么认为他们不同? – 2008-09-18 07:44:26

+0

是的,我有兴趣了解你的思维过程。 – 2008-09-18 07:50:16

+0

问题是关于调试和发布之间的区别。你会认为发布版本会使用寄存器而不是RAM,这会更高精度:FPU = 80bit,double = 64bit,float = 32bit。 – Skizz 2008-09-18 09:03:12

回答

21

他们确实可以是不同的不同。根据CLR ECMA规范:

存储位置浮点 数字(静力学,数组元素,和类的 字段)是固定大小。 支持的存储大小为 float32和float64。其他地方 (在评估堆栈上,如 自变量,返回类型和 局部变量)浮点数 使用内部浮点类型 表示。在每个 这样的实例中,变量或表达式的标称类型是R4或 R8,但其值可以在内部表示为 并且附加范围为 和/或精度。 内部浮点表示 的大小取决于实现,可能会有所不同,并且其精度应至少如表示的变量或 表达式那样大。 从float32 或float64的内部表示的隐式扩展转换为 类型从存储中加载时执行。 内部表示通常为 硬件的本地大小,或 执行操作所需的 。

什么这基本上意味着,下面的对比可能会或可能不等于:

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

拿回家的消息:从不检查平等的浮动值。

2

事实上,如果调试模式使用x87 FPU并且发布模式将SSE用于float-ops,则它们可能会有所不同。

+1

你有权威的参考或示范? – 2008-09-18 07:52:43

0

针对上述(在评论)弗兰克·克鲁格的请求差的示范:

编译GCC这个代码没有优化和-mfpmath = 387(我没有理由认为它不会在其他编译器上工作,但我没有尝试过。) 然后在不进行优化的情况下进行编译,并使用-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; 
} 
11

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

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和.Net 2.我编译为调试版本和发布,并检查了编译器的输出:

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),但不管调试/释放模式如何,它都是相同的。现在

,英特尔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的工作方式开展工作。