You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2019/12/20 17:58:15 UTC
[commons-numbers] 24/30: Fix overflow in tanh. Added edge case
tests.
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
commit 3366f35d8120ddc5c52f991c1d120a7c7f95c72f
Author: aherbert <ah...@apache.org>
AuthorDate: Fri Dec 20 12:53:06 2019 +0000
Fix overflow in tanh. Added edge case tests.
Overflow is cause by computation of Math.sin(2a) or Math.cos(2a) when a
is finite and 2a is infinite. This has been fixed to use trigonomic
identities for sin(2a) and cos(2a) in this case.
---
.../apache/commons/numbers/complex/Complex.java | 69 ++++++++++++++++++----
.../numbers/complex/ComplexEdgeCaseTest.java | 30 +++++++++-
2 files changed, 86 insertions(+), 13 deletions(-)
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 c9be094..4516be4 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
@@ -2400,6 +2400,7 @@ public final class Complex implements Serializable {
* </pre>
*
* @return the tangent of {@code this}.
+ * @see <a href="http://functions.wolfram.com/ElementaryFunctions/Tan/">Tangent</a>
*/
public Complex tan() {
// Define in terms of tanh
@@ -2420,6 +2421,7 @@ public final class Complex implements Serializable {
* <p>This is an odd function: {@code f(z) = -f(-z)}.
*
* @return the hyperbolic tangent of {@code this}.
+ * @see <a href="http://functions.wolfram.com/ElementaryFunctions/Tanh/">Tanh</a>
*/
public Complex tanh() {
return tanh(real, imaginary, Complex::ofCartesian);
@@ -2441,10 +2443,8 @@ public final class Complex implements Serializable {
// Perform edge-condition checks on twice the imaginary value.
// This handles very big imaginary numbers as infinite.
- final double imaginary2 = 2 * imaginary;
-
if (Double.isFinite(real)) {
- if (Double.isFinite(imaginary2)) {
+ if (Double.isFinite(imaginary)) {
if (real == 0) {
// Identity: sin x / (1 + cos x) = tan(x/2)
return constructor.create(real, Math.tan(imaginary));
@@ -2458,26 +2458,26 @@ public final class Complex implements Serializable {
// Math.cosh returns positive infinity for infinity.
// cosh -> inf
- final double d = Math.cosh(real2) + Math.cos(imaginary2);
+ final double divisor = Math.cosh(real2) + cos2(imaginary);
// Math.sinh returns the input infinity for infinity.
// sinh -> inf for positive x; else -inf
final double sinhRe2 = Math.sinh(real2);
// Avoid inf / inf
- if (Double.isInfinite(sinhRe2) && Double.isInfinite(d)) {
- // Fall-through to the result if infinite
- return constructor.create(Math.copySign(1, real), Math.copySign(0, Math.sin(imaginary2)));
+ if (Double.isInfinite(sinhRe2) && Double.isInfinite(divisor)) {
+ // Handle as if real was infinite
+ return constructor.create(Math.copySign(1, real), Math.copySign(0, sin2(imaginary)));
}
- return constructor.create(sinhRe2 / d,
- Math.sin(imaginary2) / d);
+ return constructor.create(sinhRe2 / divisor,
+ sin2(imaginary) / divisor);
}
// imaginary is infinite or NaN
return NAN;
}
if (Double.isInfinite(real)) {
- if (Double.isFinite(imaginary2)) {
- return constructor.create(Math.copySign(1, real), Math.copySign(0, Math.sin(imaginary2)));
+ if (Double.isFinite(imaginary)) {
+ return constructor.create(Math.copySign(1, real), Math.copySign(0, sin2(imaginary)));
}
// imaginary is infinite or NaN
return constructor.create(Math.copySign(1, real), Math.copySign(0, imaginary));
@@ -2490,7 +2490,52 @@ public final class Complex implements Serializable {
return NAN;
}
- /**
+ /**
+ * Safely compute {@code cos(2*a)} when {@code a} is finite.
+ * Note that {@link Math#cos(double)} returns NaN when the input is infinite.
+ * If {@code 2*a} is finite use {@code Math.cos(2*a)}; otherwise use the identity:
+ * <pre>
+ * <code>
+ * cos(2a) = 2 cos<sup>2</sup>(a) - 1
+ * </code>
+ * </pre>
+ *
+ * @param a Angle a.
+ * @return the cosine of 2a
+ * @see Math#cos(double)
+ */
+ private static double cos2(double a) {
+ final double twoA = 2 * a;
+ if (Double.isFinite(twoA)) {
+ return Math.cos(twoA);
+ }
+ final double cosA = Math.cos(a);
+ return 2 * cosA * cosA - 1;
+ }
+
+ /**
+ * Safely compute {@code sin(2*a)} when {@code a} is finite.
+ * Note that {@link Math#sin(double)} returns NaN when the input is infinite.
+ * If {@code 2*a} is finite use {@code Math.sin(2*a)}; otherwise use the identity:
+ * <pre>
+ * <code>
+ * sin(2a) = 2 sin(a) cos(a)
+ * </code>
+ * </pre>
+ *
+ * @param a Angle a.
+ * @return the sine of 2a
+ * @see Math#sin(double)
+ */
+ private static double sin2(double a) {
+ final double twoA = 2 * a;
+ if (Double.isFinite(twoA)) {
+ return Math.sin(twoA);
+ }
+ return 2 * Math.sin(a) * Math.cos(a);
+ }
+
+ /**
* Compute the argument of this complex number.
*
* <p>The argument is the angle phi between the positive real axis and
diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java
index fd163e8..0fb94af 100644
--- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java
+++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java
@@ -243,7 +243,35 @@ public class ComplexEdgeCaseTest {
public void testTanh() {
// tan(a + b i) = sinh(2a)/(cosh(2a)+cos(2b)) + i [sin(2b)/(cosh(2a)+cos(2b))]
// Odd function: negative real cases defined by positive real cases
- // TODO
+ final String name = "tanh";
+ final UnaryOperator<Complex> operation = Complex::tanh;
+
+ // Overflow on 2b:
+ // cos(2b) = cos(inf) = NaN
+ // sin(2b) = sin(inf) = NaN
+ assertComplex(1, Double.MAX_VALUE, name, operation, 0.76160203106265523, -0.0020838895895863505);
+
+ // Underflow on 2b:
+ // cos(2b) -> 1
+ // sin(2b) -> 0
+ assertComplex(1, Double.MIN_NORMAL, name, operation, 0.76159415595576485, 9.344739287691424e-309);
+ assertComplex(1, Double.MIN_VALUE, name, operation, 0.76159415595576485, 0);
+
+ // Overflow on 2a:
+ // sinh(2a) = sinh(inf) = inf
+ // cosh(2a) = cosh(inf) = inf
+ // Test all sign variants as this execution path to treat real as infinite
+ // is not tested else where.
+ assertComplex(Double.MAX_VALUE, 1, name, operation, 1, 0.0);
+ assertComplex(Double.MAX_VALUE, -1, name, operation, 1, -0.0);
+ assertComplex(-Double.MAX_VALUE, 1, name, operation, -1, 0.0);
+ assertComplex(-Double.MAX_VALUE, -1, name, operation, -1, -0.0);
+
+ // Underflow on 2a:
+ // sinh(2a) -> 0
+ // cosh(2a) -> 0
+ assertComplex(Double.MIN_NORMAL, 1, name, operation, 7.6220323800193346e-308, 1.5574077246549021);
+ assertComplex(Double.MIN_VALUE, 1, name, operation, 1.4821969375237396e-323, 1.5574077246549021);
}
@Test