C#/.NET 浮動小数点演算はデバッグ モードとリリース モードで精度が異なりますか?
5 に答える
それらは確かに異なる可能性があります。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 より小さくない) を生成しますが、デバッグ/リリース モードに関係なく同じです。
現在、インテル 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 コードは、手作業でコードを作成した方がコンパイラよりもはるかに優れたパフォーマンスを発揮する領域の 1 つですが、FPU の動作方法について理解する必要があります。
実際、デバッグ モードが x87 FPU を使用し、リリース モードが float-ops に SSE を使用する場合、これらは異なる場合があります。
違いのデモンストレーションに対する上記のフランク・クルーガーの要求 (コメント) に応じて:
このコードを gcc で最適化なしで -mfpmath=387 でコンパイルします (他のコンパイラでは動作しないと考える理由はありませんが、試したことはありません)。次に、最適化なしで -msse -mfpmath= でコンパイルします。せ。
出力が異なります。
#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;
}