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/cexpf.c
5// https://git.musl-libc.org/cgit/musl/tree/src/complex/cexp.c
6
7const std = @import("../../std.zig");
8const testing = std.testing;
9const math = std.math;
10const cmath = math.complex;
11const Complex = cmath.Complex;
12
13const ldexp_cexp = @import("ldexp.zig").ldexp_cexp;
14
15/// Returns e raised to the power of z (e^z).
16pub 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
26fn 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
71fn 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
122test 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
142test 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}