2008-09-18 34 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的工作方式開展工作。