| 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/cexpf.c |
| 5 | // https://git.musl-libc.org/cgit/musl/tree/src/complex/cexp.c |
| 6 | |
| 7 | const std = @import("../../std.zig"); |
| 8 | const testing = std.testing; |
| 9 | const math = std.math; |
| 10 | const cmath = math.complex; |
| 11 | const Complex = cmath.Complex; |
| 12 | |
| 13 | const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; |
| 14 | |
| 15 | /// Returns e raised to the power of z (e^z). |
| 16 | pub fn exp(z: anytype) Complex(@TypeOf(z.re, z.im)) { |
| 17 | const T = @TypeOf(z.re, z.im); |
| 18 | |
| 19 | return switch (T) { |
| 20 | f32 => exp32(z), |
| 21 | f64 => exp64(z), |
| 22 | else => @compileError("exp not implemented for " ++ @typeName(z)), |
| 23 | }; |
| 24 | } |
| 25 | |
| 26 | fn exp32(z: Complex(f32)) Complex(f32) { |
| 27 | const exp_overflow = 0x42b17218; // max_exp * ln2 ~= 88.72283955 |
| 28 | const cexp_overflow = 0x43400074; // (max_exp - min_denom_exp) * ln2 |
| 29 | |
| 30 | const x = z.re; |
| 31 | const y = z.im; |
| 32 | |
| 33 | const hy = @as(u32, @bitCast(y)) & 0x7fffffff; |
| 34 | // cexp(x + i0) = exp(x) + i0 |
| 35 | if (hy == 0) { |
| 36 | return Complex(f32).init(@exp(x), y); |
| 37 | } |
| 38 | |
| 39 | const hx = @as(u32, @bitCast(x)); |
| 40 | // cexp(0 + iy) = cos(y) + isin(y) |
| 41 | if ((hx & 0x7fffffff) == 0) { |
| 42 | return Complex(f32).init(@cos(y), @sin(y)); |
| 43 | } |
| 44 | |
| 45 | if (hy >= 0x7f800000) { |
| 46 | // cexp(finite|nan +- i inf|nan) = nan + i nan |
| 47 | if ((hx & 0x7fffffff) != 0x7f800000) { |
| 48 | return Complex(f32).init(y - y, y - y); |
| 49 | } // cexp(-inf +- i inf|nan) = 0 + i0 |
| 50 | else if (hx & 0x80000000 != 0) { |
| 51 | return Complex(f32).init(0, 0); |
| 52 | } // cexp(+inf +- i inf|nan) = inf + i nan |
| 53 | else { |
| 54 | return Complex(f32).init(x, y - y); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | // 88.7 <= x <= 192 so must scale |
| 59 | if (hx >= exp_overflow and hx <= cexp_overflow) { |
| 60 | return ldexp_cexp(z, 0); |
| 61 | } // - x < exp_overflow => exp(x) won't overflow (common) |
| 62 | // - x > cexp_overflow, so exp(x) * s overflows for s > 0 |
| 63 | // - x = +-inf |
| 64 | // - x = nan |
| 65 | else { |
| 66 | const exp_x = @exp(x); |
| 67 | return Complex(f32).init(exp_x * @cos(y), exp_x * @sin(y)); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | fn exp64(z: Complex(f64)) Complex(f64) { |
| 72 | const exp_overflow = 0x40862e42; // high bits of max_exp * ln2 ~= 710 |
| 73 | const cexp_overflow = 0x4096b8e4; // (max_exp - min_denorm_exp) * ln2 |
| 74 | |
| 75 | const x = z.re; |
| 76 | const y = z.im; |
| 77 | |
| 78 | const fy: u64 = @bitCast(y); |
| 79 | const hy: u32 = @intCast((fy >> 32) & 0x7fffffff); |
| 80 | const ly: u32 = @truncate(fy); |
| 81 | |
| 82 | // cexp(x + i0) = exp(x) + i0 |
| 83 | if (hy | ly == 0) { |
| 84 | return Complex(f64).init(@exp(x), y); |
| 85 | } |
| 86 | |
| 87 | const fx: u64 = @bitCast(x); |
| 88 | const hx: u32 = @intCast(fx >> 32); |
| 89 | const lx: u32 = @truncate(fx); |
| 90 | |
| 91 | // cexp(0 + iy) = cos(y) + isin(y) |
| 92 | if ((hx & 0x7fffffff) | lx == 0) { |
| 93 | return Complex(f64).init(@cos(y), @sin(y)); |
| 94 | } |
| 95 | |
| 96 | if (hy >= 0x7ff00000) { |
| 97 | // cexp(finite|nan +- i inf|nan) = nan + i nan |
| 98 | if (lx != 0 or (hx & 0x7fffffff) != 0x7ff00000) { |
| 99 | return Complex(f64).init(y - y, y - y); |
| 100 | } // cexp(-inf +- i inf|nan) = 0 + i0 |
| 101 | else if (hx & 0x80000000 != 0) { |
| 102 | return Complex(f64).init(0, 0); |
| 103 | } // cexp(+inf +- i inf|nan) = inf + i nan |
| 104 | else { |
| 105 | return Complex(f64).init(x, y - y); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | // 709.7 <= x <= 1454.3 so must scale |
| 110 | if (hx >= exp_overflow and hx <= cexp_overflow) { |
| 111 | return ldexp_cexp(z, 0); |
| 112 | } // - x < exp_overflow => exp(x) won't overflow (common) |
| 113 | // - x > cexp_overflow, so exp(x) * s overflows for s > 0 |
| 114 | // - x = +-inf |
| 115 | // - x = nan |
| 116 | else { |
| 117 | const exp_x = @exp(x); |
| 118 | return Complex(f64).init(exp_x * @cos(y), exp_x * @sin(y)); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | test exp32 { |
| 123 | const tolerance_f32 = @sqrt(math.floatEps(f32)); |
| 124 | |
| 125 | { |
| 126 | const a = Complex(f32).init(5, 3); |
| 127 | const c = exp(a); |
| 128 | |
| 129 | try testing.expectApproxEqRel(@as(f32, -1.46927917e+02), c.re, tolerance_f32); |
| 130 | try testing.expectApproxEqRel(@as(f32, 2.0944065e+01), c.im, tolerance_f32); |
| 131 | } |
| 132 | |
| 133 | { |
| 134 | const a = Complex(f32).init(88.8, 0x1p-149); |
| 135 | const c = exp(a); |
| 136 | |
| 137 | try testing.expectApproxEqAbs(math.inf(f32), c.re, tolerance_f32); |
| 138 | try testing.expectApproxEqAbs(@as(f32, 5.15088629e-07), c.im, tolerance_f32); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | test exp64 { |
| 143 | const tolerance_f64 = @sqrt(math.floatEps(f64)); |
| 144 | |
| 145 | { |
| 146 | const a = Complex(f64).init(5, 3); |
| 147 | const c = exp(a); |
| 148 | |
| 149 | try testing.expectApproxEqRel(@as(f64, -1.469279139083189e+02), c.re, tolerance_f64); |
| 150 | try testing.expectApproxEqRel(@as(f64, 2.094406620874596e+01), c.im, tolerance_f64); |
| 151 | } |
| 152 | |
| 153 | { |
| 154 | const a = Complex(f64).init(709.8, 0x1p-1074); |
| 155 | const c = exp(a); |
| 156 | |
| 157 | try testing.expectApproxEqAbs(math.inf(f64), c.re, tolerance_f64); |
| 158 | try testing.expectApproxEqAbs(@as(f64, 9.036659362159884e-16), c.im, tolerance_f64); |
| 159 | } |
| 160 | } |