Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guarantees concerning Math.atan2

Tags:

java

math

The documentation for Math.atan2 says

The computed result must be within 2 ulps of the exact result.

The fact that it says 2 ulps presumably means there are cases where the returned value is not the closest double to the true result. Does anyone know if it is guaranteed to return the same value for equivalent pairs of int parameters? In other words, if a, b and k are positive int values and neither a * k nor b * k overflows, is it guaranteed that

Math.atan2(a, b) == Math.atan2(a * k, b * k) 

Edit

Note that this is definitely not the case for non-overflowing long multiplications. For example

long a = 959786689;
long b = 363236985;
long k = 9675271;
System.out.println(Math.atan2(a, b));
System.out.println(Math.atan2(a * k, b * k));

prints

1.2089992287797169
1.208999228779717

but I could not find an example in int values.

like image 260
Paul Boddington Avatar asked May 20 '16 20:05

Paul Boddington


People also ask

What is atan2 in math?

The Math. atan2() method measures the counterclockwise angle θ, in radians, between the positive x-axis and the point (x, y) . Note that the arguments to this function pass the y-coordinate first and the x-coordinate second.

Can atan2 return negative?

A positive result represents a counterclockwise angle from the x-axis; a negative result represents a clockwise angle. ATAN2(a,b) equals ATAN(b/a), except that a can equal 0 in ATAN2.

What is atan2 C#?

Atan2() is an inbuilt Math class method which returns the angle whose tangent is the quotient of two specified numbers. Basically, it returns an angle θ (measured in radian) whose value lies between -π and π. This is a counterclockwise angle lies between the positive x-axis, and the point (x, y).

How does atan2 work java?

The Java Math atan2() method converts the specified rectangular coordinates (x, y) into polar coordinates (r, θ) and returns the angle theta (θ). Here, atan2() is a static method. Hence, we are accessing the method using the class name, Math .


2 Answers

Does anyone know if it is guaranteed to return the same value for equivalent pairs of int parameters?

Simply put, no. The Math documentation is the source of truth, and it provides no guarantee beyond the 2 ulp limit you reference. This is by design (as we'll see below), therefore any other source is either exposing an implementation detail or simply wrong.

Attempting to find lower bounds heuristically is impractical, since the behavior of Math is documented to be platform-specific:

Unlike some of the numeric methods of class StrictMath, all implementations of the equivalent functions of class Math are not defined to return the bit-for-bit same results. This relaxation permits better-performing implementations where strict reproducibility is not required.

Therefore even if you see tighter bounds in your tests there is no reason to believe these bounds are portable across platforms, processors, or Java versions.

However, as Math's documentation notes, StrictMath has more explicit behavior. StrictMath is documented to perform consistently across platforms, and is expected to have the same behavior as the reference implementation fdlibm. That project's readme notes:

FDLIBM is intended to provide a reasonably portable ... reference quality (below one ulp for major functions like sin,cos,exp,log) math library.

You can reference the source code for atan2 and determine precise bounds by examining its implementation; any other implementations of StrictMath.atan2() are required to give the same results as the reference implementation.

It's interesting to note that StrictMath.atan2() doesn't include the same 2 ulp comment as Math.atan2(). While it would be nice if it repeated fdlibm's "below one ulp" comment explicitly, I interpret the absence of this comment to mean StrictMath's implementation does not need to include that caveat - it will always be below one ulp.

tl;dr if you need precise results or stable results cross-platform use StrictMath. Math trades off precision for speed.

like image 174
dimo414 Avatar answered Sep 22 '22 23:09

dimo414


Edit: at first I thought this can be answered using "results must be semi-monotonic" requirement from the javadoc, but it actually can't be applied, so I re-wrote the answer.

Almost everything I can say is already covered by dimo414's answer. I just want to add: when using Math.atan2 on the same platform or even when using StrictMath.atan2 there is no formal guarantee (from the documentation) that atan2(y, x) == atan2(y * k, x * k). Sure, StrictMath's implementation actually uses y / x, so when y / x is precisely the same double value, the results will be equal (here I reasonably imply that function is deterministic), but keep it in mind.

Answering that part about int parameters: int holds 32 bits (actually, more like 31 bits plus one bit for sign) and can be represented by double type without any loss of precision, so no new problems there.


And the difference you described in the question (for non-overflowing long values) is caused by a loss of precision when converting long values to double, it has nothing to do with Math.atan2 itself, and it happens before the function is even called. double type can hold only 53 bits of mantissa, but in your case a * k requires 54 bits, so it is rounded to the nearest number a double can represent (b * k is okay though, it requires only 52 bits):

long a = 959786689;
long b = 363236985;
long k = 9675271;

System.out.println(a * k);
System.out.println((double) (a * k));
System.out.println((long) (double) (a * k));
System.out.println((long) (double) (a * k) == a * k);

System.out.println(b * k);
System.out.println((double) (b * k));
System.out.println((long) (double) (b * k));
System.out.println((long) (double) (b * k) == b * k);

Output:

9286196318267719
9.28619631826772E15
9286196318267720
false
3514416267097935
3.514416267097935E15
3514416267097935
true

And to address the example from the comment:

We have double a = 1.02551177480084, b = 1.12312341356234, k = 5;. In this case none of a, b, a * k, b * k can be represented as double without loss of precision. I'll use BigDecimal to demonstrate it, because it can show the true (not rounded) value of double:

double a = 1.02551177480084;
System.out.println("a is            " + new BigDecimal(a));
System.out.println("a * 5 is        " + new BigDecimal(a * 5));
System.out.println("a * 5 should be " + new BigDecimal(a).multiply(new BigDecimal("5")));

outputs

a is            1.0255117748008399924941613789997063577175140380859375
a * 5 is        5.12755887400420018451541182002983987331390380859375   // precision loss here
a * 5 should be 5.1275588740041999624708068949985317885875701904296875

and the difference can be clearly seen (same can be done with b instead of a).

There is a more simple test (since atan2() essentially uses a/b):

double a = 1.02551177480084, b = 1.12312341356234, k = 5;
System.out.println(a / b == (a * k) / (b * k));

outputs

false
like image 20
Roman Avatar answered Sep 19 '22 23:09

Roman