none
Math.Atan2 wrong RRS feed

  • 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.125E-17 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 Function

    Extension 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
    Friday, January 23, 2015 5:07 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.

    Friday, January 23, 2015 8:24 PM
  • You might have to report this one over at connect.microsoft.com
    Friday, January 23, 2015 8:27 PM
  • 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 E-17 is a very small number.


    jdweng

    Friday, January 23, 2015 10:18 PM
  • 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 E-17 is a very small number.


    jdweng

    Makes me wonder why the so-called "software simulation" is not the same as the processor's multiply?  You still see what I called "weird" as undesirable, though, right?
    Sunday, January 25, 2015 2:37 AM
  • @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.


    Monday, January 26, 2015 3:46 PM
  • 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

    Monday, January 26, 2015 3:50 PM
  • @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

    Monday, January 26, 2015 4:56 PM
  • One is using the microprocessor to perform the math and one is performing the math in software.

    jdweng

    Monday, January 26, 2015 5:06 PM
  • 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.

    Monday, January 26, 2015 5:09 PM
  • @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.

    Monday, January 26, 2015 5:11 PM
  •  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

    Monday, January 26, 2015 5:34 PM
  • 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

    Tuesday, January 27, 2015 1:08 PM
  • Worse still, I can reproduce the error using the same y, i.e. atan2(1,0) - atan(1,0) <> 0.

    That may be worth editing your post to correct.  I think you meant:  atan2(1,0) - atan2(1,0) <> 0

    Tuesday, January 27, 2015 2:54 PM
  • So, did you report it at connect.microsoft.com yet?
    Tuesday, February 17, 2015 8:46 PM