master
  1// Ported from musl, which is licensed under the MIT license:
  2// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT
  3//
  4// https://git.musl-libc.org/cgit/musl/tree/src/complex/ctanhf.c
  5// https://git.musl-libc.org/cgit/musl/tree/src/complex/ctanh.c
  6
  7const std = @import("../../std.zig");
  8const testing = std.testing;
  9const math = std.math;
 10const cmath = math.complex;
 11const Complex = cmath.Complex;
 12
 13/// Returns the hyperbolic tangent of z.
 14pub fn tanh(z: anytype) Complex(@TypeOf(z.re, z.im)) {
 15    const T = @TypeOf(z.re, z.im);
 16    return switch (T) {
 17        f32 => tanh32(z),
 18        f64 => tanh64(z),
 19        else => @compileError("tan not implemented for " ++ @typeName(z)),
 20    };
 21}
 22
 23fn tanh32(z: Complex(f32)) Complex(f32) {
 24    const x = z.re;
 25    const y = z.im;
 26
 27    const hx = @as(u32, @bitCast(x));
 28    const ix = hx & 0x7fffffff;
 29
 30    if (ix >= 0x7f800000) {
 31        if (ix & 0x7fffff != 0) {
 32            const r = if (y == 0) y else x * y;
 33            return Complex(f32).init(x, r);
 34        }
 35        const xx = @as(f32, @bitCast(hx - 0x40000000));
 36        const r = if (math.isInf(y)) y else @sin(y) * @cos(y);
 37        return Complex(f32).init(xx, math.copysign(@as(f32, 0.0), r));
 38    }
 39
 40    if (!math.isFinite(y)) {
 41        const r = if (ix != 0) y - y else x;
 42        return Complex(f32).init(r, y - y);
 43    }
 44
 45    // x >= 11
 46    if (ix >= 0x41300000) {
 47        const exp_mx = @exp(-@abs(x));
 48        return Complex(f32).init(math.copysign(@as(f32, 1.0), x), 4 * @sin(y) * @cos(y) * exp_mx * exp_mx);
 49    }
 50
 51    // Kahan's algorithm
 52    const t = @tan(y);
 53    const beta = 1.0 + t * t;
 54    const s = math.sinh(x);
 55    const rho = @sqrt(1 + s * s);
 56    const den = 1 + beta * s * s;
 57
 58    return Complex(f32).init((beta * rho * s) / den, t / den);
 59}
 60
 61fn tanh64(z: Complex(f64)) Complex(f64) {
 62    const x = z.re;
 63    const y = z.im;
 64
 65    const fx: u64 = @bitCast(x);
 66    // TODO: zig should allow this conversion implicitly because it can notice that the value necessarily
 67    // fits in range.
 68    const hx: u32 = @intCast(fx >> 32);
 69    const lx: u32 = @truncate(fx);
 70    const ix = hx & 0x7fffffff;
 71
 72    if (ix >= 0x7ff00000) {
 73        if ((ix & 0xfffff) | lx != 0) {
 74            const r = if (y == 0) y else x * y;
 75            return Complex(f64).init(x, r);
 76        }
 77
 78        const xx: f64 = @bitCast((@as(u64, hx - 0x40000000) << 32) | lx);
 79        const r = if (math.isInf(y)) y else @sin(y) * @cos(y);
 80        return Complex(f64).init(xx, math.copysign(@as(f64, 0.0), r));
 81    }
 82
 83    if (!math.isFinite(y)) {
 84        const r = if (ix != 0) y - y else x;
 85        return Complex(f64).init(r, y - y);
 86    }
 87
 88    // x >= 22
 89    if (ix >= 0x40360000) {
 90        const exp_mx = @exp(-@abs(x));
 91        return Complex(f64).init(math.copysign(@as(f64, 1.0), x), 4 * @sin(y) * @cos(y) * exp_mx * exp_mx);
 92    }
 93
 94    // Kahan's algorithm
 95    const t = @tan(y);
 96    const beta = 1.0 + t * t;
 97    const s = math.sinh(x);
 98    const rho = @sqrt(1 + s * s);
 99    const den = 1 + beta * s * s;
100
101    return Complex(f64).init((beta * rho * s) / den, t / den);
102}
103
104test tanh32 {
105    const epsilon = math.floatEps(f32);
106    const a = Complex(f32).init(5, 3);
107    const c = tanh(a);
108
109    try testing.expectApproxEqAbs(0.99991274, c.re, epsilon);
110    try testing.expectApproxEqAbs(-0.00002536878, c.im, epsilon);
111}
112
113test tanh64 {
114    const epsilon = math.floatEps(f64);
115    const a = Complex(f64).init(5, 3);
116    const c = tanh(a);
117
118    try testing.expectApproxEqAbs(0.9999128201513536, c.re, epsilon);
119    try testing.expectApproxEqAbs(-0.00002536867620767604, c.im, epsilon);
120}
121
122test "tanh64 musl" {
123    const epsilon = math.floatEps(f64);
124    const a = Complex(f64).init(std.math.inf(f64), std.math.inf(f64));
125    const c = tanh(a);
126
127    try testing.expectApproxEqAbs(1, c.re, epsilon);
128    try testing.expectApproxEqAbs(0, c.im, epsilon);
129}