25

I am experiencing a very weird problem with our test code in a commercial product under Windows 7 64 bit with VS 2012 .net 4.5 compiled as 64 bit.

The following test code, when executed in a separate project behaves as expected (using a NUnit test runner):

[Test]
public void Test()
{
    float x = 0.0f;
    float y = 0.0f;
    float z = 0.0f;

    if ((x * x + y * y + z * z) < (float.Epsilon))
    {
        return;
    }
    throw new Exception("This is totally bad");
}

The test returns as the comparison with < float.Epsilon is always true for x,y and z being 0.0f.

Now here is the weird part. When I run this code in the context of our commercial product then this test fails. I know how stupid this sounds but I am getting the exception thrown. I've debugged the issue and when I evaluate the condition it is always true but the compiled executable still does not go into the true branch of the condition and throws the exception.

In the commercial product this test case only fails when my test case performs additional set-up code (designed for integration tests) where a very large system is initialized (C#, CLI and a very large C++ part). I cannot dig further in this set up call as it practically bootstraps everything.

I am not aware of anything in C# that would have influence of an evaluation.

Bonus weirdness: When I compare with less-or-equal with float.Epsilon:

if ((x * x + y * y + z * z) <= (float.Epsilon)) // this works!

then the test succeeds. I have tried comparing with only less-than and float.Epsilon*10 but this did not work:

if ((x * x + y * y + z * z) < (float.Epsilon*10)) // this doesn't!

I've been unsuccessfully googling that issue and even though posts of Eric Lippert et al. tend to go towards float.Epsilon I do not fully understand what effect is applied on my code. Is it some C# setting, does the massive native-to-managed and vice versa influences the system. Something in the CLI?

Edit: Some more things to discover: I've used GetComponentParts from this MSDN page http://msdn.microsoft.com/en-us/library/system.single.epsilon%28v=vs.110%29.aspx to visualize my mantiassa end exponents and here are the results:

Test code:

 float x = 0.0f;
 float y = 0.0f;
 float z = 0.0f;
 var res = (x*x + y*y + z*z);
 Console.WriteLine(GetComponentParts(res));
 Console.WriteLine();
 Console.WriteLine(GetComponentParts(float.Epsilon));

Without the entire boostrap chain I get (test passes)

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

1.401298E-45: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000001

With full bootstrapping chain I get (test fails)

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

Things to notice: The float.Epsilon has lost its last bit in its mantissa.

I can't see how the /fp compiler flag in C++ influences the float.Epsilon representation.


Edit and final verdict While it is possible to use a separate thread to obtain float.Epsilon it will behave different than expected on the thread with reduced FPU word.

On the reduced FPU word thread this is the output of the "thread-foreign" float.Epsilon

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000001

Note that the last mantissa bit is 1 as expected but this float value will still be interpreted as 0. This of course makes sense as we are using a precision of a float that is bigger then the FPU word set but it may be a pitfall for somebody.

I've decided to move to a machine fps that is calculated once as described here: https://stackoverflow.com/a/9393079/2416394 (ported to float, of course)

Community
  • 1
  • 1
Samuel
  • 6,126
  • 35
  • 70
  • sounds very much like an optimization error (of the compiler). Do you use any optimization flags? – Ronald Jan 17 '14 at 07:54
  • 1
    See "rounding mode" http://msdn.microsoft.com/en-us/library/e7s85ffb.aspx which links to http://msdn.microsoft.com/en-us/library/e9b52ceh.aspx – ta.speot.is Jan 17 '14 at 07:56
  • 4
    Does your setup code use DirectX? This might affect the FPU settings: http://stackoverflow.com/questions/2847906/can-floating-point-precision-be-thread-dependent – Henrik Jan 17 '14 at 08:01
  • *I can't see how the /fp compiler flag in C++ influences the float.Epsilon representation.* You asked if code can change the behavior of floating point numbers. The /fp documentation links to a page that shows you how to change *The floating-point control word enables the program to change the precision, rounding, and infinity modes in the floating-point math package, depending on the platform* – ta.speot.is Jan 17 '14 at 08:05
  • @Ronald, debug mode in both runs. I've executed the same code in the same context once with and once without the bootstrap. – Samuel Jan 17 '14 at 08:12
  • @ta.speot.is I could not synthesize the information from it that using different fp settings on native code impacts how C# and therefore the MSIL behaves when using 32 bit floating point numbers. – Samuel Jan 17 '14 at 08:14
  • @Henrik, yes the native part setups and uses direct x. However I am not sure whether this is already true without the bootstrapping. (As the entire system is still booted in both cases) – Samuel Jan 17 '14 at 08:15
  • 2
    Can you modify the setup code to set D3DCREATE_FPU_PRESERVE? Alternatively, can you execute the problematic code on a new thread? – Henrik Jan 17 '14 at 08:20
  • @Henrik, you was right. While I was not able to call D3DCREATE_FPU_PRESERVE at an appropriate place I executed the test code once on the main thread and once with Task.Factory.StartNew and indeed the thread version prints the correct float.Epsilon. If you make an answer of it I would be glad to mark it – Samuel Jan 17 '14 at 08:33

3 Answers3

11

DirectX is known to modify the FPU settings. See this related question: Can floating-point precision be thread-dependent?

You can either tell DirectX to preserve the FPU settings by specifying the D3DCREATE_FPU_PRESERVE flag when calling CreateDevice or execute your floating point code on a new thread.

Community
  • 1
  • 1
Henrik
  • 23,186
  • 6
  • 42
  • 92
  • Given neither ways are feasible (preserve FPU or execute all tests in another thread) I wonder whether a proper solution would be to calculate the machine relative epsilon like in http://stackoverflow.com/a/9393079/2416394 or try to get a float.Epsilon from a utils function that fetches it from another thread (I've tested that :) ) – Samuel Jan 17 '14 at 09:00
3

If you're getting differences between when you debug and when it runs in release mode, you may be falling foul of the following:

(From MS Partition I, 12.1.3):

Storage locations for floating-point numbers (statics, array elements, and fields of classes) are of fixed size ... Everywhere else (on the evaluation stack, as arguments, as return types, and as local variables) floating-point numbers are represented using an internal floating-point type. ... its value can be represented internally with additional range and/or precision

and,

When a floating-point value whose internal representation has greater range and/or precision than its nominal type is put in a storage location, it is automatically coerced to the type of the storage location. This can involve a loss of precision or the creation of an out-of-range value

and the final note:

[Note: The use of an internal representation that is wider than float32 or float64 can cause differences in computational results when a developer makes seemingly unrelated modifications to their code, the result of which can be that a value is spilled from the internal representation (e.g., in a register) to a location on the stack. end note]

Debugging typically causes a lot of modifications - you tend to use different optimizations and you're more likely to cause such spills.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Both runs are in debug without the "Optimize code" flag turned on and I am trying to discriminate whether its fpu setting on native part. Direct x, debug issue you described or using 32 bit floats on a 64 bit machine in a 64 bit context. – Samuel Jan 17 '14 at 08:22
2

When I create a test application containing your test, the exception is not thrown. This means it's not going to be straightforward. Some ideas to further investigate:

  • If you run this test at the start of your application (e.g. in the Main/entry routine) does it fail then?
  • If the above is true, start a new project using the same target framework & architecture which runs the same test. If it passes, start adding in bits of your primary application gradually and see if you can find which bit makes it fail.
Richard
  • 29,854
  • 11
  • 77
  • 120