// Special Cases: // // - expm1(+inf) = +inf // - expm1(-inf) = -1 // - expm1(nan) = nan const builtin = @import("builtin"); const std = @import("../index.zig"); const math = std.math; const assert = std.debug.assert; pub fn expm1(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { f32 => expm1_32(x), f64 => expm1_64(x), else => @compileError("exp1m not implemented for " ++ @typeName(T)), }; } fn expm1_32(x_: f32) f32 { @setFloatMode(this, builtin.FloatMode.Strict); const o_threshold: f32 = 8.8721679688e+01; const ln2_hi: f32 = 6.9313812256e-01; const ln2_lo: f32 = 9.0580006145e-06; const invln2: f32 = 1.4426950216e+00; const Q1: f32 = -3.3333212137e-2; const Q2: f32 = 1.5807170421e-3; var x = x_; const ux = @bitCast(u32, x); const hx = ux & 0x7FFFFFFF; const sign = hx >> 31; // TODO: Shouldn't need this check explicitly. if (math.isNegativeInf(x)) { return -1.0; } // |x| >= 27 * ln2 if (hx >= 0x4195B844) { // nan if (hx > 0x7F800000) { return x; } if (sign != 0) { return -1; } if (x > o_threshold) { x *= 0x1.0p127; return x; } } var hi: f32 = undefined; var lo: f32 = undefined; var c: f32 = undefined; var k: i32 = undefined; // |x| > 0.5 * ln2 if (hx > 0x3EB17218) { // |x| < 1.5 * ln2 if (hx < 0x3F851592) { if (sign == 0) { hi = x - ln2_hi; lo = ln2_lo; k = 1; } else { hi = x + ln2_hi; lo = -ln2_lo; k = -1; } } else { var kf = invln2 * x; if (sign != 0) { kf -= 0.5; } else { kf += 0.5; } k = i32(kf); const t = f32(k); hi = x - t * ln2_hi; lo = t * ln2_lo; } x = hi - lo; c = (hi - x) - lo; } // |x| < 2^(-25) else if (hx < 0x33000000) { if (hx < 0x00800000) { math.forceEval(x * x); } return x; } else { k = 0; } const hfx = 0.5 * x; const hxs = x * hfx; const r1 = 1.0 + hxs * (Q1 + hxs * Q2); const t = 3.0 - r1 * hfx; var e = hxs * ((r1 - t) / (6.0 - x * t)); // c is 0 if (k == 0) { return x - (x * e - hxs); } e = x * (e - c) - c; e -= hxs; // exp(x) ~ 2^k (x_reduced - e + 1) if (k == -1) { return 0.5 * (x - e) - 0.5; } if (k == 1) { if (x < -0.25) { return -2.0 * (e - (x + 0.5)); } else { return 1.0 + 2.0 * (x - e); } } const twopk = @bitCast(f32, u32((0x7F +% k) << 23)); if (k < 0 or k > 56) { var y = x - e + 1.0; if (k == 128) { y = y * 2.0 * 0x1.0p127; } else { y = y * twopk; } return y - 1.0; } const uf = @bitCast(f32, u32(0x7F -% k) << 23); if (k < 23) { return (x - e + (1 - uf)) * twopk; } else { return (x - (e + uf) + 1) * twopk; } } fn expm1_64(x_: f64) f64 { @setFloatMode(this, builtin.FloatMode.Strict); const o_threshold: f64 = 7.09782712893383973096e+02; const ln2_hi: f64 = 6.93147180369123816490e-01; const ln2_lo: f64 = 1.90821492927058770002e-10; const invln2: f64 = 1.44269504088896338700e+00; const Q1: f64 = -3.33333333333331316428e-02; const Q2: f64 = 1.58730158725481460165e-03; const Q3: f64 = -7.93650757867487942473e-05; const Q4: f64 = 4.00821782732936239552e-06; const Q5: f64 = -2.01099218183624371326e-07; var x = x_; const ux = @bitCast(u64, x); const hx = u32(ux >> 32) & 0x7FFFFFFF; const sign = ux >> 63; if (math.isNegativeInf(x)) { return -1.0; } // |x| >= 56 * ln2 if (hx >= 0x4043687A) { // exp1md(nan) = nan if (hx > 0x7FF00000) { return x; } // exp1md(-ve) = -1 if (sign != 0) { return -1; } if (x > o_threshold) { math.raiseOverflow(); return math.inf(f64); } } var hi: f64 = undefined; var lo: f64 = undefined; var c: f64 = undefined; var k: i32 = undefined; // |x| > 0.5 * ln2 if (hx > 0x3FD62E42) { // |x| < 1.5 * ln2 if (hx < 0x3FF0A2B2) { if (sign == 0) { hi = x - ln2_hi; lo = ln2_lo; k = 1; } else { hi = x + ln2_hi; lo = -ln2_lo; k = -1; } } else { var kf = invln2 * x; if (sign != 0) { kf -= 0.5; } else { kf += 0.5; } k = i32(kf); const t = f64(k); hi = x - t * ln2_hi; lo = t * ln2_lo; } x = hi - lo; c = (hi - x) - lo; } // |x| < 2^(-54) else if (hx < 0x3C900000) { if (hx < 0x00100000) { math.forceEval(f32(x)); } return x; } else { k = 0; } const hfx = 0.5 * x; const hxs = x * hfx; const r1 = 1.0 + hxs * (Q1 + hxs * (Q2 + hxs * (Q3 + hxs * (Q4 + hxs * Q5)))); const t = 3.0 - r1 * hfx; var e = hxs * ((r1 - t) / (6.0 - x * t)); // c is 0 if (k == 0) { return x - (x * e - hxs); } e = x * (e - c) - c; e -= hxs; // exp(x) ~ 2^k (x_reduced - e + 1) if (k == -1) { return 0.5 * (x - e) - 0.5; } if (k == 1) { if (x < -0.25) { return -2.0 * (e - (x + 0.5)); } else { return 1.0 + 2.0 * (x - e); } } const twopk = @bitCast(f64, u64(0x3FF +% k) << 52); if (k < 0 or k > 56) { var y = x - e + 1.0; if (k == 1024) { y = y * 2.0 * 0x1.0p1023; } else { y = y * twopk; } return y - 1.0; } const uf = @bitCast(f64, u64(0x3FF -% k) << 52); if (k < 20) { return (x - e + (1 - uf)) * twopk; } else { return (x - (e + uf) + 1) * twopk; } } test "math.exp1m" { assert(expm1(f32(0.0)) == expm1_32(0.0)); assert(expm1(f64(0.0)) == expm1_64(0.0)); } test "math.expm1_32" { const epsilon = 0.000001; assert(expm1_32(0.0) == 0.0); assert(math.approxEq(f32, expm1_32(0.0), 0.0, epsilon)); assert(math.approxEq(f32, expm1_32(0.2), 0.221403, epsilon)); assert(math.approxEq(f32, expm1_32(0.8923), 1.440737, epsilon)); assert(math.approxEq(f32, expm1_32(1.5), 3.481689, epsilon)); } test "math.expm1_64" { const epsilon = 0.000001; assert(expm1_64(0.0) == 0.0); assert(math.approxEq(f64, expm1_64(0.0), 0.0, epsilon)); assert(math.approxEq(f64, expm1_64(0.2), 0.221403, epsilon)); assert(math.approxEq(f64, expm1_64(0.8923), 1.440737, epsilon)); assert(math.approxEq(f64, expm1_64(1.5), 3.481689, epsilon)); } test "math.expm1_32.special" { const epsilon = 0.000001; assert(math.isPositiveInf(expm1_32(math.inf(f32)))); assert(expm1_32(-math.inf(f32)) == -1.0); assert(math.isNan(expm1_32(math.nan(f32)))); } test "math.expm1_64.special" { const epsilon = 0.000001; assert(math.isPositiveInf(expm1_64(math.inf(f64)))); assert(expm1_64(-math.inf(f64)) == -1.0); assert(math.isNan(expm1_64(math.nan(f64)))); }