13

C#/.NET 浮動小数点演算はデバッグ モードとリリース モードで精度が異なりますか?

4

5 に答える 5

22

それらは確かに異なる可能性があります。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?).
    }
  }
}

持ち帰りメッセージ:浮動値が等しいかどうかを決してチェックしないでください。

于 2008-09-18T08:01:46.570 に答える
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 には 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 の動作方法について理解する必要があります。

于 2008-09-18T08:51:15.767 に答える
2

実際、デバッグ モードが x87 FPU を使用し、リリース モードが float-ops に SSE を使用する場合、これらは異なる場合があります。

于 2008-09-18T07:48:41.127 に答える
1

違いのデモンストレーションに対する上記のフランク・クルーガーの要求 (コメント) に応じて:

このコードを 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;
}
于 2008-09-18T07:58:37.450 に答える