Asked by:
Math.Atan2 wrong
Question

Hello
.NET 4.5.1
Unit test gives different numerical result for debug build if debugger attached or not.
<TestMethod()> _
Public Sub Angle()
'Atan2 behaves very strangely on some vectors, depending on whether the debugger is attached or not!!!!
Dim pt1 As Double() = New Double() {0, 1}
Dim pt2 As Double() = New Double() {0, 92}
'Always correct (= 0)
Dim angle As Double = Math.Atan2(pt2(1), pt2(0))  Math.Atan2(pt1(1), pt1(0))
Assert.IsTrue(angle = 0, "Angle not zero but " & angle & "(inline version)")'Only correct with debugger attached, 6.125E17 without debugger
angle = pt1.Angle(pt2)
Assert.IsTrue(angle = 0, "Angle not zero but " & angle & "( library extension version)")End Sub
<Extension()> Public Function Angle(ByVal pt1 As Double(), ByVal pt2 As Double()) As Double
Return Math.Atan2(pt2(1), pt2(0))  Math.Atan2(pt1(1), pt1(0))
End FunctionExtension method resolution problem seems unliky, putting debug.writeline in extension shows it's always hit ...
Any suggestions???????
 Edited by yvdh Friday, January 23, 2015 5:10 PM
All replies

Extremely weird. I can reproduce this. There seems to be an FPU state problem with Atan2 on x86. You can poke the state back to normal by computing something else. But not just anything. For example Atan will kick it back to normal, but Sqrt will not.
[TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { bool c = Math.Atan2( 1, 0 ) == Math.Atan2( 1, 0 ); Assert.IsTrue( c ); // FAILS } [TestMethod] public void TestMethod2() { double a = Math.Atan2( 1, 0 ); double b = Math.Atan2( 1, 0 ); double c = 0; Assert.IsTrue( a == b ); // FAILS } [TestMethod] public void TestMethod3() { double a = Math.Atan2( 1, 0 ); double b = Math.Atan2( 1, 0 ); double c = Math.Sqrt( 1.0 ); Assert.IsTrue( a == b ); // FAILS Assert.IsTrue( c > 0.0 ); } [TestMethod] public void TestMethod4() { double a = Math.Atan2( 1, 0 ); double b = Math.Atan2( 1, 0 ); double c = Math.Atan( 1.0 ); Assert.IsTrue( a == b ); // PASSES Assert.IsTrue( c > 0.0 ); } }
Tested on Visual Studio 2013: 12.0.31101.00 Update 4
Confirmed that this happens in Release mode builds in the unit test framework only.

You might have to report this one over at connect.microsoft.com


The results are not weird. It is very common. The release version of the code uses the MicroProcessor built in Multiply while the debug code does a software simulation. The difference of E17 is a very small number.
jdweng

@jdweng
It's still wrong: zero is zero! Any Atan2(y > 0, 0) should be exactly pi/2, period. Moreover, the difference between those 2 should be 0. So even if pi/2 is not computed exactly correctly (it's a transcendental number after all), the difference should be zero.
Also, your explanation does not fly as the error happens in DEBUG builds, but with the debugger NOT attached.




Zero is not Zero, just like infinity is not always infinity. One over infinity is close to zero but not exactly zero. The larger the infinity the closer one over infinity is to zero, but it is never zero.
jdweng
Let's leave infinity out of this. And in general, stop mixing mathematics and C++. π is not precisely representable as a double precision floating point number; neither is ⅓. I think that's a given and generally well appreciated.
I don't care if Atan2(1,0) equals Atan2(92,0). They may arrive at different approximations of pi/2 due to whatever implementation Atan2 uses to arrive at its result. But whatever Atan2(1,0) equals...it should equal itself. Because if I call Atan2 with the same arguments, I should get the same result. And that number is some reasonable positive number (not an infinity) that should be pretty close to +1.5707963267948966192313216916398  not infinity or zero.
But I have a big problem if I can write code where Atan2(1,0) doesn't equal Atan2(1,0) If the arguments are the same, the results should be the same, and furthermore, because it's a number (not a Nan, or infinity or anything weird) they should be equal by comparison, whatever number Atan2 returns. This should work in Debug mode, or Release mode alike, and regardless of whether the debugger is attached.
I still think that either something is going on with FPU state that affects precision, or there's a compiler bug.
@Joel Engineer, I think you have strayed very far from the issue here.

@jdweng
1. This is missing the point: why difference when debugger attached or not is the question.
2. You need to review your maths ....: 1/ inf = 0 by convention, and zero is definitively zero!!!! Do not confuse this with limits
Yves
I think Joel's point here was just that you should always be careful when comparing equality of floating point numbers in code. Instead you should always check that the difference of the two values are within some very small number.
You say that 1/inf=0 "by convention" and 0 is definitely 0. Obviously 0=0 is true in real life and algebra, but not necessarily true in computational maths when the result (i.e. 0) has been arrived at via any form of calculation. This is due to the imperfect way that computers represent floating point numbers internally (meaning errors tend to accumulate).
Having said that, your example should logically work as I would expect the same calculation to come up with the exact same result (even if not exactly 0), and its a surprise to me that the debugger and release builds have different results  I never knew that the debugger runs software simulation as Joel claims! So, as Wyck suggests, report this to Microsoft and see what they say.

I never knew that the debugger runs software simulation as Joel claims!
Yeah, me neither. I still don't believe it does. I'm of the belief that the IL gets JIT compiled and evaluated natively one way or the other.
For example, it's easy to get direct evidence that contradicts the claim that multiply uses a simulation:
Behold the disassembly: fmul

Hi there,
like a said, pi is a transcendental number and cannot be represented exactly. It should however be deterministic and always return the same number (even if slightly wrong). Regarding Atan2, normally no series expansion or other approximations (other than for pi, and that should be fixed) should be used for cases where x = 0. The result is sign(y) * Pi / 2 (for y <> 0) and that should always be the same, whether y = 1 or y = 91. Worse still, I can reproduce the error using the same y, i.e. atan2(1,0)  atan(1,0) <> 0. That is completely absurd, by any reckoning!
Yves

