// Special Cases: // // - atan(+-0) = +-0 // - atan(+-inf) = +-pi/2 const math = @import("index.zig"); const assert = @import("../debug.zig").assert; pub fn atan(x: var) -> @typeOf(x) { const T = @typeOf(x); return switch (T) { f32 => atan32(x), f64 => atan64(x), else => @compileError("atan not implemented for " ++ @typeName(T)), }; } fn atan32(x_: f32) -> f32 { const atanhi = []const f32 { 4.6364760399e-01, // atan(0.5)hi 7.8539812565e-01, // atan(1.0)hi 9.8279368877e-01, // atan(1.5)hi 1.5707962513e+00, // atan(inf)hi }; const atanlo = []const f32 { 5.0121582440e-09, // atan(0.5)lo 3.7748947079e-08, // atan(1.0)lo 3.4473217170e-08, // atan(1.5)lo 7.5497894159e-08, // atan(inf)lo }; const aT = []const f32 { 3.3333328366e-01, -1.9999158382e-01, 1.4253635705e-01, -1.0648017377e-01, 6.1687607318e-02, }; var x = x_; var ix: u32 = @bitCast(u32, x); const sign = ix >> 31; ix &= 0x7FFFFFFF; // |x| >= 2^26 if (ix >= 0x4C800000) { if (math.isNan(x)) { return x; } else { const z = atanhi[3] + 0x1.0p-120; return if (sign != 0) -z else z; } } var id: ?usize = undefined; // |x| < 0.4375 if (ix < 0x3EE00000) { // |x| < 2^(-12) if (ix < 0x39800000) { if (ix < 0x00800000) { math.forceEval(x * x); } return x; } id = null; } else { x = math.fabs(x); // |x| < 1.1875 if (ix < 0x3F980000) { // 7/16 <= |x| < 11/16 if (ix < 0x3F300000) { id = 0; x = (2.0 * x - 1.0) / (2.0 + x); } // 11/16 <= |x| < 19/16 else { id = 1; x = (x - 1.0) / (x + 1.0); } } else { // |x| < 2.4375 if (ix < 0x401C0000) { id = 2; x = (x - 1.5) / (1.0 + 1.5 * x); } // 2.4375 <= |x| < 2^26 else { id = 3; x = -1.0 / x; } } } const z = x * x; const w = z * z; const s1 = z * (aT[0] + w * (aT[2] + w * aT[4])); const s2 = w * (aT[1] + w * aT[3]); if (id) |id_value| { const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x); return if (sign != 0) -zz else zz; } else { return x - x * (s1 + s2); } } fn atan64(x_: f64) -> f64 { const atanhi = []const f64 { 4.63647609000806093515e-01, // atan(0.5)hi 7.85398163397448278999e-01, // atan(1.0)hi 9.82793723247329054082e-01, // atan(1.5)hi 1.57079632679489655800e+00, // atan(inf)hi }; const atanlo = []const f64 { 2.26987774529616870924e-17, // atan(0.5)lo 3.06161699786838301793e-17, // atan(1.0)lo 1.39033110312309984516e-17, // atan(1.5)lo 6.12323399573676603587e-17, // atan(inf)lo }; const aT = []const f64 { 3.33333333333329318027e-01, -1.99999999998764832476e-01, 1.42857142725034663711e-01, -1.11111104054623557880e-01, 9.09088713343650656196e-02, -7.69187620504482999495e-02, 6.66107313738753120669e-02, -5.83357013379057348645e-02, 4.97687799461593236017e-02, -3.65315727442169155270e-02, 1.62858201153657823623e-02, }; var x = x_; var ux = @bitCast(u64, x); var ix = u32(ux >> 32); const sign = ix >> 31; ix &= 0x7FFFFFFF; // |x| >= 2^66 if (ix >= 0x44100000) { if (math.isNan(x)) { return x; } else { const z = atanhi[3] + 0x1.0p-120; return if (sign != 0) -z else z; } } var id: ?usize = undefined; // |x| < 0.4375 if (ix < 0x3DFC0000) { // |x| < 2^(-27) if (ix < 0x3E400000) { if (ix < 0x00100000) { math.forceEval(f32(x)); } return x; } id = null; } else { x = math.fabs(x); // |x| < 1.1875 if (ix < 0x3FF30000) { // 7/16 <= |x| < 11/16 if (ix < 0x3FE60000) { id = 0; x = (2.0 * x - 1.0) / (2.0 + x); } // 11/16 <= |x| < 19/16 else { id = 1; x = (x - 1.0) / (x + 1.0); } } else { // |x| < 2.4375 if (ix < 0x40038000) { id = 2; x = (x - 1.5) / (1.0 + 1.5 * x); } // 2.4375 <= |x| < 2^66 else { id = 3; x = -1.0 / x; } } } const z = x * x; const w = z * z; const s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * aT[10]))))); const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * aT[9])))); if (id) |id_value| { const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x); return if (sign != 0) -zz else zz; } else { return x - x * (s1 + s2); } } test "math.atan" { assert(@bitCast(u32, atan(f32(0.2))) == @bitCast(u32, atan32(0.2))); assert(atan(f64(0.2)) == atan64(0.2)); } test "math.atan32" { const epsilon = 0.000001; assert(math.approxEq(f32, atan32(0.2), 0.197396, epsilon)); assert(math.approxEq(f32, atan32(-0.2), -0.197396, epsilon)); assert(math.approxEq(f32, atan32(0.3434), 0.330783, epsilon)); assert(math.approxEq(f32, atan32(0.8923), 0.728545, epsilon)); assert(math.approxEq(f32, atan32(1.5), 0.982794, epsilon)); } test "math.atan64" { const epsilon = 0.000001; assert(math.approxEq(f64, atan64(0.2), 0.197396, epsilon)); assert(math.approxEq(f64, atan64(-0.2), -0.197396, epsilon)); assert(math.approxEq(f64, atan64(0.3434), 0.330783, epsilon)); assert(math.approxEq(f64, atan64(0.8923), 0.728545, epsilon)); assert(math.approxEq(f64, atan64(1.5), 0.982794, epsilon)); } test "math.atan32.special" { const epsilon = 0.000001; assert(atan32(0.0) == 0.0); assert(atan32(-0.0) == -0.0); assert(math.approxEq(f32, atan32(math.inf(f32)), math.pi / 2.0, epsilon)); assert(math.approxEq(f32, atan32(-math.inf(f32)), -math.pi / 2.0, epsilon)); } test "math.atan64.special" { const epsilon = 0.000001; assert(atan64(0.0) == 0.0); assert(atan64(-0.0) == -0.0); assert(math.approxEq(f64, atan64(math.inf(f64)), math.pi / 2.0, epsilon)); assert(math.approxEq(f64, atan64(-math.inf(f64)), -math.pi / 2.0, epsilon)); }