MATH-494 FastMath atan2 does not agree with StrictMath for special cases

Add doubleHighPart() method to better handle splitting high absolute values
Add getSign() utility method

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1062253 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Sebastian Bazley 2011-01-22 20:19:40 +00:00
parent 3729cef21a
commit 093d8d914a
1 changed files with 65 additions and 23 deletions

View File

@ -175,10 +175,21 @@ public class FastMath {
1.2599210498948732,
1.5874010519681994 };
/*
* There are 52 bits in the mantissa of a double.
* For additional precision, the code splits double numbers into two parts,
* by clearing the low order 30 bits if possible, and then performs the arithmetic
* on each half separately.
*/
/**
* 0x40000000 - used to split a double into two parts, both with the low order bits cleared.
* Equivalent to 2^30.
*/
private static final double HEX_40000000 = 1073741824.0;
private static final long HEX_40000000 = 0x40000000L; // 1073741824L
/** Mask used to clear low order 30 bits */
private static final long MASK_30BITS = -1L - (HEX_40000000 -1); // 0xFFFFFFFFC0000000L;
/** 2^52 - double numbers this large must be integral (no fraction) or NaN or Infinite */
private static final double TWO_POWER_52 = 4503599627370496.0;
@ -233,6 +244,36 @@ public class FastMath {
private FastMath() {
}
// Generic helper methods
/**
* Get the sign information (works even for 0).
*
* @param d the value to check
*
* @return +1.0 or -1.0, never 0.0
*/
private static double getSign(double d){ // TODO perhaps move to MathUtils?
long l = Double.doubleToLongBits(d);
return l < 0 ? -1.0 : 1.0;
}
/**
* Get the high order bits from the mantissa.
* Equivalent to adding and subtracting HEX_40000 but also works for very large numbers
*
* @param d the value to split
* @return the high order part of the mantissa
*/
private static double doubleHighPart(double d) {
if (d > -MathUtils.SAFE_MIN && d < MathUtils.SAFE_MIN){
return d; // These are un-normalised - don't try to convert
}
long xl = Double.doubleToLongBits(d);
xl = xl & MASK_30BITS; // Drop low order bits
return Double.longBitsToDouble(xl);
}
/** Compute the square root of a number.
* @param a number on which evaluation is done
* @return square root of a
@ -2750,14 +2791,14 @@ public class FastMath {
* @param xa number from which arctangent is requested
* @param xb extra bits for x (may be 0.0)
* @param leftPlane if true, result angle must be put in the left half plane
* @return atan(xa + xb) (or angle shifted by &pi; if leftPlane is true)
* @return atan(xa + xb) (or angle shifted by {@code PI} if leftPlane is true)
*/
private static double atan(double xa, double xb, boolean leftPlane) {
boolean negate = false;
int idx;
if (xa == 0.0) { // Matches +/- 0.0; return correct sign
return xa;
return leftPlane ? getSign(xa) * Math.PI : xa;
}
if (xa < 0) {
@ -2914,16 +2955,12 @@ public class FastMath {
if (invx == 0.0) { // X is infinite
if (x > 0) {
return 0.0;
return y; // return +/- 0.0
} else {
return Math.PI;
return getSign(y) * Math.PI;
}
}
if (result != result) { // y must be infinite
return x/y;
}
if (x < 0.0 || invx < 0.0) {
if (y < 0.0 || invy < 0.0) {
return -Math.PI;
@ -2935,6 +2972,8 @@ public class FastMath {
}
}
// y cannot now be zero
if (y == Double.POSITIVE_INFINITY) {
if (x == Double.POSITIVE_INFINITY) {
return Math.PI/4.0;
@ -2980,6 +3019,8 @@ public class FastMath {
}
}
// Neither y nor x can be infinite or NAN here
if (x == 0) {
if (y > 0.0 || 1/y > 0.0) {
return Math.PI/2.0;
@ -2990,28 +3031,29 @@ public class FastMath {
}
}
if (x > 8e298 || x < -8e298) { // This would cause split of x to fail
x *= 9.31322574615478515625E-10;
y *= 9.31322574615478515625E-10;
// Compute ratio r = y/x
final double r = y/x;
if (Double.isInfinite(r)) { // bypass calculations that can create NaN
return atan(r, 0, x < 0);
}
// Split y
double temp = x * HEX_40000000;
final double xa = x + temp - temp;
final double xb = x - xa;
// Compute ratio r = x/y
final double r = y/x;
temp = r * HEX_40000000;
double ra = r + temp - temp;
double ra = doubleHighPart(r);
double rb = r - ra;
// Split x
final double xa = doubleHighPart(x);
final double xb = x - xa;
rb += (y - ra * xa - ra * xb - rb * xa - rb * xb) / x;
temp = ra + rb;
double temp = ra + rb;
rb = -(temp - ra - rb);
ra = temp;
if (ra == 0 && (y < 0)) { // Fix up the sign so atan works correctly
ra = -0.0;
}
// Call atan
double result = atan(ra, rb, x < 0);
@ -3290,7 +3332,7 @@ public class FastMath {
double result = xb * factb + xb * facta + xa * factb + xa * facta;
if (result == 0) {
result = result * x; // ensure correct sign
result = result * x; // ensure correct sign if calculation underflows
}
return result;
}