Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can something in C# change float comparison behaviour at runtime? [x64]

Tags:

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)

like image 976
Samuel Avatar asked Jan 17 '14 07:01

Samuel


People also ask

Can operator be used with C?

C language supports a rich set of built-in operators. An operator is a special symbol that tells the compiler to perform specific mathematical or logical operations. Operators in programming languages are taken from mathematics.

Can you use += in C?

C programming has two operators increment ++ and decrement -- to change the value of an operand (constant or variable) by 1. Increment ++ increases the value by 1 whereas decrement -- decreases the value by 1.

What does /= in C mean?

/= Divide AND assignment operator. It divides the left operand with the right operand and assigns the result to the left operand.

What does := mean in C?

:= is not a valid operator in C. It does however have use in other languages, for example ALGOL 68. Basically, for what you want to know, the := in this example is used to assign the variable PTW32_TRUE to localPty->wNodeptr->spin.


2 Answers

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.

like image 145
Henrik Avatar answered Dec 05 '22 09:12

Henrik


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.

like image 29
Damien_The_Unbeliever Avatar answered Dec 05 '22 08:12

Damien_The_Unbeliever