@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
に、宣言で指定する必要がありました。ControlFPS
2 番目に、安全でないコードを使用して .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 で何が起こっているかを判断するには、上記のサンプル アプリを各マシンで実行します。次のいずれかが見つかると思います。
- マシン A とマシン B は、最初から _controlfp_s の設定が異なっています。サンプル アプリでは、マシン A の出力の最初のブロックで、マシン B とは異なるコントロール ワード値が表示されます。アプリが Denormal コントロールを強制的に SAVE にすると、出力は一致するはずです。この場合、アプリケーションの起動時にマシン B で非正規制御を強制的に保存することができます。
- マシン A とマシン B は _controlfp_s に同じ設定を使用しており、サンプル アプリの出力は両方のマシンでまったく同じです。その場合、マシン A ではなくマシン B で _controlfp_s 設定を反転しているアプリケーション (おそらく DirectX、WPF?) にコードが含まれている必要があります。
各マシンでサンプル アプリを試す機会があれば、結果をコメントで更新してください。どうなるか興味があります。