调试/发布模式下的浮点/双精度
在调试模式和发布模式之间,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的工作方式。