C#/ .NET浮点运算在调试模式和发布模式之间的精度是否不同?
他们确实可以是不同的.根据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?). } } }
带回家的消息:永远不要检查浮动值是否相等.
这是一个有趣的问题,所以我做了一些实验.我用过这段代码:
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),但无论调试/释放模式如何,它都是相同的.
现在,Intel IA32 FPU有8个浮点寄存器,你会认为编译器会在优化而不是写入内存时使用寄存器来存储值,从而提高性能,类似于:
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的工作方式.