32

走っていることがわかりました

Math.Log10(double.Epsilon) 

-324マシン Aについては戻りますが-Infinity、マシン B では戻ります。

彼らは元々、 を返すことで同じように振る舞いました-324

どちらのマシンも、最初は同じ OS (WinXP SP3) と .NET バージョン (3.5 SP1) を使用していました。マシン B で Windows の更新が行われた可能性がありますが、それ以外の変更は発生していません。

行動の違いを説明できるものは何ですか?

コメントでの議論からの詳細:

  • マシン A の CPU は 32 ビット Intel Core Duo T2500 2 GHz
  • マシン B の CPU は 32 ビット Intel P4 2.4 GHz
  • 複数のサードパーティ コンポーネントを使用して、大規模なアプリケーションで実行されているコードから収集された結果。ただし、同じ .exe およびコンポーネント バージョンが両方のマシンで実行されています。
  • Math.Log10(double.Epsilon)マシン B の単純なコンソール アプリケーションで印刷すると、-324ではなく が印刷されます。-Infinity
  • 両方のマシンの FPU コントロール ワードは常に0x9001F( で読み取られます_controlfp()) です。

更新: 最後のポイント (FPU コントロール ワード) はもはや真実ではありません: _controlfp() の新しいバージョンを使用すると、異なるコントロール ワードが明らかになり、一貫性のない動作が説明されました。(詳細については、以下の rsbarro の回答を参照してください。)

4

2 に答える 2

22

@CodeInChaos と @Alexandre C のコメントに基づいて、自分の PC (Win7 x64、.NET 4.0) で問題を再現するコードをまとめることができました。この問題は、_controlfp_sを使用して設定できる非正規化制御が原因のようです。double.Epsilon の値はどちらも同じですが、デノーマル制御が SAVE から FLUSH に切り替わると評価の仕方が変わります。

サンプルコードは次のとおりです。

using System;
using System.Runtime.InteropServices;

namespace fpuconsole
{
    class Program
    {
        [DllImport("msvcrt.dll", EntryPoint = "_controlfp_s",
            CallingConvention = CallingConvention.Cdecl)]
        public static extern int ControlFPS(IntPtr currentControl, 
            uint newControl, uint mask);

        public const int MCW_DN= 0x03000000;
        public const int _DN_SAVE = 0x00000000;
        public const int _DN_FLUSH = 0x01000000;

        static void PrintLog10()
        {
            //Display original values
            Console.WriteLine("_controlfp_s Denormal Control untouched");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}",
                Math.Log10(double.Epsilon));
            Console.WriteLine("");

            //Set Denormal to Save, calculate Math.Log10(double.Epsilon)
            var controlWord = new UIntPtr();
            var err = ControlFPS(controlWord, _DN_SAVE, MCW_DN);
            if (err != 0)
            {
                Console.WriteLine("Error setting _controlfp_s: {0}", err);
                return;
            }
            Console.WriteLine("_controlfp_s Denormal Control set to SAVE");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}", 
                Math.Log10(double.Epsilon));
            Console.WriteLine("");

            //Set Denormal to Flush, calculate Math.Log10(double.Epsilon)
            err = ControlFPS(controlWord, _DN_FLUSH, MCW_DN);
            if (err != 0)
            {
                Console.WriteLine("Error setting _controlfp_s: {0}", err);
                return;
            }
            Console.WriteLine("_controlfp_s Denormal Control set to FLUSH");
            Console.WriteLine("\tCurrent _controlfp_s control word: 0x{0:X8}", 
                GetCurrentControlWord());
            Console.WriteLine("\tdouble.Epsilon = {0}", double.Epsilon);
            Console.WriteLine("\tMath.Log10(double.Epsilon) = {0}", 
                Math.Log10(double.Epsilon));
            Console.WriteLine("");
        }

        static int GetCurrentControlWord()
        {
            unsafe
            {
                var controlWord = 0;
                var controlWordPtr = &controlWord;
                ControlFPS((IntPtr)controlWordPtr, 0, 0);
                return controlWord;
            }
        }

        static void Main(string[] args)
        {
            PrintLog10();
        }
    }
}

注意すべき点がいくつかあります。まず、デバッグ中にアンバランス スタック例外が発生しないようCallingConvention = CallingConvention.Cdeclに、宣言で指定する必要がありました。ControlFPS2 番目に、安全でないコードを使用して .xml のコントロール ワードの値を取得する必要がありましたGetCurrentControlWord()。誰かがそのメソッドを書くためのより良い方法を知っているなら、私に知らせてください。

出力は次のとおりです。

_controlfp_s Denormal Control untouched
        Current _controlfp_s control word: 0x0009001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -323.306215343116

_controlfp_s Denormal Control set to SAVE
        Current _controlfp_s control word: 0x0009001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -323.306215343116

_controlfp_s Denormal Control set to FLUSH
        Current _controlfp_s control word: 0x0109001F
        double.Epsilon = 4.94065645841247E-324
        Math.Log10(double.Epsilon) = -Infinity

マシン A とマシン B で何が起こっているかを判断するには、上記のサンプル アプリを各マシンで実行します。次のいずれかが見つかると思います。

  1. マシン A とマシン B は、最初から _controlfp_s の設定が異なっています。サンプル アプリでは、マシン A の出力の最初のブロックで、マシン B とは異なるコントロール ワード値が表示されます。アプリが Denormal コントロールを強制的に SAVE にすると、出力は一致するはずです。この場合、アプリケーションの起動時にマシン B で非正規制御を強制的に保存することができます。
  2. マシン A とマシン B は _controlfp_s に同じ設定を使用しており、サンプル アプリの出力は両方のマシンでまったく同じです。その場合、マシン A ではなくマシン B で _controlfp_s 設定を反転しているアプリケーション (おそらく DirectX、WPF?) にコードが含まれている必要があります。

各マシンでサンプル アプリを試す機会があれば、結果をコメントで更新してください。どうなるか興味があります。

于 2011-08-09T15:05:13.927 に答える
8

x87 浮動小数点フラグをいじったプロセスに dll がロードされた可能性があります。DirectX/OpenGL 関連のライブラリはこれで有名です。

jitted コードにも違いがある可能性があります (浮動小数点が .net で特定の方法で動作する必要はありません) が、同じ .net と OS バージョンを使用しているため、その可能性はほとんどありません。

.net では、定数が呼び出しコードに組み込まれるため、double.Epsilons.

于 2011-08-09T09:27:27.400 に答える