2019-05-01 07:15:57 +01:00
|
|
|
// Ported from musl, which is licensed under the MIT license:
|
|
|
|
// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT
|
|
|
|
//
|
|
|
|
// https://git.musl-libc.org/cgit/musl/tree/src/complex/cexpf.c
|
|
|
|
// https://git.musl-libc.org/cgit/musl/tree/src/complex/cexp.c
|
|
|
|
|
2019-03-02 21:46:04 +00:00
|
|
|
const std = @import("../../std.zig");
|
2019-02-08 23:18:47 +00:00
|
|
|
const testing = std.testing;
|
2018-04-24 08:18:31 +01:00
|
|
|
const math = std.math;
|
|
|
|
const cmath = math.complex;
|
|
|
|
const Complex = cmath.Complex;
|
|
|
|
|
|
|
|
const ldexp_cexp = @import("ldexp.zig").ldexp_cexp;
|
|
|
|
|
2019-05-01 07:15:57 +01:00
|
|
|
/// Returns e raised to the power of z (e^z).
|
2023-11-07 20:10:43 +00:00
|
|
|
pub fn exp(z: anytype) Complex(@TypeOf(z.re, z.im)) {
|
|
|
|
const T = @TypeOf(z.re, z.im);
|
2018-04-24 08:18:31 +01:00
|
|
|
|
|
|
|
return switch (T) {
|
|
|
|
f32 => exp32(z),
|
|
|
|
f64 => exp64(z),
|
|
|
|
else => @compileError("exp not implemented for " ++ @typeName(z)),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-06-17 07:57:07 +01:00
|
|
|
fn exp32(z: Complex(f32)) Complex(f32) {
|
2018-05-17 05:56:14 +01:00
|
|
|
const exp_overflow = 0x42b17218; // max_exp * ln2 ~= 88.72283955
|
|
|
|
const cexp_overflow = 0x43400074; // (max_exp - min_denom_exp) * ln2
|
2018-04-24 08:18:31 +01:00
|
|
|
|
|
|
|
const x = z.re;
|
|
|
|
const y = z.im;
|
|
|
|
|
2023-06-22 18:46:56 +01:00
|
|
|
const hy = @as(u32, @bitCast(y)) & 0x7fffffff;
|
2018-04-24 08:18:31 +01:00
|
|
|
// cexp(x + i0) = exp(x) + i0
|
|
|
|
if (hy == 0) {
|
2022-04-26 18:13:55 +01:00
|
|
|
return Complex(f32).init(@exp(x), y);
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
|
2023-06-22 18:46:56 +01:00
|
|
|
const hx = @as(u32, @bitCast(x));
|
2018-04-24 08:18:31 +01:00
|
|
|
// cexp(0 + iy) = cos(y) + isin(y)
|
|
|
|
if ((hx & 0x7fffffff) == 0) {
|
2022-04-28 03:35:28 +01:00
|
|
|
return Complex(f32).init(@cos(y), @sin(y));
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (hy >= 0x7f800000) {
|
|
|
|
// cexp(finite|nan +- i inf|nan) = nan + i nan
|
|
|
|
if ((hx & 0x7fffffff) != 0x7f800000) {
|
2021-05-17 20:57:51 +01:00
|
|
|
return Complex(f32).init(y - y, y - y);
|
2018-05-17 05:56:14 +01:00
|
|
|
} // cexp(-inf +- i inf|nan) = 0 + i0
|
2018-04-24 08:18:31 +01:00
|
|
|
else if (hx & 0x80000000 != 0) {
|
2021-05-17 20:57:51 +01:00
|
|
|
return Complex(f32).init(0, 0);
|
2018-05-17 05:56:14 +01:00
|
|
|
} // cexp(+inf +- i inf|nan) = inf + i nan
|
2018-04-24 08:18:31 +01:00
|
|
|
else {
|
2021-05-17 20:57:51 +01:00
|
|
|
return Complex(f32).init(x, y - y);
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 88.7 <= x <= 192 so must scale
|
|
|
|
if (hx >= exp_overflow and hx <= cexp_overflow) {
|
|
|
|
return ldexp_cexp(z, 0);
|
2018-05-17 05:56:14 +01:00
|
|
|
} // - x < exp_overflow => exp(x) won't overflow (common)
|
2018-04-24 08:18:31 +01:00
|
|
|
// - x > cexp_overflow, so exp(x) * s overflows for s > 0
|
|
|
|
// - x = +-inf
|
|
|
|
// - x = nan
|
|
|
|
else {
|
2022-04-26 18:13:55 +01:00
|
|
|
const exp_x = @exp(x);
|
2022-04-28 03:35:28 +01:00
|
|
|
return Complex(f32).init(exp_x * @cos(y), exp_x * @sin(y));
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-17 07:57:07 +01:00
|
|
|
fn exp64(z: Complex(f64)) Complex(f64) {
|
2018-05-17 05:56:14 +01:00
|
|
|
const exp_overflow = 0x40862e42; // high bits of max_exp * ln2 ~= 710
|
|
|
|
const cexp_overflow = 0x4096b8e4; // (max_exp - min_denorm_exp) * ln2
|
2018-04-24 08:18:31 +01:00
|
|
|
|
|
|
|
const x = z.re;
|
|
|
|
const y = z.im;
|
|
|
|
|
2023-07-24 11:34:16 +01:00
|
|
|
const fy: u64 = @bitCast(y);
|
|
|
|
const hy: u32 = @intCast((fy >> 32) & 0x7fffffff);
|
|
|
|
const ly: u32 = @truncate(fy);
|
2018-04-24 08:18:31 +01:00
|
|
|
|
|
|
|
// cexp(x + i0) = exp(x) + i0
|
|
|
|
if (hy | ly == 0) {
|
2022-04-26 18:13:55 +01:00
|
|
|
return Complex(f64).init(@exp(x), y);
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
|
2023-07-24 11:34:16 +01:00
|
|
|
const fx: u64 = @bitCast(x);
|
|
|
|
const hx: u32 = @intCast(fx >> 32);
|
|
|
|
const lx: u32 = @truncate(fx);
|
2018-04-24 08:18:31 +01:00
|
|
|
|
|
|
|
// cexp(0 + iy) = cos(y) + isin(y)
|
|
|
|
if ((hx & 0x7fffffff) | lx == 0) {
|
2022-04-28 03:35:28 +01:00
|
|
|
return Complex(f64).init(@cos(y), @sin(y));
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (hy >= 0x7ff00000) {
|
|
|
|
// cexp(finite|nan +- i inf|nan) = nan + i nan
|
|
|
|
if (lx != 0 or (hx & 0x7fffffff) != 0x7ff00000) {
|
2021-05-17 20:57:51 +01:00
|
|
|
return Complex(f64).init(y - y, y - y);
|
2018-05-17 05:56:14 +01:00
|
|
|
} // cexp(-inf +- i inf|nan) = 0 + i0
|
2018-04-24 08:18:31 +01:00
|
|
|
else if (hx & 0x80000000 != 0) {
|
2021-05-17 20:57:51 +01:00
|
|
|
return Complex(f64).init(0, 0);
|
2018-05-17 05:56:14 +01:00
|
|
|
} // cexp(+inf +- i inf|nan) = inf + i nan
|
2018-04-24 08:18:31 +01:00
|
|
|
else {
|
2021-05-17 20:57:51 +01:00
|
|
|
return Complex(f64).init(x, y - y);
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 709.7 <= x <= 1454.3 so must scale
|
|
|
|
if (hx >= exp_overflow and hx <= cexp_overflow) {
|
2018-09-13 09:33:05 +01:00
|
|
|
return ldexp_cexp(z, 0);
|
2018-05-17 05:56:14 +01:00
|
|
|
} // - x < exp_overflow => exp(x) won't overflow (common)
|
2018-04-24 08:18:31 +01:00
|
|
|
// - x > cexp_overflow, so exp(x) * s overflows for s > 0
|
|
|
|
// - x = +-inf
|
|
|
|
// - x = nan
|
|
|
|
else {
|
2022-04-26 18:13:55 +01:00
|
|
|
const exp_x = @exp(x);
|
2022-04-28 03:35:28 +01:00
|
|
|
return Complex(f64).init(exp_x * @cos(y), exp_x * @sin(y));
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-26 23:14:43 +00:00
|
|
|
test exp32 {
|
2022-04-26 18:13:55 +01:00
|
|
|
const tolerance_f32 = @sqrt(math.floatEps(f32));
|
2021-06-12 14:35:52 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
const a = Complex(f32).init(5, 3);
|
|
|
|
const c = exp(a);
|
|
|
|
|
|
|
|
try testing.expectApproxEqRel(@as(f32, -1.46927917e+02), c.re, tolerance_f32);
|
|
|
|
try testing.expectApproxEqRel(@as(f32, 2.0944065e+01), c.im, tolerance_f32);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const a = Complex(f32).init(88.8, 0x1p-149);
|
|
|
|
const c = exp(a);
|
2018-04-24 08:18:31 +01:00
|
|
|
|
2021-06-12 14:35:52 +01:00
|
|
|
try testing.expectApproxEqAbs(math.inf(f32), c.re, tolerance_f32);
|
|
|
|
try testing.expectApproxEqAbs(@as(f32, 5.15088629e-07), c.im, tolerance_f32);
|
|
|
|
}
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|
|
|
|
|
2024-02-26 23:14:43 +00:00
|
|
|
test exp64 {
|
2022-04-26 18:13:55 +01:00
|
|
|
const tolerance_f64 = @sqrt(math.floatEps(f64));
|
2018-04-24 08:18:31 +01:00
|
|
|
|
2021-06-12 14:35:52 +01:00
|
|
|
{
|
|
|
|
const a = Complex(f64).init(5, 3);
|
|
|
|
const c = exp(a);
|
|
|
|
|
|
|
|
try testing.expectApproxEqRel(@as(f64, -1.469279139083189e+02), c.re, tolerance_f64);
|
|
|
|
try testing.expectApproxEqRel(@as(f64, 2.094406620874596e+01), c.im, tolerance_f64);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const a = Complex(f64).init(709.8, 0x1p-1074);
|
|
|
|
const c = exp(a);
|
|
|
|
|
|
|
|
try testing.expectApproxEqAbs(math.inf(f64), c.re, tolerance_f64);
|
|
|
|
try testing.expectApproxEqAbs(@as(f64, 9.036659362159884e-16), c.im, tolerance_f64);
|
|
|
|
}
|
2018-04-24 08:18:31 +01:00
|
|
|
}
|