I found that running
Math.Log10(double.Epsilon)
will return about -324
on machine A, but will return -Infinity
on machine B.
They originally behaved the same way by returning -324
.
Both machines started out using the same OS (WinXP SP3) and .NET version (3.5 SP1). There may have been Windows updates on machine B, but otherwise no changes are known to have happened.
What could explain the difference in behavior?
More details from discussions in comments:
Math.Log10(double.Epsilon)
in a simple console application on machine B prints -324
, NOT -Infinity
0x9001F
(read with _controlfp()
).UPDATE: The last point (FPU control word) is no longer true: Using a newer version of _controlfp() revealed different control words, which explains the inconsistent behavior. (See rsbarro's answer below for details.)
Based on the comments by @CodeInChaos and @Alexandre C, I was able to throw together some code to reproduce the issue on my PC (Win7 x64, .NET 4.0). It appears this issue is due to the denormal control that can be set using _controlfp_s. The value of double.Epsilon is the same in both cases, but the way it is evaluated changes when the denormal control is switched from SAVE to FLUSH.
Here is the sample code:
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(); } } }
A couple things to note. First, I had to specify CallingConvention = CallingConvention.Cdecl
on the ControlFPS
declaration to avoid getting an unbalanced stack exception while debugging. Second, I had to resort to unsafe code to retrieve the value of the control word in GetCurrentControlWord()
. If anyone knows of a better way to write that method, please let me know.
Here is the output:
_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
To determine what is going on with machine A and machine B, you could take the sample app above and run it on each machine. I think you're going to find that either:
If you get a chance to try out the sample app on each machine, please update the comments with the results. I'm interested to see what happens.
It's possible that a dll was loaded into the process that messed with the x87 floating-point flags. DirectX/OpenGL related libraries are notorious for this.
There could also be differences in the jitted code(There is no requirement for floating points to behave a specific way in .net), but that's very unlikely since you use the same .net and OS version.
In .net constants get baked into the calling code, so there should be no differences between the double.Epsilons
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With