in commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java [2459:2552]
private static Complex tanh(double real, double imaginary, ComplexConstructor constructor) {
// Cache the absolute real value
final double x = Math.abs(real);
// Handle inf or nan.
if (!isPosFinite(x) || !Double.isFinite(imaginary)) {
if (isPosInfinite(x)) {
if (Double.isFinite(imaginary)) {
// The sign is copied from sin(2y)
// The identity sin(2a) = 2 sin(a) cos(a) is used for consistency
// with the computation below. Only the magnitude is important
// so drop the 2. When |y| is small sign(sin(2y)) = sign(y).
final double sign = Math.abs(imaginary) < PI_OVER_2 ?
imaginary :
Math.sin(imaginary) * Math.cos(imaginary);
return constructor.create(Math.copySign(1, real),
Math.copySign(0, sign));
}
// imaginary is infinite or NaN
return constructor.create(Math.copySign(1, real), Math.copySign(0, imaginary));
}
// Remaining cases:
// (0 + i inf), returns (0 + i NaN)
// (0 + i NaN), returns (0 + i NaN)
// (x + i inf), returns (NaN + i NaN) for non-zero x (including infinite)
// (x + i NaN), returns (NaN + i NaN) for non-zero x (including infinite)
// (NaN + i 0), returns (NaN + i 0)
// (NaN + i y), returns (NaN + i NaN) for non-zero y (including infinite)
// (NaN + i NaN), returns (NaN + i NaN)
return constructor.create(real == 0 ? real : Double.NaN,
imaginary == 0 ? imaginary : Double.NaN);
}
// Finite components
// tanh(x+iy) = (sinh(2x) + i sin(2y)) / (cosh(2x) + cos(2y))
if (real == 0) {
// Imaginary-only tanh(iy) = i tan(y)
// Identity: sin 2y / (1 + cos 2y) = tan(y)
return constructor.create(real, Math.tan(imaginary));
}
if (imaginary == 0) {
// Identity: sinh 2x / (1 + cosh 2x) = tanh(x)
return constructor.create(Math.tanh(real), imaginary);
}
// The double angles can be avoided using the identities:
// sinh(2x) = 2 sinh(x) cosh(x)
// sin(2y) = 2 sin(y) cos(y)
// cosh(2x) = 2 sinh^2(x) + 1
// cos(2y) = 2 cos^2(y) - 1
// tanh(x+iy) = (sinh(x)cosh(x) + i sin(y)cos(y)) / (sinh^2(x) + cos^2(y))
// To avoid a junction when swapping between the double angles and the identities
// the identities are used in all cases.
if (x > SAFE_EXP / 2) {
// Potential overflow in sinh/cosh(2x).
// Approximate sinh/cosh using exp^x.
// Ignore cos^2(y) in the divisor as it is insignificant.
// real = sinh(x)cosh(x) / sinh^2(x) = +/-1
final double re = Math.copySign(1, real);
// imag = sin(2y) / 2 sinh^2(x)
// sinh(x) -> sign(x) * e^|x| / 2 when x is large.
// sinh^2(x) -> e^2|x| / 4 when x is large.
// imag = sin(2y) / 2 (e^2|x| / 4) = 2 sin(2y) / e^2|x|
// = 4 * sin(y) cos(y) / e^2|x|
// Underflow safe divide as e^2|x| may overflow:
// imag = 4 * sin(y) cos(y) / e^m / e^(2|x| - m)
// (|im| is a maximum of 2)
double im = Math.sin(imaginary) * Math.cos(imaginary);
if (x > SAFE_EXP) {
// e^2|x| > e^m * e^m
// This will underflow 2.0 / e^m / e^m
im = Math.copySign(0.0, im);
} else {
// e^2|x| = e^m * e^(2|x| - m)
im = 4 * im / EXP_M / Math.exp(2 * x - SAFE_EXP);
}
return constructor.create(re, im);
}
// No overflow of sinh(2x) and cosh(2x)
// Note: This does not use the definitional formula but uses the identity:
// tanh(x+iy) = (sinh(x)cosh(x) + i sin(y)cos(y)) / (sinh^2(x) + cos^2(y))
final double sinhx = Math.sinh(real);
final double coshx = Math.cosh(real);
final double siny = Math.sin(imaginary);
final double cosy = Math.cos(imaginary);
final double divisor = sinhx * sinhx + cosy * cosy;
return constructor.create(sinhx * coshx / divisor,
siny * cosy / divisor);
}