Update log() to use the overflow/underflow safe method of Hull et al.
This maintains the existing overflow safe functionality but adds
underflow protection.
diff --git a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java
index 8db24d6..c926e6e 100644
--- a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java
+++ b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java
@@ -451,21 +451,6 @@
}
/**
- * Compute the absolute of the complex number.
- *
- * <p>This function exists for use in trigonomic functions.
- *
- * @param real Real part.
- * @param imaginary Imaginary part.
- * @return the absolute value.
- * @see Math#hypot(double, double)
- */
- private static double getAbsolute(double real, double imaginary) {
- // Delegate
- return Math.hypot(real, imaginary);
- }
-
- /**
* Return the squared norm value of this complex number. This is also called the absolute
* square.
* <pre>norm(a + b i) = a^2 + b^2</pre>
@@ -2414,9 +2399,9 @@
* Compute the square root of the complex number.
* Implements the following algorithm to compute {@code sqrt(a + b i)}:
* <ol>
- * <li>Let {@code t = sqrt((|a| + |a + b i|) / 2)}
- * <li>if {@code (a >= 0)} return {@code t + (b / 2t) i}
- * <li>else return {@code |b| / 2t + sign(b)t i }
+ * <li>Let {@code t = sqrt(2 * (|a| + |a + b i|))}
+ * <li>if {@code (a >= 0)} return {@code (t / 2) + (b / t) i}
+ * <li>else return {@code (|b| / t) + (sign(b) * t / 2) i }
* </ol>
* where:
* <ul>
@@ -2425,76 +2410,82 @@
* <li>{@code sign(b) = }{@link Math#copySign(double,double) copySign}(1.0, b)
* </ul>
*
+ * <p>The implementation is overflow and underflow safe based on the method described in:</p>
+ * <blockquote>
+ * T E Hull, Thomas F Fairgrieve and Ping Tak Peter Tang (1994)
+ * Implementing complex elementary functions using exception handling.
+ * ACM Transactions on Mathematical Software, Vol 20, No 2, pp 215-244.
+ * </blockquote>
+ *
* @param real Real component.
* @param imaginary Imaginary component.
* @return the square root of the complex number.
*/
private static Complex sqrt(double real, double imaginary) {
- // Special case for infinite imaginary for all real including nan
- if (Double.isInfinite(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, imaginary);
- }
- if (Double.isInfinite(real)) {
- // imaginary is finite or NaN
- final double part = Double.isNaN(imaginary) ? Double.NaN : 0;
- if (real == Double.NEGATIVE_INFINITY) {
- return new Complex(part, Math.copySign(Double.POSITIVE_INFINITY, imaginary));
- }
- return new Complex(Double.POSITIVE_INFINITY, Math.copySign(part, imaginary));
- }
+ // Handle NaN
if (Double.isNaN(real) || Double.isNaN(imaginary)) {
+ // Check for infinite
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Double.POSITIVE_INFINITY, imaginary);
+ }
+ if (Double.isInfinite(real)) {
+ if (real == Double.NEGATIVE_INFINITY) {
+ return new Complex(Double.NaN, Math.copySign(Double.POSITIVE_INFINITY, imaginary));
+ }
+ return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
+ }
return NAN;
}
- // Finite real and imaginary
- // Edge case for real numbers
- if (imaginary == 0) {
- final double sqrtAbs = Math.sqrt(Math.abs(real));
- if (real < 0) {
- return new Complex(0, Math.copySign(sqrtAbs, imaginary));
- }
- return new Complex(sqrtAbs, imaginary);
- }
- // Get the absolute of the real
- final double absA = Math.abs(real);
- // Compute |a + b i|
- double absC = getAbsolute(real, imaginary);
+ // Compute with positive values and determine sign at the end
+ final double x = Math.abs(real);
+ final double y = Math.abs(imaginary);
- // t = sqrt((|a| + |a + b i|) / 2)
- // This is always representable as this complex is finite.
+ // Compute
double t;
- // Overflow safe
- if (absC == Double.POSITIVE_INFINITY) {
- // Complex is too large.
- // Divide by the largest absolute component,
- // compute the required sqrt and then scale back.
- // Use the equality: sqrt(n) = sqrt(scale) * sqrt(n/scale)
- // t = sqrt(max) * sqrt((|a|/max + |a + b i|/max) / 2)
- // Note: The function may be non-monotonic at the junction.
- // The alternative of returning infinity for a finite input is worse.
- // Use precise scaling with:
- // scale ~ 2^exponent
- // Make exponent even for fast rescaling using sqrt(2^exponent).
- final int exponent = getMaxExponent(absA, imaginary) & MASK_INT_TO_EVEN;
- // Implement scaling using 2^-exponent
- final double scaleA = Math.scalb(absA, -exponent);
- final double scaleB = Math.scalb(imaginary, -exponent);
- absC = getAbsolute(scaleA, scaleB);
- // t = Math.sqrt(2^exponent) * Math.sqrt((scaleA + absC) / 2)
- // This works if exponent is even:
- // sqrt(2^exponent) = (2^exponent)^0.5 = 2^(exponent*0.5)
- t = Math.scalb(Math.sqrt((scaleA + absC) / 2), exponent / 2);
+ if (inRegion(x, y, SAFE_MIN, SAFE_MAX)) {
+ // No over/underflow of x^2 + y^2
+ t = Math.sqrt(2 * (Math.sqrt(x * x + y * y) + x));
} else {
- // Over-flow safe average: absA < absC and abdC is finite.
- t = Math.sqrt(absA + (absC - absA) / 2);
+ // Potential over/underflow. First check infinites and real/imaginary only.
+
+ // Check for infinite
+ if (isPosInfinite(y)) {
+ return new Complex(Double.POSITIVE_INFINITY, imaginary);
+ } else if (isPosInfinite(x)) {
+ if (real == Double.NEGATIVE_INFINITY) {
+ return new Complex(0, Math.copySign(Double.POSITIVE_INFINITY, imaginary));
+ }
+ return new Complex(Double.POSITIVE_INFINITY, Math.copySign(0, imaginary));
+ } else if (y == 0) {
+ // Real only
+ final double sqrtAbs = Math.sqrt(x);
+ if (real < 0) {
+ return new Complex(0, Math.copySign(sqrtAbs, imaginary));
+ }
+ return new Complex(sqrtAbs, imaginary);
+ } else if (x == 0) {
+ // Imaginary only
+ final double sqrtAbs = Math.sqrt(y) / ROOT2;
+ return new Complex(sqrtAbs, Math.copySign(sqrtAbs, imaginary));
+ } else {
+ // Over/underflow
+ // scale so that abs(x) is near 1, with even exponent.
+ final int scale = getMaxExponent(x, y) & MASK_INT_TO_EVEN;
+ final double sx = Math.scalb(x, -scale);
+ final double sy = Math.scalb(y, -scale);
+ final double st = Math.sqrt(2 * (Math.sqrt(sx * sx + sy * sy) + sx));
+ // Rescale. This works if exponent is even:
+ // st * sqrt(2^scale) = st * (2^scale)^0.5 = st * 2^(scale*0.5)
+ t = Math.scalb(st, scale / 2);
+ }
}
if (real >= 0) {
- return new Complex(t, imaginary / (2 * t));
+ return new Complex(t / 2, imaginary / t);
}
- return new Complex(Math.abs(imaginary) / (2 * t),
- Math.copySign(1.0, imaginary) * t);
+ return new Complex(y / t, Math.copySign(t / 2, imaginary));
}
/**