diff options
Diffstat (limited to 'lib/std/math')
70 files changed, 11519 insertions, 0 deletions
diff --git a/lib/std/math/acos.zig b/lib/std/math/acos.zig new file mode 100644 index 0000000000..de07da8fe0 --- /dev/null +++ b/lib/std/math/acos.zig @@ -0,0 +1,186 @@ +// 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/math/acosf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/acos.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the arc-cosine of x. +/// +/// Special cases: +/// - acos(x) = nan if x < -1 or x > 1 +pub fn acos(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => acos32(x), + f64 => acos64(x), + else => @compileError("acos not implemented for " ++ @typeName(T)), + }; +} + +fn r32(z: f32) f32 { + const pS0 = 1.6666586697e-01; + const pS1 = -4.2743422091e-02; + const pS2 = -8.6563630030e-03; + const qS1 = -7.0662963390e-01; + + const p = z * (pS0 + z * (pS1 + z * pS2)); + const q = 1.0 + z * qS1; + return p / q; +} + +fn acos32(x: f32) f32 { + const pio2_hi = 1.5707962513e+00; + const pio2_lo = 7.5497894159e-08; + + const hx: u32 = @bitCast(u32, x); + const ix: u32 = hx & 0x7FFFFFFF; + + // |x| >= 1 or nan + if (ix >= 0x3F800000) { + if (ix == 0x3F800000) { + if (hx >> 31 != 0) { + return 2.0 * pio2_hi + 0x1.0p-120; + } else { + return 0.0; + } + } else { + return math.nan(f32); + } + } + + // |x| < 0.5 + if (ix < 0x3F000000) { + if (ix <= 0x32800000) { // |x| < 2^(-26) + return pio2_hi + 0x1.0p-120; + } else { + return pio2_hi - (x - (pio2_lo - x * r32(x * x))); + } + } + + // x < -0.5 + if (hx >> 31 != 0) { + const z = (1 + x) * 0.5; + const s = math.sqrt(z); + const w = r32(z) * s - pio2_lo; + return 2 * (pio2_hi - (s + w)); + } + + // x > 0.5 + const z = (1.0 - x) * 0.5; + const s = math.sqrt(z); + const jx = @bitCast(u32, s); + const df = @bitCast(f32, jx & 0xFFFFF000); + const c = (z - df * df) / (s + df); + const w = r32(z) * s + c; + return 2 * (df + w); +} + +fn r64(z: f64) f64 { + const pS0: f64 = 1.66666666666666657415e-01; + const pS1: f64 = -3.25565818622400915405e-01; + const pS2: f64 = 2.01212532134862925881e-01; + const pS3: f64 = -4.00555345006794114027e-02; + const pS4: f64 = 7.91534994289814532176e-04; + const pS5: f64 = 3.47933107596021167570e-05; + const qS1: f64 = -2.40339491173441421878e+00; + const qS2: f64 = 2.02094576023350569471e+00; + const qS3: f64 = -6.88283971605453293030e-01; + const qS4: f64 = 7.70381505559019352791e-02; + + const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * pS5))))); + const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * qS4))); + return p / q; +} + +fn acos64(x: f64) f64 { + const pio2_hi: f64 = 1.57079632679489655800e+00; + const pio2_lo: f64 = 6.12323399573676603587e-17; + + const ux = @bitCast(u64, x); + const hx = @intCast(u32, ux >> 32); + const ix = hx & 0x7FFFFFFF; + + // |x| >= 1 or nan + if (ix >= 0x3FF00000) { + const lx = @intCast(u32, ux & 0xFFFFFFFF); + + // acos(1) = 0, acos(-1) = pi + if ((ix - 0x3FF00000) | lx == 0) { + if (hx >> 31 != 0) { + return 2 * pio2_hi + 0x1.0p-120; + } else { + return 0; + } + } + + return math.nan(f32); + } + + // |x| < 0.5 + if (ix < 0x3FE00000) { + // |x| < 2^(-57) + if (ix <= 0x3C600000) { + return pio2_hi + 0x1.0p-120; + } else { + return pio2_hi - (x - (pio2_lo - x * r64(x * x))); + } + } + + // x < -0.5 + if (hx >> 31 != 0) { + const z = (1.0 + x) * 0.5; + const s = math.sqrt(z); + const w = r64(z) * s - pio2_lo; + return 2 * (pio2_hi - (s + w)); + } + + // x > 0.5 + const z = (1.0 - x) * 0.5; + const s = math.sqrt(z); + const jx = @bitCast(u64, s); + const df = @bitCast(f64, jx & 0xFFFFFFFF00000000); + const c = (z - df * df) / (s + df); + const w = r64(z) * s + c; + return 2 * (df + w); +} + +test "math.acos" { + expect(acos(f32(0.0)) == acos32(0.0)); + expect(acos(f64(0.0)) == acos64(0.0)); +} + +test "math.acos32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, acos32(0.0), 1.570796, epsilon)); + expect(math.approxEq(f32, acos32(0.2), 1.369438, epsilon)); + expect(math.approxEq(f32, acos32(0.3434), 1.220262, epsilon)); + expect(math.approxEq(f32, acos32(0.5), 1.047198, epsilon)); + expect(math.approxEq(f32, acos32(0.8923), 0.468382, epsilon)); + expect(math.approxEq(f32, acos32(-0.2), 1.772154, epsilon)); +} + +test "math.acos64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, acos64(0.0), 1.570796, epsilon)); + expect(math.approxEq(f64, acos64(0.2), 1.369438, epsilon)); + expect(math.approxEq(f64, acos64(0.3434), 1.220262, epsilon)); + expect(math.approxEq(f64, acos64(0.5), 1.047198, epsilon)); + expect(math.approxEq(f64, acos64(0.8923), 0.468382, epsilon)); + expect(math.approxEq(f64, acos64(-0.2), 1.772154, epsilon)); +} + +test "math.acos32.special" { + expect(math.isNan(acos32(-2))); + expect(math.isNan(acos32(1.5))); +} + +test "math.acos64.special" { + expect(math.isNan(acos64(-2))); + expect(math.isNan(acos64(1.5))); +} diff --git a/lib/std/math/acosh.zig b/lib/std/math/acosh.zig new file mode 100644 index 0000000000..503c0433fc --- /dev/null +++ b/lib/std/math/acosh.zig @@ -0,0 +1,94 @@ +// 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/math/acoshf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/acosh.c + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the hyperbolic arc-cosine of x. +/// +/// Special cases: +/// - acosh(x) = snan if x < 1 +/// - acosh(nan) = nan +pub fn acosh(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => acosh32(x), + f64 => acosh64(x), + else => @compileError("acosh not implemented for " ++ @typeName(T)), + }; +} + +// acosh(x) = log(x + sqrt(x * x - 1)) +fn acosh32(x: f32) f32 { + const u = @bitCast(u32, x); + const i = u & 0x7FFFFFFF; + + // |x| < 2, invalid if x < 1 or nan + if (i < 0x3F800000 + (1 << 23)) { + return math.log1p(x - 1 + math.sqrt((x - 1) * (x - 1) + 2 * (x - 1))); + } + // |x| < 0x1p12 + else if (i < 0x3F800000 + (12 << 23)) { + return math.ln(2 * x - 1 / (x + math.sqrt(x * x - 1))); + } + // |x| >= 0x1p12 + else { + return math.ln(x) + 0.693147180559945309417232121458176568; + } +} + +fn acosh64(x: f64) f64 { + const u = @bitCast(u64, x); + const e = (u >> 52) & 0x7FF; + + // |x| < 2, invalid if x < 1 or nan + if (e < 0x3FF + 1) { + return math.log1p(x - 1 + math.sqrt((x - 1) * (x - 1) + 2 * (x - 1))); + } + // |x| < 0x1p26 + else if (e < 0x3FF + 26) { + return math.ln(2 * x - 1 / (x + math.sqrt(x * x - 1))); + } + // |x| >= 0x1p26 or nan + else { + return math.ln(x) + 0.693147180559945309417232121458176568; + } +} + +test "math.acosh" { + expect(acosh(f32(1.5)) == acosh32(1.5)); + expect(acosh(f64(1.5)) == acosh64(1.5)); +} + +test "math.acosh32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, acosh32(1.5), 0.962424, epsilon)); + expect(math.approxEq(f32, acosh32(37.45), 4.315976, epsilon)); + expect(math.approxEq(f32, acosh32(89.123), 5.183133, epsilon)); + expect(math.approxEq(f32, acosh32(123123.234375), 12.414088, epsilon)); +} + +test "math.acosh64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, acosh64(1.5), 0.962424, epsilon)); + expect(math.approxEq(f64, acosh64(37.45), 4.315976, epsilon)); + expect(math.approxEq(f64, acosh64(89.123), 5.183133, epsilon)); + expect(math.approxEq(f64, acosh64(123123.234375), 12.414088, epsilon)); +} + +test "math.acosh32.special" { + expect(math.isNan(acosh32(math.nan(f32)))); + expect(math.isSignalNan(acosh32(0.5))); +} + +test "math.acosh64.special" { + expect(math.isNan(acosh64(math.nan(f64)))); + expect(math.isSignalNan(acosh64(0.5))); +} diff --git a/lib/std/math/asin.zig b/lib/std/math/asin.zig new file mode 100644 index 0000000000..2db9f86ff1 --- /dev/null +++ b/lib/std/math/asin.zig @@ -0,0 +1,183 @@ +// 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/math/asinf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/asin.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the arc-sin of x. +/// +/// Special Cases: +/// - asin(+-0) = +-0 +/// - asin(x) = nan if x < -1 or x > 1 +pub fn asin(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => asin32(x), + f64 => asin64(x), + else => @compileError("asin not implemented for " ++ @typeName(T)), + }; +} + +fn r32(z: f32) f32 { + const pS0 = 1.6666586697e-01; + const pS1 = -4.2743422091e-02; + const pS2 = -8.6563630030e-03; + const qS1 = -7.0662963390e-01; + + const p = z * (pS0 + z * (pS1 + z * pS2)); + const q = 1.0 + z * qS1; + return p / q; +} + +fn asin32(x: f32) f32 { + const pio2 = 1.570796326794896558e+00; + + const hx: u32 = @bitCast(u32, x); + const ix: u32 = hx & 0x7FFFFFFF; + + // |x| >= 1 + if (ix >= 0x3F800000) { + // |x| >= 1 + if (ix == 0x3F800000) { + return x * pio2 + 0x1.0p-120; // asin(+-1) = +-pi/2 with inexact + } else { + return math.nan(f32); // asin(|x| > 1) is nan + } + } + + // |x| < 0.5 + if (ix < 0x3F000000) { + // 0x1p-126 <= |x| < 0x1p-12 + if (ix < 0x39800000 and ix >= 0x00800000) { + return x; + } else { + return x + x * r32(x * x); + } + } + + // 1 > |x| >= 0.5 + const z = (1 - math.fabs(x)) * 0.5; + const s = math.sqrt(z); + const fx = pio2 - 2 * (s + s * r32(z)); + + if (hx >> 31 != 0) { + return -fx; + } else { + return fx; + } +} + +fn r64(z: f64) f64 { + const pS0: f64 = 1.66666666666666657415e-01; + const pS1: f64 = -3.25565818622400915405e-01; + const pS2: f64 = 2.01212532134862925881e-01; + const pS3: f64 = -4.00555345006794114027e-02; + const pS4: f64 = 7.91534994289814532176e-04; + const pS5: f64 = 3.47933107596021167570e-05; + const qS1: f64 = -2.40339491173441421878e+00; + const qS2: f64 = 2.02094576023350569471e+00; + const qS3: f64 = -6.88283971605453293030e-01; + const qS4: f64 = 7.70381505559019352791e-02; + + const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * pS5))))); + const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * qS4))); + return p / q; +} + +fn asin64(x: f64) f64 { + const pio2_hi: f64 = 1.57079632679489655800e+00; + const pio2_lo: f64 = 6.12323399573676603587e-17; + + const ux = @bitCast(u64, x); + const hx = @intCast(u32, ux >> 32); + const ix = hx & 0x7FFFFFFF; + + // |x| >= 1 or nan + if (ix >= 0x3FF00000) { + const lx = @intCast(u32, ux & 0xFFFFFFFF); + + // asin(1) = +-pi/2 with inexact + if ((ix - 0x3FF00000) | lx == 0) { + return x * pio2_hi + 0x1.0p-120; + } else { + return math.nan(f64); + } + } + + // |x| < 0.5 + if (ix < 0x3FE00000) { + // if 0x1p-1022 <= |x| < 0x1p-26 avoid raising overflow + if (ix < 0x3E500000 and ix >= 0x00100000) { + return x; + } else { + return x + x * r64(x * x); + } + } + + // 1 > |x| >= 0.5 + const z = (1 - math.fabs(x)) * 0.5; + const s = math.sqrt(z); + const r = r64(z); + var fx: f64 = undefined; + + // |x| > 0.975 + if (ix >= 0x3FEF3333) { + fx = pio2_hi - 2 * (s + s * r); + } else { + const jx = @bitCast(u64, s); + const df = @bitCast(f64, jx & 0xFFFFFFFF00000000); + const c = (z - df * df) / (s + df); + fx = 0.5 * pio2_hi - (2 * s * r - (pio2_lo - 2 * c) - (0.5 * pio2_hi - 2 * df)); + } + + if (hx >> 31 != 0) { + return -fx; + } else { + return fx; + } +} + +test "math.asin" { + expect(asin(f32(0.0)) == asin32(0.0)); + expect(asin(f64(0.0)) == asin64(0.0)); +} + +test "math.asin32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, asin32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, asin32(0.2), 0.201358, epsilon)); + expect(math.approxEq(f32, asin32(-0.2), -0.201358, epsilon)); + expect(math.approxEq(f32, asin32(0.3434), 0.350535, epsilon)); + expect(math.approxEq(f32, asin32(0.5), 0.523599, epsilon)); + expect(math.approxEq(f32, asin32(0.8923), 1.102415, epsilon)); +} + +test "math.asin64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, asin64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, asin64(0.2), 0.201358, epsilon)); + expect(math.approxEq(f64, asin64(-0.2), -0.201358, epsilon)); + expect(math.approxEq(f64, asin64(0.3434), 0.350535, epsilon)); + expect(math.approxEq(f64, asin64(0.5), 0.523599, epsilon)); + expect(math.approxEq(f64, asin64(0.8923), 1.102415, epsilon)); +} + +test "math.asin32.special" { + expect(asin32(0.0) == 0.0); + expect(asin32(-0.0) == -0.0); + expect(math.isNan(asin32(-2))); + expect(math.isNan(asin32(1.5))); +} + +test "math.asin64.special" { + expect(asin64(0.0) == 0.0); + expect(asin64(-0.0) == -0.0); + expect(math.isNan(asin64(-2))); + expect(math.isNan(asin64(1.5))); +} diff --git a/lib/std/math/asinh.zig b/lib/std/math/asinh.zig new file mode 100644 index 0000000000..0fb51d1b43 --- /dev/null +++ b/lib/std/math/asinh.zig @@ -0,0 +1,134 @@ +// 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/math/asinhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/asinh.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns the hyperbolic arc-sin of x. +/// +/// Special Cases: +/// - asinh(+-0) = +-0 +/// - asinh(+-inf) = +-inf +/// - asinh(nan) = nan +pub fn asinh(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => asinh32(x), + f64 => asinh64(x), + else => @compileError("asinh not implemented for " ++ @typeName(T)), + }; +} + +// asinh(x) = sign(x) * log(|x| + sqrt(x * x + 1)) ~= x - x^3/6 + o(x^5) +fn asinh32(x: f32) f32 { + const u = @bitCast(u32, x); + const i = u & 0x7FFFFFFF; + const s = i >> 31; + + var rx = @bitCast(f32, i); // |x| + + // TODO: Shouldn't need this explicit check. + if (math.isNegativeInf(x)) { + return x; + } + + // |x| >= 0x1p12 or inf or nan + if (i >= 0x3F800000 + (12 << 23)) { + rx = math.ln(rx) + 0.69314718055994530941723212145817656; + } + // |x| >= 2 + else if (i >= 0x3F800000 + (1 << 23)) { + rx = math.ln(2 * x + 1 / (math.sqrt(x * x + 1) + x)); + } + // |x| >= 0x1p-12, up to 1.6ulp error + else if (i >= 0x3F800000 - (12 << 23)) { + rx = math.log1p(x + x * x / (math.sqrt(x * x + 1) + 1)); + } + // |x| < 0x1p-12, inexact if x != 0 + else { + math.forceEval(x + 0x1.0p120); + } + + return if (s != 0) -rx else rx; +} + +fn asinh64(x: f64) f64 { + const u = @bitCast(u64, x); + const e = (u >> 52) & 0x7FF; + const s = u >> 63; + + var rx = @bitCast(f64, u & (maxInt(u64) >> 1)); // |x| + + if (math.isNegativeInf(x)) { + return x; + } + + // |x| >= 0x1p26 or inf or nan + if (e >= 0x3FF + 26) { + rx = math.ln(rx) + 0.693147180559945309417232121458176568; + } + // |x| >= 2 + else if (e >= 0x3FF + 1) { + rx = math.ln(2 * x + 1 / (math.sqrt(x * x + 1) + x)); + } + // |x| >= 0x1p-12, up to 1.6ulp error + else if (e >= 0x3FF - 26) { + rx = math.log1p(x + x * x / (math.sqrt(x * x + 1) + 1)); + } + // |x| < 0x1p-12, inexact if x != 0 + else { + math.forceEval(x + 0x1.0p120); + } + + return if (s != 0) -rx else rx; +} + +test "math.asinh" { + expect(asinh(f32(0.0)) == asinh32(0.0)); + expect(asinh(f64(0.0)) == asinh64(0.0)); +} + +test "math.asinh32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, asinh32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, asinh32(0.2), 0.198690, epsilon)); + expect(math.approxEq(f32, asinh32(0.8923), 0.803133, epsilon)); + expect(math.approxEq(f32, asinh32(1.5), 1.194763, epsilon)); + expect(math.approxEq(f32, asinh32(37.45), 4.316332, epsilon)); + expect(math.approxEq(f32, asinh32(89.123), 5.183196, epsilon)); + expect(math.approxEq(f32, asinh32(123123.234375), 12.414088, epsilon)); +} + +test "math.asinh64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, asinh64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, asinh64(0.2), 0.198690, epsilon)); + expect(math.approxEq(f64, asinh64(0.8923), 0.803133, epsilon)); + expect(math.approxEq(f64, asinh64(1.5), 1.194763, epsilon)); + expect(math.approxEq(f64, asinh64(37.45), 4.316332, epsilon)); + expect(math.approxEq(f64, asinh64(89.123), 5.183196, epsilon)); + expect(math.approxEq(f64, asinh64(123123.234375), 12.414088, epsilon)); +} + +test "math.asinh32.special" { + expect(asinh32(0.0) == 0.0); + expect(asinh32(-0.0) == -0.0); + expect(math.isPositiveInf(asinh32(math.inf(f32)))); + expect(math.isNegativeInf(asinh32(-math.inf(f32)))); + expect(math.isNan(asinh32(math.nan(f32)))); +} + +test "math.asinh64.special" { + expect(asinh64(0.0) == 0.0); + expect(asinh64(-0.0) == -0.0); + expect(math.isPositiveInf(asinh64(math.inf(f64)))); + expect(math.isNegativeInf(asinh64(-math.inf(f64)))); + expect(math.isNan(asinh64(math.nan(f64)))); +} diff --git a/lib/std/math/atan.zig b/lib/std/math/atan.zig new file mode 100644 index 0000000000..5790eba8cf --- /dev/null +++ b/lib/std/math/atan.zig @@ -0,0 +1,255 @@ +// 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/math/atanf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atan.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the arc-tangent of x. +/// +/// Special Cases: +/// - atan(+-0) = +-0 +/// - atan(+-inf) = +-pi/2 +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 = [_]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 = [_]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 = [_]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 = [_]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 = [_]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 = [_]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 = @intCast(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(@floatCast(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" { + expect(@bitCast(u32, atan(f32(0.2))) == @bitCast(u32, atan32(0.2))); + expect(atan(f64(0.2)) == atan64(0.2)); +} + +test "math.atan32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, atan32(0.2), 0.197396, epsilon)); + expect(math.approxEq(f32, atan32(-0.2), -0.197396, epsilon)); + expect(math.approxEq(f32, atan32(0.3434), 0.330783, epsilon)); + expect(math.approxEq(f32, atan32(0.8923), 0.728545, epsilon)); + expect(math.approxEq(f32, atan32(1.5), 0.982794, epsilon)); +} + +test "math.atan64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, atan64(0.2), 0.197396, epsilon)); + expect(math.approxEq(f64, atan64(-0.2), -0.197396, epsilon)); + expect(math.approxEq(f64, atan64(0.3434), 0.330783, epsilon)); + expect(math.approxEq(f64, atan64(0.8923), 0.728545, epsilon)); + expect(math.approxEq(f64, atan64(1.5), 0.982794, epsilon)); +} + +test "math.atan32.special" { + const epsilon = 0.000001; + + expect(atan32(0.0) == 0.0); + expect(atan32(-0.0) == -0.0); + expect(math.approxEq(f32, atan32(math.inf(f32)), math.pi / 2.0, epsilon)); + expect(math.approxEq(f32, atan32(-math.inf(f32)), -math.pi / 2.0, epsilon)); +} + +test "math.atan64.special" { + const epsilon = 0.000001; + + expect(atan64(0.0) == 0.0); + expect(atan64(-0.0) == -0.0); + expect(math.approxEq(f64, atan64(math.inf(f64)), math.pi / 2.0, epsilon)); + expect(math.approxEq(f64, atan64(-math.inf(f64)), -math.pi / 2.0, epsilon)); +} diff --git a/lib/std/math/atan2.zig b/lib/std/math/atan2.zig new file mode 100644 index 0000000000..68e381607d --- /dev/null +++ b/lib/std/math/atan2.zig @@ -0,0 +1,289 @@ +// 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/math/atan2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atan2.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the arc-tangent of y/x. +/// +/// Special Cases: +/// - atan2(y, nan) = nan +/// - atan2(nan, x) = nan +/// - atan2(+0, x>=0) = +0 +/// - atan2(-0, x>=0) = -0 +/// - atan2(+0, x<=-0) = +pi +/// - atan2(-0, x<=-0) = -pi +/// - atan2(y>0, 0) = +pi/2 +/// - atan2(y<0, 0) = -pi/2 +/// - atan2(+inf, +inf) = +pi/4 +/// - atan2(-inf, +inf) = -pi/4 +/// - atan2(+inf, -inf) = 3pi/4 +/// - atan2(-inf, -inf) = -3pi/4 +/// - atan2(y, +inf) = 0 +/// - atan2(y>0, -inf) = +pi +/// - atan2(y<0, -inf) = -pi +/// - atan2(+inf, x) = +pi/2 +/// - atan2(-inf, x) = -pi/2 +pub fn atan2(comptime T: type, y: T, x: T) T { + return switch (T) { + f32 => atan2_32(y, x), + f64 => atan2_64(y, x), + else => @compileError("atan2 not implemented for " ++ @typeName(T)), + }; +} + +fn atan2_32(y: f32, x: f32) f32 { + const pi: f32 = 3.1415927410e+00; + const pi_lo: f32 = -8.7422776573e-08; + + if (math.isNan(x) or math.isNan(y)) { + return x + y; + } + + var ix = @bitCast(u32, x); + var iy = @bitCast(u32, y); + + // x = 1.0 + if (ix == 0x3F800000) { + return math.atan(y); + } + + // 2 * sign(x) + sign(y) + const m = ((iy >> 31) & 1) | ((ix >> 30) & 2); + ix &= 0x7FFFFFFF; + iy &= 0x7FFFFFFF; + + if (iy == 0) { + switch (m) { + 0, 1 => return y, // atan(+-0, +...) + 2 => return pi, // atan(+0, -...) + 3 => return -pi, // atan(-0, -...) + else => unreachable, + } + } + + if (ix == 0) { + if (m & 1 != 0) { + return -pi / 2; + } else { + return pi / 2; + } + } + + if (ix == 0x7F800000) { + if (iy == 0x7F800000) { + switch (m) { + 0 => return pi / 4, // atan(+inf, +inf) + 1 => return -pi / 4, // atan(-inf, +inf) + 2 => return 3 * pi / 4, // atan(+inf, -inf) + 3 => return -3 * pi / 4, // atan(-inf, -inf) + else => unreachable, + } + } else { + switch (m) { + 0 => return 0.0, // atan(+..., +inf) + 1 => return -0.0, // atan(-..., +inf) + 2 => return pi, // atan(+..., -inf) + 3 => return -pi, // atan(-...f, -inf) + else => unreachable, + } + } + } + + // |y / x| > 0x1p26 + if (ix + (26 << 23) < iy or iy == 0x7F800000) { + if (m & 1 != 0) { + return -pi / 2; + } else { + return pi / 2; + } + } + + // z = atan(|y / x|) with correct underflow + var z = z: { + if ((m & 2) != 0 and iy + (26 << 23) < ix) { + break :z 0.0; + } else { + break :z math.atan(math.fabs(y / x)); + } + }; + + switch (m) { + 0 => return z, // atan(+, +) + 1 => return -z, // atan(-, +) + 2 => return pi - (z - pi_lo), // atan(+, -) + 3 => return (z - pi_lo) - pi, // atan(-, -) + else => unreachable, + } +} + +fn atan2_64(y: f64, x: f64) f64 { + const pi: f64 = 3.1415926535897931160E+00; + const pi_lo: f64 = 1.2246467991473531772E-16; + + if (math.isNan(x) or math.isNan(y)) { + return x + y; + } + + var ux = @bitCast(u64, x); + var ix = @intCast(u32, ux >> 32); + var lx = @intCast(u32, ux & 0xFFFFFFFF); + + var uy = @bitCast(u64, y); + var iy = @intCast(u32, uy >> 32); + var ly = @intCast(u32, uy & 0xFFFFFFFF); + + // x = 1.0 + if ((ix -% 0x3FF00000) | lx == 0) { + return math.atan(y); + } + + // 2 * sign(x) + sign(y) + const m = ((iy >> 31) & 1) | ((ix >> 30) & 2); + ix &= 0x7FFFFFFF; + iy &= 0x7FFFFFFF; + + if (iy | ly == 0) { + switch (m) { + 0, 1 => return y, // atan(+-0, +...) + 2 => return pi, // atan(+0, -...) + 3 => return -pi, // atan(-0, -...) + else => unreachable, + } + } + + if (ix | lx == 0) { + if (m & 1 != 0) { + return -pi / 2; + } else { + return pi / 2; + } + } + + if (ix == 0x7FF00000) { + if (iy == 0x7FF00000) { + switch (m) { + 0 => return pi / 4, // atan(+inf, +inf) + 1 => return -pi / 4, // atan(-inf, +inf) + 2 => return 3 * pi / 4, // atan(+inf, -inf) + 3 => return -3 * pi / 4, // atan(-inf, -inf) + else => unreachable, + } + } else { + switch (m) { + 0 => return 0.0, // atan(+..., +inf) + 1 => return -0.0, // atan(-..., +inf) + 2 => return pi, // atan(+..., -inf) + 3 => return -pi, // atan(-...f, -inf) + else => unreachable, + } + } + } + + // |y / x| > 0x1p64 + if (ix +% (64 << 20) < iy or iy == 0x7FF00000) { + if (m & 1 != 0) { + return -pi / 2; + } else { + return pi / 2; + } + } + + // z = atan(|y / x|) with correct underflow + var z = z: { + if ((m & 2) != 0 and iy +% (64 << 20) < ix) { + break :z 0.0; + } else { + break :z math.atan(math.fabs(y / x)); + } + }; + + switch (m) { + 0 => return z, // atan(+, +) + 1 => return -z, // atan(-, +) + 2 => return pi - (z - pi_lo), // atan(+, -) + 3 => return (z - pi_lo) - pi, // atan(-, -) + else => unreachable, + } +} + +test "math.atan2" { + expect(atan2(f32, 0.2, 0.21) == atan2_32(0.2, 0.21)); + expect(atan2(f64, 0.2, 0.21) == atan2_64(0.2, 0.21)); +} + +test "math.atan2_32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, atan2_32(0.0, 0.0), 0.0, epsilon)); + expect(math.approxEq(f32, atan2_32(0.2, 0.2), 0.785398, epsilon)); + expect(math.approxEq(f32, atan2_32(-0.2, 0.2), -0.785398, epsilon)); + expect(math.approxEq(f32, atan2_32(0.2, -0.2), 2.356194, epsilon)); + expect(math.approxEq(f32, atan2_32(-0.2, -0.2), -2.356194, epsilon)); + expect(math.approxEq(f32, atan2_32(0.34, -0.4), 2.437099, epsilon)); + expect(math.approxEq(f32, atan2_32(0.34, 1.243), 0.267001, epsilon)); +} + +test "math.atan2_64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, atan2_64(0.0, 0.0), 0.0, epsilon)); + expect(math.approxEq(f64, atan2_64(0.2, 0.2), 0.785398, epsilon)); + expect(math.approxEq(f64, atan2_64(-0.2, 0.2), -0.785398, epsilon)); + expect(math.approxEq(f64, atan2_64(0.2, -0.2), 2.356194, epsilon)); + expect(math.approxEq(f64, atan2_64(-0.2, -0.2), -2.356194, epsilon)); + expect(math.approxEq(f64, atan2_64(0.34, -0.4), 2.437099, epsilon)); + expect(math.approxEq(f64, atan2_64(0.34, 1.243), 0.267001, epsilon)); +} + +test "math.atan2_32.special" { + const epsilon = 0.000001; + + expect(math.isNan(atan2_32(1.0, math.nan(f32)))); + expect(math.isNan(atan2_32(math.nan(f32), 1.0))); + expect(atan2_32(0.0, 5.0) == 0.0); + expect(atan2_32(-0.0, 5.0) == -0.0); + expect(math.approxEq(f32, atan2_32(0.0, -5.0), math.pi, epsilon)); + //expect(math.approxEq(f32, atan2_32(-0.0, -5.0), -math.pi, epsilon)); TODO support negative zero? + expect(math.approxEq(f32, atan2_32(1.0, 0.0), math.pi / 2.0, epsilon)); + expect(math.approxEq(f32, atan2_32(1.0, -0.0), math.pi / 2.0, epsilon)); + expect(math.approxEq(f32, atan2_32(-1.0, 0.0), -math.pi / 2.0, epsilon)); + expect(math.approxEq(f32, atan2_32(-1.0, -0.0), -math.pi / 2.0, epsilon)); + expect(math.approxEq(f32, atan2_32(math.inf(f32), math.inf(f32)), math.pi / 4.0, epsilon)); + expect(math.approxEq(f32, atan2_32(-math.inf(f32), math.inf(f32)), -math.pi / 4.0, epsilon)); + expect(math.approxEq(f32, atan2_32(math.inf(f32), -math.inf(f32)), 3.0 * math.pi / 4.0, epsilon)); + expect(math.approxEq(f32, atan2_32(-math.inf(f32), -math.inf(f32)), -3.0 * math.pi / 4.0, epsilon)); + expect(atan2_32(1.0, math.inf(f32)) == 0.0); + expect(math.approxEq(f32, atan2_32(1.0, -math.inf(f32)), math.pi, epsilon)); + expect(math.approxEq(f32, atan2_32(-1.0, -math.inf(f32)), -math.pi, epsilon)); + expect(math.approxEq(f32, atan2_32(math.inf(f32), 1.0), math.pi / 2.0, epsilon)); + expect(math.approxEq(f32, atan2_32(-math.inf(f32), 1.0), -math.pi / 2.0, epsilon)); +} + +test "math.atan2_64.special" { + const epsilon = 0.000001; + + expect(math.isNan(atan2_64(1.0, math.nan(f64)))); + expect(math.isNan(atan2_64(math.nan(f64), 1.0))); + expect(atan2_64(0.0, 5.0) == 0.0); + expect(atan2_64(-0.0, 5.0) == -0.0); + expect(math.approxEq(f64, atan2_64(0.0, -5.0), math.pi, epsilon)); + //expect(math.approxEq(f64, atan2_64(-0.0, -5.0), -math.pi, epsilon)); TODO support negative zero? + expect(math.approxEq(f64, atan2_64(1.0, 0.0), math.pi / 2.0, epsilon)); + expect(math.approxEq(f64, atan2_64(1.0, -0.0), math.pi / 2.0, epsilon)); + expect(math.approxEq(f64, atan2_64(-1.0, 0.0), -math.pi / 2.0, epsilon)); + expect(math.approxEq(f64, atan2_64(-1.0, -0.0), -math.pi / 2.0, epsilon)); + expect(math.approxEq(f64, atan2_64(math.inf(f64), math.inf(f64)), math.pi / 4.0, epsilon)); + expect(math.approxEq(f64, atan2_64(-math.inf(f64), math.inf(f64)), -math.pi / 4.0, epsilon)); + expect(math.approxEq(f64, atan2_64(math.inf(f64), -math.inf(f64)), 3.0 * math.pi / 4.0, epsilon)); + expect(math.approxEq(f64, atan2_64(-math.inf(f64), -math.inf(f64)), -3.0 * math.pi / 4.0, epsilon)); + expect(atan2_64(1.0, math.inf(f64)) == 0.0); + expect(math.approxEq(f64, atan2_64(1.0, -math.inf(f64)), math.pi, epsilon)); + expect(math.approxEq(f64, atan2_64(-1.0, -math.inf(f64)), -math.pi, epsilon)); + expect(math.approxEq(f64, atan2_64(math.inf(f64), 1.0), math.pi / 2.0, epsilon)); + expect(math.approxEq(f64, atan2_64(-math.inf(f64), 1.0), -math.pi / 2.0, epsilon)); +} diff --git a/lib/std/math/atanh.zig b/lib/std/math/atanh.zig new file mode 100644 index 0000000000..8ba29be761 --- /dev/null +++ b/lib/std/math/atanh.zig @@ -0,0 +1,121 @@ +// 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/math/atanhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atanh.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns the hyperbolic arc-tangent of x. +/// +/// Special Cases: +/// - atanh(+-1) = +-inf with signal +/// - atanh(x) = nan if |x| > 1 with signal +/// - atanh(nan) = nan +pub fn atanh(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => atanh_32(x), + f64 => atanh_64(x), + else => @compileError("atanh not implemented for " ++ @typeName(T)), + }; +} + +// atanh(x) = log((1 + x) / (1 - x)) / 2 = log1p(2x / (1 - x)) / 2 ~= x + x^3 / 3 + o(x^5) +fn atanh_32(x: f32) f32 { + const u = @bitCast(u32, x); + const i = u & 0x7FFFFFFF; + const s = u >> 31; + + var y = @bitCast(f32, i); // |x| + + if (y == 1.0) { + return math.copysign(f32, math.inf(f32), x); + } + + if (u < 0x3F800000 - (1 << 23)) { + if (u < 0x3F800000 - (32 << 23)) { + // underflow + if (u < (1 << 23)) { + math.forceEval(y * y); + } + } + // |x| < 0.5 + else { + y = 0.5 * math.log1p(2 * y + 2 * y * y / (1 - y)); + } + } else { + y = 0.5 * math.log1p(2 * (y / (1 - y))); + } + + return if (s != 0) -y else y; +} + +fn atanh_64(x: f64) f64 { + const u = @bitCast(u64, x); + const e = (u >> 52) & 0x7FF; + const s = u >> 63; + + var y = @bitCast(f64, u & (maxInt(u64) >> 1)); // |x| + + if (y == 1.0) { + return math.copysign(f64, math.inf(f64), x); + } + + if (e < 0x3FF - 1) { + if (e < 0x3FF - 32) { + // underflow + if (e == 0) { + math.forceEval(@floatCast(f32, y)); + } + } + // |x| < 0.5 + else { + y = 0.5 * math.log1p(2 * y + 2 * y * y / (1 - y)); + } + } else { + y = 0.5 * math.log1p(2 * (y / (1 - y))); + } + + return if (s != 0) -y else y; +} + +test "math.atanh" { + expect(atanh(f32(0.0)) == atanh_32(0.0)); + expect(atanh(f64(0.0)) == atanh_64(0.0)); +} + +test "math.atanh_32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, atanh_32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, atanh_32(0.2), 0.202733, epsilon)); + expect(math.approxEq(f32, atanh_32(0.8923), 1.433099, epsilon)); +} + +test "math.atanh_64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, atanh_64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, atanh_64(0.2), 0.202733, epsilon)); + expect(math.approxEq(f64, atanh_64(0.8923), 1.433099, epsilon)); +} + +test "math.atanh32.special" { + expect(math.isPositiveInf(atanh_32(1))); + expect(math.isNegativeInf(atanh_32(-1))); + expect(math.isSignalNan(atanh_32(1.5))); + expect(math.isSignalNan(atanh_32(-1.5))); + expect(math.isNan(atanh_32(math.nan(f32)))); +} + +test "math.atanh64.special" { + expect(math.isPositiveInf(atanh_64(1))); + expect(math.isNegativeInf(atanh_64(-1))); + expect(math.isSignalNan(atanh_64(1.5))); + expect(math.isSignalNan(atanh_64(-1.5))); + expect(math.isNan(atanh_64(math.nan(f64)))); +} diff --git a/lib/std/math/big.zig b/lib/std/math/big.zig new file mode 100644 index 0000000000..8105beb506 --- /dev/null +++ b/lib/std/math/big.zig @@ -0,0 +1,7 @@ +pub usingnamespace @import("big/int.zig"); +pub usingnamespace @import("big/rational.zig"); + +test "math.big" { + _ = @import("big/int.zig"); + _ = @import("big/rational.zig"); +} diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig new file mode 100644 index 0000000000..8a6f6c1f75 --- /dev/null +++ b/lib/std/math/big/int.zig @@ -0,0 +1,2314 @@ +const std = @import("../../std.zig"); +const builtin = @import("builtin"); +const debug = std.debug; +const testing = std.testing; +const math = std.math; +const mem = std.mem; +const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; + +const TypeId = builtin.TypeId; + +pub const Limb = usize; +pub const DoubleLimb = @IntType(false, 2 * Limb.bit_count); +pub const Log2Limb = math.Log2Int(Limb); + +comptime { + debug.assert(math.floorPowerOfTwo(usize, Limb.bit_count) == Limb.bit_count); + debug.assert(Limb.bit_count <= 64); // u128 set is unsupported + debug.assert(Limb.is_signed == false); +} + +/// An arbitrary-precision big integer. +/// +/// Memory is allocated by an Int as needed to ensure operations never overflow. The range of an +/// Int is bounded only by available memory. +pub const Int = struct { + const sign_bit: usize = 1 << (usize.bit_count - 1); + + /// Default number of limbs to allocate on creation of an Int. + pub const default_capacity = 4; + + /// Allocator used by the Int when requesting memory. + allocator: ?*Allocator, + + /// Raw digits. These are: + /// + /// * Little-endian ordered + /// * limbs.len >= 1 + /// * Zero is represent as Int.len() == 1 with limbs[0] == 0. + /// + /// Accessing limbs directly should be avoided. + limbs: []Limb, + + /// High bit is the sign bit. If set, Int is negative, else Int is positive. + /// The remaining bits represent the number of limbs used by Int. + metadata: usize, + + /// Creates a new Int. default_capacity limbs will be allocated immediately. + /// Int will be zeroed. + pub fn init(allocator: *Allocator) !Int { + return try Int.initCapacity(allocator, default_capacity); + } + + /// Creates a new Int. Int will be set to `value`. + /// + /// This is identical to an `init`, followed by a `set`. + pub fn initSet(allocator: *Allocator, value: var) !Int { + var s = try Int.init(allocator); + try s.set(value); + return s; + } + + /// Creates a new Int with a specific capacity. If capacity < default_capacity then the + /// default capacity will be used instead. + pub fn initCapacity(allocator: *Allocator, capacity: usize) !Int { + return Int{ + .allocator = allocator, + .metadata = 1, + .limbs = block: { + var limbs = try allocator.alloc(Limb, math.max(default_capacity, capacity)); + limbs[0] = 0; + break :block limbs; + }, + }; + } + + /// Returns the number of limbs currently in use. + pub fn len(self: Int) usize { + return self.metadata & ~sign_bit; + } + + /// Returns whether an Int is positive. + pub fn isPositive(self: Int) bool { + return self.metadata & sign_bit == 0; + } + + /// Sets the sign of an Int. + pub fn setSign(self: *Int, positive: bool) void { + if (positive) { + self.metadata &= ~sign_bit; + } else { + self.metadata |= sign_bit; + } + } + + /// Sets the length of an Int. + /// + /// If setLen is used, then the Int must be normalized to suit. + pub fn setLen(self: *Int, new_len: usize) void { + self.metadata &= sign_bit; + self.metadata |= new_len; + } + + /// Returns an Int backed by a fixed set of limb values. + /// This is read-only and cannot be used as a result argument. If the Int tries to allocate + /// memory a runtime panic will occur. + pub fn initFixed(limbs: []const Limb) Int { + var self = Int{ + .allocator = null, + .metadata = limbs.len, + // Cast away the const, invalid use to pass as a pointer argument. + .limbs = @intToPtr([*]Limb, @ptrToInt(limbs.ptr))[0..limbs.len], + }; + + self.normalize(limbs.len); + return self; + } + + /// Ensures an Int has enough space allocated for capacity limbs. If the Int does not have + /// sufficient capacity, the exact amount will be allocated. This occurs even if the requested + /// capacity is only greater than the current capacity by one limb. + pub fn ensureCapacity(self: *Int, capacity: usize) !void { + self.assertWritable(); + if (capacity <= self.limbs.len) { + return; + } + + self.limbs = try self.allocator.?.realloc(self.limbs, capacity); + } + + fn assertWritable(self: Int) void { + if (self.allocator == null) { + @panic("provided Int value is read-only but must be writable"); + } + } + + /// Frees all memory associated with an Int. + pub fn deinit(self: *Int) void { + self.assertWritable(); + self.allocator.?.free(self.limbs); + self.* = undefined; + } + + /// Clones an Int and returns a new Int with the same value. The new Int is a deep copy and + /// can be modified separately from the original. + pub fn clone(other: Int) !Int { + other.assertWritable(); + return Int{ + .allocator = other.allocator, + .metadata = other.metadata, + .limbs = block: { + var limbs = try other.allocator.?.alloc(Limb, other.len()); + mem.copy(Limb, limbs[0..], other.limbs[0..other.len()]); + break :block limbs; + }, + }; + } + + /// Copies the value of an Int to an existing Int so that they both have the same value. + /// Extra memory will be allocated if the receiver does not have enough capacity. + pub fn copy(self: *Int, other: Int) !void { + self.assertWritable(); + if (self.limbs.ptr == other.limbs.ptr) { + return; + } + + try self.ensureCapacity(other.len()); + mem.copy(Limb, self.limbs[0..], other.limbs[0..other.len()]); + self.metadata = other.metadata; + } + + /// Efficiently swap an Int with another. This swaps the limb pointers and a full copy is not + /// performed. The address of the limbs field will not be the same after this function. + pub fn swap(self: *Int, other: *Int) void { + self.assertWritable(); + mem.swap(Int, self, other); + } + + pub fn dump(self: Int) void { + for (self.limbs) |limb| { + debug.warn("{x} ", limb); + } + debug.warn("\n"); + } + + /// Negate the sign of an Int. + pub fn negate(self: *Int) void { + self.metadata ^= sign_bit; + } + + /// Make an Int positive. + pub fn abs(self: *Int) void { + self.metadata &= ~sign_bit; + } + + /// Returns true if an Int is odd. + pub fn isOdd(self: Int) bool { + return self.limbs[0] & 1 != 0; + } + + /// Returns true if an Int is even. + pub fn isEven(self: Int) bool { + return !self.isOdd(); + } + + /// Returns the number of bits required to represent the absolute value an Int. + fn bitCountAbs(self: Int) usize { + return (self.len() - 1) * Limb.bit_count + (Limb.bit_count - @clz(Limb, self.limbs[self.len() - 1])); + } + + /// Returns the number of bits required to represent the integer in twos-complement form. + /// + /// If the integer is negative the value returned is the number of bits needed by a signed + /// integer to represent the value. If positive the value is the number of bits for an + /// unsigned integer. Any unsigned integer will fit in the signed integer with bitcount + /// one greater than the returned value. + /// + /// e.g. -127 returns 8 as it will fit in an i8. 127 returns 7 since it fits in a u7. + fn bitCountTwosComp(self: Int) usize { + var bits = self.bitCountAbs(); + + // If the entire value has only one bit set (e.g. 0b100000000) then the negation in twos + // complement requires one less bit. + if (!self.isPositive()) block: { + bits += 1; + + if (@popCount(Limb, self.limbs[self.len() - 1]) == 1) { + for (self.limbs[0 .. self.len() - 1]) |limb| { + if (@popCount(Limb, limb) != 0) { + break :block; + } + } + + bits -= 1; + } + } + + return bits; + } + + fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool { + if (self.eqZero()) { + return true; + } + if (!is_signed and !self.isPositive()) { + return false; + } + + const req_bits = self.bitCountTwosComp() + @boolToInt(self.isPositive() and is_signed); + return bit_count >= req_bits; + } + + /// Returns whether self can fit into an integer of the requested type. + pub fn fits(self: Int, comptime T: type) bool { + return self.fitsInTwosComp(T.is_signed, T.bit_count); + } + + /// Returns the approximate size of the integer in the given base. Negative values accommodate for + /// the minus sign. This is used for determining the number of characters needed to print the + /// value. It is inexact and may exceed the given value by ~1-2 bytes. + pub fn sizeInBase(self: Int, base: usize) usize { + const bit_count = usize(@boolToInt(!self.isPositive())) + self.bitCountAbs(); + return (bit_count / math.log2(base)) + 1; + } + + /// Sets an Int to value. Value must be an primitive integer type. + pub fn set(self: *Int, value: var) Allocator.Error!void { + self.assertWritable(); + const T = @typeOf(value); + + switch (@typeInfo(T)) { + TypeId.Int => |info| { + const UT = if (T.is_signed) @IntType(false, T.bit_count - 1) else T; + + try self.ensureCapacity(@sizeOf(UT) / @sizeOf(Limb)); + self.metadata = 0; + self.setSign(value >= 0); + + var w_value: UT = if (value < 0) @intCast(UT, -value) else @intCast(UT, value); + + if (info.bits <= Limb.bit_count) { + self.limbs[0] = Limb(w_value); + self.metadata += 1; + } else { + var i: usize = 0; + while (w_value != 0) : (i += 1) { + self.limbs[i] = @truncate(Limb, w_value); + self.metadata += 1; + + // TODO: shift == 64 at compile-time fails. Fails on u128 limbs. + w_value >>= Limb.bit_count / 2; + w_value >>= Limb.bit_count / 2; + } + } + }, + TypeId.ComptimeInt => { + comptime var w_value = if (value < 0) -value else value; + + const req_limbs = @divFloor(math.log2(w_value), Limb.bit_count) + 1; + try self.ensureCapacity(req_limbs); + + self.metadata = req_limbs; + self.setSign(value >= 0); + + if (w_value <= maxInt(Limb)) { + self.limbs[0] = w_value; + } else { + const mask = (1 << Limb.bit_count) - 1; + + comptime var i = 0; + inline while (w_value != 0) : (i += 1) { + self.limbs[i] = w_value & mask; + + w_value >>= Limb.bit_count / 2; + w_value >>= Limb.bit_count / 2; + } + } + }, + else => { + @compileError("cannot set Int using type " ++ @typeName(T)); + }, + } + } + + pub const ConvertError = error{ + NegativeIntoUnsigned, + TargetTooSmall, + }; + + /// Convert self to type T. + /// + /// Returns an error if self cannot be narrowed into the requested type without truncation. + pub fn to(self: Int, comptime T: type) ConvertError!T { + switch (@typeId(T)) { + TypeId.Int => { + const UT = @IntType(false, T.bit_count); + + if (self.bitCountTwosComp() > T.bit_count) { + return error.TargetTooSmall; + } + + var r: UT = 0; + + if (@sizeOf(UT) <= @sizeOf(Limb)) { + r = @intCast(UT, self.limbs[0]); + } else { + for (self.limbs[0..self.len()]) |_, ri| { + const limb = self.limbs[self.len() - ri - 1]; + r <<= Limb.bit_count; + r |= limb; + } + } + + if (!T.is_signed) { + return if (self.isPositive()) @intCast(T, r) else error.NegativeIntoUnsigned; + } else { + if (self.isPositive()) { + return @intCast(T, r); + } else { + if (math.cast(T, r)) |ok| { + return -ok; + } else |_| { + return minInt(T); + } + } + } + }, + else => { + @compileError("cannot convert Int to type " ++ @typeName(T)); + }, + } + } + + fn charToDigit(ch: u8, base: u8) !u8 { + const d = switch (ch) { + '0'...'9' => ch - '0', + 'a'...'f' => (ch - 'a') + 0xa, + else => return error.InvalidCharForDigit, + }; + + return if (d < base) d else return error.DigitTooLargeForBase; + } + + fn digitToChar(d: u8, base: u8) !u8 { + if (d >= base) { + return error.DigitTooLargeForBase; + } + + return switch (d) { + 0...9 => '0' + d, + 0xa...0xf => ('a' - 0xa) + d, + else => unreachable, + }; + } + + /// Set self from the string representation `value`. + /// + /// value must contain only digits <= `base`. Base prefixes are not allowed (e.g. 0x43 should + /// simply be 43). + /// + /// Returns an error if memory could not be allocated or `value` has invalid digits for the + /// requested base. + pub fn setString(self: *Int, base: u8, value: []const u8) !void { + self.assertWritable(); + if (base < 2 or base > 16) { + return error.InvalidBase; + } + + var i: usize = 0; + var positive = true; + if (value.len > 0 and value[0] == '-') { + positive = false; + i += 1; + } + + const ap_base = Int.initFixed(([_]Limb{base})[0..]); + try self.set(0); + + for (value[i..]) |ch| { + const d = try charToDigit(ch, base); + + const ap_d = Int.initFixed(([_]Limb{d})[0..]); + + try self.mul(self.*, ap_base); + try self.add(self.*, ap_d); + } + self.setSign(positive); + } + + /// Converts self to a string in the requested base. Memory is allocated from the provided + /// allocator and not the one present in self. + /// TODO make this call format instead of the other way around + pub fn toString(self: Int, allocator: *Allocator, base: u8) ![]const u8 { + if (base < 2 or base > 16) { + return error.InvalidBase; + } + + var digits = ArrayList(u8).init(allocator); + try digits.ensureCapacity(self.sizeInBase(base) + 1); + defer digits.deinit(); + + if (self.eqZero()) { + try digits.append('0'); + return digits.toOwnedSlice(); + } + + // Power of two: can do a single pass and use masks to extract digits. + if (math.isPowerOfTwo(base)) { + const base_shift = math.log2_int(Limb, base); + + for (self.limbs[0..self.len()]) |limb| { + var shift: usize = 0; + while (shift < Limb.bit_count) : (shift += base_shift) { + const r = @intCast(u8, (limb >> @intCast(Log2Limb, shift)) & Limb(base - 1)); + const ch = try digitToChar(r, base); + try digits.append(ch); + } + } + + while (true) { + // always will have a non-zero digit somewhere + const c = digits.pop(); + if (c != '0') { + digits.append(c) catch unreachable; + break; + } + } + } // Non power-of-two: batch divisions per word size. + else { + const digits_per_limb = math.log(Limb, base, maxInt(Limb)); + var limb_base: Limb = 1; + var j: usize = 0; + while (j < digits_per_limb) : (j += 1) { + limb_base *= base; + } + + var q = try self.clone(); + q.abs(); + var r = try Int.init(allocator); + var b = try Int.initSet(allocator, limb_base); + + while (q.len() >= 2) { + try Int.divTrunc(&q, &r, q, b); + + var r_word = r.limbs[0]; + var i: usize = 0; + while (i < digits_per_limb) : (i += 1) { + const ch = try digitToChar(@intCast(u8, r_word % base), base); + r_word /= base; + try digits.append(ch); + } + } + + { + debug.assert(q.len() == 1); + + var r_word = q.limbs[0]; + while (r_word != 0) { + const ch = try digitToChar(@intCast(u8, r_word % base), base); + r_word /= base; + try digits.append(ch); + } + } + } + + if (!self.isPositive()) { + try digits.append('-'); + } + + var s = digits.toOwnedSlice(); + mem.reverse(u8, s); + return s; + } + + /// To allow `std.fmt.printf` to work with Int. + /// TODO make this non-allocating + pub fn format( + self: Int, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + context: var, + comptime FmtError: type, + output: fn (@typeOf(context), []const u8) FmtError!void, + ) FmtError!void { + self.assertWritable(); + // TODO look at fmt and support other bases + // TODO support read-only fixed integers + const str = self.toString(self.allocator.?, 10) catch @panic("TODO make this non allocating"); + defer self.allocator.?.free(str); + return output(context, str); + } + + /// Returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. + pub fn cmpAbs(a: Int, b: Int) i8 { + if (a.len() < b.len()) { + return -1; + } + if (a.len() > b.len()) { + return 1; + } + + var i: usize = a.len() - 1; + while (i != 0) : (i -= 1) { + if (a.limbs[i] != b.limbs[i]) { + break; + } + } + + if (a.limbs[i] < b.limbs[i]) { + return -1; + } else if (a.limbs[i] > b.limbs[i]) { + return 1; + } else { + return 0; + } + } + + /// Returns -1, 0, 1 if a < b, a == b or a > b respectively. + pub fn cmp(a: Int, b: Int) i8 { + if (a.isPositive() != b.isPositive()) { + return if (a.isPositive()) i8(1) else -1; + } else { + const r = cmpAbs(a, b); + return if (a.isPositive()) r else -r; + } + } + + /// Returns true if a == 0. + pub fn eqZero(a: Int) bool { + return a.len() == 1 and a.limbs[0] == 0; + } + + /// Returns true if |a| == |b|. + pub fn eqAbs(a: Int, b: Int) bool { + return cmpAbs(a, b) == 0; + } + + /// Returns true if a == b. + pub fn eq(a: Int, b: Int) bool { + return cmp(a, b) == 0; + } + + // Normalize a possible sequence of leading zeros. + // + // [1, 2, 3, 4, 0] -> [1, 2, 3, 4] + // [1, 2, 0, 0, 0] -> [1, 2] + // [0, 0, 0, 0, 0] -> [0] + fn normalize(r: *Int, length: usize) void { + debug.assert(length > 0); + debug.assert(length <= r.limbs.len); + + var j = length; + while (j > 0) : (j -= 1) { + if (r.limbs[j - 1] != 0) { + break; + } + } + + // Handle zero + r.setLen(if (j != 0) j else 1); + } + + // Cannot be used as a result argument to any function. + fn readOnlyPositive(a: Int) Int { + return Int{ + .allocator = null, + .metadata = a.len(), + .limbs = a.limbs, + }; + } + + /// r = a + b + /// + /// r, a and b may be aliases. + /// + /// Returns an error if memory could not be allocated. + pub fn add(r: *Int, a: Int, b: Int) Allocator.Error!void { + r.assertWritable(); + if (a.eqZero()) { + try r.copy(b); + return; + } else if (b.eqZero()) { + try r.copy(a); + return; + } + + if (a.isPositive() != b.isPositive()) { + if (a.isPositive()) { + // (a) + (-b) => a - b + try r.sub(a, readOnlyPositive(b)); + } else { + // (-a) + (b) => b - a + try r.sub(b, readOnlyPositive(a)); + } + } else { + if (a.len() >= b.len()) { + try r.ensureCapacity(a.len() + 1); + lladd(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len() + 1); + } else { + try r.ensureCapacity(b.len() + 1); + lladd(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len() + 1); + } + + r.setSign(a.isPositive()); + } + } + + // Knuth 4.3.1, Algorithm A. + fn lladd(r: []Limb, a: []const Limb, b: []const Limb) void { + @setRuntimeSafety(false); + debug.assert(a.len != 0 and b.len != 0); + debug.assert(a.len >= b.len); + debug.assert(r.len >= a.len + 1); + + var i: usize = 0; + var carry: Limb = 0; + + while (i < b.len) : (i += 1) { + var c: Limb = 0; + c += @boolToInt(@addWithOverflow(Limb, a[i], b[i], &r[i])); + c += @boolToInt(@addWithOverflow(Limb, r[i], carry, &r[i])); + carry = c; + } + + while (i < a.len) : (i += 1) { + carry = @boolToInt(@addWithOverflow(Limb, a[i], carry, &r[i])); + } + + r[i] = carry; + } + + /// r = a - b + /// + /// r, a and b may be aliases. + /// + /// Returns an error if memory could not be allocated. + pub fn sub(r: *Int, a: Int, b: Int) !void { + r.assertWritable(); + if (a.isPositive() != b.isPositive()) { + if (a.isPositive()) { + // (a) - (-b) => a + b + try r.add(a, readOnlyPositive(b)); + } else { + // (-a) - (b) => -(a + b) + try r.add(readOnlyPositive(a), b); + r.setSign(false); + } + } else { + if (a.isPositive()) { + // (a) - (b) => a - b + if (a.cmp(b) >= 0) { + try r.ensureCapacity(a.len() + 1); + llsub(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len()); + r.setSign(true); + } else { + try r.ensureCapacity(b.len() + 1); + llsub(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len()); + r.setSign(false); + } + } else { + // (-a) - (-b) => -(a - b) + if (a.cmp(b) < 0) { + try r.ensureCapacity(a.len() + 1); + llsub(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len()); + r.setSign(false); + } else { + try r.ensureCapacity(b.len() + 1); + llsub(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len()); + r.setSign(true); + } + } + } + } + + // Knuth 4.3.1, Algorithm S. + fn llsub(r: []Limb, a: []const Limb, b: []const Limb) void { + @setRuntimeSafety(false); + debug.assert(a.len != 0 and b.len != 0); + debug.assert(a.len > b.len or (a.len == b.len and a[a.len - 1] >= b[b.len - 1])); + debug.assert(r.len >= a.len); + + var i: usize = 0; + var borrow: Limb = 0; + + while (i < b.len) : (i += 1) { + var c: Limb = 0; + c += @boolToInt(@subWithOverflow(Limb, a[i], b[i], &r[i])); + c += @boolToInt(@subWithOverflow(Limb, r[i], borrow, &r[i])); + borrow = c; + } + + while (i < a.len) : (i += 1) { + borrow = @boolToInt(@subWithOverflow(Limb, a[i], borrow, &r[i])); + } + + debug.assert(borrow == 0); + } + + /// rma = a * b + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn mul(rma: *Int, a: Int, b: Int) !void { + rma.assertWritable(); + + var r = rma; + var aliased = rma.limbs.ptr == a.limbs.ptr or rma.limbs.ptr == b.limbs.ptr; + + var sr: Int = undefined; + if (aliased) { + sr = try Int.initCapacity(rma.allocator.?, a.len() + b.len()); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try r.ensureCapacity(a.len() + b.len()); + + if (a.len() >= b.len()) { + llmul(r.limbs, a.limbs[0..a.len()], b.limbs[0..b.len()]); + } else { + llmul(r.limbs, b.limbs[0..b.len()], a.limbs[0..a.len()]); + } + + r.normalize(a.len() + b.len()); + r.setSign(a.isPositive() == b.isPositive()); + } + + // a + b * c + *carry, sets carry to the overflow bits + pub fn addMulLimbWithCarry(a: Limb, b: Limb, c: Limb, carry: *Limb) Limb { + var r1: Limb = undefined; + + // r1 = a + *carry + const c1: Limb = @boolToInt(@addWithOverflow(Limb, a, carry.*, &r1)); + + // r2 = b * c + const bc = DoubleLimb(math.mulWide(Limb, b, c)); + const r2 = @truncate(Limb, bc); + const c2 = @truncate(Limb, bc >> Limb.bit_count); + + // r1 = r1 + r2 + const c3: Limb = @boolToInt(@addWithOverflow(Limb, r1, r2, &r1)); + + // This never overflows, c1, c3 are either 0 or 1 and if both are 1 then + // c2 is at least <= maxInt(Limb) - 2. + carry.* = c1 + c2 + c3; + + return r1; + } + + // Knuth 4.3.1, Algorithm M. + // + // r MUST NOT alias any of a or b. + fn llmul(r: []Limb, a: []const Limb, b: []const Limb) void { + @setRuntimeSafety(false); + debug.assert(a.len >= b.len); + debug.assert(r.len >= a.len + b.len); + + mem.set(Limb, r[0 .. a.len + b.len], 0); + + var i: usize = 0; + while (i < a.len) : (i += 1) { + var carry: Limb = 0; + var j: usize = 0; + while (j < b.len) : (j += 1) { + r[i + j] = @inlineCall(addMulLimbWithCarry, r[i + j], a[i], b[j], &carry); + } + r[i + j] = carry; + } + } + + /// q = a / b (rem r) + /// + /// a / b are floored (rounded towards 0). + pub fn divFloor(q: *Int, r: *Int, a: Int, b: Int) !void { + try div(q, r, a, b); + + // Trunc -> Floor. + if (!q.isPositive()) { + const one = Int.initFixed(([_]Limb{1})[0..]); + try q.sub(q.*, one); + try r.add(q.*, one); + } + r.setSign(b.isPositive()); + } + + /// q = a / b (rem r) + /// + /// a / b are truncated (rounded towards -inf). + pub fn divTrunc(q: *Int, r: *Int, a: Int, b: Int) !void { + try div(q, r, a, b); + r.setSign(a.isPositive()); + } + + // Truncates by default. + fn div(quo: *Int, rem: *Int, a: Int, b: Int) !void { + quo.assertWritable(); + rem.assertWritable(); + + if (b.eqZero()) { + @panic("division by zero"); + } + if (quo == rem) { + @panic("quo and rem cannot be same variable"); + } + + if (a.cmpAbs(b) < 0) { + // quo may alias a so handle rem first + try rem.copy(a); + rem.setSign(a.isPositive() == b.isPositive()); + + quo.metadata = 1; + quo.limbs[0] = 0; + return; + } + + // Handle trailing zero-words of divisor/dividend. These are not handled in the following + // algorithms. + const a_zero_limb_count = blk: { + var i: usize = 0; + while (i < a.len()) : (i += 1) { + if (a.limbs[i] != 0) break; + } + break :blk i; + }; + const b_zero_limb_count = blk: { + var i: usize = 0; + while (i < b.len()) : (i += 1) { + if (b.limbs[i] != 0) break; + } + break :blk i; + }; + + const ab_zero_limb_count = std.math.min(a_zero_limb_count, b_zero_limb_count); + + if (b.len() - ab_zero_limb_count == 1) { + try quo.ensureCapacity(a.len()); + + lldiv1(quo.limbs[0..], &rem.limbs[0], a.limbs[ab_zero_limb_count..a.len()], b.limbs[b.len() - 1]); + quo.normalize(a.len() - ab_zero_limb_count); + quo.setSign(a.isPositive() == b.isPositive()); + + rem.metadata = 1; + } else { + // x and y are modified during division + var x = try Int.initCapacity(quo.allocator.?, a.len()); + defer x.deinit(); + try x.copy(a); + + var y = try Int.initCapacity(quo.allocator.?, b.len()); + defer y.deinit(); + try y.copy(b); + + // x may grow one limb during normalization + try quo.ensureCapacity(a.len() + y.len()); + + // Shrink x, y such that the trailing zero limbs shared between are removed. + if (ab_zero_limb_count != 0) { + std.mem.copy(Limb, x.limbs[0..], x.limbs[ab_zero_limb_count..]); + std.mem.copy(Limb, y.limbs[0..], y.limbs[ab_zero_limb_count..]); + x.metadata -= ab_zero_limb_count; + y.metadata -= ab_zero_limb_count; + } + + try divN(quo.allocator.?, quo, rem, &x, &y); + quo.setSign(a.isPositive() == b.isPositive()); + } + + if (ab_zero_limb_count != 0) { + try rem.shiftLeft(rem.*, ab_zero_limb_count * Limb.bit_count); + } + } + + // Knuth 4.3.1, Exercise 16. + fn lldiv1(quo: []Limb, rem: *Limb, a: []const Limb, b: Limb) void { + @setRuntimeSafety(false); + debug.assert(a.len > 1 or a[0] >= b); + debug.assert(quo.len >= a.len); + + rem.* = 0; + for (a) |_, ri| { + const i = a.len - ri - 1; + const pdiv = ((DoubleLimb(rem.*) << Limb.bit_count) | a[i]); + + if (pdiv == 0) { + quo[i] = 0; + rem.* = 0; + } else if (pdiv < b) { + quo[i] = 0; + rem.* = @truncate(Limb, pdiv); + } else if (pdiv == b) { + quo[i] = 1; + rem.* = 0; + } else { + quo[i] = @truncate(Limb, @divTrunc(pdiv, b)); + rem.* = @truncate(Limb, pdiv - (quo[i] *% b)); + } + } + } + + // Handbook of Applied Cryptography, 14.20 + // + // x = qy + r where 0 <= r < y + fn divN(allocator: *Allocator, q: *Int, r: *Int, x: *Int, y: *Int) !void { + debug.assert(y.len() >= 2); + debug.assert(x.len() >= y.len()); + debug.assert(q.limbs.len >= x.len() + y.len() - 1); + debug.assert(default_capacity >= 3); // see 3.2 + + var tmp = try Int.init(allocator); + defer tmp.deinit(); + + // Normalize so y > Limb.bit_count / 2 (i.e. leading bit is set) and even + var norm_shift = @clz(Limb, y.limbs[y.len() - 1]); + if (norm_shift == 0 and y.isOdd()) { + norm_shift = Limb.bit_count; + } + try x.shiftLeft(x.*, norm_shift); + try y.shiftLeft(y.*, norm_shift); + + const n = x.len() - 1; + const t = y.len() - 1; + + // 1. + q.metadata = n - t + 1; + mem.set(Limb, q.limbs[0..q.len()], 0); + + // 2. + try tmp.shiftLeft(y.*, Limb.bit_count * (n - t)); + while (x.cmp(tmp) >= 0) { + q.limbs[n - t] += 1; + try x.sub(x.*, tmp); + } + + // 3. + var i = n; + while (i > t) : (i -= 1) { + // 3.1 + if (x.limbs[i] == y.limbs[t]) { + q.limbs[i - t - 1] = maxInt(Limb); + } else { + const num = (DoubleLimb(x.limbs[i]) << Limb.bit_count) | DoubleLimb(x.limbs[i - 1]); + const z = @intCast(Limb, num / DoubleLimb(y.limbs[t])); + q.limbs[i - t - 1] = if (z > maxInt(Limb)) maxInt(Limb) else Limb(z); + } + + // 3.2 + tmp.limbs[0] = if (i >= 2) x.limbs[i - 2] else 0; + tmp.limbs[1] = if (i >= 1) x.limbs[i - 1] else 0; + tmp.limbs[2] = x.limbs[i]; + tmp.normalize(3); + + while (true) { + // 2x1 limb multiplication unrolled against single-limb q[i-t-1] + var carry: Limb = 0; + r.limbs[0] = addMulLimbWithCarry(0, if (t >= 1) y.limbs[t - 1] else 0, q.limbs[i - t - 1], &carry); + r.limbs[1] = addMulLimbWithCarry(0, y.limbs[t], q.limbs[i - t - 1], &carry); + r.limbs[2] = carry; + r.normalize(3); + + if (r.cmpAbs(tmp) <= 0) { + break; + } + + q.limbs[i - t - 1] -= 1; + } + + // 3.3 + try tmp.set(q.limbs[i - t - 1]); + try tmp.mul(tmp, y.*); + try tmp.shiftLeft(tmp, Limb.bit_count * (i - t - 1)); + try x.sub(x.*, tmp); + + if (!x.isPositive()) { + try tmp.shiftLeft(y.*, Limb.bit_count * (i - t - 1)); + try x.add(x.*, tmp); + q.limbs[i - t - 1] -= 1; + } + } + + // Denormalize + q.normalize(q.len()); + + try r.shiftRight(x.*, norm_shift); + r.normalize(r.len()); + } + + /// r = a << shift, in other words, r = a * 2^shift + pub fn shiftLeft(r: *Int, a: Int, shift: usize) !void { + r.assertWritable(); + + try r.ensureCapacity(a.len() + (shift / Limb.bit_count) + 1); + llshl(r.limbs[0..], a.limbs[0..a.len()], shift); + r.normalize(a.len() + (shift / Limb.bit_count) + 1); + r.setSign(a.isPositive()); + } + + fn llshl(r: []Limb, a: []const Limb, shift: usize) void { + @setRuntimeSafety(false); + debug.assert(a.len >= 1); + debug.assert(r.len >= a.len + (shift / Limb.bit_count) + 1); + + const limb_shift = shift / Limb.bit_count + 1; + const interior_limb_shift = @intCast(Log2Limb, shift % Limb.bit_count); + + var carry: Limb = 0; + var i: usize = 0; + while (i < a.len) : (i += 1) { + const src_i = a.len - i - 1; + const dst_i = src_i + limb_shift; + + const src_digit = a[src_i]; + r[dst_i] = carry | @inlineCall(math.shr, Limb, src_digit, Limb.bit_count - @intCast(Limb, interior_limb_shift)); + carry = (src_digit << interior_limb_shift); + } + + r[limb_shift - 1] = carry; + mem.set(Limb, r[0 .. limb_shift - 1], 0); + } + + /// r = a >> shift + pub fn shiftRight(r: *Int, a: Int, shift: usize) !void { + r.assertWritable(); + + if (a.len() <= shift / Limb.bit_count) { + r.metadata = 1; + r.limbs[0] = 0; + return; + } + + try r.ensureCapacity(a.len() - (shift / Limb.bit_count)); + const r_len = llshr(r.limbs[0..], a.limbs[0..a.len()], shift); + r.metadata = a.len() - (shift / Limb.bit_count); + r.setSign(a.isPositive()); + } + + fn llshr(r: []Limb, a: []const Limb, shift: usize) void { + @setRuntimeSafety(false); + debug.assert(a.len >= 1); + debug.assert(r.len >= a.len - (shift / Limb.bit_count)); + + const limb_shift = shift / Limb.bit_count; + const interior_limb_shift = @intCast(Log2Limb, shift % Limb.bit_count); + + var carry: Limb = 0; + var i: usize = 0; + while (i < a.len - limb_shift) : (i += 1) { + const src_i = a.len - i - 1; + const dst_i = src_i - limb_shift; + + const src_digit = a[src_i]; + r[dst_i] = carry | (src_digit >> interior_limb_shift); + carry = @inlineCall(math.shl, Limb, src_digit, Limb.bit_count - @intCast(Limb, interior_limb_shift)); + } + } + + /// r = a | b + /// + /// a and b are zero-extended to the longer of a or b. + pub fn bitOr(r: *Int, a: Int, b: Int) !void { + r.assertWritable(); + + if (a.len() > b.len()) { + try r.ensureCapacity(a.len()); + llor(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.setLen(a.len()); + } else { + try r.ensureCapacity(b.len()); + llor(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.setLen(b.len()); + } + } + + fn llor(r: []Limb, a: []const Limb, b: []const Limb) void { + @setRuntimeSafety(false); + debug.assert(r.len >= a.len); + debug.assert(a.len >= b.len); + + var i: usize = 0; + while (i < b.len) : (i += 1) { + r[i] = a[i] | b[i]; + } + while (i < a.len) : (i += 1) { + r[i] = a[i]; + } + } + + /// r = a & b + pub fn bitAnd(r: *Int, a: Int, b: Int) !void { + r.assertWritable(); + + if (a.len() > b.len()) { + try r.ensureCapacity(b.len()); + lland(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(b.len()); + } else { + try r.ensureCapacity(a.len()); + lland(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(a.len()); + } + } + + fn lland(r: []Limb, a: []const Limb, b: []const Limb) void { + @setRuntimeSafety(false); + debug.assert(r.len >= b.len); + debug.assert(a.len >= b.len); + + var i: usize = 0; + while (i < b.len) : (i += 1) { + r[i] = a[i] & b[i]; + } + } + + /// r = a ^ b + pub fn bitXor(r: *Int, a: Int, b: Int) !void { + r.assertWritable(); + + if (a.len() > b.len()) { + try r.ensureCapacity(a.len()); + llxor(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len()); + } else { + try r.ensureCapacity(b.len()); + llxor(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len()); + } + } + + fn llxor(r: []Limb, a: []const Limb, b: []const Limb) void { + @setRuntimeSafety(false); + debug.assert(r.len >= a.len); + debug.assert(a.len >= b.len); + + var i: usize = 0; + while (i < b.len) : (i += 1) { + r[i] = a[i] ^ b[i]; + } + while (i < a.len) : (i += 1) { + r[i] = a[i]; + } + } +}; + +// NOTE: All the following tests assume the max machine-word will be 64-bit. +// +// They will still run on larger than this and should pass, but the multi-limb code-paths +// may be untested in some cases. + +var buffer: [64 * 8192]u8 = undefined; +var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); +const al = &fixed.allocator; + +test "big.int comptime_int set" { + comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab; + var a = try Int.initSet(al, s); + + const s_limb_count = 128 / Limb.bit_count; + + comptime var i: usize = 0; + inline while (i < s_limb_count) : (i += 1) { + const result = Limb(s & maxInt(Limb)); + s >>= Limb.bit_count / 2; + s >>= Limb.bit_count / 2; + testing.expect(a.limbs[i] == result); + } +} + +test "big.int comptime_int set negative" { + var a = try Int.initSet(al, -10); + + testing.expect(a.limbs[0] == 10); + testing.expect(a.isPositive() == false); +} + +test "big.int int set unaligned small" { + var a = try Int.initSet(al, u7(45)); + + testing.expect(a.limbs[0] == 45); + testing.expect(a.isPositive() == true); +} + +test "big.int comptime_int to" { + const a = try Int.initSet(al, 0xefffffff00000001eeeeeeefaaaaaaab); + + testing.expect((try a.to(u128)) == 0xefffffff00000001eeeeeeefaaaaaaab); +} + +test "big.int sub-limb to" { + const a = try Int.initSet(al, 10); + + testing.expect((try a.to(u8)) == 10); +} + +test "big.int to target too small error" { + const a = try Int.initSet(al, 0xffffffff); + + testing.expectError(error.TargetTooSmall, a.to(u8)); +} + +test "big.int normalize" { + var a = try Int.init(al); + try a.ensureCapacity(8); + + a.limbs[0] = 1; + a.limbs[1] = 2; + a.limbs[2] = 3; + a.limbs[3] = 0; + a.normalize(4); + testing.expect(a.len() == 3); + + a.limbs[0] = 1; + a.limbs[1] = 2; + a.limbs[2] = 3; + a.normalize(3); + testing.expect(a.len() == 3); + + a.limbs[0] = 0; + a.limbs[1] = 0; + a.normalize(2); + testing.expect(a.len() == 1); + + a.limbs[0] = 0; + a.normalize(1); + testing.expect(a.len() == 1); +} + +test "big.int normalize multi" { + var a = try Int.init(al); + try a.ensureCapacity(8); + + a.limbs[0] = 1; + a.limbs[1] = 2; + a.limbs[2] = 0; + a.limbs[3] = 0; + a.normalize(4); + testing.expect(a.len() == 2); + + a.limbs[0] = 1; + a.limbs[1] = 2; + a.limbs[2] = 3; + a.normalize(3); + testing.expect(a.len() == 3); + + a.limbs[0] = 0; + a.limbs[1] = 0; + a.limbs[2] = 0; + a.limbs[3] = 0; + a.normalize(4); + testing.expect(a.len() == 1); + + a.limbs[0] = 0; + a.normalize(1); + testing.expect(a.len() == 1); +} + +test "big.int parity" { + var a = try Int.init(al); + try a.set(0); + testing.expect(a.isEven()); + testing.expect(!a.isOdd()); + + try a.set(7); + testing.expect(!a.isEven()); + testing.expect(a.isOdd()); +} + +test "big.int bitcount + sizeInBase" { + var a = try Int.init(al); + + try a.set(0b100); + testing.expect(a.bitCountAbs() == 3); + testing.expect(a.sizeInBase(2) >= 3); + testing.expect(a.sizeInBase(10) >= 1); + + a.negate(); + testing.expect(a.bitCountAbs() == 3); + testing.expect(a.sizeInBase(2) >= 4); + testing.expect(a.sizeInBase(10) >= 2); + + try a.set(0xffffffff); + testing.expect(a.bitCountAbs() == 32); + testing.expect(a.sizeInBase(2) >= 32); + testing.expect(a.sizeInBase(10) >= 10); + + try a.shiftLeft(a, 5000); + testing.expect(a.bitCountAbs() == 5032); + testing.expect(a.sizeInBase(2) >= 5032); + a.setSign(false); + + testing.expect(a.bitCountAbs() == 5032); + testing.expect(a.sizeInBase(2) >= 5033); +} + +test "big.int bitcount/to" { + var a = try Int.init(al); + + try a.set(0); + testing.expect(a.bitCountTwosComp() == 0); + + testing.expect((try a.to(u0)) == 0); + testing.expect((try a.to(i0)) == 0); + + try a.set(-1); + testing.expect(a.bitCountTwosComp() == 1); + testing.expect((try a.to(i1)) == -1); + + try a.set(-8); + testing.expect(a.bitCountTwosComp() == 4); + testing.expect((try a.to(i4)) == -8); + + try a.set(127); + testing.expect(a.bitCountTwosComp() == 7); + testing.expect((try a.to(u7)) == 127); + + try a.set(-128); + testing.expect(a.bitCountTwosComp() == 8); + testing.expect((try a.to(i8)) == -128); + + try a.set(-129); + testing.expect(a.bitCountTwosComp() == 9); + testing.expect((try a.to(i9)) == -129); +} + +test "big.int fits" { + var a = try Int.init(al); + + try a.set(0); + testing.expect(a.fits(u0)); + testing.expect(a.fits(i0)); + + try a.set(255); + testing.expect(!a.fits(u0)); + testing.expect(!a.fits(u1)); + testing.expect(!a.fits(i8)); + testing.expect(a.fits(u8)); + testing.expect(a.fits(u9)); + testing.expect(a.fits(i9)); + + try a.set(-128); + testing.expect(!a.fits(i7)); + testing.expect(a.fits(i8)); + testing.expect(a.fits(i9)); + testing.expect(!a.fits(u9)); + + try a.set(0x1ffffffffeeeeeeee); + testing.expect(!a.fits(u32)); + testing.expect(!a.fits(u64)); + testing.expect(a.fits(u65)); +} + +test "big.int string set" { + var a = try Int.init(al); + try a.setString(10, "120317241209124781241290847124"); + + testing.expect((try a.to(u128)) == 120317241209124781241290847124); +} + +test "big.int string negative" { + var a = try Int.init(al); + try a.setString(10, "-1023"); + testing.expect((try a.to(i32)) == -1023); +} + +test "big.int string set bad char error" { + var a = try Int.init(al); + testing.expectError(error.InvalidCharForDigit, a.setString(10, "x")); +} + +test "big.int string set bad base error" { + var a = try Int.init(al); + testing.expectError(error.InvalidBase, a.setString(45, "10")); +} + +test "big.int string to" { + const a = try Int.initSet(al, 120317241209124781241290847124); + + const as = try a.toString(al, 10); + const es = "120317241209124781241290847124"; + + testing.expect(mem.eql(u8, as, es)); +} + +test "big.int string to base base error" { + const a = try Int.initSet(al, 0xffffffff); + + testing.expectError(error.InvalidBase, a.toString(al, 45)); +} + +test "big.int string to base 2" { + const a = try Int.initSet(al, -0b1011); + + const as = try a.toString(al, 2); + const es = "-1011"; + + testing.expect(mem.eql(u8, as, es)); +} + +test "big.int string to base 16" { + const a = try Int.initSet(al, 0xefffffff00000001eeeeeeefaaaaaaab); + + const as = try a.toString(al, 16); + const es = "efffffff00000001eeeeeeefaaaaaaab"; + + testing.expect(mem.eql(u8, as, es)); +} + +test "big.int neg string to" { + const a = try Int.initSet(al, -123907434); + + const as = try a.toString(al, 10); + const es = "-123907434"; + + testing.expect(mem.eql(u8, as, es)); +} + +test "big.int zero string to" { + const a = try Int.initSet(al, 0); + + const as = try a.toString(al, 10); + const es = "0"; + + testing.expect(mem.eql(u8, as, es)); +} + +test "big.int clone" { + var a = try Int.initSet(al, 1234); + const b = try a.clone(); + + testing.expect((try a.to(u32)) == 1234); + testing.expect((try b.to(u32)) == 1234); + + try a.set(77); + testing.expect((try a.to(u32)) == 77); + testing.expect((try b.to(u32)) == 1234); +} + +test "big.int swap" { + var a = try Int.initSet(al, 1234); + var b = try Int.initSet(al, 5678); + + testing.expect((try a.to(u32)) == 1234); + testing.expect((try b.to(u32)) == 5678); + + a.swap(&b); + + testing.expect((try a.to(u32)) == 5678); + testing.expect((try b.to(u32)) == 1234); +} + +test "big.int to negative" { + var a = try Int.initSet(al, -10); + + testing.expect((try a.to(i32)) == -10); +} + +test "big.int compare" { + var a = try Int.initSet(al, -11); + var b = try Int.initSet(al, 10); + + testing.expect(a.cmpAbs(b) == 1); + testing.expect(a.cmp(b) == -1); +} + +test "big.int compare similar" { + var a = try Int.initSet(al, 0xffffffffeeeeeeeeffffffffeeeeeeee); + var b = try Int.initSet(al, 0xffffffffeeeeeeeeffffffffeeeeeeef); + + testing.expect(a.cmpAbs(b) == -1); + testing.expect(b.cmpAbs(a) == 1); +} + +test "big.int compare different limb size" { + var a = try Int.initSet(al, maxInt(Limb) + 1); + var b = try Int.initSet(al, 1); + + testing.expect(a.cmpAbs(b) == 1); + testing.expect(b.cmpAbs(a) == -1); +} + +test "big.int compare multi-limb" { + var a = try Int.initSet(al, -0x7777777799999999ffffeeeeffffeeeeffffeeeef); + var b = try Int.initSet(al, 0x7777777799999999ffffeeeeffffeeeeffffeeeee); + + testing.expect(a.cmpAbs(b) == 1); + testing.expect(a.cmp(b) == -1); +} + +test "big.int equality" { + var a = try Int.initSet(al, 0xffffffff1); + var b = try Int.initSet(al, -0xffffffff1); + + testing.expect(a.eqAbs(b)); + testing.expect(!a.eq(b)); +} + +test "big.int abs" { + var a = try Int.initSet(al, -5); + + a.abs(); + testing.expect((try a.to(u32)) == 5); + + a.abs(); + testing.expect((try a.to(u32)) == 5); +} + +test "big.int negate" { + var a = try Int.initSet(al, 5); + + a.negate(); + testing.expect((try a.to(i32)) == -5); + + a.negate(); + testing.expect((try a.to(i32)) == 5); +} + +test "big.int add single-single" { + var a = try Int.initSet(al, 50); + var b = try Int.initSet(al, 5); + + var c = try Int.init(al); + try c.add(a, b); + + testing.expect((try c.to(u32)) == 55); +} + +test "big.int add multi-single" { + var a = try Int.initSet(al, maxInt(Limb) + 1); + var b = try Int.initSet(al, 1); + + var c = try Int.init(al); + + try c.add(a, b); + testing.expect((try c.to(DoubleLimb)) == maxInt(Limb) + 2); + + try c.add(b, a); + testing.expect((try c.to(DoubleLimb)) == maxInt(Limb) + 2); +} + +test "big.int add multi-multi" { + const op1 = 0xefefefef7f7f7f7f; + const op2 = 0xfefefefe9f9f9f9f; + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var c = try Int.init(al); + try c.add(a, b); + + testing.expect((try c.to(u128)) == op1 + op2); +} + +test "big.int add zero-zero" { + var a = try Int.initSet(al, 0); + var b = try Int.initSet(al, 0); + + var c = try Int.init(al); + try c.add(a, b); + + testing.expect((try c.to(u32)) == 0); +} + +test "big.int add alias multi-limb nonzero-zero" { + const op1 = 0xffffffff777777771; + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, 0); + + try a.add(a, b); + + testing.expect((try a.to(u128)) == op1); +} + +test "big.int add sign" { + var a = try Int.init(al); + + const one = try Int.initSet(al, 1); + const two = try Int.initSet(al, 2); + const neg_one = try Int.initSet(al, -1); + const neg_two = try Int.initSet(al, -2); + + try a.add(one, two); + testing.expect((try a.to(i32)) == 3); + + try a.add(neg_one, two); + testing.expect((try a.to(i32)) == 1); + + try a.add(one, neg_two); + testing.expect((try a.to(i32)) == -1); + + try a.add(neg_one, neg_two); + testing.expect((try a.to(i32)) == -3); +} + +test "big.int sub single-single" { + var a = try Int.initSet(al, 50); + var b = try Int.initSet(al, 5); + + var c = try Int.init(al); + try c.sub(a, b); + + testing.expect((try c.to(u32)) == 45); +} + +test "big.int sub multi-single" { + var a = try Int.initSet(al, maxInt(Limb) + 1); + var b = try Int.initSet(al, 1); + + var c = try Int.init(al); + try c.sub(a, b); + + testing.expect((try c.to(Limb)) == maxInt(Limb)); +} + +test "big.int sub multi-multi" { + const op1 = 0xefefefefefefefefefefefef; + const op2 = 0xabababababababababababab; + + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var c = try Int.init(al); + try c.sub(a, b); + + testing.expect((try c.to(u128)) == op1 - op2); +} + +test "big.int sub equal" { + var a = try Int.initSet(al, 0x11efefefefefefefefefefefef); + var b = try Int.initSet(al, 0x11efefefefefefefefefefefef); + + var c = try Int.init(al); + try c.sub(a, b); + + testing.expect((try c.to(u32)) == 0); +} + +test "big.int sub sign" { + var a = try Int.init(al); + + const one = try Int.initSet(al, 1); + const two = try Int.initSet(al, 2); + const neg_one = try Int.initSet(al, -1); + const neg_two = try Int.initSet(al, -2); + + try a.sub(one, two); + testing.expect((try a.to(i32)) == -1); + + try a.sub(neg_one, two); + testing.expect((try a.to(i32)) == -3); + + try a.sub(one, neg_two); + testing.expect((try a.to(i32)) == 3); + + try a.sub(neg_one, neg_two); + testing.expect((try a.to(i32)) == 1); + + try a.sub(neg_two, neg_one); + testing.expect((try a.to(i32)) == -1); +} + +test "big.int mul single-single" { + var a = try Int.initSet(al, 50); + var b = try Int.initSet(al, 5); + + var c = try Int.init(al); + try c.mul(a, b); + + testing.expect((try c.to(u64)) == 250); +} + +test "big.int mul multi-single" { + var a = try Int.initSet(al, maxInt(Limb)); + var b = try Int.initSet(al, 2); + + var c = try Int.init(al); + try c.mul(a, b); + + testing.expect((try c.to(DoubleLimb)) == 2 * maxInt(Limb)); +} + +test "big.int mul multi-multi" { + const op1 = 0x998888efefefefefefefef; + const op2 = 0x333000abababababababab; + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var c = try Int.init(al); + try c.mul(a, b); + + testing.expect((try c.to(u256)) == op1 * op2); +} + +test "big.int mul alias r with a" { + var a = try Int.initSet(al, maxInt(Limb)); + var b = try Int.initSet(al, 2); + + try a.mul(a, b); + + testing.expect((try a.to(DoubleLimb)) == 2 * maxInt(Limb)); +} + +test "big.int mul alias r with b" { + var a = try Int.initSet(al, maxInt(Limb)); + var b = try Int.initSet(al, 2); + + try a.mul(b, a); + + testing.expect((try a.to(DoubleLimb)) == 2 * maxInt(Limb)); +} + +test "big.int mul alias r with a and b" { + var a = try Int.initSet(al, maxInt(Limb)); + + try a.mul(a, a); + + testing.expect((try a.to(DoubleLimb)) == maxInt(Limb) * maxInt(Limb)); +} + +test "big.int mul a*0" { + var a = try Int.initSet(al, 0xefefefefefefefef); + var b = try Int.initSet(al, 0); + + var c = try Int.init(al); + try c.mul(a, b); + + testing.expect((try c.to(u32)) == 0); +} + +test "big.int mul 0*0" { + var a = try Int.initSet(al, 0); + var b = try Int.initSet(al, 0); + + var c = try Int.init(al); + try c.mul(a, b); + + testing.expect((try c.to(u32)) == 0); +} + +test "big.int div single-single no rem" { + var a = try Int.initSet(al, 50); + var b = try Int.initSet(al, 5); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u32)) == 10); + testing.expect((try r.to(u32)) == 0); +} + +test "big.int div single-single with rem" { + var a = try Int.initSet(al, 49); + var b = try Int.initSet(al, 5); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u32)) == 9); + testing.expect((try r.to(u32)) == 4); +} + +test "big.int div multi-single no rem" { + const op1 = 0xffffeeeeddddcccc; + const op2 = 34; + + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u64)) == op1 / op2); + testing.expect((try r.to(u64)) == 0); +} + +test "big.int div multi-single with rem" { + const op1 = 0xffffeeeeddddcccf; + const op2 = 34; + + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u64)) == op1 / op2); + testing.expect((try r.to(u64)) == 3); +} + +test "big.int div multi>2-single" { + const op1 = 0xfefefefefefefefefefefefefefefefe; + const op2 = 0xefab8; + + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == op1 / op2); + testing.expect((try r.to(u32)) == 0x3e4e); +} + +test "big.int div single-single q < r" { + var a = try Int.initSet(al, 0x0078f432); + var b = try Int.initSet(al, 0x01000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u64)) == 0); + testing.expect((try r.to(u64)) == 0x0078f432); +} + +test "big.int div single-single q == r" { + var a = try Int.initSet(al, 10); + var b = try Int.initSet(al, 10); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u64)) == 1); + testing.expect((try r.to(u64)) == 0); +} + +test "big.int div q=0 alias" { + var a = try Int.initSet(al, 3); + var b = try Int.initSet(al, 10); + + try Int.divTrunc(&a, &b, a, b); + + testing.expect((try a.to(u64)) == 0); + testing.expect((try b.to(u64)) == 3); +} + +test "big.int div multi-multi q < r" { + const op1 = 0x1ffffffff0078f432; + const op2 = 0x1ffffffff01000000; + var a = try Int.initSet(al, op1); + var b = try Int.initSet(al, op2); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0); + testing.expect((try r.to(u128)) == op1); +} + +test "big.int div trunc single-single +/+" { + const u: i32 = 5; + const v: i32 = 3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + // n = q * d + r + // 5 = 1 * 3 + 2 + const eq = @divTrunc(u, v); + const er = @mod(u, v); + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div trunc single-single -/+" { + const u: i32 = -5; + const v: i32 = 3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + // n = q * d + r + // -5 = 1 * -3 - 2 + const eq = -1; + const er = -2; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div trunc single-single +/-" { + const u: i32 = 5; + const v: i32 = -3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + // n = q * d + r + // 5 = -1 * -3 + 2 + const eq = -1; + const er = 2; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div trunc single-single -/-" { + const u: i32 = -5; + const v: i32 = -3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + // n = q * d + r + // -5 = 1 * -3 - 2 + const eq = 1; + const er = -2; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div floor single-single +/+" { + const u: i32 = 5; + const v: i32 = 3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divFloor(&q, &r, a, b); + + // n = q * d + r + // 5 = 1 * 3 + 2 + const eq = 1; + const er = 2; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div floor single-single -/+" { + const u: i32 = -5; + const v: i32 = 3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divFloor(&q, &r, a, b); + + // n = q * d + r + // -5 = -2 * 3 + 1 + const eq = -2; + const er = 1; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div floor single-single +/-" { + const u: i32 = 5; + const v: i32 = -3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divFloor(&q, &r, a, b); + + // n = q * d + r + // 5 = -2 * -3 - 1 + const eq = -2; + const er = -1; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div floor single-single -/-" { + const u: i32 = -5; + const v: i32 = -3; + + var a = try Int.initSet(al, u); + var b = try Int.initSet(al, v); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divFloor(&q, &r, a, b); + + // n = q * d + r + // -5 = 2 * -3 + 1 + const eq = 1; + const er = -2; + + testing.expect((try q.to(i32)) == eq); + testing.expect((try r.to(i32)) == er); +} + +test "big.int div multi-multi with rem" { + var a = try Int.initSet(al, 0x8888999911110000ffffeeeeddddccccbbbbaaaa9999); + var b = try Int.initSet(al, 0x99990000111122223333); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0xe38f38e39161aaabd03f0f1b); + testing.expect((try r.to(u128)) == 0x28de0acacd806823638); +} + +test "big.int div multi-multi no rem" { + var a = try Int.initSet(al, 0x8888999911110000ffffeeeedb4fec200ee3a4286361); + var b = try Int.initSet(al, 0x99990000111122223333); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0xe38f38e39161aaabd03f0f1b); + testing.expect((try r.to(u128)) == 0); +} + +test "big.int div multi-multi (2 branch)" { + var a = try Int.initSet(al, 0x866666665555555588888887777777761111111111111111); + var b = try Int.initSet(al, 0x86666666555555554444444433333333); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0x10000000000000000); + testing.expect((try r.to(u128)) == 0x44444443444444431111111111111111); +} + +test "big.int div multi-multi (3.1/3.3 branch)" { + var a = try Int.initSet(al, 0x11111111111111111111111111111111111111111111111111111111111111); + var b = try Int.initSet(al, 0x1111111111111111111111111111111111111111171); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0xfffffffffffffffffff); + testing.expect((try r.to(u256)) == 0x1111111111111111111110b12222222222222222282); +} + +test "big.int div multi-single zero-limb trailing" { + var a = try Int.initSet(al, 0x60000000000000000000000000000000000000000000000000000000000000000); + var b = try Int.initSet(al, 0x10000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + var expected = try Int.initSet(al, 0x6000000000000000000000000000000000000000000000000); + testing.expect(q.eq(expected)); + testing.expect(r.eqZero()); +} + +test "big.int div multi-multi zero-limb trailing (with rem)" { + var a = try Int.initSet(al, 0x86666666555555558888888777777776111111111111111100000000000000000000000000000000); + var b = try Int.initSet(al, 0x8666666655555555444444443333333300000000000000000000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0x10000000000000000); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "4444444344444443111111111111111100000000000000000000000000000000")); +} + +test "big.int div multi-multi zero-limb trailing (with rem) and dividend zero-limb count > divisor zero-limb count" { + var a = try Int.initSet(al, 0x8666666655555555888888877777777611111111111111110000000000000000); + var b = try Int.initSet(al, 0x8666666655555555444444443333333300000000000000000000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0x1); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "444444434444444311111111111111110000000000000000")); +} + +test "big.int div multi-multi zero-limb trailing (with rem) and dividend zero-limb count < divisor zero-limb count" { + var a = try Int.initSet(al, 0x86666666555555558888888777777776111111111111111100000000000000000000000000000000); + var b = try Int.initSet(al, 0x866666665555555544444444333333330000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + const qs = try q.toString(al, 16); + testing.expect(std.mem.eql(u8, qs, "10000000000000000820820803105186f")); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "4e11f2baa5896a321d463b543d0104e30000000000000000")); +} + +test "big.int div multi-multi fuzz case #1" { + var a = try Int.init(al); + var b = try Int.init(al); + + try a.setString(16, "ffffffffffffffffffffffffffffc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + try b.setString(16, "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffc000000000000000000000000000000007fffffffffff"); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + const qs = try q.toString(al, 16); + testing.expect(std.mem.eql(u8, qs, "3ffffffffffffffffffffffffffff0000000000000000000000000000000000001ffffffffffffffffffffffffffff7fffffffe000000000000000000000000000180000000000000000000003fffffbfffffffdfffffffffffffeffff800000100101000000100000000020003fffffdfbfffffe3ffffffffffffeffff7fffc00800a100000017ffe000002000400007efbfff7fe9f00000037ffff3fff7fffa004006100000009ffe00000190038200bf7d2ff7fefe80400060000f7d7f8fbf9401fe38e0403ffc0bdffffa51102c300d7be5ef9df4e5060007b0127ad3fa69f97d0f820b6605ff617ddf7f32ad7a05c0d03f2e7bc78a6000e087a8bbcdc59e07a5a079128a7861f553ddebed7e8e56701756f9ead39b48cd1b0831889ea6ec1fddf643d0565b075ff07e6caea4e2854ec9227fd635ed60a2f5eef2893052ffd54718fa08604acbf6a15e78a467c4a3c53c0278af06c4416573f925491b195e8fd79302cb1aaf7caf4ecfc9aec1254cc969786363ac729f914c6ddcc26738d6b0facd54eba026580aba2eb6482a088b0d224a8852420b91ec1")); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "310d1d4c414426b4836c2635bad1df3a424e50cbdd167ffccb4dfff57d36b4aae0d6ca0910698220171a0f3373c1060a046c2812f0027e321f72979daa5e7973214170d49e885de0c0ecc167837d44502430674a82522e5df6a0759548052420b91ec1")); +} + +test "big.int div multi-multi fuzz case #2" { + var a = try Int.init(al); + var b = try Int.init(al); + + try a.setString(16, "3ffffffffe00000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000000000000000000000000000000000000000000000000001fffffffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffc000000000000000000000000000000000000000000000000000000000000000"); + try b.setString(16, "ffc0000000000000000000000000000000000000000000000000"); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + const qs = try q.toString(al, 16); + testing.expect(std.mem.eql(u8, qs, "40100400fe3f8fe3f8fe3f8fe3f8fe3f8fe4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f91e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4992649926499264991e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4792e4b92e4b92e4b92e4b92a4a92a4a92a4")); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "a900000000000000000000000000000000000000000000000000")); +} + +test "big.int shift-right single" { + var a = try Int.initSet(al, 0xffff0000); + try a.shiftRight(a, 16); + + testing.expect((try a.to(u32)) == 0xffff); +} + +test "big.int shift-right multi" { + var a = try Int.initSet(al, 0xffff0000eeee1111dddd2222cccc3333); + try a.shiftRight(a, 67); + + testing.expect((try a.to(u64)) == 0x1fffe0001dddc222); +} + +test "big.int shift-left single" { + var a = try Int.initSet(al, 0xffff); + try a.shiftLeft(a, 16); + + testing.expect((try a.to(u64)) == 0xffff0000); +} + +test "big.int shift-left multi" { + var a = try Int.initSet(al, 0x1fffe0001dddc222); + try a.shiftLeft(a, 67); + + testing.expect((try a.to(u128)) == 0xffff0000eeee11100000000000000000); +} + +test "big.int shift-right negative" { + var a = try Int.init(al); + + try a.shiftRight(try Int.initSet(al, -20), 2); + testing.expect((try a.to(i32)) == -20 >> 2); + + try a.shiftRight(try Int.initSet(al, -5), 10); + testing.expect((try a.to(i32)) == -5 >> 10); +} + +test "big.int shift-left negative" { + var a = try Int.init(al); + + try a.shiftRight(try Int.initSet(al, -10), 1232); + testing.expect((try a.to(i32)) == -10 >> 1232); +} + +test "big.int bitwise and simple" { + var a = try Int.initSet(al, 0xffffffff11111111); + var b = try Int.initSet(al, 0xeeeeeeee22222222); + + try a.bitAnd(a, b); + + testing.expect((try a.to(u64)) == 0xeeeeeeee00000000); +} + +test "big.int bitwise and multi-limb" { + var a = try Int.initSet(al, maxInt(Limb) + 1); + var b = try Int.initSet(al, maxInt(Limb)); + + try a.bitAnd(a, b); + + testing.expect((try a.to(u128)) == 0); +} + +test "big.int bitwise xor simple" { + var a = try Int.initSet(al, 0xffffffff11111111); + var b = try Int.initSet(al, 0xeeeeeeee22222222); + + try a.bitXor(a, b); + + testing.expect((try a.to(u64)) == 0x1111111133333333); +} + +test "big.int bitwise xor multi-limb" { + var a = try Int.initSet(al, maxInt(Limb) + 1); + var b = try Int.initSet(al, maxInt(Limb)); + + try a.bitXor(a, b); + + testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) ^ maxInt(Limb)); +} + +test "big.int bitwise or simple" { + var a = try Int.initSet(al, 0xffffffff11111111); + var b = try Int.initSet(al, 0xeeeeeeee22222222); + + try a.bitOr(a, b); + + testing.expect((try a.to(u64)) == 0xffffffff33333333); +} + +test "big.int bitwise or multi-limb" { + var a = try Int.initSet(al, maxInt(Limb) + 1); + var b = try Int.initSet(al, maxInt(Limb)); + + try a.bitOr(a, b); + + // TODO: big.int.cpp or is wrong on multi-limb. + testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb)); +} + +test "big.int var args" { + var a = try Int.initSet(al, 5); + + try a.add(a, try Int.initSet(al, 6)); + testing.expect((try a.to(u64)) == 11); + + testing.expect(a.cmp(try Int.initSet(al, 11)) == 0); + testing.expect(a.cmp(try Int.initSet(al, 14)) <= 0); +} diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig new file mode 100644 index 0000000000..6a51931e3c --- /dev/null +++ b/lib/std/math/big/rational.zig @@ -0,0 +1,946 @@ +const std = @import("../../std.zig"); +const builtin = @import("builtin"); +const debug = std.debug; +const math = std.math; +const mem = std.mem; +const testing = std.testing; +const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; + +const TypeId = builtin.TypeId; + +const bn = @import("int.zig"); +const Limb = bn.Limb; +const DoubleLimb = bn.DoubleLimb; +const Int = bn.Int; + +/// An arbitrary-precision rational number. +/// +/// Memory is allocated as needed for operations to ensure full precision is kept. The precision +/// of a Rational is only bounded by memory. +/// +/// Rational's are always normalized. That is, for a Rational r = p/q where p and q are integers, +/// gcd(p, q) = 1 always. +pub const Rational = struct { + /// Numerator. Determines the sign of the Rational. + p: Int, + + /// Denominator. Sign is ignored. + q: Int, + + /// Create a new Rational. A small amount of memory will be allocated on initialization. + /// This will be 2 * Int.default_capacity. + pub fn init(a: *Allocator) !Rational { + return Rational{ + .p = try Int.init(a), + .q = try Int.initSet(a, 1), + }; + } + + /// Frees all memory associated with a Rational. + pub fn deinit(self: *Rational) void { + self.p.deinit(); + self.q.deinit(); + } + + /// Set a Rational from a primitive integer type. + pub fn setInt(self: *Rational, a: var) !void { + try self.p.set(a); + try self.q.set(1); + } + + /// Set a Rational from a string of the form `A/B` where A and B are base-10 integers. + pub fn setFloatString(self: *Rational, str: []const u8) !void { + // TODO: Accept a/b fractions and exponent form + if (str.len == 0) { + return error.InvalidFloatString; + } + + const State = enum { + Integer, + Fractional, + }; + + var state = State.Integer; + var point: ?usize = null; + + var start: usize = 0; + if (str[0] == '-') { + start += 1; + } + + for (str) |c, i| { + switch (state) { + State.Integer => { + switch (c) { + '.' => { + state = State.Fractional; + point = i; + }, + '0'...'9' => { + // okay + }, + else => { + return error.InvalidFloatString; + }, + } + }, + State.Fractional => { + switch (c) { + '0'...'9' => { + // okay + }, + else => { + return error.InvalidFloatString; + }, + } + }, + } + } + + // TODO: batch the multiplies by 10 + if (point) |i| { + try self.p.setString(10, str[0..i]); + + const base = Int.initFixed(([_]Limb{10})[0..]); + + var j: usize = start; + while (j < str.len - i - 1) : (j += 1) { + try self.p.mul(self.p, base); + } + + try self.q.setString(10, str[i + 1 ..]); + try self.p.add(self.p, self.q); + + try self.q.set(1); + var k: usize = i + 1; + while (k < str.len) : (k += 1) { + try self.q.mul(self.q, base); + } + + try self.reduce(); + } else { + try self.p.setString(10, str[0..]); + try self.q.set(1); + } + } + + /// Set a Rational from a floating-point value. The rational will have enough precision to + /// completely represent the provided float. + pub fn setFloat(self: *Rational, comptime T: type, f: T) !void { + // Translated from golang.go/src/math/big/rat.go. + debug.assert(@typeId(T) == builtin.TypeId.Float); + + const UnsignedIntType = @IntType(false, T.bit_count); + const f_bits = @bitCast(UnsignedIntType, f); + + const exponent_bits = math.floatExponentBits(T); + const exponent_bias = (1 << (exponent_bits - 1)) - 1; + const mantissa_bits = math.floatMantissaBits(T); + + const exponent_mask = (1 << exponent_bits) - 1; + const mantissa_mask = (1 << mantissa_bits) - 1; + + var exponent = @intCast(i16, (f_bits >> mantissa_bits) & exponent_mask); + var mantissa = f_bits & mantissa_mask; + + switch (exponent) { + exponent_mask => { + return error.NonFiniteFloat; + }, + 0 => { + // denormal + exponent -= exponent_bias - 1; + }, + else => { + // normal + mantissa |= 1 << mantissa_bits; + exponent -= exponent_bias; + }, + } + + var shift: i16 = mantissa_bits - exponent; + + // factor out powers of two early from rational + while (mantissa & 1 == 0 and shift > 0) { + mantissa >>= 1; + shift -= 1; + } + + try self.p.set(mantissa); + self.p.setSign(f >= 0); + + try self.q.set(1); + if (shift >= 0) { + try self.q.shiftLeft(self.q, @intCast(usize, shift)); + } else { + try self.p.shiftLeft(self.p, @intCast(usize, -shift)); + } + + try self.reduce(); + } + + /// Return a floating-point value that is the closest value to a Rational. + /// + /// The result may not be exact if the Rational is too precise or too large for the + /// target type. + pub fn toFloat(self: Rational, comptime T: type) !T { + // Translated from golang.go/src/math/big/rat.go. + // TODO: Indicate whether the result is not exact. + debug.assert(@typeId(T) == builtin.TypeId.Float); + + const fsize = T.bit_count; + const BitReprType = @IntType(false, T.bit_count); + + const msize = math.floatMantissaBits(T); + const msize1 = msize + 1; + const msize2 = msize1 + 1; + + const esize = math.floatExponentBits(T); + const ebias = (1 << (esize - 1)) - 1; + const emin = 1 - ebias; + const emax = ebias; + + if (self.p.eqZero()) { + return 0; + } + + // 1. left-shift a or sub so that a/b is in [1 << msize1, 1 << (msize2 + 1)] + var exp = @intCast(isize, self.p.bitCountTwosComp()) - @intCast(isize, self.q.bitCountTwosComp()); + + var a2 = try self.p.clone(); + defer a2.deinit(); + + var b2 = try self.q.clone(); + defer b2.deinit(); + + const shift = msize2 - exp; + if (shift >= 0) { + try a2.shiftLeft(a2, @intCast(usize, shift)); + } else { + try b2.shiftLeft(b2, @intCast(usize, -shift)); + } + + // 2. compute quotient and remainder + var q = try Int.init(self.p.allocator.?); + defer q.deinit(); + + // unused + var r = try Int.init(self.p.allocator.?); + defer r.deinit(); + + try Int.divTrunc(&q, &r, a2, b2); + + var mantissa = extractLowBits(q, BitReprType); + var have_rem = r.len() > 0; + + // 3. q didn't fit in msize2 bits, redo division b2 << 1 + if (mantissa >> msize2 == 1) { + if (mantissa & 1 == 1) { + have_rem = true; + } + mantissa >>= 1; + exp += 1; + } + if (mantissa >> msize1 != 1) { + // NOTE: This can be hit if the limb size is small (u8/16). + @panic("unexpected bits in result"); + } + + // 4. Rounding + if (emin - msize <= exp and exp <= emin) { + // denormal + const shift1 = @intCast(math.Log2Int(BitReprType), emin - (exp - 1)); + const lost_bits = mantissa & ((@intCast(BitReprType, 1) << shift1) - 1); + have_rem = have_rem or lost_bits != 0; + mantissa >>= shift1; + exp = 2 - ebias; + } + + // round q using round-half-to-even + var exact = !have_rem; + if (mantissa & 1 != 0) { + exact = false; + if (have_rem or (mantissa & 2 != 0)) { + mantissa += 1; + if (mantissa >= 1 << msize2) { + // 11...1 => 100...0 + mantissa >>= 1; + exp += 1; + } + } + } + mantissa >>= 1; + + const f = math.scalbn(@intToFloat(T, mantissa), @intCast(i32, exp - msize1)); + if (math.isInf(f)) { + exact = false; + } + + return if (self.p.isPositive()) f else -f; + } + + /// Set a rational from an integer ratio. + pub fn setRatio(self: *Rational, p: var, q: var) !void { + try self.p.set(p); + try self.q.set(q); + + self.p.setSign(@boolToInt(self.p.isPositive()) ^ @boolToInt(self.q.isPositive()) == 0); + self.q.setSign(true); + + try self.reduce(); + + if (self.q.eqZero()) { + @panic("cannot set rational with denominator = 0"); + } + } + + /// Set a Rational directly from an Int. + pub fn copyInt(self: *Rational, a: Int) !void { + try self.p.copy(a); + try self.q.set(1); + } + + /// Set a Rational directly from a ratio of two Int's. + pub fn copyRatio(self: *Rational, a: Int, b: Int) !void { + try self.p.copy(a); + try self.q.copy(b); + + self.p.setSign(@boolToInt(self.p.isPositive()) ^ @boolToInt(self.q.isPositive()) == 0); + self.q.setSign(true); + + try self.reduce(); + } + + /// Make a Rational positive. + pub fn abs(r: *Rational) void { + r.p.abs(); + } + + /// Negate the sign of a Rational. + pub fn negate(r: *Rational) void { + r.p.negate(); + } + + /// Efficiently swap a Rational with another. This swaps the limb pointers and a full copy is not + /// performed. The address of the limbs field will not be the same after this function. + pub fn swap(r: *Rational, other: *Rational) void { + r.p.swap(&other.p); + r.q.swap(&other.q); + } + + /// Returns -1, 0, 1 if a < b, a == b or a > b respectively. + pub fn cmp(a: Rational, b: Rational) !i8 { + return cmpInternal(a, b, true); + } + + /// Returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. + pub fn cmpAbs(a: Rational, b: Rational) !i8 { + return cmpInternal(a, b, false); + } + + // p/q > x/y iff p*y > x*q + fn cmpInternal(a: Rational, b: Rational, is_abs: bool) !i8 { + // TODO: Would a div compare algorithm of sorts be viable and quicker? Can we avoid + // the memory allocations here? + var q = try Int.init(a.p.allocator.?); + defer q.deinit(); + + var p = try Int.init(b.p.allocator.?); + defer p.deinit(); + + try q.mul(a.p, b.q); + try p.mul(b.p, a.q); + + return if (is_abs) q.cmpAbs(p) else q.cmp(p); + } + + /// rma = a + b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn add(rma: *Rational, a: Rational, b: Rational) !void { + var r = rma; + var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr; + + var sr: Rational = undefined; + if (aliased) { + sr = try Rational.init(rma.p.allocator.?); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try r.p.mul(a.p, b.q); + try r.q.mul(b.p, a.q); + try r.p.add(r.p, r.q); + + try r.q.mul(a.q, b.q); + try r.reduce(); + } + + /// rma = a - b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn sub(rma: *Rational, a: Rational, b: Rational) !void { + var r = rma; + var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr; + + var sr: Rational = undefined; + if (aliased) { + sr = try Rational.init(rma.p.allocator.?); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try r.p.mul(a.p, b.q); + try r.q.mul(b.p, a.q); + try r.p.sub(r.p, r.q); + + try r.q.mul(a.q, b.q); + try r.reduce(); + } + + /// rma = a * b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn mul(r: *Rational, a: Rational, b: Rational) !void { + try r.p.mul(a.p, b.p); + try r.q.mul(a.q, b.q); + try r.reduce(); + } + + /// rma = a / b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn div(r: *Rational, a: Rational, b: Rational) !void { + if (b.p.eqZero()) { + @panic("division by zero"); + } + + try r.p.mul(a.p, b.q); + try r.q.mul(b.p, a.q); + try r.reduce(); + } + + /// Invert the numerator and denominator fields of a Rational. p/q => q/p. + pub fn invert(r: *Rational) void { + Int.swap(&r.p, &r.q); + } + + // reduce r/q such that gcd(r, q) = 1 + fn reduce(r: *Rational) !void { + var a = try Int.init(r.p.allocator.?); + defer a.deinit(); + + const sign = r.p.isPositive(); + r.p.abs(); + try gcd(&a, r.p, r.q); + r.p.setSign(sign); + + const one = Int.initFixed(([_]Limb{1})[0..]); + if (a.cmp(one) != 0) { + var unused = try Int.init(r.p.allocator.?); + defer unused.deinit(); + + // TODO: divexact would be useful here + // TODO: don't copy r.q for div + try Int.divTrunc(&r.p, &unused, r.p, a); + try Int.divTrunc(&r.q, &unused, r.q, a); + } + } +}; + +const SignedDoubleLimb = @IntType(true, DoubleLimb.bit_count); + +fn gcd(rma: *Int, x: Int, y: Int) !void { + rma.assertWritable(); + var r = rma; + var aliased = rma.limbs.ptr == x.limbs.ptr or rma.limbs.ptr == y.limbs.ptr; + + var sr: Int = undefined; + if (aliased) { + sr = try Int.initCapacity(rma.allocator.?, math.max(x.len(), y.len())); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try gcdLehmer(r, x, y); +} + +// Storage must live for the lifetime of the returned value +fn FixedIntFromSignedDoubleLimb(A: SignedDoubleLimb, storage: []Limb) Int { + std.debug.assert(storage.len >= 2); + + var A_is_positive = A >= 0; + const Au = @intCast(DoubleLimb, if (A < 0) -A else A); + storage[0] = @truncate(Limb, Au); + storage[1] = @truncate(Limb, Au >> Limb.bit_count); + var Ap = Int.initFixed(storage[0..2]); + Ap.setSign(A_is_positive); + return Ap; +} + +fn gcdLehmer(r: *Int, xa: Int, ya: Int) !void { + var x = try xa.clone(); + x.abs(); + defer x.deinit(); + + var y = try ya.clone(); + y.abs(); + defer y.deinit(); + + if (x.cmp(y) < 0) { + x.swap(&y); + } + + var T = try Int.init(r.allocator.?); + defer T.deinit(); + + while (y.len() > 1) { + debug.assert(x.isPositive() and y.isPositive()); + debug.assert(x.len() >= y.len()); + + var xh: SignedDoubleLimb = x.limbs[x.len() - 1]; + var yh: SignedDoubleLimb = if (x.len() > y.len()) 0 else y.limbs[x.len() - 1]; + + var A: SignedDoubleLimb = 1; + var B: SignedDoubleLimb = 0; + var C: SignedDoubleLimb = 0; + var D: SignedDoubleLimb = 1; + + while (yh + C != 0 and yh + D != 0) { + const q = @divFloor(xh + A, yh + C); + const qp = @divFloor(xh + B, yh + D); + if (q != qp) { + break; + } + + var t = A - q * C; + A = C; + C = t; + t = B - q * D; + B = D; + D = t; + + t = xh - q * yh; + xh = yh; + yh = t; + } + + if (B == 0) { + // T = x % y, r is unused + try Int.divTrunc(r, &T, x, y); + debug.assert(T.isPositive()); + + x.swap(&y); + y.swap(&T); + } else { + var storage: [8]Limb = undefined; + const Ap = FixedIntFromSignedDoubleLimb(A, storage[0..2]); + const Bp = FixedIntFromSignedDoubleLimb(B, storage[2..4]); + const Cp = FixedIntFromSignedDoubleLimb(C, storage[4..6]); + const Dp = FixedIntFromSignedDoubleLimb(D, storage[6..8]); + + // T = Ax + By + try r.mul(x, Ap); + try T.mul(y, Bp); + try T.add(r.*, T); + + // u = Cx + Dy, r as u + try x.mul(x, Cp); + try r.mul(y, Dp); + try r.add(x, r.*); + + x.swap(&T); + y.swap(r); + } + } + + // euclidean algorithm + debug.assert(x.cmp(y) >= 0); + + while (!y.eqZero()) { + try Int.divTrunc(&T, r, x, y); + x.swap(&y); + y.swap(r); + } + + r.swap(&x); +} + +var buffer: [64 * 8192]u8 = undefined; +var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); +var al = &fixed.allocator; + +test "big.rational gcd non-one small" { + var a = try Int.initSet(al, 17); + var b = try Int.initSet(al, 97); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u32)) == 1); +} + +test "big.rational gcd non-one small" { + var a = try Int.initSet(al, 4864); + var b = try Int.initSet(al, 3458); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u32)) == 38); +} + +test "big.rational gcd non-one large" { + var a = try Int.initSet(al, 0xffffffffffffffff); + var b = try Int.initSet(al, 0xffffffffffffffff7777); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u32)) == 4369); +} + +test "big.rational gcd large multi-limb result" { + var a = try Int.initSet(al, 0x12345678123456781234567812345678123456781234567812345678); + var b = try Int.initSet(al, 0x12345671234567123456712345671234567123456712345671234567); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u256)) == 0xf000000ff00000fff0000ffff000fffff00ffffff1); +} + +test "big.rational gcd one large" { + var a = try Int.initSet(al, 1897056385327307); + var b = try Int.initSet(al, 2251799813685248); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u64)) == 1); +} + +fn extractLowBits(a: Int, comptime T: type) T { + testing.expect(@typeId(T) == builtin.TypeId.Int); + + if (T.bit_count <= Limb.bit_count) { + return @truncate(T, a.limbs[0]); + } else { + var r: T = 0; + comptime var i: usize = 0; + + // Remainder is always 0 since if T.bit_count >= Limb.bit_count -> Limb | T and both + // are powers of two. + inline while (i < T.bit_count / Limb.bit_count) : (i += 1) { + r |= math.shl(T, a.limbs[i], i * Limb.bit_count); + } + + return r; + } +} + +test "big.rational extractLowBits" { + var a = try Int.initSet(al, 0x11112222333344441234567887654321); + + const a1 = extractLowBits(a, u8); + testing.expect(a1 == 0x21); + + const a2 = extractLowBits(a, u16); + testing.expect(a2 == 0x4321); + + const a3 = extractLowBits(a, u32); + testing.expect(a3 == 0x87654321); + + const a4 = extractLowBits(a, u64); + testing.expect(a4 == 0x1234567887654321); + + const a5 = extractLowBits(a, u128); + testing.expect(a5 == 0x11112222333344441234567887654321); +} + +test "big.rational set" { + var a = try Rational.init(al); + + try a.setInt(5); + testing.expect((try a.p.to(u32)) == 5); + testing.expect((try a.q.to(u32)) == 1); + + try a.setRatio(7, 3); + testing.expect((try a.p.to(u32)) == 7); + testing.expect((try a.q.to(u32)) == 3); + + try a.setRatio(9, 3); + testing.expect((try a.p.to(i32)) == 3); + testing.expect((try a.q.to(i32)) == 1); + + try a.setRatio(-9, 3); + testing.expect((try a.p.to(i32)) == -3); + testing.expect((try a.q.to(i32)) == 1); + + try a.setRatio(9, -3); + testing.expect((try a.p.to(i32)) == -3); + testing.expect((try a.q.to(i32)) == 1); + + try a.setRatio(-9, -3); + testing.expect((try a.p.to(i32)) == 3); + testing.expect((try a.q.to(i32)) == 1); +} + +test "big.rational setFloat" { + var a = try Rational.init(al); + + try a.setFloat(f64, 2.5); + testing.expect((try a.p.to(i32)) == 5); + testing.expect((try a.q.to(i32)) == 2); + + try a.setFloat(f32, -2.5); + testing.expect((try a.p.to(i32)) == -5); + testing.expect((try a.q.to(i32)) == 2); + + try a.setFloat(f32, 3.141593); + + // = 3.14159297943115234375 + testing.expect((try a.p.to(u32)) == 3294199); + testing.expect((try a.q.to(u32)) == 1048576); + + try a.setFloat(f64, 72.141593120712409172417410926841290461290467124); + + // = 72.1415931207124145885245525278151035308837890625 + testing.expect((try a.p.to(u128)) == 5076513310880537); + testing.expect((try a.q.to(u128)) == 70368744177664); +} + +test "big.rational setFloatString" { + var a = try Rational.init(al); + + try a.setFloatString("72.14159312071241458852455252781510353"); + + // = 72.1415931207124145885245525278151035308837890625 + testing.expect((try a.p.to(u128)) == 7214159312071241458852455252781510353); + testing.expect((try a.q.to(u128)) == 100000000000000000000000000000000000); +} + +test "big.rational toFloat" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + var a = try Rational.init(al); + + // = 3.14159297943115234375 + try a.setRatio(3294199, 1048576); + testing.expect((try a.toFloat(f64)) == 3.14159297943115234375); + + // = 72.1415931207124145885245525278151035308837890625 + try a.setRatio(5076513310880537, 70368744177664); + testing.expect((try a.toFloat(f64)) == 72.141593120712409172417410926841290461290467124); +} + +test "big.rational set/to Float round-trip" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + var a = try Rational.init(al); + var prng = std.rand.DefaultPrng.init(0x5EED); + var i: usize = 0; + while (i < 512) : (i += 1) { + const r = prng.random.float(f64); + try a.setFloat(f64, r); + testing.expect((try a.toFloat(f64)) == r); + } +} + +test "big.rational copy" { + var a = try Rational.init(al); + + const b = try Int.initSet(al, 5); + + try a.copyInt(b); + testing.expect((try a.p.to(u32)) == 5); + testing.expect((try a.q.to(u32)) == 1); + + const c = try Int.initSet(al, 7); + const d = try Int.initSet(al, 3); + + try a.copyRatio(c, d); + testing.expect((try a.p.to(u32)) == 7); + testing.expect((try a.q.to(u32)) == 3); + + const e = try Int.initSet(al, 9); + const f = try Int.initSet(al, 3); + + try a.copyRatio(e, f); + testing.expect((try a.p.to(u32)) == 3); + testing.expect((try a.q.to(u32)) == 1); +} + +test "big.rational negate" { + var a = try Rational.init(al); + + try a.setInt(-50); + testing.expect((try a.p.to(i32)) == -50); + testing.expect((try a.q.to(i32)) == 1); + + a.negate(); + testing.expect((try a.p.to(i32)) == 50); + testing.expect((try a.q.to(i32)) == 1); + + a.negate(); + testing.expect((try a.p.to(i32)) == -50); + testing.expect((try a.q.to(i32)) == 1); +} + +test "big.rational abs" { + var a = try Rational.init(al); + + try a.setInt(-50); + testing.expect((try a.p.to(i32)) == -50); + testing.expect((try a.q.to(i32)) == 1); + + a.abs(); + testing.expect((try a.p.to(i32)) == 50); + testing.expect((try a.q.to(i32)) == 1); + + a.abs(); + testing.expect((try a.p.to(i32)) == 50); + testing.expect((try a.q.to(i32)) == 1); +} + +test "big.rational swap" { + var a = try Rational.init(al); + var b = try Rational.init(al); + + try a.setRatio(50, 23); + try b.setRatio(17, 3); + + testing.expect((try a.p.to(u32)) == 50); + testing.expect((try a.q.to(u32)) == 23); + + testing.expect((try b.p.to(u32)) == 17); + testing.expect((try b.q.to(u32)) == 3); + + a.swap(&b); + + testing.expect((try a.p.to(u32)) == 17); + testing.expect((try a.q.to(u32)) == 3); + + testing.expect((try b.p.to(u32)) == 50); + testing.expect((try b.q.to(u32)) == 23); +} + +test "big.rational cmp" { + var a = try Rational.init(al); + var b = try Rational.init(al); + + try a.setRatio(500, 231); + try b.setRatio(18903, 8584); + testing.expect((try a.cmp(b)) < 0); + + try a.setRatio(890, 10); + try b.setRatio(89, 1); + testing.expect((try a.cmp(b)) == 0); +} + +test "big.rational add single-limb" { + var a = try Rational.init(al); + var b = try Rational.init(al); + + try a.setRatio(500, 231); + try b.setRatio(18903, 8584); + testing.expect((try a.cmp(b)) < 0); + + try a.setRatio(890, 10); + try b.setRatio(89, 1); + testing.expect((try a.cmp(b)) == 0); +} + +test "big.rational add" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.add(a, b); + + try r.setRatio(984786924199, 290395044174); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational sub" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.sub(a, b); + + try r.setRatio(979040510045, 290395044174); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational mul" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.mul(a, b); + + try r.setRatio(571481443, 17082061422); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational div" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.div(a, b); + + try r.setRatio(75531824394, 221015929); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational div" { + var a = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + a.invert(); + + try r.setRatio(23341, 78923); + testing.expect((try a.cmp(r)) == 0); + + try a.setRatio(-78923, 23341); + a.invert(); + + try r.setRatio(-23341, 78923); + testing.expect((try a.cmp(r)) == 0); +} diff --git a/lib/std/math/cbrt.zig b/lib/std/math/cbrt.zig new file mode 100644 index 0000000000..5241e31323 --- /dev/null +++ b/lib/std/math/cbrt.zig @@ -0,0 +1,163 @@ +// 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/math/cbrtf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/cbrt.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the cube root of x. +/// +/// Special Cases: +/// - cbrt(+-0) = +-0 +/// - cbrt(+-inf) = +-inf +/// - cbrt(nan) = nan +pub fn cbrt(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => cbrt32(x), + f64 => cbrt64(x), + else => @compileError("cbrt not implemented for " ++ @typeName(T)), + }; +} + +fn cbrt32(x: f32) f32 { + const B1: u32 = 709958130; // (127 - 127.0 / 3 - 0.03306235651) * 2^23 + const B2: u32 = 642849266; // (127 - 127.0 / 3 - 24 / 3 - 0.03306235651) * 2^23 + + var u = @bitCast(u32, x); + var hx = u & 0x7FFFFFFF; + + // cbrt(nan, inf) = itself + if (hx >= 0x7F800000) { + return x + x; + } + + // cbrt to ~5bits + if (hx < 0x00800000) { + // cbrt(+-0) = itself + if (hx == 0) { + return x; + } + u = @bitCast(u32, x * 0x1.0p24); + hx = u & 0x7FFFFFFF; + hx = hx / 3 + B2; + } else { + hx = hx / 3 + B1; + } + + u &= 0x80000000; + u |= hx; + + // first step newton to 16 bits + var t: f64 = @bitCast(f32, u); + var r: f64 = t * t * t; + t = t * (f64(x) + x + r) / (x + r + r); + + // second step newton to 47 bits + r = t * t * t; + t = t * (f64(x) + x + r) / (x + r + r); + + return @floatCast(f32, t); +} + +fn cbrt64(x: f64) f64 { + const B1: u32 = 715094163; // (1023 - 1023 / 3 - 0.03306235651 * 2^20 + const B2: u32 = 696219795; // (1023 - 1023 / 3 - 54 / 3 - 0.03306235651 * 2^20 + + // |1 / cbrt(x) - p(x)| < 2^(23.5) + const P0: f64 = 1.87595182427177009643; + const P1: f64 = -1.88497979543377169875; + const P2: f64 = 1.621429720105354466140; + const P3: f64 = -0.758397934778766047437; + const P4: f64 = 0.145996192886612446982; + + var u = @bitCast(u64, x); + var hx = @intCast(u32, u >> 32) & 0x7FFFFFFF; + + // cbrt(nan, inf) = itself + if (hx >= 0x7FF00000) { + return x + x; + } + + // cbrt to ~5bits + if (hx < 0x00100000) { + u = @bitCast(u64, x * 0x1.0p54); + hx = @intCast(u32, u >> 32) & 0x7FFFFFFF; + + // cbrt(0) is itself + if (hx == 0) { + return 0; + } + hx = hx / 3 + B2; + } else { + hx = hx / 3 + B1; + } + + u &= 1 << 63; + u |= u64(hx) << 32; + var t = @bitCast(f64, u); + + // cbrt to 23 bits + // cbrt(x) = t * cbrt(x / t^3) ~= t * P(t^3 / x) + var r = (t * t) * (t / x); + t = t * ((P0 + r * (P1 + r * P2)) + ((r * r) * r) * (P3 + r * P4)); + + // Round t away from 0 to 23 bits + u = @bitCast(u64, t); + u = (u + 0x80000000) & 0xFFFFFFFFC0000000; + t = @bitCast(f64, u); + + // one step newton to 53 bits + const s = t * t; + var q = x / s; + var w = t + t; + q = (q - t) / (w + q); + + return t + t * q; +} + +test "math.cbrt" { + expect(cbrt(f32(0.0)) == cbrt32(0.0)); + expect(cbrt(f64(0.0)) == cbrt64(0.0)); +} + +test "math.cbrt32" { + const epsilon = 0.000001; + + expect(cbrt32(0.0) == 0.0); + expect(math.approxEq(f32, cbrt32(0.2), 0.584804, epsilon)); + expect(math.approxEq(f32, cbrt32(0.8923), 0.962728, epsilon)); + expect(math.approxEq(f32, cbrt32(1.5), 1.144714, epsilon)); + expect(math.approxEq(f32, cbrt32(37.45), 3.345676, epsilon)); + expect(math.approxEq(f32, cbrt32(123123.234375), 49.748501, epsilon)); +} + +test "math.cbrt64" { + const epsilon = 0.000001; + + expect(cbrt64(0.0) == 0.0); + expect(math.approxEq(f64, cbrt64(0.2), 0.584804, epsilon)); + expect(math.approxEq(f64, cbrt64(0.8923), 0.962728, epsilon)); + expect(math.approxEq(f64, cbrt64(1.5), 1.144714, epsilon)); + expect(math.approxEq(f64, cbrt64(37.45), 3.345676, epsilon)); + expect(math.approxEq(f64, cbrt64(123123.234375), 49.748501, epsilon)); +} + +test "math.cbrt.special" { + expect(cbrt32(0.0) == 0.0); + expect(cbrt32(-0.0) == -0.0); + expect(math.isPositiveInf(cbrt32(math.inf(f32)))); + expect(math.isNegativeInf(cbrt32(-math.inf(f32)))); + expect(math.isNan(cbrt32(math.nan(f32)))); +} + +test "math.cbrt64.special" { + expect(cbrt64(0.0) == 0.0); + expect(cbrt64(-0.0) == -0.0); + expect(math.isPositiveInf(cbrt64(math.inf(f64)))); + expect(math.isNegativeInf(cbrt64(-math.inf(f64)))); + expect(math.isNan(cbrt64(math.nan(f64)))); +} diff --git a/lib/std/math/ceil.zig b/lib/std/math/ceil.zig new file mode 100644 index 0000000000..5f86093a6d --- /dev/null +++ b/lib/std/math/ceil.zig @@ -0,0 +1,120 @@ +// 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/math/ceilf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/ceil.c + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the least integer value greater than of equal to x. +/// +/// Special Cases: +/// - ceil(+-0) = +-0 +/// - ceil(+-inf) = +-inf +/// - ceil(nan) = nan +pub fn ceil(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => ceil32(x), + f64 => ceil64(x), + else => @compileError("ceil not implemented for " ++ @typeName(T)), + }; +} + +fn ceil32(x: f32) f32 { + var u = @bitCast(u32, x); + var e = @intCast(i32, (u >> 23) & 0xFF) - 0x7F; + var m: u32 = undefined; + + // TODO: Shouldn't need this explicit check. + if (x == 0.0) { + return x; + } + + if (e >= 23) { + return x; + } else if (e >= 0) { + m = u32(0x007FFFFF) >> @intCast(u5, e); + if (u & m == 0) { + return x; + } + math.forceEval(x + 0x1.0p120); + if (u >> 31 == 0) { + u += m; + } + u &= ~m; + return @bitCast(f32, u); + } else { + math.forceEval(x + 0x1.0p120); + if (u >> 31 != 0) { + return -0.0; + } else { + return 1.0; + } + } +} + +fn ceil64(x: f64) f64 { + const u = @bitCast(u64, x); + const e = (u >> 52) & 0x7FF; + var y: f64 = undefined; + + if (e >= 0x3FF + 52 or x == 0) { + return x; + } + + if (u >> 63 != 0) { + y = x - math.f64_toint + math.f64_toint - x; + } else { + y = x + math.f64_toint - math.f64_toint - x; + } + + if (e <= 0x3FF - 1) { + math.forceEval(y); + if (u >> 63 != 0) { + return -0.0; + } else { + return 1.0; + } + } else if (y < 0) { + return x + y + 1; + } else { + return x + y; + } +} + +test "math.ceil" { + expect(ceil(f32(0.0)) == ceil32(0.0)); + expect(ceil(f64(0.0)) == ceil64(0.0)); +} + +test "math.ceil32" { + expect(ceil32(1.3) == 2.0); + expect(ceil32(-1.3) == -1.0); + expect(ceil32(0.2) == 1.0); +} + +test "math.ceil64" { + expect(ceil64(1.3) == 2.0); + expect(ceil64(-1.3) == -1.0); + expect(ceil64(0.2) == 1.0); +} + +test "math.ceil32.special" { + expect(ceil32(0.0) == 0.0); + expect(ceil32(-0.0) == -0.0); + expect(math.isPositiveInf(ceil32(math.inf(f32)))); + expect(math.isNegativeInf(ceil32(-math.inf(f32)))); + expect(math.isNan(ceil32(math.nan(f32)))); +} + +test "math.ceil64.special" { + expect(ceil64(0.0) == 0.0); + expect(ceil64(-0.0) == -0.0); + expect(math.isPositiveInf(ceil64(math.inf(f64)))); + expect(math.isNegativeInf(ceil64(-math.inf(f64)))); + expect(math.isNan(ceil64(math.nan(f64)))); +} diff --git a/lib/std/math/complex.zig b/lib/std/math/complex.zig new file mode 100644 index 0000000000..e5574f9cee --- /dev/null +++ b/lib/std/math/complex.zig @@ -0,0 +1,183 @@ +const std = @import("../std.zig"); +const testing = std.testing; +const math = std.math; + +pub const abs = @import("complex/abs.zig").abs; +pub const acosh = @import("complex/acosh.zig").acosh; +pub const acos = @import("complex/acos.zig").acos; +pub const arg = @import("complex/arg.zig").arg; +pub const asinh = @import("complex/asinh.zig").asinh; +pub const asin = @import("complex/asin.zig").asin; +pub const atanh = @import("complex/atanh.zig").atanh; +pub const atan = @import("complex/atan.zig").atan; +pub const conj = @import("complex/conj.zig").conj; +pub const cosh = @import("complex/cosh.zig").cosh; +pub const cos = @import("complex/cos.zig").cos; +pub const exp = @import("complex/exp.zig").exp; +pub const log = @import("complex/log.zig").log; +pub const pow = @import("complex/pow.zig").pow; +pub const proj = @import("complex/proj.zig").proj; +pub const sinh = @import("complex/sinh.zig").sinh; +pub const sin = @import("complex/sin.zig").sin; +pub const sqrt = @import("complex/sqrt.zig").sqrt; +pub const tanh = @import("complex/tanh.zig").tanh; +pub const tan = @import("complex/tan.zig").tan; + +/// A complex number consisting of a real an imaginary part. T must be a floating-point value. +pub fn Complex(comptime T: type) type { + return struct { + const Self = @This(); + + /// Real part. + re: T, + + /// Imaginary part. + im: T, + + /// Create a new Complex number from the given real and imaginary parts. + pub fn new(re: T, im: T) Self { + return Self{ + .re = re, + .im = im, + }; + } + + /// Returns the sum of two complex numbers. + pub fn add(self: Self, other: Self) Self { + return Self{ + .re = self.re + other.re, + .im = self.im + other.im, + }; + } + + /// Returns the subtraction of two complex numbers. + pub fn sub(self: Self, other: Self) Self { + return Self{ + .re = self.re - other.re, + .im = self.im - other.im, + }; + } + + /// Returns the product of two complex numbers. + pub fn mul(self: Self, other: Self) Self { + return Self{ + .re = self.re * other.re - self.im * other.im, + .im = self.im * other.re + self.re * other.im, + }; + } + + /// Returns the quotient of two complex numbers. + pub fn div(self: Self, other: Self) Self { + const re_num = self.re * other.re + self.im * other.im; + const im_num = self.im * other.re - self.re * other.im; + const den = other.re * other.re + other.im * other.im; + + return Self{ + .re = re_num / den, + .im = im_num / den, + }; + } + + /// Returns the complex conjugate of a number. + pub fn conjugate(self: Self) Self { + return Self{ + .re = self.re, + .im = -self.im, + }; + } + + /// Returns the reciprocal of a complex number. + pub fn reciprocal(self: Self) Self { + const m = self.re * self.re + self.im * self.im; + return Self{ + .re = self.re / m, + .im = -self.im / m, + }; + } + + /// Returns the magnitude of a complex number. + pub fn magnitude(self: Self) T { + return math.sqrt(self.re * self.re + self.im * self.im); + } + }; +} + +const epsilon = 0.0001; + +test "complex.add" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.add(b); + + testing.expect(c.re == 7 and c.im == 10); +} + +test "complex.sub" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.sub(b); + + testing.expect(c.re == 3 and c.im == -4); +} + +test "complex.mul" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.mul(b); + + testing.expect(c.re == -11 and c.im == 41); +} + +test "complex.div" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.div(b); + + testing.expect(math.approxEq(f32, c.re, f32(31) / 53, epsilon) and + math.approxEq(f32, c.im, f32(-29) / 53, epsilon)); +} + +test "complex.conjugate" { + const a = Complex(f32).new(5, 3); + const c = a.conjugate(); + + testing.expect(c.re == 5 and c.im == -3); +} + +test "complex.reciprocal" { + const a = Complex(f32).new(5, 3); + const c = a.reciprocal(); + + testing.expect(math.approxEq(f32, c.re, f32(5) / 34, epsilon) and + math.approxEq(f32, c.im, f32(-3) / 34, epsilon)); +} + +test "complex.magnitude" { + const a = Complex(f32).new(5, 3); + const c = a.magnitude(); + + testing.expect(math.approxEq(f32, c, 5.83095, epsilon)); +} + +test "complex.cmath" { + _ = @import("complex/abs.zig"); + _ = @import("complex/acosh.zig"); + _ = @import("complex/acos.zig"); + _ = @import("complex/arg.zig"); + _ = @import("complex/asinh.zig"); + _ = @import("complex/asin.zig"); + _ = @import("complex/atanh.zig"); + _ = @import("complex/atan.zig"); + _ = @import("complex/conj.zig"); + _ = @import("complex/cosh.zig"); + _ = @import("complex/cos.zig"); + _ = @import("complex/exp.zig"); + _ = @import("complex/log.zig"); + _ = @import("complex/pow.zig"); + _ = @import("complex/proj.zig"); + _ = @import("complex/sinh.zig"); + _ = @import("complex/sin.zig"); + _ = @import("complex/sqrt.zig"); + _ = @import("complex/tanh.zig"); + _ = @import("complex/tan.zig"); +} diff --git a/lib/std/math/complex/abs.zig b/lib/std/math/complex/abs.zig new file mode 100644 index 0000000000..8105f57218 --- /dev/null +++ b/lib/std/math/complex/abs.zig @@ -0,0 +1,19 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the absolute value (modulus) of z. +pub fn abs(z: var) @typeOf(z.re) { + const T = @typeOf(z.re); + return math.hypot(T, z.re, z.im); +} + +const epsilon = 0.0001; + +test "complex.cabs" { + const a = Complex(f32).new(5, 3); + const c = abs(a); + testing.expect(math.approxEq(f32, c, 5.83095, epsilon)); +} diff --git a/lib/std/math/complex/acos.zig b/lib/std/math/complex/acos.zig new file mode 100644 index 0000000000..f3526cc9ff --- /dev/null +++ b/lib/std/math/complex/acos.zig @@ -0,0 +1,22 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the arc-cosine of z. +pub fn acos(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = cmath.asin(z); + return Complex(T).new(T(math.pi) / 2 - q.re, -q.im); +} + +const epsilon = 0.0001; + +test "complex.cacos" { + const a = Complex(f32).new(5, 3); + const c = acos(a); + + testing.expect(math.approxEq(f32, c.re, 0.546975, epsilon)); + testing.expect(math.approxEq(f32, c.im, -2.452914, epsilon)); +} diff --git a/lib/std/math/complex/acosh.zig b/lib/std/math/complex/acosh.zig new file mode 100644 index 0000000000..6f0fd2e36c --- /dev/null +++ b/lib/std/math/complex/acosh.zig @@ -0,0 +1,22 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the hyperbolic arc-cosine of z. +pub fn acosh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = cmath.acos(z); + return Complex(T).new(-q.im, q.re); +} + +const epsilon = 0.0001; + +test "complex.cacosh" { + const a = Complex(f32).new(5, 3); + const c = acosh(a); + + testing.expect(math.approxEq(f32, c.re, 2.452914, epsilon)); + testing.expect(math.approxEq(f32, c.im, 0.546975, epsilon)); +} diff --git a/lib/std/math/complex/arg.zig b/lib/std/math/complex/arg.zig new file mode 100644 index 0000000000..d0c9588b8d --- /dev/null +++ b/lib/std/math/complex/arg.zig @@ -0,0 +1,19 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the angular component (in radians) of z. +pub fn arg(z: var) @typeOf(z.re) { + const T = @typeOf(z.re); + return math.atan2(T, z.im, z.re); +} + +const epsilon = 0.0001; + +test "complex.carg" { + const a = Complex(f32).new(5, 3); + const c = arg(a); + testing.expect(math.approxEq(f32, c, 0.540420, epsilon)); +} diff --git a/lib/std/math/complex/asin.zig b/lib/std/math/complex/asin.zig new file mode 100644 index 0000000000..76f94a286c --- /dev/null +++ b/lib/std/math/complex/asin.zig @@ -0,0 +1,28 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +// Returns the arc-sine of z. +pub fn asin(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const x = z.re; + const y = z.im; + + const p = Complex(T).new(1.0 - (x - y) * (x + y), -2.0 * x * y); + const q = Complex(T).new(-y, x); + const r = cmath.log(q.add(cmath.sqrt(p))); + + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.casin" { + const a = Complex(f32).new(5, 3); + const c = asin(a); + + testing.expect(math.approxEq(f32, c.re, 1.023822, epsilon)); + testing.expect(math.approxEq(f32, c.im, 2.452914, epsilon)); +} diff --git a/lib/std/math/complex/asinh.zig b/lib/std/math/complex/asinh.zig new file mode 100644 index 0000000000..da065aad01 --- /dev/null +++ b/lib/std/math/complex/asinh.zig @@ -0,0 +1,23 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the hyperbolic arc-sine of z. +pub fn asinh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = Complex(T).new(-z.im, z.re); + const r = cmath.asin(q); + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.casinh" { + const a = Complex(f32).new(5, 3); + const c = asinh(a); + + testing.expect(math.approxEq(f32, c.re, 2.459831, epsilon)); + testing.expect(math.approxEq(f32, c.im, 0.533999, epsilon)); +} diff --git a/lib/std/math/complex/atan.zig b/lib/std/math/complex/atan.zig new file mode 100644 index 0000000000..3cd19961c8 --- /dev/null +++ b/lib/std/math/complex/atan.zig @@ -0,0 +1,142 @@ +// 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/catanf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/catan.c + +const std = @import("../../std.zig"); +const builtin = @import("builtin"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the arc-tangent of z. +pub fn atan(z: var) @typeOf(z) { + const T = @typeOf(z.re); + return switch (T) { + f32 => atan32(z), + f64 => atan64(z), + else => @compileError("atan not implemented for " ++ @typeName(z)), + }; +} + +fn redupif32(x: f32) f32 { + const DP1 = 3.140625; + const DP2 = 9.67502593994140625e-4; + const DP3 = 1.509957990978376432e-7; + + var t = x / math.pi; + if (t >= 0.0) { + t += 0.5; + } else { + t -= 0.5; + } + + const u = @intToFloat(f32, @floatToInt(i32, t)); + return ((x - u * DP1) - u * DP2) - t * DP3; +} + +fn atan32(z: Complex(f32)) Complex(f32) { + const maxnum = 1.0e38; + + const x = z.re; + const y = z.im; + + if ((x == 0.0) and (y > 1.0)) { + // overflow + return Complex(f32).new(maxnum, maxnum); + } + + const x2 = x * x; + var a = 1.0 - x2 - (y * y); + if (a == 0.0) { + // overflow + return Complex(f32).new(maxnum, maxnum); + } + + var t = 0.5 * math.atan2(f32, 2.0 * x, a); + var w = redupif32(t); + + t = y - 1.0; + a = x2 + t * t; + if (a == 0.0) { + // overflow + return Complex(f32).new(maxnum, maxnum); + } + + t = y + 1.0; + a = (x2 + (t * t)) / a; + return Complex(f32).new(w, 0.25 * math.ln(a)); +} + +fn redupif64(x: f64) f64 { + const DP1 = 3.14159265160560607910; + const DP2 = 1.98418714791870343106e-9; + const DP3 = 1.14423774522196636802e-17; + + var t = x / math.pi; + if (t >= 0.0) { + t += 0.5; + } else { + t -= 0.5; + } + + const u = @intToFloat(f64, @floatToInt(i64, t)); + return ((x - u * DP1) - u * DP2) - t * DP3; +} + +fn atan64(z: Complex(f64)) Complex(f64) { + const maxnum = 1.0e308; + + const x = z.re; + const y = z.im; + + if ((x == 0.0) and (y > 1.0)) { + // overflow + return Complex(f64).new(maxnum, maxnum); + } + + const x2 = x * x; + var a = 1.0 - x2 - (y * y); + if (a == 0.0) { + // overflow + return Complex(f64).new(maxnum, maxnum); + } + + var t = 0.5 * math.atan2(f64, 2.0 * x, a); + var w = redupif64(t); + + t = y - 1.0; + a = x2 + t * t; + if (a == 0.0) { + // overflow + return Complex(f64).new(maxnum, maxnum); + } + + t = y + 1.0; + a = (x2 + (t * t)) / a; + return Complex(f64).new(w, 0.25 * math.ln(a)); +} + +const epsilon = 0.0001; + +test "complex.catan32" { + const a = Complex(f32).new(5, 3); + const c = atan(a); + + testing.expect(math.approxEq(f32, c.re, 1.423679, epsilon)); + testing.expect(math.approxEq(f32, c.im, 0.086569, epsilon)); +} + +test "complex.catan64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const a = Complex(f64).new(5, 3); + const c = atan(a); + + testing.expect(math.approxEq(f64, c.re, 1.423679, epsilon)); + testing.expect(math.approxEq(f64, c.im, 0.086569, epsilon)); +} diff --git a/lib/std/math/complex/atanh.zig b/lib/std/math/complex/atanh.zig new file mode 100644 index 0000000000..225e7c61de --- /dev/null +++ b/lib/std/math/complex/atanh.zig @@ -0,0 +1,23 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the hyperbolic arc-tangent of z. +pub fn atanh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = Complex(T).new(-z.im, z.re); + const r = cmath.atan(q); + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.catanh" { + const a = Complex(f32).new(5, 3); + const c = atanh(a); + + testing.expect(math.approxEq(f32, c.re, 0.146947, epsilon)); + testing.expect(math.approxEq(f32, c.im, 1.480870, epsilon)); +} diff --git a/lib/std/math/complex/conj.zig b/lib/std/math/complex/conj.zig new file mode 100644 index 0000000000..bd71ca3c06 --- /dev/null +++ b/lib/std/math/complex/conj.zig @@ -0,0 +1,18 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the complex conjugate of z. +pub fn conj(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return Complex(T).new(z.re, -z.im); +} + +test "complex.conj" { + const a = Complex(f32).new(5, 3); + const c = a.conjugate(); + + testing.expect(c.re == 5 and c.im == -3); +} diff --git a/lib/std/math/complex/cos.zig b/lib/std/math/complex/cos.zig new file mode 100644 index 0000000000..332009ffe5 --- /dev/null +++ b/lib/std/math/complex/cos.zig @@ -0,0 +1,22 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the cosine of z. +pub fn cos(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const p = Complex(T).new(-z.im, z.re); + return cmath.cosh(p); +} + +const epsilon = 0.0001; + +test "complex.ccos" { + const a = Complex(f32).new(5, 3); + const c = cos(a); + + testing.expect(math.approxEq(f32, c.re, 2.855815, epsilon)); + testing.expect(math.approxEq(f32, c.im, 9.606383, epsilon)); +} diff --git a/lib/std/math/complex/cosh.zig b/lib/std/math/complex/cosh.zig new file mode 100644 index 0000000000..89afcac42e --- /dev/null +++ b/lib/std/math/complex/cosh.zig @@ -0,0 +1,177 @@ +// 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/ccoshf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/ccosh.c + +const builtin = @import("builtin"); +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; + +/// Returns the hyperbolic arc-cosine of z. +pub fn cosh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return switch (T) { + f32 => cosh32(z), + f64 => cosh64(z), + else => @compileError("cosh not implemented for " ++ @typeName(z)), + }; +} + +fn cosh32(z: Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + const hx = @bitCast(u32, x); + const ix = hx & 0x7fffffff; + + const hy = @bitCast(u32, y); + const iy = hy & 0x7fffffff; + + if (ix < 0x7f800000 and iy < 0x7f800000) { + if (iy == 0) { + return Complex(f32).new(math.cosh(x), y); + } + // small x: normal case + if (ix < 0x41100000) { + return Complex(f32).new(math.cosh(x) * math.cos(y), math.sinh(x) * math.sin(y)); + } + + // |x|>= 9, so cosh(x) ~= exp(|x|) + if (ix < 0x42b17218) { + // x < 88.7: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f32).new(math.copysign(f32, h, x) * math.cos(y), h * math.sin(y)); + } + // x < 192.7: scale to avoid overflow + else if (ix < 0x4340b1e7) { + const v = Complex(f32).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f32).new(r.re, r.im * math.copysign(f32, 1, x)); + } + // x >= 192.7: result always overflows + else { + const h = 0x1p127 * x; + return Complex(f32).new(h * h * math.cos(y), h * math.sin(y)); + } + } + + if (ix == 0 and iy >= 0x7f800000) { + return Complex(f32).new(y - y, math.copysign(f32, 0, x * (y - y))); + } + + if (iy == 0 and ix >= 0x7f800000) { + if (hx & 0x7fffff == 0) { + return Complex(f32).new(x * x, math.copysign(f32, 0, x) * y); + } + return Complex(f32).new(x, math.copysign(f32, 0, (x + x) * y)); + } + + if (ix < 0x7f800000 and iy >= 0x7f800000) { + return Complex(f32).new(y - y, x * (y - y)); + } + + if (ix >= 0x7f800000 and (hx & 0x7fffff) == 0) { + if (iy >= 0x7f800000) { + return Complex(f32).new(x * x, x * (y - y)); + } + return Complex(f32).new((x * x) * math.cos(y), x * math.sin(y)); + } + + return Complex(f32).new((x * x) * (y - y), (x + x) * (y - y)); +} + +fn cosh64(z: Complex(f64)) Complex(f64) { + const x = z.re; + const y = z.im; + + const fx = @bitCast(u64, x); + const hx = @intCast(u32, fx >> 32); + const lx = @truncate(u32, fx); + const ix = hx & 0x7fffffff; + + const fy = @bitCast(u64, y); + const hy = @intCast(u32, fy >> 32); + const ly = @truncate(u32, fy); + const iy = hy & 0x7fffffff; + + // nearly non-exceptional case where x, y are finite + if (ix < 0x7ff00000 and iy < 0x7ff00000) { + if (iy | ly == 0) { + return Complex(f64).new(math.cosh(x), x * y); + } + // small x: normal case + if (ix < 0x40360000) { + return Complex(f64).new(math.cosh(x) * math.cos(y), math.sinh(x) * math.sin(y)); + } + + // |x|>= 22, so cosh(x) ~= exp(|x|) + if (ix < 0x40862e42) { + // x < 710: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f64).new(h * math.cos(y), math.copysign(f64, h, x) * math.sin(y)); + } + // x < 1455: scale to avoid overflow + else if (ix < 0x4096bbaa) { + const v = Complex(f64).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f64).new(r.re, r.im * math.copysign(f64, 1, x)); + } + // x >= 1455: result always overflows + else { + const h = 0x1p1023; + return Complex(f64).new(h * h * math.cos(y), h * math.sin(y)); + } + } + + if (ix | lx == 0 and iy >= 0x7ff00000) { + return Complex(f64).new(y - y, math.copysign(f64, 0, x * (y - y))); + } + + if (iy | ly == 0 and ix >= 0x7ff00000) { + if ((hx & 0xfffff) | lx == 0) { + return Complex(f64).new(x * x, math.copysign(f64, 0, x) * y); + } + return Complex(f64).new(x * x, math.copysign(f64, 0, (x + x) * y)); + } + + if (ix < 0x7ff00000 and iy >= 0x7ff00000) { + return Complex(f64).new(y - y, x * (y - y)); + } + + if (ix >= 0x7ff00000 and (hx & 0xfffff) | lx == 0) { + if (iy >= 0x7ff00000) { + return Complex(f64).new(x * x, x * (y - y)); + } + return Complex(f64).new(x * x * math.cos(y), x * math.sin(y)); + } + + return Complex(f64).new((x * x) * (y - y), (x + x) * (y - y)); +} + +const epsilon = 0.0001; + +test "complex.ccosh32" { + const a = Complex(f32).new(5, 3); + const c = cosh(a); + + testing.expect(math.approxEq(f32, c.re, -73.467300, epsilon)); + testing.expect(math.approxEq(f32, c.im, 10.471557, epsilon)); +} + +test "complex.ccosh64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const a = Complex(f64).new(5, 3); + const c = cosh(a); + + testing.expect(math.approxEq(f64, c.re, -73.467300, epsilon)); + testing.expect(math.approxEq(f64, c.im, 10.471557, epsilon)); +} diff --git a/lib/std/math/complex/exp.zig b/lib/std/math/complex/exp.zig new file mode 100644 index 0000000000..5cd1cb4ed6 --- /dev/null +++ b/lib/std/math/complex/exp.zig @@ -0,0 +1,143 @@ +// 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 + +const builtin = @import("builtin"); +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; + +/// Returns e raised to the power of z (e^z). +pub fn exp(z: var) @typeOf(z) { + const T = @typeOf(z.re); + + return switch (T) { + f32 => exp32(z), + f64 => exp64(z), + else => @compileError("exp not implemented for " ++ @typeName(z)), + }; +} + +fn exp32(z: Complex(f32)) Complex(f32) { + const exp_overflow = 0x42b17218; // max_exp * ln2 ~= 88.72283955 + const cexp_overflow = 0x43400074; // (max_exp - min_denom_exp) * ln2 + + const x = z.re; + const y = z.im; + + const hy = @bitCast(u32, y) & 0x7fffffff; + // cexp(x + i0) = exp(x) + i0 + if (hy == 0) { + return Complex(f32).new(math.exp(x), y); + } + + const hx = @bitCast(u32, x); + // cexp(0 + iy) = cos(y) + isin(y) + if ((hx & 0x7fffffff) == 0) { + return Complex(f32).new(math.cos(y), math.sin(y)); + } + + if (hy >= 0x7f800000) { + // cexp(finite|nan +- i inf|nan) = nan + i nan + if ((hx & 0x7fffffff) != 0x7f800000) { + return Complex(f32).new(y - y, y - y); + } // cexp(-inf +- i inf|nan) = 0 + i0 + else if (hx & 0x80000000 != 0) { + return Complex(f32).new(0, 0); + } // cexp(+inf +- i inf|nan) = inf + i nan + else { + return Complex(f32).new(x, y - y); + } + } + + // 88.7 <= x <= 192 so must scale + if (hx >= exp_overflow and hx <= cexp_overflow) { + return ldexp_cexp(z, 0); + } // - x < exp_overflow => exp(x) won't overflow (common) + // - x > cexp_overflow, so exp(x) * s overflows for s > 0 + // - x = +-inf + // - x = nan + else { + const exp_x = math.exp(x); + return Complex(f32).new(exp_x * math.cos(y), exp_x * math.sin(y)); + } +} + +fn exp64(z: Complex(f64)) Complex(f64) { + const exp_overflow = 0x40862e42; // high bits of max_exp * ln2 ~= 710 + const cexp_overflow = 0x4096b8e4; // (max_exp - min_denorm_exp) * ln2 + + const x = z.re; + const y = z.im; + + const fy = @bitCast(u64, y); + const hy = @intCast(u32, (fy >> 32) & 0x7fffffff); + const ly = @truncate(u32, fy); + + // cexp(x + i0) = exp(x) + i0 + if (hy | ly == 0) { + return Complex(f64).new(math.exp(x), y); + } + + const fx = @bitCast(u64, x); + const hx = @intCast(u32, fx >> 32); + const lx = @truncate(u32, fx); + + // cexp(0 + iy) = cos(y) + isin(y) + if ((hx & 0x7fffffff) | lx == 0) { + return Complex(f64).new(math.cos(y), math.sin(y)); + } + + if (hy >= 0x7ff00000) { + // cexp(finite|nan +- i inf|nan) = nan + i nan + if (lx != 0 or (hx & 0x7fffffff) != 0x7ff00000) { + return Complex(f64).new(y - y, y - y); + } // cexp(-inf +- i inf|nan) = 0 + i0 + else if (hx & 0x80000000 != 0) { + return Complex(f64).new(0, 0); + } // cexp(+inf +- i inf|nan) = inf + i nan + else { + return Complex(f64).new(x, y - y); + } + } + + // 709.7 <= x <= 1454.3 so must scale + if (hx >= exp_overflow and hx <= cexp_overflow) { + return ldexp_cexp(z, 0); + } // - x < exp_overflow => exp(x) won't overflow (common) + // - x > cexp_overflow, so exp(x) * s overflows for s > 0 + // - x = +-inf + // - x = nan + else { + const exp_x = math.exp(x); + return Complex(f64).new(exp_x * math.cos(y), exp_x * math.sin(y)); + } +} + +const epsilon = 0.0001; + +test "complex.cexp32" { + const a = Complex(f32).new(5, 3); + const c = exp(a); + + testing.expect(math.approxEq(f32, c.re, -146.927917, epsilon)); + testing.expect(math.approxEq(f32, c.im, 20.944065, epsilon)); +} + +test "complex.cexp64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const a = Complex(f64).new(5, 3); + const c = exp(a); + + testing.expect(math.approxEq(f64, c.re, -146.927917, epsilon)); + testing.expect(math.approxEq(f64, c.im, 20.944065, epsilon)); +} diff --git a/lib/std/math/complex/ldexp.zig b/lib/std/math/complex/ldexp.zig new file mode 100644 index 0000000000..d6f810793f --- /dev/null +++ b/lib/std/math/complex/ldexp.zig @@ -0,0 +1,80 @@ +// 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 + +const std = @import("../../std.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns exp(z) scaled to avoid overflow. +pub fn ldexp_cexp(z: var, expt: i32) @typeOf(z) { + const T = @typeOf(z.re); + + return switch (T) { + f32 => ldexp_cexp32(z, expt), + f64 => ldexp_cexp64(z, expt), + else => unreachable, + }; +} + +fn frexp_exp32(x: f32, expt: *i32) f32 { + const k = 235; // reduction constant + const kln2 = 162.88958740; // k * ln2 + + const exp_x = math.exp(x - kln2); + const hx = @bitCast(u32, exp_x); + // TODO zig should allow this cast implicitly because it should know the value is in range + expt.* = @intCast(i32, hx >> 23) - (0x7f + 127) + k; + return @bitCast(f32, (hx & 0x7fffff) | ((0x7f + 127) << 23)); +} + +fn ldexp_cexp32(z: Complex(f32), expt: i32) Complex(f32) { + var ex_expt: i32 = undefined; + const exp_x = frexp_exp32(z.re, &ex_expt); + const exptf = expt + ex_expt; + + const half_expt1 = @divTrunc(exptf, 2); + const scale1 = @bitCast(f32, (0x7f + half_expt1) << 23); + + const half_expt2 = exptf - half_expt1; + const scale2 = @bitCast(f32, (0x7f + half_expt2) << 23); + + return Complex(f32).new(math.cos(z.im) * exp_x * scale1 * scale2, math.sin(z.im) * exp_x * scale1 * scale2); +} + +fn frexp_exp64(x: f64, expt: *i32) f64 { + const k = 1799; // reduction constant + const kln2 = 1246.97177782734161156; // k * ln2 + + const exp_x = math.exp(x - kln2); + + const fx = @bitCast(u64, x); + const hx = @intCast(u32, fx >> 32); + const lx = @truncate(u32, fx); + + expt.* = @intCast(i32, hx >> 20) - (0x3ff + 1023) + k; + + const high_word = (hx & 0xfffff) | ((0x3ff + 1023) << 20); + return @bitCast(f64, (u64(high_word) << 32) | lx); +} + +fn ldexp_cexp64(z: Complex(f64), expt: i32) Complex(f64) { + var ex_expt: i32 = undefined; + const exp_x = frexp_exp64(z.re, &ex_expt); + const exptf = i64(expt + ex_expt); + + const half_expt1 = @divTrunc(exptf, 2); + const scale1 = @bitCast(f64, (0x3ff + half_expt1) << 20); + + const half_expt2 = exptf - half_expt1; + const scale2 = @bitCast(f64, (0x3ff + half_expt2) << 20); + + return Complex(f64).new( + math.cos(z.im) * exp_x * scale1 * scale2, + math.sin(z.im) * exp_x * scale1 * scale2, + ); +} diff --git a/lib/std/math/complex/log.zig b/lib/std/math/complex/log.zig new file mode 100644 index 0000000000..762b4fde9a --- /dev/null +++ b/lib/std/math/complex/log.zig @@ -0,0 +1,24 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the natural logarithm of z. +pub fn log(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const r = cmath.abs(z); + const phi = cmath.arg(z); + + return Complex(T).new(math.ln(r), phi); +} + +const epsilon = 0.0001; + +test "complex.clog" { + const a = Complex(f32).new(5, 3); + const c = log(a); + + testing.expect(math.approxEq(f32, c.re, 1.763180, epsilon)); + testing.expect(math.approxEq(f32, c.im, 0.540419, epsilon)); +} diff --git a/lib/std/math/complex/pow.zig b/lib/std/math/complex/pow.zig new file mode 100644 index 0000000000..a2480453fc --- /dev/null +++ b/lib/std/math/complex/pow.zig @@ -0,0 +1,23 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns z raised to the complex power of c. +pub fn pow(comptime T: type, z: T, c: T) T { + const p = cmath.log(z); + const q = c.mul(p); + return cmath.exp(q); +} + +const epsilon = 0.0001; + +test "complex.cpow" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2.3, -1.3); + const c = pow(Complex(f32), a, b); + + testing.expect(math.approxEq(f32, c.re, 58.049110, epsilon)); + testing.expect(math.approxEq(f32, c.im, -101.003433, epsilon)); +} diff --git a/lib/std/math/complex/proj.zig b/lib/std/math/complex/proj.zig new file mode 100644 index 0000000000..c8f2d9fc6d --- /dev/null +++ b/lib/std/math/complex/proj.zig @@ -0,0 +1,25 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the projection of z onto the riemann sphere. +pub fn proj(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + + if (math.isInf(z.re) or math.isInf(z.im)) { + return Complex(T).new(math.inf(T), math.copysign(T, 0, z.re)); + } + + return Complex(T).new(z.re, z.im); +} + +const epsilon = 0.0001; + +test "complex.cproj" { + const a = Complex(f32).new(5, 3); + const c = proj(a); + + testing.expect(c.re == 5 and c.im == 3); +} diff --git a/lib/std/math/complex/sin.zig b/lib/std/math/complex/sin.zig new file mode 100644 index 0000000000..9ddc3a7a80 --- /dev/null +++ b/lib/std/math/complex/sin.zig @@ -0,0 +1,23 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the sine of z. +pub fn sin(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const p = Complex(T).new(-z.im, z.re); + const q = cmath.sinh(p); + return Complex(T).new(q.im, -q.re); +} + +const epsilon = 0.0001; + +test "complex.csin" { + const a = Complex(f32).new(5, 3); + const c = sin(a); + + testing.expect(math.approxEq(f32, c.re, -9.654126, epsilon)); + testing.expect(math.approxEq(f32, c.im, 2.841692, epsilon)); +} diff --git a/lib/std/math/complex/sinh.zig b/lib/std/math/complex/sinh.zig new file mode 100644 index 0000000000..0b1294bb6a --- /dev/null +++ b/lib/std/math/complex/sinh.zig @@ -0,0 +1,176 @@ +// 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/csinhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/csinh.c + +const builtin = @import("builtin"); +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; + +/// Returns the hyperbolic sine of z. +pub fn sinh(z: var) @typeOf(z) { + const T = @typeOf(z.re); + return switch (T) { + f32 => sinh32(z), + f64 => sinh64(z), + else => @compileError("tan not implemented for " ++ @typeName(z)), + }; +} + +fn sinh32(z: Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + const hx = @bitCast(u32, x); + const ix = hx & 0x7fffffff; + + const hy = @bitCast(u32, y); + const iy = hy & 0x7fffffff; + + if (ix < 0x7f800000 and iy < 0x7f800000) { + if (iy == 0) { + return Complex(f32).new(math.sinh(x), y); + } + // small x: normal case + if (ix < 0x41100000) { + return Complex(f32).new(math.sinh(x) * math.cos(y), math.cosh(x) * math.sin(y)); + } + + // |x|>= 9, so cosh(x) ~= exp(|x|) + if (ix < 0x42b17218) { + // x < 88.7: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f32).new(math.copysign(f32, h, x) * math.cos(y), h * math.sin(y)); + } + // x < 192.7: scale to avoid overflow + else if (ix < 0x4340b1e7) { + const v = Complex(f32).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f32).new(r.re * math.copysign(f32, 1, x), r.im); + } + // x >= 192.7: result always overflows + else { + const h = 0x1p127 * x; + return Complex(f32).new(h * math.cos(y), h * h * math.sin(y)); + } + } + + if (ix == 0 and iy >= 0x7f800000) { + return Complex(f32).new(math.copysign(f32, 0, x * (y - y)), y - y); + } + + if (iy == 0 and ix >= 0x7f800000) { + if (hx & 0x7fffff == 0) { + return Complex(f32).new(x, y); + } + return Complex(f32).new(x, math.copysign(f32, 0, y)); + } + + if (ix < 0x7f800000 and iy >= 0x7f800000) { + return Complex(f32).new(y - y, x * (y - y)); + } + + if (ix >= 0x7f800000 and (hx & 0x7fffff) == 0) { + if (iy >= 0x7f800000) { + return Complex(f32).new(x * x, x * (y - y)); + } + return Complex(f32).new(x * math.cos(y), math.inf_f32 * math.sin(y)); + } + + return Complex(f32).new((x * x) * (y - y), (x + x) * (y - y)); +} + +fn sinh64(z: Complex(f64)) Complex(f64) { + const x = z.re; + const y = z.im; + + const fx = @bitCast(u64, x); + const hx = @intCast(u32, fx >> 32); + const lx = @truncate(u32, fx); + const ix = hx & 0x7fffffff; + + const fy = @bitCast(u64, y); + const hy = @intCast(u32, fy >> 32); + const ly = @truncate(u32, fy); + const iy = hy & 0x7fffffff; + + if (ix < 0x7ff00000 and iy < 0x7ff00000) { + if (iy | ly == 0) { + return Complex(f64).new(math.sinh(x), y); + } + // small x: normal case + if (ix < 0x40360000) { + return Complex(f64).new(math.sinh(x) * math.cos(y), math.cosh(x) * math.sin(y)); + } + + // |x|>= 22, so cosh(x) ~= exp(|x|) + if (ix < 0x40862e42) { + // x < 710: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f64).new(math.copysign(f64, h, x) * math.cos(y), h * math.sin(y)); + } + // x < 1455: scale to avoid overflow + else if (ix < 0x4096bbaa) { + const v = Complex(f64).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f64).new(r.re * math.copysign(f64, 1, x), r.im); + } + // x >= 1455: result always overflows + else { + const h = 0x1p1023 * x; + return Complex(f64).new(h * math.cos(y), h * h * math.sin(y)); + } + } + + if (ix | lx == 0 and iy >= 0x7ff00000) { + return Complex(f64).new(math.copysign(f64, 0, x * (y - y)), y - y); + } + + if (iy | ly == 0 and ix >= 0x7ff00000) { + if ((hx & 0xfffff) | lx == 0) { + return Complex(f64).new(x, y); + } + return Complex(f64).new(x, math.copysign(f64, 0, y)); + } + + if (ix < 0x7ff00000 and iy >= 0x7ff00000) { + return Complex(f64).new(y - y, x * (y - y)); + } + + if (ix >= 0x7ff00000 and (hx & 0xfffff) | lx == 0) { + if (iy >= 0x7ff00000) { + return Complex(f64).new(x * x, x * (y - y)); + } + return Complex(f64).new(x * math.cos(y), math.inf_f64 * math.sin(y)); + } + + return Complex(f64).new((x * x) * (y - y), (x + x) * (y - y)); +} + +const epsilon = 0.0001; + +test "complex.csinh32" { + const a = Complex(f32).new(5, 3); + const c = sinh(a); + + testing.expect(math.approxEq(f32, c.re, -73.460617, epsilon)); + testing.expect(math.approxEq(f32, c.im, 10.472508, epsilon)); +} + +test "complex.csinh64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const a = Complex(f64).new(5, 3); + const c = sinh(a); + + testing.expect(math.approxEq(f64, c.re, -73.460617, epsilon)); + testing.expect(math.approxEq(f64, c.im, 10.472508, epsilon)); +} diff --git a/lib/std/math/complex/sqrt.zig b/lib/std/math/complex/sqrt.zig new file mode 100644 index 0000000000..36f4c28e29 --- /dev/null +++ b/lib/std/math/complex/sqrt.zig @@ -0,0 +1,146 @@ +// 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/csqrtf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/csqrt.c + +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the square root of z. The real and imaginary parts of the result have the same sign +/// as the imaginary part of z. +pub fn sqrt(z: var) @typeOf(z) { + const T = @typeOf(z.re); + + return switch (T) { + f32 => sqrt32(z), + f64 => sqrt64(z), + else => @compileError("sqrt not implemented for " ++ @typeName(T)), + }; +} + +fn sqrt32(z: Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + if (x == 0 and y == 0) { + return Complex(f32).new(0, y); + } + if (math.isInf(y)) { + return Complex(f32).new(math.inf(f32), y); + } + if (math.isNan(x)) { + // raise invalid if y is not nan + const t = (y - y) / (y - y); + return Complex(f32).new(x, t); + } + if (math.isInf(x)) { + // sqrt(inf + i nan) = inf + nan i + // sqrt(inf + iy) = inf + i0 + // sqrt(-inf + i nan) = nan +- inf i + // sqrt(-inf + iy) = 0 + inf i + if (math.signbit(x)) { + return Complex(f32).new(math.fabs(x - y), math.copysign(f32, x, y)); + } else { + return Complex(f32).new(x, math.copysign(f32, y - y, y)); + } + } + + // y = nan special case is handled fine below + + // double-precision avoids overflow with correct rounding. + const dx = f64(x); + const dy = f64(y); + + if (dx >= 0) { + const t = math.sqrt((dx + math.hypot(f64, dx, dy)) * 0.5); + return Complex(f32).new( + @floatCast(f32, t), + @floatCast(f32, dy / (2.0 * t)), + ); + } else { + const t = math.sqrt((-dx + math.hypot(f64, dx, dy)) * 0.5); + return Complex(f32).new( + @floatCast(f32, math.fabs(y) / (2.0 * t)), + @floatCast(f32, math.copysign(f64, t, y)), + ); + } +} + +fn sqrt64(z: Complex(f64)) Complex(f64) { + // may encounter overflow for im,re >= DBL_MAX / (1 + sqrt(2)) + const threshold = 0x1.a827999fcef32p+1022; + + var x = z.re; + var y = z.im; + + if (x == 0 and y == 0) { + return Complex(f64).new(0, y); + } + if (math.isInf(y)) { + return Complex(f64).new(math.inf(f64), y); + } + if (math.isNan(x)) { + // raise invalid if y is not nan + const t = (y - y) / (y - y); + return Complex(f64).new(x, t); + } + if (math.isInf(x)) { + // sqrt(inf + i nan) = inf + nan i + // sqrt(inf + iy) = inf + i0 + // sqrt(-inf + i nan) = nan +- inf i + // sqrt(-inf + iy) = 0 + inf i + if (math.signbit(x)) { + return Complex(f64).new(math.fabs(x - y), math.copysign(f64, x, y)); + } else { + return Complex(f64).new(x, math.copysign(f64, y - y, y)); + } + } + + // y = nan special case is handled fine below + + // scale to avoid overflow + var scale = false; + if (math.fabs(x) >= threshold or math.fabs(y) >= threshold) { + x *= 0.25; + y *= 0.25; + scale = true; + } + + var result: Complex(f64) = undefined; + if (x >= 0) { + const t = math.sqrt((x + math.hypot(f64, x, y)) * 0.5); + result = Complex(f64).new(t, y / (2.0 * t)); + } else { + const t = math.sqrt((-x + math.hypot(f64, x, y)) * 0.5); + result = Complex(f64).new(math.fabs(y) / (2.0 * t), math.copysign(f64, t, y)); + } + + if (scale) { + result.re *= 2; + result.im *= 2; + } + + return result; +} + +const epsilon = 0.0001; + +test "complex.csqrt32" { + const a = Complex(f32).new(5, 3); + const c = sqrt(a); + + testing.expect(math.approxEq(f32, c.re, 2.327117, epsilon)); + testing.expect(math.approxEq(f32, c.im, 0.644574, epsilon)); +} + +test "complex.csqrt64" { + const a = Complex(f64).new(5, 3); + const c = sqrt(a); + + testing.expect(math.approxEq(f64, c.re, 2.3271175190399496, epsilon)); + testing.expect(math.approxEq(f64, c.im, 0.6445742373246469, epsilon)); +} diff --git a/lib/std/math/complex/tan.zig b/lib/std/math/complex/tan.zig new file mode 100644 index 0000000000..398b8295ca --- /dev/null +++ b/lib/std/math/complex/tan.zig @@ -0,0 +1,23 @@ +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the tanget of z. +pub fn tan(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = Complex(T).new(-z.im, z.re); + const r = cmath.tanh(q); + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.ctan" { + const a = Complex(f32).new(5, 3); + const c = tan(a); + + testing.expect(math.approxEq(f32, c.re, -0.002708233, epsilon)); + testing.expect(math.approxEq(f32, c.im, 1.004165, epsilon)); +} diff --git a/lib/std/math/complex/tanh.zig b/lib/std/math/complex/tanh.zig new file mode 100644 index 0000000000..6895e8a769 --- /dev/null +++ b/lib/std/math/complex/tanh.zig @@ -0,0 +1,125 @@ +// 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/ctanhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/ctanh.c + +const builtin = @import("builtin"); +const std = @import("../../std.zig"); +const testing = std.testing; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +/// Returns the hyperbolic tangent of z. +pub fn tanh(z: var) @typeOf(z) { + const T = @typeOf(z.re); + return switch (T) { + f32 => tanh32(z), + f64 => tanh64(z), + else => @compileError("tan not implemented for " ++ @typeName(z)), + }; +} + +fn tanh32(z: Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + const hx = @bitCast(u32, x); + const ix = hx & 0x7fffffff; + + if (ix >= 0x7f800000) { + if (ix & 0x7fffff != 0) { + const r = if (y == 0) y else x * y; + return Complex(f32).new(x, r); + } + const xx = @bitCast(f32, hx - 0x40000000); + const r = if (math.isInf(y)) y else math.sin(y) * math.cos(y); + return Complex(f32).new(xx, math.copysign(f32, 0, r)); + } + + if (!math.isFinite(y)) { + const r = if (ix != 0) y - y else x; + return Complex(f32).new(r, y - y); + } + + // x >= 11 + if (ix >= 0x41300000) { + const exp_mx = math.exp(-math.fabs(x)); + return Complex(f32).new(math.copysign(f32, 1, x), 4 * math.sin(y) * math.cos(y) * exp_mx * exp_mx); + } + + // Kahan's algorithm + const t = math.tan(y); + const beta = 1.0 + t * t; + const s = math.sinh(x); + const rho = math.sqrt(1 + s * s); + const den = 1 + beta * s * s; + + return Complex(f32).new((beta * rho * s) / den, t / den); +} + +fn tanh64(z: Complex(f64)) Complex(f64) { + const x = z.re; + const y = z.im; + + const fx = @bitCast(u64, x); + // TODO: zig should allow this conversion implicitly because it can notice that the value necessarily + // fits in range. + const hx = @intCast(u32, fx >> 32); + const lx = @truncate(u32, fx); + const ix = hx & 0x7fffffff; + + if (ix >= 0x7ff00000) { + if ((ix & 0x7fffff) | lx != 0) { + const r = if (y == 0) y else x * y; + return Complex(f64).new(x, r); + } + + const xx = @bitCast(f64, (u64(hx - 0x40000000) << 32) | lx); + const r = if (math.isInf(y)) y else math.sin(y) * math.cos(y); + return Complex(f64).new(xx, math.copysign(f64, 0, r)); + } + + if (!math.isFinite(y)) { + const r = if (ix != 0) y - y else x; + return Complex(f64).new(r, y - y); + } + + // x >= 22 + if (ix >= 0x40360000) { + const exp_mx = math.exp(-math.fabs(x)); + return Complex(f64).new(math.copysign(f64, 1, x), 4 * math.sin(y) * math.cos(y) * exp_mx * exp_mx); + } + + // Kahan's algorithm + const t = math.tan(y); + const beta = 1.0 + t * t; + const s = math.sinh(x); + const rho = math.sqrt(1 + s * s); + const den = 1 + beta * s * s; + + return Complex(f64).new((beta * rho * s) / den, t / den); +} + +const epsilon = 0.0001; + +test "complex.ctanh32" { + const a = Complex(f32).new(5, 3); + const c = tanh(a); + + testing.expect(math.approxEq(f32, c.re, 0.999913, epsilon)); + testing.expect(math.approxEq(f32, c.im, -0.000025, epsilon)); +} + +test "complex.ctanh64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const a = Complex(f64).new(5, 3); + const c = tanh(a); + + testing.expect(math.approxEq(f64, c.re, 0.999913, epsilon)); + testing.expect(math.approxEq(f64, c.im, -0.000025, epsilon)); +} diff --git a/lib/std/math/copysign.zig b/lib/std/math/copysign.zig new file mode 100644 index 0000000000..e4d90c395e --- /dev/null +++ b/lib/std/math/copysign.zig @@ -0,0 +1,74 @@ +// 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/math/copysignf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/copysign.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns a value with the magnitude of x and the sign of y. +pub fn copysign(comptime T: type, x: T, y: T) T { + return switch (T) { + f16 => copysign16(x, y), + f32 => copysign32(x, y), + f64 => copysign64(x, y), + else => @compileError("copysign not implemented for " ++ @typeName(T)), + }; +} + +fn copysign16(x: f16, y: f16) f16 { + const ux = @bitCast(u16, x); + const uy = @bitCast(u16, y); + + const h1 = ux & (maxInt(u16) / 2); + const h2 = uy & (u16(1) << 15); + return @bitCast(f16, h1 | h2); +} + +fn copysign32(x: f32, y: f32) f32 { + const ux = @bitCast(u32, x); + const uy = @bitCast(u32, y); + + const h1 = ux & (maxInt(u32) / 2); + const h2 = uy & (u32(1) << 31); + return @bitCast(f32, h1 | h2); +} + +fn copysign64(x: f64, y: f64) f64 { + const ux = @bitCast(u64, x); + const uy = @bitCast(u64, y); + + const h1 = ux & (maxInt(u64) / 2); + const h2 = uy & (u64(1) << 63); + return @bitCast(f64, h1 | h2); +} + +test "math.copysign" { + expect(copysign(f16, 1.0, 1.0) == copysign16(1.0, 1.0)); + expect(copysign(f32, 1.0, 1.0) == copysign32(1.0, 1.0)); + expect(copysign(f64, 1.0, 1.0) == copysign64(1.0, 1.0)); +} + +test "math.copysign16" { + expect(copysign16(5.0, 1.0) == 5.0); + expect(copysign16(5.0, -1.0) == -5.0); + expect(copysign16(-5.0, -1.0) == -5.0); + expect(copysign16(-5.0, 1.0) == 5.0); +} + +test "math.copysign32" { + expect(copysign32(5.0, 1.0) == 5.0); + expect(copysign32(5.0, -1.0) == -5.0); + expect(copysign32(-5.0, -1.0) == -5.0); + expect(copysign32(-5.0, 1.0) == 5.0); +} + +test "math.copysign64" { + expect(copysign64(5.0, 1.0) == 5.0); + expect(copysign64(5.0, -1.0) == -5.0); + expect(copysign64(-5.0, -1.0) == -5.0); + expect(copysign64(-5.0, 1.0) == 5.0); +} diff --git a/lib/std/math/cos.zig b/lib/std/math/cos.zig new file mode 100644 index 0000000000..5261a25f80 --- /dev/null +++ b/lib/std/math/cos.zig @@ -0,0 +1,128 @@ +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE +// +// https://golang.org/src/math/sin.go + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the cosine of the radian value x. +/// +/// Special Cases: +/// - cos(+-inf) = nan +/// - cos(nan) = nan +pub fn cos(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => cos_(f32, x), + f64 => cos_(f64, x), + else => @compileError("cos not implemented for " ++ @typeName(T)), + }; +} + +// sin polynomial coefficients +const S0 = 1.58962301576546568060E-10; +const S1 = -2.50507477628578072866E-8; +const S2 = 2.75573136213857245213E-6; +const S3 = -1.98412698295895385996E-4; +const S4 = 8.33333333332211858878E-3; +const S5 = -1.66666666666666307295E-1; + +// cos polynomial coeffiecients +const C0 = -1.13585365213876817300E-11; +const C1 = 2.08757008419747316778E-9; +const C2 = -2.75573141792967388112E-7; +const C3 = 2.48015872888517045348E-5; +const C4 = -1.38888888888730564116E-3; +const C5 = 4.16666666666665929218E-2; + +const pi4a = 7.85398125648498535156e-1; +const pi4b = 3.77489470793079817668E-8; +const pi4c = 2.69515142907905952645E-15; +const m4pi = 1.273239544735162542821171882678754627704620361328125; + +fn cos_(comptime T: type, x_: T) T { + const I = @IntType(true, T.bit_count); + + var x = x_; + if (math.isNan(x) or math.isInf(x)) { + return math.nan(T); + } + + var sign = false; + x = math.fabs(x); + + var y = math.floor(x * m4pi); + var j = @floatToInt(I, y); + + if (j & 1 == 1) { + j += 1; + y += 1; + } + + j &= 7; + if (j > 3) { + j -= 4; + sign = !sign; + } + if (j > 1) { + sign = !sign; + } + + const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; + const w = z * z; + + const r = if (j == 1 or j == 2) + z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))) + else + 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))); + + return if (sign) -r else r; +} + +test "math.cos" { + expect(cos(f32(0.0)) == cos_(f32, 0.0)); + expect(cos(f64(0.0)) == cos_(f64, 0.0)); +} + +test "math.cos32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, cos_(f32, 0.0), 1.0, epsilon)); + expect(math.approxEq(f32, cos_(f32, 0.2), 0.980067, epsilon)); + expect(math.approxEq(f32, cos_(f32, 0.8923), 0.627623, epsilon)); + expect(math.approxEq(f32, cos_(f32, 1.5), 0.070737, epsilon)); + expect(math.approxEq(f32, cos_(f32, -1.5), 0.070737, epsilon)); + expect(math.approxEq(f32, cos_(f32, 37.45), 0.969132, epsilon)); + expect(math.approxEq(f32, cos_(f32, 89.123), 0.400798, epsilon)); +} + +test "math.cos64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const epsilon = 0.000001; + + expect(math.approxEq(f64, cos_(f64, 0.0), 1.0, epsilon)); + expect(math.approxEq(f64, cos_(f64, 0.2), 0.980067, epsilon)); + expect(math.approxEq(f64, cos_(f64, 0.8923), 0.627623, epsilon)); + expect(math.approxEq(f64, cos_(f64, 1.5), 0.070737, epsilon)); + expect(math.approxEq(f64, cos_(f64, -1.5), 0.070737, epsilon)); + expect(math.approxEq(f64, cos_(f64, 37.45), 0.969132, epsilon)); + expect(math.approxEq(f64, cos_(f64, 89.123), 0.40080, epsilon)); +} + +test "math.cos32.special" { + expect(math.isNan(cos_(f32, math.inf(f32)))); + expect(math.isNan(cos_(f32, -math.inf(f32)))); + expect(math.isNan(cos_(f32, math.nan(f32)))); +} + +test "math.cos64.special" { + expect(math.isNan(cos_(f64, math.inf(f64)))); + expect(math.isNan(cos_(f64, -math.inf(f64)))); + expect(math.isNan(cos_(f64, math.nan(f64)))); +} diff --git a/lib/std/math/cosh.zig b/lib/std/math/cosh.zig new file mode 100644 index 0000000000..75c5c15ec1 --- /dev/null +++ b/lib/std/math/cosh.zig @@ -0,0 +1,127 @@ +// 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/math/coshf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/cosh.c + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expo2 = @import("expo2.zig").expo2; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns the hyperbolic cosine of x. +/// +/// Special Cases: +/// - cosh(+-0) = 1 +/// - cosh(+-inf) = +inf +/// - cosh(nan) = nan +pub fn cosh(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => cosh32(x), + f64 => cosh64(x), + else => @compileError("cosh not implemented for " ++ @typeName(T)), + }; +} + +// cosh(x) = (exp(x) + 1 / exp(x)) / 2 +// = 1 + 0.5 * (exp(x) - 1) * (exp(x) - 1) / exp(x) +// = 1 + (x * x) / 2 + o(x^4) +fn cosh32(x: f32) f32 { + const u = @bitCast(u32, x); + const ux = u & 0x7FFFFFFF; + const ax = @bitCast(f32, ux); + + // |x| < log(2) + if (ux < 0x3F317217) { + if (ux < 0x3F800000 - (12 << 23)) { + math.raiseOverflow(); + return 1.0; + } + const t = math.expm1(ax); + return 1 + t * t / (2 * (1 + t)); + } + + // |x| < log(FLT_MAX) + if (ux < 0x42B17217) { + const t = math.exp(ax); + return 0.5 * (t + 1 / t); + } + + // |x| > log(FLT_MAX) or nan + return expo2(ax); +} + +fn cosh64(x: f64) f64 { + const u = @bitCast(u64, x); + const w = @intCast(u32, u >> 32); + const ax = @bitCast(f64, u & (maxInt(u64) >> 1)); + + // TODO: Shouldn't need this explicit check. + if (x == 0.0) { + return 1.0; + } + + // |x| < log(2) + if (w < 0x3FE62E42) { + if (w < 0x3FF00000 - (26 << 20)) { + if (x != 0) { + math.raiseInexact(); + } + return 1.0; + } + const t = math.expm1(ax); + return 1 + t * t / (2 * (1 + t)); + } + + // |x| < log(DBL_MAX) + if (w < 0x40862E42) { + const t = math.exp(ax); + // NOTE: If x > log(0x1p26) then 1/t is not required. + return 0.5 * (t + 1 / t); + } + + // |x| > log(CBL_MAX) or nan + return expo2(ax); +} + +test "math.cosh" { + expect(cosh(f32(1.5)) == cosh32(1.5)); + expect(cosh(f64(1.5)) == cosh64(1.5)); +} + +test "math.cosh32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, cosh32(0.0), 1.0, epsilon)); + expect(math.approxEq(f32, cosh32(0.2), 1.020067, epsilon)); + expect(math.approxEq(f32, cosh32(0.8923), 1.425225, epsilon)); + expect(math.approxEq(f32, cosh32(1.5), 2.352410, epsilon)); +} + +test "math.cosh64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, cosh64(0.0), 1.0, epsilon)); + expect(math.approxEq(f64, cosh64(0.2), 1.020067, epsilon)); + expect(math.approxEq(f64, cosh64(0.8923), 1.425225, epsilon)); + expect(math.approxEq(f64, cosh64(1.5), 2.352410, epsilon)); +} + +test "math.cosh32.special" { + expect(cosh32(0.0) == 1.0); + expect(cosh32(-0.0) == 1.0); + expect(math.isPositiveInf(cosh32(math.inf(f32)))); + expect(math.isPositiveInf(cosh32(-math.inf(f32)))); + expect(math.isNan(cosh32(math.nan(f32)))); +} + +test "math.cosh64.special" { + expect(cosh64(0.0) == 1.0); + expect(cosh64(-0.0) == 1.0); + expect(math.isPositiveInf(cosh64(math.inf(f64)))); + expect(math.isPositiveInf(cosh64(-math.inf(f64)))); + expect(math.isNan(cosh64(math.nan(f64)))); +} diff --git a/lib/std/math/exp.zig b/lib/std/math/exp.zig new file mode 100644 index 0000000000..718bbcd476 --- /dev/null +++ b/lib/std/math/exp.zig @@ -0,0 +1,218 @@ +// 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/math/expf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/exp.c + +const std = @import("../std.zig"); +const math = std.math; +const assert = std.debug.assert; +const builtin = @import("builtin"); + +/// Returns e raised to the power of x (e^x). +/// +/// Special Cases: +/// - exp(+inf) = +inf +/// - exp(nan) = nan +pub fn exp(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => exp32(x), + f64 => exp64(x), + else => @compileError("exp not implemented for " ++ @typeName(T)), + }; +} + +fn exp32(x_: f32) f32 { + const half = [_]f32{ 0.5, -0.5 }; + const ln2hi = 6.9314575195e-1; + const ln2lo = 1.4286067653e-6; + const invln2 = 1.4426950216e+0; + const P1 = 1.6666625440e-1; + const P2 = -2.7667332906e-3; + + var x = x_; + var hx = @bitCast(u32, x); + const sign = @intCast(i32, hx >> 31); + hx &= 0x7FFFFFFF; + + if (math.isNan(x)) { + return x; + } + + // |x| >= -87.33655 or nan + if (hx >= 0x42AEAC50) { + // nan + if (hx > 0x7F800000) { + return x; + } + // x >= 88.722839 + if (hx >= 0x42b17218 and sign == 0) { + return x * 0x1.0p127; + } + if (sign != 0) { + math.forceEval(-0x1.0p-149 / x); // overflow + // x <= -103.972084 + if (hx >= 0x42CFF1B5) { + return 0; + } + } + } + + var k: i32 = undefined; + var hi: f32 = undefined; + var lo: f32 = undefined; + + // |x| > 0.5 * ln2 + if (hx > 0x3EB17218) { + // |x| > 1.5 * ln2 + if (hx > 0x3F851592) { + k = @floatToInt(i32, invln2 * x + half[@intCast(usize, sign)]); + } else { + k = 1 - sign - sign; + } + + const fk = @intToFloat(f32, k); + hi = x - fk * ln2hi; + lo = fk * ln2lo; + x = hi - lo; + } + // |x| > 2^(-14) + else if (hx > 0x39000000) { + k = 0; + hi = x; + lo = 0; + } else { + math.forceEval(0x1.0p127 + x); // inexact + return 1 + x; + } + + const xx = x * x; + const c = x - xx * (P1 + xx * P2); + const y = 1 + (x * c / (2 - c) - lo + hi); + + if (k == 0) { + return y; + } else { + return math.scalbn(y, k); + } +} + +fn exp64(x_: f64) f64 { + const half = [_]f64{ 0.5, -0.5 }; + const ln2hi: f64 = 6.93147180369123816490e-01; + const ln2lo: f64 = 1.90821492927058770002e-10; + const invln2: f64 = 1.44269504088896338700e+00; + const P1: f64 = 1.66666666666666019037e-01; + const P2: f64 = -2.77777777770155933842e-03; + const P3: f64 = 6.61375632143793436117e-05; + const P4: f64 = -1.65339022054652515390e-06; + const P5: f64 = 4.13813679705723846039e-08; + + var x = x_; + var ux = @bitCast(u64, x); + var hx = ux >> 32; + const sign = @intCast(i32, hx >> 31); + hx &= 0x7FFFFFFF; + + if (math.isNan(x)) { + return x; + } + + // |x| >= 708.39 or nan + if (hx >= 0x4086232B) { + // nan + if (hx > 0x7FF00000) { + return x; + } + if (x > 709.782712893383973096) { + // overflow if x != inf + if (!math.isInf(x)) { + math.raiseOverflow(); + } + return math.inf(f64); + } + if (x < -708.39641853226410622) { + // underflow if x != -inf + // math.forceEval(f32(-0x1.0p-149 / x)); + if (x < -745.13321910194110842) { + return 0; + } + } + } + + // argument reduction + var k: i32 = undefined; + var hi: f64 = undefined; + var lo: f64 = undefined; + + // |x| > 0.5 * ln2 + if (hx > 0x3EB17218) { + // |x| >= 1.5 * ln2 + if (hx > 0x3FF0A2B2) { + k = @floatToInt(i32, invln2 * x + half[@intCast(usize, sign)]); + } else { + k = 1 - sign - sign; + } + + const dk = @intToFloat(f64, k); + hi = x - dk * ln2hi; + lo = dk * ln2lo; + x = hi - lo; + } + // |x| > 2^(-28) + else if (hx > 0x3E300000) { + k = 0; + hi = x; + lo = 0; + } else { + // inexact if x != 0 + // math.forceEval(0x1.0p1023 + x); + return 1 + x; + } + + const xx = x * x; + const c = x - xx * (P1 + xx * (P2 + xx * (P3 + xx * (P4 + xx * P5)))); + const y = 1 + (x * c / (2 - c) - lo + hi); + + if (k == 0) { + return y; + } else { + return math.scalbn(y, k); + } +} + +test "math.exp" { + assert(exp(f32(0.0)) == exp32(0.0)); + assert(exp(f64(0.0)) == exp64(0.0)); +} + +test "math.exp32" { + const epsilon = 0.000001; + + assert(exp32(0.0) == 1.0); + assert(math.approxEq(f32, exp32(0.0), 1.0, epsilon)); + assert(math.approxEq(f32, exp32(0.2), 1.221403, epsilon)); + assert(math.approxEq(f32, exp32(0.8923), 2.440737, epsilon)); + assert(math.approxEq(f32, exp32(1.5), 4.481689, epsilon)); +} + +test "math.exp64" { + const epsilon = 0.000001; + + assert(exp64(0.0) == 1.0); + assert(math.approxEq(f64, exp64(0.0), 1.0, epsilon)); + assert(math.approxEq(f64, exp64(0.2), 1.221403, epsilon)); + assert(math.approxEq(f64, exp64(0.8923), 2.440737, epsilon)); + assert(math.approxEq(f64, exp64(1.5), 4.481689, epsilon)); +} + +test "math.exp32.special" { + assert(math.isPositiveInf(exp32(math.inf(f32)))); + assert(math.isNan(exp32(math.nan(f32)))); +} + +test "math.exp64.special" { + assert(math.isPositiveInf(exp64(math.inf(f64)))); + assert(math.isNan(exp64(math.nan(f64)))); +} diff --git a/lib/std/math/exp2.zig b/lib/std/math/exp2.zig new file mode 100644 index 0000000000..57f6620d77 --- /dev/null +++ b/lib/std/math/exp2.zig @@ -0,0 +1,455 @@ +// 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/math/exp2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/exp2.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns 2 raised to the power of x (2^x). +/// +/// Special Cases: +/// - exp2(+inf) = +inf +/// - exp2(nan) = nan +pub fn exp2(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => exp2_32(x), + f64 => exp2_64(x), + else => @compileError("exp2 not implemented for " ++ @typeName(T)), + }; +} + +const exp2ft = [_]f64{ + 0x1.6a09e667f3bcdp-1, + 0x1.7a11473eb0187p-1, + 0x1.8ace5422aa0dbp-1, + 0x1.9c49182a3f090p-1, + 0x1.ae89f995ad3adp-1, + 0x1.c199bdd85529cp-1, + 0x1.d5818dcfba487p-1, + 0x1.ea4afa2a490dap-1, + 0x1.0000000000000p+0, + 0x1.0b5586cf9890fp+0, + 0x1.172b83c7d517bp+0, + 0x1.2387a6e756238p+0, + 0x1.306fe0a31b715p+0, + 0x1.3dea64c123422p+0, + 0x1.4bfdad5362a27p+0, + 0x1.5ab07dd485429p+0, +}; + +fn exp2_32(x: f32) f32 { + const tblsiz = @intCast(u32, exp2ft.len); + const redux: f32 = 0x1.8p23 / @intToFloat(f32, tblsiz); + const P1: f32 = 0x1.62e430p-1; + const P2: f32 = 0x1.ebfbe0p-3; + const P3: f32 = 0x1.c6b348p-5; + const P4: f32 = 0x1.3b2c9cp-7; + + var u = @bitCast(u32, x); + const ix = u & 0x7FFFFFFF; + + // |x| > 126 + if (ix > 0x42FC0000) { + // nan + if (ix > 0x7F800000) { + return x; + } + // x >= 128 + if (u >= 0x43000000 and u < 0x80000000) { + return x * 0x1.0p127; + } + // x < -126 + if (u >= 0x80000000) { + if (u >= 0xC3160000 or u & 0x000FFFF != 0) { + math.forceEval(-0x1.0p-149 / x); + } + // x <= -150 + if (u >= 0x3160000) { + return 0; + } + } + } + // |x| <= 0x1p-25 + else if (ix <= 0x33000000) { + return 1.0 + x; + } + + var uf = x + redux; + var i_0 = @bitCast(u32, uf); + i_0 += tblsiz / 2; + + const k = i_0 / tblsiz; + // NOTE: musl relies on undefined overflow shift behaviour. Appears that this produces the + // intended result but should confirm how GCC/Clang handle this to ensure. + const uk = @bitCast(f64, u64(0x3FF + k) << 52); + i_0 &= tblsiz - 1; + uf -= redux; + + const z: f64 = x - uf; + var r: f64 = exp2ft[i_0]; + const t: f64 = r * z; + r = r + t * (P1 + z * P2) + t * (z * z) * (P3 + z * P4); + return @floatCast(f32, r * uk); +} + +const exp2dt = [_]f64{ + // exp2(z + eps) eps + 0x1.6a09e667f3d5dp-1, 0x1.9880p-44, + 0x1.6b052fa751744p-1, 0x1.8000p-50, + 0x1.6c012750bd9fep-1, -0x1.8780p-45, + 0x1.6cfdcddd476bfp-1, 0x1.ec00p-46, + 0x1.6dfb23c651a29p-1, -0x1.8000p-50, + 0x1.6ef9298593ae3p-1, -0x1.c000p-52, + 0x1.6ff7df9519386p-1, -0x1.fd80p-45, + 0x1.70f7466f42da3p-1, -0x1.c880p-45, + 0x1.71f75e8ec5fc3p-1, 0x1.3c00p-46, + 0x1.72f8286eacf05p-1, -0x1.8300p-44, + 0x1.73f9a48a58152p-1, -0x1.0c00p-47, + 0x1.74fbd35d7ccfcp-1, 0x1.f880p-45, + 0x1.75feb564267f1p-1, 0x1.3e00p-47, + 0x1.77024b1ab6d48p-1, -0x1.7d00p-45, + 0x1.780694fde5d38p-1, -0x1.d000p-50, + 0x1.790b938ac1d00p-1, 0x1.3000p-49, + 0x1.7a11473eb0178p-1, -0x1.d000p-49, + 0x1.7b17b0976d060p-1, 0x1.0400p-45, + 0x1.7c1ed0130c133p-1, 0x1.0000p-53, + 0x1.7d26a62ff8636p-1, -0x1.6900p-45, + 0x1.7e2f336cf4e3bp-1, -0x1.2e00p-47, + 0x1.7f3878491c3e8p-1, -0x1.4580p-45, + 0x1.80427543e1b4ep-1, 0x1.3000p-44, + 0x1.814d2add1071ap-1, 0x1.f000p-47, + 0x1.82589994ccd7ep-1, -0x1.1c00p-45, + 0x1.8364c1eb942d0p-1, 0x1.9d00p-45, + 0x1.8471a4623cab5p-1, 0x1.7100p-43, + 0x1.857f4179f5bbcp-1, 0x1.2600p-45, + 0x1.868d99b4491afp-1, -0x1.2c40p-44, + 0x1.879cad931a395p-1, -0x1.3000p-45, + 0x1.88ac7d98a65b8p-1, -0x1.a800p-45, + 0x1.89bd0a4785800p-1, -0x1.d000p-49, + 0x1.8ace5422aa223p-1, 0x1.3280p-44, + 0x1.8be05bad619fap-1, 0x1.2b40p-43, + 0x1.8cf3216b54383p-1, -0x1.ed00p-45, + 0x1.8e06a5e08664cp-1, -0x1.0500p-45, + 0x1.8f1ae99157807p-1, 0x1.8280p-45, + 0x1.902fed0282c0ep-1, -0x1.cb00p-46, + 0x1.9145b0b91ff96p-1, -0x1.5e00p-47, + 0x1.925c353aa2ff9p-1, 0x1.5400p-48, + 0x1.93737b0cdc64ap-1, 0x1.7200p-46, + 0x1.948b82b5f98aep-1, -0x1.9000p-47, + 0x1.95a44cbc852cbp-1, 0x1.5680p-45, + 0x1.96bdd9a766f21p-1, -0x1.6d00p-44, + 0x1.97d829fde4e2ap-1, -0x1.1000p-47, + 0x1.98f33e47a23a3p-1, 0x1.d000p-45, + 0x1.9a0f170ca0604p-1, -0x1.8a40p-44, + 0x1.9b2bb4d53ff89p-1, 0x1.55c0p-44, + 0x1.9c49182a3f15bp-1, 0x1.6b80p-45, + 0x1.9d674194bb8c5p-1, -0x1.c000p-49, + 0x1.9e86319e3238ep-1, 0x1.7d00p-46, + 0x1.9fa5e8d07f302p-1, 0x1.6400p-46, + 0x1.a0c667b5de54dp-1, -0x1.5000p-48, + 0x1.a1e7aed8eb8f6p-1, 0x1.9e00p-47, + 0x1.a309bec4a2e27p-1, 0x1.ad80p-45, + 0x1.a42c980460a5dp-1, -0x1.af00p-46, + 0x1.a5503b23e259bp-1, 0x1.b600p-47, + 0x1.a674a8af46213p-1, 0x1.8880p-44, + 0x1.a799e1330b3a7p-1, 0x1.1200p-46, + 0x1.a8bfe53c12e8dp-1, 0x1.6c00p-47, + 0x1.a9e6b5579fcd2p-1, -0x1.9b80p-45, + 0x1.ab0e521356fb8p-1, 0x1.b700p-45, + 0x1.ac36bbfd3f381p-1, 0x1.9000p-50, + 0x1.ad5ff3a3c2780p-1, 0x1.4000p-49, + 0x1.ae89f995ad2a3p-1, -0x1.c900p-45, + 0x1.afb4ce622f367p-1, 0x1.6500p-46, + 0x1.b0e07298db790p-1, 0x1.fd40p-45, + 0x1.b20ce6c9a89a9p-1, 0x1.2700p-46, + 0x1.b33a2b84f1a4bp-1, 0x1.d470p-43, + 0x1.b468415b747e7p-1, -0x1.8380p-44, + 0x1.b59728de5593ap-1, 0x1.8000p-54, + 0x1.b6c6e29f1c56ap-1, 0x1.ad00p-47, + 0x1.b7f76f2fb5e50p-1, 0x1.e800p-50, + 0x1.b928cf22749b2p-1, -0x1.4c00p-47, + 0x1.ba5b030a10603p-1, -0x1.d700p-47, + 0x1.bb8e0b79a6f66p-1, 0x1.d900p-47, + 0x1.bcc1e904bc1ffp-1, 0x1.2a00p-47, + 0x1.bdf69c3f3a16fp-1, -0x1.f780p-46, + 0x1.bf2c25bd71db8p-1, -0x1.0a00p-46, + 0x1.c06286141b2e9p-1, -0x1.1400p-46, + 0x1.c199bdd8552e0p-1, 0x1.be00p-47, + 0x1.c2d1cd9fa64eep-1, -0x1.9400p-47, + 0x1.c40ab5fffd02fp-1, -0x1.ed00p-47, + 0x1.c544778fafd15p-1, 0x1.9660p-44, + 0x1.c67f12e57d0cbp-1, -0x1.a100p-46, + 0x1.c7ba88988c1b6p-1, -0x1.8458p-42, + 0x1.c8f6d9406e733p-1, -0x1.a480p-46, + 0x1.ca3405751c4dfp-1, 0x1.b000p-51, + 0x1.cb720dcef9094p-1, 0x1.1400p-47, + 0x1.ccb0f2e6d1689p-1, 0x1.0200p-48, + 0x1.cdf0b555dc412p-1, 0x1.3600p-48, + 0x1.cf3155b5bab3bp-1, -0x1.6900p-47, + 0x1.d072d4a0789bcp-1, 0x1.9a00p-47, + 0x1.d1b532b08c8fap-1, -0x1.5e00p-46, + 0x1.d2f87080d8a85p-1, 0x1.d280p-46, + 0x1.d43c8eacaa203p-1, 0x1.1a00p-47, + 0x1.d5818dcfba491p-1, 0x1.f000p-50, + 0x1.d6c76e862e6a1p-1, -0x1.3a00p-47, + 0x1.d80e316c9834ep-1, -0x1.cd80p-47, + 0x1.d955d71ff6090p-1, 0x1.4c00p-48, + 0x1.da9e603db32aep-1, 0x1.f900p-48, + 0x1.dbe7cd63a8325p-1, 0x1.9800p-49, + 0x1.dd321f301b445p-1, -0x1.5200p-48, + 0x1.de7d5641c05bfp-1, -0x1.d700p-46, + 0x1.dfc97337b9aecp-1, -0x1.6140p-46, + 0x1.e11676b197d5ep-1, 0x1.b480p-47, + 0x1.e264614f5a3e7p-1, 0x1.0ce0p-43, + 0x1.e3b333b16ee5cp-1, 0x1.c680p-47, + 0x1.e502ee78b3fb4p-1, -0x1.9300p-47, + 0x1.e653924676d68p-1, -0x1.5000p-49, + 0x1.e7a51fbc74c44p-1, -0x1.7f80p-47, + 0x1.e8f7977cdb726p-1, -0x1.3700p-48, + 0x1.ea4afa2a490e8p-1, 0x1.5d00p-49, + 0x1.eb9f4867ccae4p-1, 0x1.61a0p-46, + 0x1.ecf482d8e680dp-1, 0x1.5500p-48, + 0x1.ee4aaa2188514p-1, 0x1.6400p-51, + 0x1.efa1bee615a13p-1, -0x1.e800p-49, + 0x1.f0f9c1cb64106p-1, -0x1.a880p-48, + 0x1.f252b376bb963p-1, -0x1.c900p-45, + 0x1.f3ac948dd7275p-1, 0x1.a000p-53, + 0x1.f50765b6e4524p-1, -0x1.4f00p-48, + 0x1.f6632798844fdp-1, 0x1.a800p-51, + 0x1.f7bfdad9cbe38p-1, 0x1.abc0p-48, + 0x1.f91d802243c82p-1, -0x1.4600p-50, + 0x1.fa7c1819e908ep-1, -0x1.b0c0p-47, + 0x1.fbdba3692d511p-1, -0x1.0e00p-51, + 0x1.fd3c22b8f7194p-1, -0x1.0de8p-46, + 0x1.fe9d96b2a23eep-1, 0x1.e430p-49, + 0x1.0000000000000p+0, 0x0.0000p+0, + 0x1.00b1afa5abcbep+0, -0x1.3400p-52, + 0x1.0163da9fb3303p+0, -0x1.2170p-46, + 0x1.02168143b0282p+0, 0x1.a400p-52, + 0x1.02c9a3e77806cp+0, 0x1.f980p-49, + 0x1.037d42e11bbcap+0, -0x1.7400p-51, + 0x1.04315e86e7f89p+0, 0x1.8300p-50, + 0x1.04e5f72f65467p+0, -0x1.a3f0p-46, + 0x1.059b0d315855ap+0, -0x1.2840p-47, + 0x1.0650a0e3c1f95p+0, 0x1.1600p-48, + 0x1.0706b29ddf71ap+0, 0x1.5240p-46, + 0x1.07bd42b72a82dp+0, -0x1.9a00p-49, + 0x1.0874518759bd0p+0, 0x1.6400p-49, + 0x1.092bdf66607c8p+0, -0x1.0780p-47, + 0x1.09e3ecac6f383p+0, -0x1.8000p-54, + 0x1.0a9c79b1f3930p+0, 0x1.fa00p-48, + 0x1.0b5586cf988fcp+0, -0x1.ac80p-48, + 0x1.0c0f145e46c8ap+0, 0x1.9c00p-50, + 0x1.0cc922b724816p+0, 0x1.5200p-47, + 0x1.0d83b23395dd8p+0, -0x1.ad00p-48, + 0x1.0e3ec32d3d1f3p+0, 0x1.bac0p-46, + 0x1.0efa55fdfa9a6p+0, -0x1.4e80p-47, + 0x1.0fb66affed2f0p+0, -0x1.d300p-47, + 0x1.1073028d7234bp+0, 0x1.1500p-48, + 0x1.11301d0125b5bp+0, 0x1.c000p-49, + 0x1.11edbab5e2af9p+0, 0x1.6bc0p-46, + 0x1.12abdc06c31d5p+0, 0x1.8400p-49, + 0x1.136a814f2047dp+0, -0x1.ed00p-47, + 0x1.1429aaea92de9p+0, 0x1.8e00p-49, + 0x1.14e95934f3138p+0, 0x1.b400p-49, + 0x1.15a98c8a58e71p+0, 0x1.5300p-47, + 0x1.166a45471c3dfp+0, 0x1.3380p-47, + 0x1.172b83c7d5211p+0, 0x1.8d40p-45, + 0x1.17ed48695bb9fp+0, -0x1.5d00p-47, + 0x1.18af9388c8d93p+0, -0x1.c880p-46, + 0x1.1972658375d66p+0, 0x1.1f00p-46, + 0x1.1a35beb6fcba7p+0, 0x1.0480p-46, + 0x1.1af99f81387e3p+0, -0x1.7390p-43, + 0x1.1bbe084045d54p+0, 0x1.4e40p-45, + 0x1.1c82f95281c43p+0, -0x1.a200p-47, + 0x1.1d4873168b9b2p+0, 0x1.3800p-49, + 0x1.1e0e75eb44031p+0, 0x1.ac00p-49, + 0x1.1ed5022fcd938p+0, 0x1.1900p-47, + 0x1.1f9c18438cdf7p+0, -0x1.b780p-46, + 0x1.2063b88628d8fp+0, 0x1.d940p-45, + 0x1.212be3578a81ep+0, 0x1.8000p-50, + 0x1.21f49917ddd41p+0, 0x1.b340p-45, + 0x1.22bdda2791323p+0, 0x1.9f80p-46, + 0x1.2387a6e7561e7p+0, -0x1.9c80p-46, + 0x1.2451ffb821427p+0, 0x1.2300p-47, + 0x1.251ce4fb2a602p+0, -0x1.3480p-46, + 0x1.25e85711eceb0p+0, 0x1.2700p-46, + 0x1.26b4565e27d16p+0, 0x1.1d00p-46, + 0x1.2780e341de00fp+0, 0x1.1ee0p-44, + 0x1.284dfe1f5633ep+0, -0x1.4c00p-46, + 0x1.291ba7591bb30p+0, -0x1.3d80p-46, + 0x1.29e9df51fdf09p+0, 0x1.8b00p-47, + 0x1.2ab8a66d10e9bp+0, -0x1.27c0p-45, + 0x1.2b87fd0dada3ap+0, 0x1.a340p-45, + 0x1.2c57e39771af9p+0, -0x1.0800p-46, + 0x1.2d285a6e402d9p+0, -0x1.ed00p-47, + 0x1.2df961f641579p+0, -0x1.4200p-48, + 0x1.2ecafa93e2ecfp+0, -0x1.4980p-45, + 0x1.2f9d24abd8822p+0, -0x1.6300p-46, + 0x1.306fe0a31b625p+0, -0x1.2360p-44, + 0x1.31432edeea50bp+0, -0x1.0df8p-40, + 0x1.32170fc4cd7b8p+0, -0x1.2480p-45, + 0x1.32eb83ba8e9a2p+0, -0x1.5980p-45, + 0x1.33c08b2641766p+0, 0x1.ed00p-46, + 0x1.3496266e3fa27p+0, -0x1.c000p-50, + 0x1.356c55f929f0fp+0, -0x1.0d80p-44, + 0x1.36431a2de88b9p+0, 0x1.2c80p-45, + 0x1.371a7373aaa39p+0, 0x1.0600p-45, + 0x1.37f26231e74fep+0, -0x1.6600p-46, + 0x1.38cae6d05d838p+0, -0x1.ae00p-47, + 0x1.39a401b713ec3p+0, -0x1.4720p-43, + 0x1.3a7db34e5a020p+0, 0x1.8200p-47, + 0x1.3b57fbfec6e95p+0, 0x1.e800p-44, + 0x1.3c32dc313a8f2p+0, 0x1.f800p-49, + 0x1.3d0e544ede122p+0, -0x1.7a00p-46, + 0x1.3dea64c1234bbp+0, 0x1.6300p-45, + 0x1.3ec70df1c4eccp+0, -0x1.8a60p-43, + 0x1.3fa4504ac7e8cp+0, -0x1.cdc0p-44, + 0x1.40822c367a0bbp+0, 0x1.5b80p-45, + 0x1.4160a21f72e95p+0, 0x1.ec00p-46, + 0x1.423fb27094646p+0, -0x1.3600p-46, + 0x1.431f5d950a920p+0, 0x1.3980p-45, + 0x1.43ffa3f84b9ebp+0, 0x1.a000p-48, + 0x1.44e0860618919p+0, -0x1.6c00p-48, + 0x1.45c2042a7d201p+0, -0x1.bc00p-47, + 0x1.46a41ed1d0016p+0, -0x1.2800p-46, + 0x1.4786d668b3326p+0, 0x1.0e00p-44, + 0x1.486a2b5c13c00p+0, -0x1.d400p-45, + 0x1.494e1e192af04p+0, 0x1.c200p-47, + 0x1.4a32af0d7d372p+0, -0x1.e500p-46, + 0x1.4b17dea6db801p+0, 0x1.7800p-47, + 0x1.4bfdad53629e1p+0, -0x1.3800p-46, + 0x1.4ce41b817c132p+0, 0x1.0800p-47, + 0x1.4dcb299fddddbp+0, 0x1.c700p-45, + 0x1.4eb2d81d8ab96p+0, -0x1.ce00p-46, + 0x1.4f9b2769d2d02p+0, 0x1.9200p-46, + 0x1.508417f4531c1p+0, -0x1.8c00p-47, + 0x1.516daa2cf662ap+0, -0x1.a000p-48, + 0x1.5257de83f51eap+0, 0x1.a080p-43, + 0x1.5342b569d4edap+0, -0x1.6d80p-45, + 0x1.542e2f4f6ac1ap+0, -0x1.2440p-44, + 0x1.551a4ca5d94dbp+0, 0x1.83c0p-43, + 0x1.56070dde9116bp+0, 0x1.4b00p-45, + 0x1.56f4736b529dep+0, 0x1.15a0p-43, + 0x1.57e27dbe2c40ep+0, -0x1.9e00p-45, + 0x1.58d12d497c76fp+0, -0x1.3080p-45, + 0x1.59c0827ff0b4cp+0, 0x1.dec0p-43, + 0x1.5ab07dd485427p+0, -0x1.4000p-51, + 0x1.5ba11fba87af4p+0, 0x1.0080p-44, + 0x1.5c9268a59460bp+0, -0x1.6c80p-45, + 0x1.5d84590998e3fp+0, 0x1.69a0p-43, + 0x1.5e76f15ad20e1p+0, -0x1.b400p-46, + 0x1.5f6a320dcebcap+0, 0x1.7700p-46, + 0x1.605e1b976dcb8p+0, 0x1.6f80p-45, + 0x1.6152ae6cdf715p+0, 0x1.1000p-47, + 0x1.6247eb03a5531p+0, -0x1.5d00p-46, + 0x1.633dd1d1929b5p+0, -0x1.2d00p-46, + 0x1.6434634ccc313p+0, -0x1.a800p-49, + 0x1.652b9febc8efap+0, -0x1.8600p-45, + 0x1.6623882553397p+0, 0x1.1fe0p-40, + 0x1.671c1c708328ep+0, -0x1.7200p-44, + 0x1.68155d44ca97ep+0, 0x1.6800p-49, + 0x1.690f4b19e9471p+0, -0x1.9780p-45, +}; + +fn exp2_64(x: f64) f64 { + const tblsiz = @intCast(u32, exp2dt.len / 2); + const redux: f64 = 0x1.8p52 / @intToFloat(f64, tblsiz); + const P1: f64 = 0x1.62e42fefa39efp-1; + const P2: f64 = 0x1.ebfbdff82c575p-3; + const P3: f64 = 0x1.c6b08d704a0a6p-5; + const P4: f64 = 0x1.3b2ab88f70400p-7; + const P5: f64 = 0x1.5d88003875c74p-10; + + const ux = @bitCast(u64, x); + const ix = @intCast(u32, ux >> 32) & 0x7FFFFFFF; + + // TODO: This should be handled beneath. + if (math.isNan(x)) { + return math.nan(f64); + } + + // |x| >= 1022 or nan + if (ix >= 0x408FF000) { + // x >= 1024 or nan + if (ix >= 0x40900000 and ux >> 63 == 0) { + math.raiseOverflow(); + return math.inf(f64); + } + // -inf or -nan + if (ix >= 0x7FF00000) { + return -1 / x; + } + // x <= -1022 + if (ux >> 63 != 0) { + // underflow + if (x <= -1075 or x - 0x1.0p52 + 0x1.0p52 != x) { + math.forceEval(@floatCast(f32, -0x1.0p-149 / x)); + } + if (x <= -1075) { + return 0; + } + } + } + // |x| < 0x1p-54 + else if (ix < 0x3C900000) { + return 1.0 + x; + } + + // reduce x + var uf = x + redux; + // NOTE: musl performs an implicit 64-bit to 32-bit u32 truncation here + var i_0 = @truncate(u32, @bitCast(u64, uf)); + i_0 += tblsiz / 2; + + const k: u32 = i_0 / tblsiz * tblsiz; + const ik = @bitCast(i32, k / tblsiz); + i_0 %= tblsiz; + uf -= redux; + + // r = exp2(y) = exp2t[i_0] * p(z - eps[i]) + var z = x - uf; + const t = exp2dt[2 * i_0]; + z -= exp2dt[2 * i_0 + 1]; + const r = t + t * z * (P1 + z * (P2 + z * (P3 + z * (P4 + z * P5)))); + + return math.scalbn(r, ik); +} + +test "math.exp2" { + expect(exp2(f32(0.8923)) == exp2_32(0.8923)); + expect(exp2(f64(0.8923)) == exp2_64(0.8923)); +} + +test "math.exp2_32" { + const epsilon = 0.000001; + + expect(exp2_32(0.0) == 1.0); + expect(math.approxEq(f32, exp2_32(0.2), 1.148698, epsilon)); + expect(math.approxEq(f32, exp2_32(0.8923), 1.856133, epsilon)); + expect(math.approxEq(f32, exp2_32(1.5), 2.828427, epsilon)); + expect(math.approxEq(f32, exp2_32(37.45), 187747237888, epsilon)); +} + +test "math.exp2_64" { + const epsilon = 0.000001; + + expect(exp2_64(0.0) == 1.0); + expect(math.approxEq(f64, exp2_64(0.2), 1.148698, epsilon)); + expect(math.approxEq(f64, exp2_64(0.8923), 1.856133, epsilon)); + expect(math.approxEq(f64, exp2_64(1.5), 2.828427, epsilon)); +} + +test "math.exp2_32.special" { + expect(math.isPositiveInf(exp2_32(math.inf(f32)))); + expect(math.isNan(exp2_32(math.nan(f32)))); +} + +test "math.exp2_64.special" { + expect(math.isPositiveInf(exp2_64(math.inf(f64)))); + expect(math.isNan(exp2_64(math.nan(f64)))); +} diff --git a/lib/std/math/expm1.zig b/lib/std/math/expm1.zig new file mode 100644 index 0000000000..5e347f86f6 --- /dev/null +++ b/lib/std/math/expm1.zig @@ -0,0 +1,328 @@ +// 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/math/expmf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/expm.c + +// TODO: Updated recently. + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns e raised to the power of x, minus 1 (e^x - 1). This is more accurate than exp(e, x) - 1 +/// when x is near 0. +/// +/// Special Cases: +/// - expm1(+inf) = +inf +/// - expm1(-inf) = -1 +/// - expm1(nan) = nan +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 { + if (math.isNan(x_)) + return math.nan(f32); + + 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 = @floatToInt(i32, kf); + const t = @intToFloat(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, @intCast(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, @intCast(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 { + if (math.isNan(x_)) + return math.nan(f64); + + 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 = @intCast(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 = @floatToInt(i32, kf); + const t = @intToFloat(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(@floatCast(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, @intCast(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, @intCast(u64, 0x3FF -% k) << 52); + if (k < 20) { + return (x - e + (1 - uf)) * twopk; + } else { + return (x - (e + uf) + 1) * twopk; + } +} + +test "math.exp1m" { + expect(expm1(f32(0.0)) == expm1_32(0.0)); + expect(expm1(f64(0.0)) == expm1_64(0.0)); +} + +test "math.expm1_32" { + const epsilon = 0.000001; + + expect(expm1_32(0.0) == 0.0); + expect(math.approxEq(f32, expm1_32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, expm1_32(0.2), 0.221403, epsilon)); + expect(math.approxEq(f32, expm1_32(0.8923), 1.440737, epsilon)); + expect(math.approxEq(f32, expm1_32(1.5), 3.481689, epsilon)); +} + +test "math.expm1_64" { + const epsilon = 0.000001; + + expect(expm1_64(0.0) == 0.0); + expect(math.approxEq(f64, expm1_64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, expm1_64(0.2), 0.221403, epsilon)); + expect(math.approxEq(f64, expm1_64(0.8923), 1.440737, epsilon)); + expect(math.approxEq(f64, expm1_64(1.5), 3.481689, epsilon)); +} + +test "math.expm1_32.special" { + const epsilon = 0.000001; + + expect(math.isPositiveInf(expm1_32(math.inf(f32)))); + expect(expm1_32(-math.inf(f32)) == -1.0); + expect(math.isNan(expm1_32(math.nan(f32)))); +} + +test "math.expm1_64.special" { + const epsilon = 0.000001; + + expect(math.isPositiveInf(expm1_64(math.inf(f64)))); + expect(expm1_64(-math.inf(f64)) == -1.0); + expect(math.isNan(expm1_64(math.nan(f64)))); +} diff --git a/lib/std/math/expo2.zig b/lib/std/math/expo2.zig new file mode 100644 index 0000000000..c00098a5a7 --- /dev/null +++ b/lib/std/math/expo2.zig @@ -0,0 +1,35 @@ +// 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/math/__expo2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/__expo2.c + +const math = @import("../math.zig"); + +/// Returns exp(x) / 2 for x >= log(maxFloat(T)). +pub fn expo2(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => expo2f(x), + f64 => expo2d(x), + else => @compileError("expo2 not implemented for " ++ @typeName(T)), + }; +} + +fn expo2f(x: f32) f32 { + const k: u32 = 235; + const kln2 = 0x1.45C778p+7; + + const u = (0x7F + k / 2) << 23; + const scale = @bitCast(f32, u); + return math.exp(x - kln2) * scale * scale; +} + +fn expo2d(x: f64) f64 { + const k: u32 = 2043; + const kln2 = 0x1.62066151ADD8BP+10; + + const u = (0x3FF + k / 2) << 20; + const scale = @bitCast(f64, u64(u) << 32); + return math.exp(x - kln2) * scale * scale; +} diff --git a/lib/std/math/fabs.zig b/lib/std/math/fabs.zig new file mode 100644 index 0000000000..6469f38835 --- /dev/null +++ b/lib/std/math/fabs.zig @@ -0,0 +1,101 @@ +// 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/math/fabsf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/fabs.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns the absolute value of x. +/// +/// Special Cases: +/// - fabs(+-inf) = +inf +/// - fabs(nan) = nan +pub fn fabs(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f16 => fabs16(x), + f32 => fabs32(x), + f64 => fabs64(x), + f128 => fabs128(x), + else => @compileError("fabs not implemented for " ++ @typeName(T)), + }; +} + +fn fabs16(x: f16) f16 { + var u = @bitCast(u16, x); + u &= 0x7FFF; + return @bitCast(f16, u); +} + +fn fabs32(x: f32) f32 { + var u = @bitCast(u32, x); + u &= 0x7FFFFFFF; + return @bitCast(f32, u); +} + +fn fabs64(x: f64) f64 { + var u = @bitCast(u64, x); + u &= maxInt(u64) >> 1; + return @bitCast(f64, u); +} + +fn fabs128(x: f128) f128 { + var u = @bitCast(u128, x); + u &= maxInt(u128) >> 1; + return @bitCast(f128, u); +} + +test "math.fabs" { + expect(fabs(f16(1.0)) == fabs16(1.0)); + expect(fabs(f32(1.0)) == fabs32(1.0)); + expect(fabs(f64(1.0)) == fabs64(1.0)); + expect(fabs(f128(1.0)) == fabs128(1.0)); +} + +test "math.fabs16" { + expect(fabs16(1.0) == 1.0); + expect(fabs16(-1.0) == 1.0); +} + +test "math.fabs32" { + expect(fabs32(1.0) == 1.0); + expect(fabs32(-1.0) == 1.0); +} + +test "math.fabs64" { + expect(fabs64(1.0) == 1.0); + expect(fabs64(-1.0) == 1.0); +} + +test "math.fabs128" { + expect(fabs128(1.0) == 1.0); + expect(fabs128(-1.0) == 1.0); +} + +test "math.fabs16.special" { + expect(math.isPositiveInf(fabs(math.inf(f16)))); + expect(math.isPositiveInf(fabs(-math.inf(f16)))); + expect(math.isNan(fabs(math.nan(f16)))); +} + +test "math.fabs32.special" { + expect(math.isPositiveInf(fabs(math.inf(f32)))); + expect(math.isPositiveInf(fabs(-math.inf(f32)))); + expect(math.isNan(fabs(math.nan(f32)))); +} + +test "math.fabs64.special" { + expect(math.isPositiveInf(fabs(math.inf(f64)))); + expect(math.isPositiveInf(fabs(-math.inf(f64)))); + expect(math.isNan(fabs(math.nan(f64)))); +} + +test "math.fabs128.special" { + expect(math.isPositiveInf(fabs(math.inf(f128)))); + expect(math.isPositiveInf(fabs(-math.inf(f128)))); + expect(math.isNan(fabs(math.nan(f128)))); +} diff --git a/lib/std/math/floor.zig b/lib/std/math/floor.zig new file mode 100644 index 0000000000..e5ff2b1fc1 --- /dev/null +++ b/lib/std/math/floor.zig @@ -0,0 +1,171 @@ +// 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/math/floorf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/floor.c + +const builtin = @import("builtin"); +const expect = std.testing.expect; +const std = @import("../std.zig"); +const math = std.math; + +/// Returns the greatest integer value less than or equal to x. +/// +/// Special Cases: +/// - floor(+-0) = +-0 +/// - floor(+-inf) = +-inf +/// - floor(nan) = nan +pub fn floor(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f16 => floor16(x), + f32 => floor32(x), + f64 => floor64(x), + else => @compileError("floor not implemented for " ++ @typeName(T)), + }; +} + +fn floor16(x: f16) f16 { + var u = @bitCast(u16, x); + const e = @intCast(i16, (u >> 10) & 31) - 15; + var m: u16 = undefined; + + // TODO: Shouldn't need this explicit check. + if (x == 0.0) { + return x; + } + + if (e >= 10) { + return x; + } + + if (e >= 0) { + m = u16(1023) >> @intCast(u4, e); + if (u & m == 0) { + return x; + } + math.forceEval(x + 0x1.0p120); + if (u >> 15 != 0) { + u += m; + } + return @bitCast(f16, u & ~m); + } else { + math.forceEval(x + 0x1.0p120); + if (u >> 15 == 0) { + return 0.0; + } else { + return -1.0; + } + } +} + +fn floor32(x: f32) f32 { + var u = @bitCast(u32, x); + const e = @intCast(i32, (u >> 23) & 0xFF) - 0x7F; + var m: u32 = undefined; + + // TODO: Shouldn't need this explicit check. + if (x == 0.0) { + return x; + } + + if (e >= 23) { + return x; + } + + if (e >= 0) { + m = u32(0x007FFFFF) >> @intCast(u5, e); + if (u & m == 0) { + return x; + } + math.forceEval(x + 0x1.0p120); + if (u >> 31 != 0) { + u += m; + } + return @bitCast(f32, u & ~m); + } else { + math.forceEval(x + 0x1.0p120); + if (u >> 31 == 0) { + return 0.0; + } else { + return -1.0; + } + } +} + +fn floor64(x: f64) f64 { + const u = @bitCast(u64, x); + const e = (u >> 52) & 0x7FF; + var y: f64 = undefined; + + if (e >= 0x3FF + 52 or x == 0) { + return x; + } + + if (u >> 63 != 0) { + y = x - math.f64_toint + math.f64_toint - x; + } else { + y = x + math.f64_toint - math.f64_toint - x; + } + + if (e <= 0x3FF - 1) { + math.forceEval(y); + if (u >> 63 != 0) { + return -1.0; + } else { + return 0.0; + } + } else if (y > 0) { + return x + y - 1; + } else { + return x + y; + } +} + +test "math.floor" { + expect(floor(f16(1.3)) == floor16(1.3)); + expect(floor(f32(1.3)) == floor32(1.3)); + expect(floor(f64(1.3)) == floor64(1.3)); +} + +test "math.floor16" { + expect(floor16(1.3) == 1.0); + expect(floor16(-1.3) == -2.0); + expect(floor16(0.2) == 0.0); +} + +test "math.floor32" { + expect(floor32(1.3) == 1.0); + expect(floor32(-1.3) == -2.0); + expect(floor32(0.2) == 0.0); +} + +test "math.floor64" { + expect(floor64(1.3) == 1.0); + expect(floor64(-1.3) == -2.0); + expect(floor64(0.2) == 0.0); +} + +test "math.floor16.special" { + expect(floor16(0.0) == 0.0); + expect(floor16(-0.0) == -0.0); + expect(math.isPositiveInf(floor16(math.inf(f16)))); + expect(math.isNegativeInf(floor16(-math.inf(f16)))); + expect(math.isNan(floor16(math.nan(f16)))); +} + +test "math.floor32.special" { + expect(floor32(0.0) == 0.0); + expect(floor32(-0.0) == -0.0); + expect(math.isPositiveInf(floor32(math.inf(f32)))); + expect(math.isNegativeInf(floor32(-math.inf(f32)))); + expect(math.isNan(floor32(math.nan(f32)))); +} + +test "math.floor64.special" { + expect(floor64(0.0) == 0.0); + expect(floor64(-0.0) == -0.0); + expect(math.isPositiveInf(floor64(math.inf(f64)))); + expect(math.isNegativeInf(floor64(-math.inf(f64)))); + expect(math.isNan(floor64(math.nan(f64)))); +} diff --git a/lib/std/math/fma.zig b/lib/std/math/fma.zig new file mode 100644 index 0000000000..19c306fa2a --- /dev/null +++ b/lib/std/math/fma.zig @@ -0,0 +1,172 @@ +// 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/math/fmaf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/fma.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns x * y + z with a single rounding error. +pub fn fma(comptime T: type, x: T, y: T, z: T) T { + return switch (T) { + f32 => fma32(x, y, z), + f64 => fma64(x, y, z), + else => @compileError("fma not implemented for " ++ @typeName(T)), + }; +} + +fn fma32(x: f32, y: f32, z: f32) f32 { + const xy = f64(x) * y; + const xy_z = xy + z; + const u = @bitCast(u64, xy_z); + const e = (u >> 52) & 0x7FF; + + if ((u & 0x1FFFFFFF) != 0x10000000 or e == 0x7FF or (xy_z - xy == z and xy_z - z == xy)) { + return @floatCast(f32, xy_z); + } else { + // TODO: Handle inexact case with double-rounding + return @floatCast(f32, xy_z); + } +} + +// NOTE: Upstream fma.c has been rewritten completely to raise fp exceptions more accurately. +fn fma64(x: f64, y: f64, z: f64) f64 { + if (!math.isFinite(x) or !math.isFinite(y)) { + return x * y + z; + } + if (!math.isFinite(z)) { + return z; + } + if (x == 0.0 or y == 0.0) { + return x * y + z; + } + if (z == 0.0) { + return x * y; + } + + const x1 = math.frexp(x); + var ex = x1.exponent; + var xs = x1.significand; + const x2 = math.frexp(y); + var ey = x2.exponent; + var ys = x2.significand; + const x3 = math.frexp(z); + var ez = x3.exponent; + var zs = x3.significand; + + var spread = ex + ey - ez; + if (spread <= 53 * 2) { + zs = math.scalbn(zs, -spread); + } else { + zs = math.copysign(f64, math.f64_min, zs); + } + + const xy = dd_mul(xs, ys); + const r = dd_add(xy.hi, zs); + spread = ex + ey; + + if (r.hi == 0.0) { + return xy.hi + zs + math.scalbn(xy.lo, spread); + } + + const adj = add_adjusted(r.lo, xy.lo); + if (spread + math.ilogb(r.hi) > -1023) { + return math.scalbn(r.hi + adj, spread); + } else { + return add_and_denorm(r.hi, adj, spread); + } +} + +const dd = struct { + hi: f64, + lo: f64, +}; + +fn dd_add(a: f64, b: f64) dd { + var ret: dd = undefined; + ret.hi = a + b; + const s = ret.hi - a; + ret.lo = (a - (ret.hi - s)) + (b - s); + return ret; +} + +fn dd_mul(a: f64, b: f64) dd { + var ret: dd = undefined; + const split: f64 = 0x1.0p27 + 1.0; + + var p = a * split; + var ha = a - p; + ha += p; + var la = a - ha; + + p = b * split; + var hb = b - p; + hb += p; + var lb = b - hb; + + p = ha * hb; + var q = ha * lb + la * hb; + + ret.hi = p + q; + ret.lo = p - ret.hi + q + la * lb; + return ret; +} + +fn add_adjusted(a: f64, b: f64) f64 { + var sum = dd_add(a, b); + if (sum.lo != 0) { + var uhii = @bitCast(u64, sum.hi); + if (uhii & 1 == 0) { + // hibits += copysign(1.0, sum.hi, sum.lo) + const uloi = @bitCast(u64, sum.lo); + uhii += 1 - ((uhii ^ uloi) >> 62); + sum.hi = @bitCast(f64, uhii); + } + } + return sum.hi; +} + +fn add_and_denorm(a: f64, b: f64, scale: i32) f64 { + var sum = dd_add(a, b); + if (sum.lo != 0) { + var uhii = @bitCast(u64, sum.hi); + const bits_lost = -@intCast(i32, (uhii >> 52) & 0x7FF) - scale + 1; + if ((bits_lost != 1) == (uhii & 1 != 0)) { + const uloi = @bitCast(u64, sum.lo); + uhii += 1 - (((uhii ^ uloi) >> 62) & 2); + sum.hi = @bitCast(f64, uhii); + } + } + return math.scalbn(sum.hi, scale); +} + +test "math.fma" { + expect(fma(f32, 0.0, 1.0, 1.0) == fma32(0.0, 1.0, 1.0)); + expect(fma(f64, 0.0, 1.0, 1.0) == fma64(0.0, 1.0, 1.0)); +} + +test "math.fma32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, fma32(0.0, 5.0, 9.124), 9.124, epsilon)); + expect(math.approxEq(f32, fma32(0.2, 5.0, 9.124), 10.124, epsilon)); + expect(math.approxEq(f32, fma32(0.8923, 5.0, 9.124), 13.5855, epsilon)); + expect(math.approxEq(f32, fma32(1.5, 5.0, 9.124), 16.624, epsilon)); + expect(math.approxEq(f32, fma32(37.45, 5.0, 9.124), 196.374004, epsilon)); + expect(math.approxEq(f32, fma32(89.123, 5.0, 9.124), 454.739005, epsilon)); + expect(math.approxEq(f32, fma32(123123.234375, 5.0, 9.124), 615625.295875, epsilon)); +} + +test "math.fma64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, fma64(0.0, 5.0, 9.124), 9.124, epsilon)); + expect(math.approxEq(f64, fma64(0.2, 5.0, 9.124), 10.124, epsilon)); + expect(math.approxEq(f64, fma64(0.8923, 5.0, 9.124), 13.5855, epsilon)); + expect(math.approxEq(f64, fma64(1.5, 5.0, 9.124), 16.624, epsilon)); + expect(math.approxEq(f64, fma64(37.45, 5.0, 9.124), 196.374, epsilon)); + expect(math.approxEq(f64, fma64(89.123, 5.0, 9.124), 454.739, epsilon)); + expect(math.approxEq(f64, fma64(123123.234375, 5.0, 9.124), 615625.295875, epsilon)); +} diff --git a/lib/std/math/frexp.zig b/lib/std/math/frexp.zig new file mode 100644 index 0000000000..2759cd6492 --- /dev/null +++ b/lib/std/math/frexp.zig @@ -0,0 +1,178 @@ +// 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/math/frexpf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/frexp.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +fn frexp_result(comptime T: type) type { + return struct { + significand: T, + exponent: i32, + }; +} +pub const frexp32_result = frexp_result(f32); +pub const frexp64_result = frexp_result(f64); + +/// Breaks x into a normalized fraction and an integral power of two. +/// f == frac * 2^exp, with |frac| in the interval [0.5, 1). +/// +/// Special Cases: +/// - frexp(+-0) = +-0, 0 +/// - frexp(+-inf) = +-inf, 0 +/// - frexp(nan) = nan, undefined +pub fn frexp(x: var) frexp_result(@typeOf(x)) { + const T = @typeOf(x); + return switch (T) { + f32 => frexp32(x), + f64 => frexp64(x), + else => @compileError("frexp not implemented for " ++ @typeName(T)), + }; +} + +fn frexp32(x: f32) frexp32_result { + var result: frexp32_result = undefined; + + var y = @bitCast(u32, x); + const e = @intCast(i32, y >> 23) & 0xFF; + + if (e == 0) { + if (x != 0) { + // subnormal + result = frexp32(x * 0x1.0p64); + result.exponent -= 64; + } else { + // frexp(+-0) = (+-0, 0) + result.significand = x; + result.exponent = 0; + } + return result; + } else if (e == 0xFF) { + // frexp(nan) = (nan, undefined) + result.significand = x; + result.exponent = undefined; + + // frexp(+-inf) = (+-inf, 0) + if (math.isInf(x)) { + result.exponent = 0; + } + + return result; + } + + result.exponent = e - 0x7E; + y &= 0x807FFFFF; + y |= 0x3F000000; + result.significand = @bitCast(f32, y); + return result; +} + +fn frexp64(x: f64) frexp64_result { + var result: frexp64_result = undefined; + + var y = @bitCast(u64, x); + const e = @intCast(i32, y >> 52) & 0x7FF; + + if (e == 0) { + if (x != 0) { + // subnormal + result = frexp64(x * 0x1.0p64); + result.exponent -= 64; + } else { + // frexp(+-0) = (+-0, 0) + result.significand = x; + result.exponent = 0; + } + return result; + } else if (e == 0x7FF) { + // frexp(nan) = (nan, undefined) + result.significand = x; + result.exponent = undefined; + + // frexp(+-inf) = (+-inf, 0) + if (math.isInf(x)) { + result.exponent = 0; + } + + return result; + } + + result.exponent = e - 0x3FE; + y &= 0x800FFFFFFFFFFFFF; + y |= 0x3FE0000000000000; + result.significand = @bitCast(f64, y); + return result; +} + +test "math.frexp" { + const a = frexp(f32(1.3)); + const b = frexp32(1.3); + expect(a.significand == b.significand and a.exponent == b.exponent); + + const c = frexp(f64(1.3)); + const d = frexp64(1.3); + expect(c.significand == d.significand and c.exponent == d.exponent); +} + +test "math.frexp32" { + const epsilon = 0.000001; + var r: frexp32_result = undefined; + + r = frexp32(1.3); + expect(math.approxEq(f32, r.significand, 0.65, epsilon) and r.exponent == 1); + + r = frexp32(78.0234); + expect(math.approxEq(f32, r.significand, 0.609558, epsilon) and r.exponent == 7); +} + +test "math.frexp64" { + const epsilon = 0.000001; + var r: frexp64_result = undefined; + + r = frexp64(1.3); + expect(math.approxEq(f64, r.significand, 0.65, epsilon) and r.exponent == 1); + + r = frexp64(78.0234); + expect(math.approxEq(f64, r.significand, 0.609558, epsilon) and r.exponent == 7); +} + +test "math.frexp32.special" { + var r: frexp32_result = undefined; + + r = frexp32(0.0); + expect(r.significand == 0.0 and r.exponent == 0); + + r = frexp32(-0.0); + expect(r.significand == -0.0 and r.exponent == 0); + + r = frexp32(math.inf(f32)); + expect(math.isPositiveInf(r.significand) and r.exponent == 0); + + r = frexp32(-math.inf(f32)); + expect(math.isNegativeInf(r.significand) and r.exponent == 0); + + r = frexp32(math.nan(f32)); + expect(math.isNan(r.significand)); +} + +test "math.frexp64.special" { + var r: frexp64_result = undefined; + + r = frexp64(0.0); + expect(r.significand == 0.0 and r.exponent == 0); + + r = frexp64(-0.0); + expect(r.significand == -0.0 and r.exponent == 0); + + r = frexp64(math.inf(f64)); + expect(math.isPositiveInf(r.significand) and r.exponent == 0); + + r = frexp64(-math.inf(f64)); + expect(math.isNegativeInf(r.significand) and r.exponent == 0); + + r = frexp64(math.nan(f64)); + expect(math.isNan(r.significand)); +} diff --git a/lib/std/math/hypot.zig b/lib/std/math/hypot.zig new file mode 100644 index 0000000000..c15da1495e --- /dev/null +++ b/lib/std/math/hypot.zig @@ -0,0 +1,168 @@ +// 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/math/hypotf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/hypot.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns sqrt(x * x + y * y), avoiding unncessary overflow and underflow. +/// +/// Special Cases: +/// - hypot(+-inf, y) = +inf +/// - hypot(x, +-inf) = +inf +/// - hypot(nan, y) = nan +/// - hypot(x, nan) = nan +pub fn hypot(comptime T: type, x: T, y: T) T { + return switch (T) { + f32 => hypot32(x, y), + f64 => hypot64(x, y), + else => @compileError("hypot not implemented for " ++ @typeName(T)), + }; +} + +fn hypot32(x: f32, y: f32) f32 { + var ux = @bitCast(u32, x); + var uy = @bitCast(u32, y); + + ux &= maxInt(u32) >> 1; + uy &= maxInt(u32) >> 1; + if (ux < uy) { + const tmp = ux; + ux = uy; + uy = tmp; + } + + var xx = @bitCast(f32, ux); + var yy = @bitCast(f32, uy); + if (uy == 0xFF << 23) { + return yy; + } + if (ux >= 0xFF << 23 or uy == 0 or ux - uy >= (25 << 23)) { + return xx + yy; + } + + var z: f32 = 1.0; + if (ux >= (0x7F + 60) << 23) { + z = 0x1.0p90; + xx *= 0x1.0p-90; + yy *= 0x1.0p-90; + } else if (uy < (0x7F - 60) << 23) { + z = 0x1.0p-90; + xx *= 0x1.0p-90; + yy *= 0x1.0p-90; + } + + return z * math.sqrt(@floatCast(f32, f64(x) * x + f64(y) * y)); +} + +fn sq(hi: *f64, lo: *f64, x: f64) void { + const split: f64 = 0x1.0p27 + 1.0; + const xc = x * split; + const xh = x - xc + xc; + const xl = x - xh; + hi.* = x * x; + lo.* = xh * xh - hi.* + 2 * xh * xl + xl * xl; +} + +fn hypot64(x: f64, y: f64) f64 { + var ux = @bitCast(u64, x); + var uy = @bitCast(u64, y); + + ux &= maxInt(u64) >> 1; + uy &= maxInt(u64) >> 1; + if (ux < uy) { + const tmp = ux; + ux = uy; + uy = tmp; + } + + const ex = ux >> 52; + const ey = uy >> 52; + var xx = @bitCast(f64, ux); + var yy = @bitCast(f64, uy); + + // hypot(inf, nan) == inf + if (ey == 0x7FF) { + return yy; + } + if (ex == 0x7FF or uy == 0) { + return xx; + } + + // hypot(x, y) ~= x + y * y / x / 2 with inexact for small y/x + if (ex - ey > 64) { + return xx + yy; + } + + var z: f64 = 1; + if (ex > 0x3FF + 510) { + z = 0x1.0p700; + xx *= 0x1.0p-700; + yy *= 0x1.0p-700; + } else if (ey < 0x3FF - 450) { + z = 0x1.0p-700; + xx *= 0x1.0p700; + yy *= 0x1.0p700; + } + + var hx: f64 = undefined; + var lx: f64 = undefined; + var hy: f64 = undefined; + var ly: f64 = undefined; + + sq(&hx, &lx, x); + sq(&hy, &ly, y); + + return z * math.sqrt(ly + lx + hy + hx); +} + +test "math.hypot" { + expect(hypot(f32, 0.0, -1.2) == hypot32(0.0, -1.2)); + expect(hypot(f64, 0.0, -1.2) == hypot64(0.0, -1.2)); +} + +test "math.hypot32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, hypot32(0.0, -1.2), 1.2, epsilon)); + expect(math.approxEq(f32, hypot32(0.2, -0.34), 0.394462, epsilon)); + expect(math.approxEq(f32, hypot32(0.8923, 2.636890), 2.783772, epsilon)); + expect(math.approxEq(f32, hypot32(1.5, 5.25), 5.460083, epsilon)); + expect(math.approxEq(f32, hypot32(37.45, 159.835), 164.163742, epsilon)); + expect(math.approxEq(f32, hypot32(89.123, 382.028905), 392.286865, epsilon)); + expect(math.approxEq(f32, hypot32(123123.234375, 529428.707813), 543556.875, epsilon)); +} + +test "math.hypot64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, hypot64(0.0, -1.2), 1.2, epsilon)); + expect(math.approxEq(f64, hypot64(0.2, -0.34), 0.394462, epsilon)); + expect(math.approxEq(f64, hypot64(0.8923, 2.636890), 2.783772, epsilon)); + expect(math.approxEq(f64, hypot64(1.5, 5.25), 5.460082, epsilon)); + expect(math.approxEq(f64, hypot64(37.45, 159.835), 164.163728, epsilon)); + expect(math.approxEq(f64, hypot64(89.123, 382.028905), 392.286876, epsilon)); + expect(math.approxEq(f64, hypot64(123123.234375, 529428.707813), 543556.885247, epsilon)); +} + +test "math.hypot32.special" { + expect(math.isPositiveInf(hypot32(math.inf(f32), 0.0))); + expect(math.isPositiveInf(hypot32(-math.inf(f32), 0.0))); + expect(math.isPositiveInf(hypot32(0.0, math.inf(f32)))); + expect(math.isPositiveInf(hypot32(0.0, -math.inf(f32)))); + expect(math.isNan(hypot32(math.nan(f32), 0.0))); + expect(math.isNan(hypot32(0.0, math.nan(f32)))); +} + +test "math.hypot64.special" { + expect(math.isPositiveInf(hypot64(math.inf(f64), 0.0))); + expect(math.isPositiveInf(hypot64(-math.inf(f64), 0.0))); + expect(math.isPositiveInf(hypot64(0.0, math.inf(f64)))); + expect(math.isPositiveInf(hypot64(0.0, -math.inf(f64)))); + expect(math.isNan(hypot64(math.nan(f64), 0.0))); + expect(math.isNan(hypot64(0.0, math.nan(f64)))); +} diff --git a/lib/std/math/ilogb.zig b/lib/std/math/ilogb.zig new file mode 100644 index 0000000000..fe4158a6dd --- /dev/null +++ b/lib/std/math/ilogb.zig @@ -0,0 +1,138 @@ +// 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/math/ilogbf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/ilogb.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; + +/// Returns the binary exponent of x as an integer. +/// +/// Special Cases: +/// - ilogb(+-inf) = maxInt(i32) +/// - ilogb(0) = maxInt(i32) +/// - ilogb(nan) = maxInt(i32) +pub fn ilogb(x: var) i32 { + const T = @typeOf(x); + return switch (T) { + f32 => ilogb32(x), + f64 => ilogb64(x), + else => @compileError("ilogb not implemented for " ++ @typeName(T)), + }; +} + +// NOTE: Should these be exposed publicly? +const fp_ilogbnan = -1 - i32(maxInt(u32) >> 1); +const fp_ilogb0 = fp_ilogbnan; + +fn ilogb32(x: f32) i32 { + var u = @bitCast(u32, x); + var e = @intCast(i32, (u >> 23) & 0xFF); + + // TODO: We should be able to merge this with the lower check. + if (math.isNan(x)) { + return maxInt(i32); + } + + if (e == 0) { + u <<= 9; + if (u == 0) { + math.raiseInvalid(); + return fp_ilogb0; + } + + // subnormal + e = -0x7F; + while (u >> 31 == 0) : (u <<= 1) { + e -= 1; + } + return e; + } + + if (e == 0xFF) { + math.raiseInvalid(); + if (u << 9 != 0) { + return fp_ilogbnan; + } else { + return maxInt(i32); + } + } + + return e - 0x7F; +} + +fn ilogb64(x: f64) i32 { + var u = @bitCast(u64, x); + var e = @intCast(i32, (u >> 52) & 0x7FF); + + if (math.isNan(x)) { + return maxInt(i32); + } + + if (e == 0) { + u <<= 12; + if (u == 0) { + math.raiseInvalid(); + return fp_ilogb0; + } + + // subnormal + e = -0x3FF; + while (u >> 63 == 0) : (u <<= 1) { + e -= 1; + } + return e; + } + + if (e == 0x7FF) { + math.raiseInvalid(); + if (u << 12 != 0) { + return fp_ilogbnan; + } else { + return maxInt(i32); + } + } + + return e - 0x3FF; +} + +test "math.ilogb" { + expect(ilogb(f32(0.2)) == ilogb32(0.2)); + expect(ilogb(f64(0.2)) == ilogb64(0.2)); +} + +test "math.ilogb32" { + expect(ilogb32(0.0) == fp_ilogb0); + expect(ilogb32(0.5) == -1); + expect(ilogb32(0.8923) == -1); + expect(ilogb32(10.0) == 3); + expect(ilogb32(-123984) == 16); + expect(ilogb32(2398.23) == 11); +} + +test "math.ilogb64" { + expect(ilogb64(0.0) == fp_ilogb0); + expect(ilogb64(0.5) == -1); + expect(ilogb64(0.8923) == -1); + expect(ilogb64(10.0) == 3); + expect(ilogb64(-123984) == 16); + expect(ilogb64(2398.23) == 11); +} + +test "math.ilogb32.special" { + expect(ilogb32(math.inf(f32)) == maxInt(i32)); + expect(ilogb32(-math.inf(f32)) == maxInt(i32)); + expect(ilogb32(0.0) == minInt(i32)); + expect(ilogb32(math.nan(f32)) == maxInt(i32)); +} + +test "math.ilogb64.special" { + expect(ilogb64(math.inf(f64)) == maxInt(i32)); + expect(ilogb64(-math.inf(f64)) == maxInt(i32)); + expect(ilogb64(0.0) == minInt(i32)); + expect(ilogb64(math.nan(f64)) == maxInt(i32)); +} diff --git a/lib/std/math/inf.zig b/lib/std/math/inf.zig new file mode 100644 index 0000000000..86ff245533 --- /dev/null +++ b/lib/std/math/inf.zig @@ -0,0 +1,13 @@ +const std = @import("../std.zig"); +const math = std.math; + +/// Returns value inf for the type T. +pub fn inf(comptime T: type) T { + return switch (T) { + f16 => math.inf_f16, + f32 => math.inf_f32, + f64 => math.inf_f64, + f128 => math.inf_f128, + else => @compileError("inf not implemented for " ++ @typeName(T)), + }; +} diff --git a/lib/std/math/isfinite.zig b/lib/std/math/isfinite.zig new file mode 100644 index 0000000000..99eba668f9 --- /dev/null +++ b/lib/std/math/isfinite.zig @@ -0,0 +1,41 @@ +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns whether x is a finite value. +pub fn isFinite(x: var) bool { + const T = @typeOf(x); + switch (T) { + f16 => { + const bits = @bitCast(u16, x); + return bits & 0x7FFF < 0x7C00; + }, + f32 => { + const bits = @bitCast(u32, x); + return bits & 0x7FFFFFFF < 0x7F800000; + }, + f64 => { + const bits = @bitCast(u64, x); + return bits & (maxInt(u64) >> 1) < (0x7FF << 52); + }, + else => { + @compileError("isFinite not implemented for " ++ @typeName(T)); + }, + } +} + +test "math.isFinite" { + expect(isFinite(f16(0.0))); + expect(isFinite(f16(-0.0))); + expect(isFinite(f32(0.0))); + expect(isFinite(f32(-0.0))); + expect(isFinite(f64(0.0))); + expect(isFinite(f64(-0.0))); + expect(!isFinite(math.inf(f16))); + expect(!isFinite(-math.inf(f16))); + expect(!isFinite(math.inf(f32))); + expect(!isFinite(-math.inf(f32))); + expect(!isFinite(math.inf(f64))); + expect(!isFinite(-math.inf(f64))); +} diff --git a/lib/std/math/isinf.zig b/lib/std/math/isinf.zig new file mode 100644 index 0000000000..37934f4cf4 --- /dev/null +++ b/lib/std/math/isinf.zig @@ -0,0 +1,131 @@ +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns whether x is an infinity, ignoring sign. +pub fn isInf(x: var) bool { + const T = @typeOf(x); + switch (T) { + f16 => { + const bits = @bitCast(u16, x); + return bits & 0x7FFF == 0x7C00; + }, + f32 => { + const bits = @bitCast(u32, x); + return bits & 0x7FFFFFFF == 0x7F800000; + }, + f64 => { + const bits = @bitCast(u64, x); + return bits & (maxInt(u64) >> 1) == (0x7FF << 52); + }, + f128 => { + const bits = @bitCast(u128, x); + return bits & (maxInt(u128) >> 1) == (0x7FFF << 112); + }, + else => { + @compileError("isInf not implemented for " ++ @typeName(T)); + }, + } +} + +/// Returns whether x is an infinity with a positive sign. +pub fn isPositiveInf(x: var) bool { + const T = @typeOf(x); + switch (T) { + f16 => { + return @bitCast(u16, x) == 0x7C00; + }, + f32 => { + return @bitCast(u32, x) == 0x7F800000; + }, + f64 => { + return @bitCast(u64, x) == 0x7FF << 52; + }, + f128 => { + return @bitCast(u128, x) == 0x7FFF << 112; + }, + else => { + @compileError("isPositiveInf not implemented for " ++ @typeName(T)); + }, + } +} + +/// Returns whether x is an infinity with a negative sign. +pub fn isNegativeInf(x: var) bool { + const T = @typeOf(x); + switch (T) { + f16 => { + return @bitCast(u16, x) == 0xFC00; + }, + f32 => { + return @bitCast(u32, x) == 0xFF800000; + }, + f64 => { + return @bitCast(u64, x) == 0xFFF << 52; + }, + f128 => { + return @bitCast(u128, x) == 0xFFFF << 112; + }, + else => { + @compileError("isNegativeInf not implemented for " ++ @typeName(T)); + }, + } +} + +test "math.isInf" { + expect(!isInf(f16(0.0))); + expect(!isInf(f16(-0.0))); + expect(!isInf(f32(0.0))); + expect(!isInf(f32(-0.0))); + expect(!isInf(f64(0.0))); + expect(!isInf(f64(-0.0))); + expect(!isInf(f128(0.0))); + expect(!isInf(f128(-0.0))); + expect(isInf(math.inf(f16))); + expect(isInf(-math.inf(f16))); + expect(isInf(math.inf(f32))); + expect(isInf(-math.inf(f32))); + expect(isInf(math.inf(f64))); + expect(isInf(-math.inf(f64))); + expect(isInf(math.inf(f128))); + expect(isInf(-math.inf(f128))); +} + +test "math.isPositiveInf" { + expect(!isPositiveInf(f16(0.0))); + expect(!isPositiveInf(f16(-0.0))); + expect(!isPositiveInf(f32(0.0))); + expect(!isPositiveInf(f32(-0.0))); + expect(!isPositiveInf(f64(0.0))); + expect(!isPositiveInf(f64(-0.0))); + expect(!isPositiveInf(f128(0.0))); + expect(!isPositiveInf(f128(-0.0))); + expect(isPositiveInf(math.inf(f16))); + expect(!isPositiveInf(-math.inf(f16))); + expect(isPositiveInf(math.inf(f32))); + expect(!isPositiveInf(-math.inf(f32))); + expect(isPositiveInf(math.inf(f64))); + expect(!isPositiveInf(-math.inf(f64))); + expect(isPositiveInf(math.inf(f128))); + expect(!isPositiveInf(-math.inf(f128))); +} + +test "math.isNegativeInf" { + expect(!isNegativeInf(f16(0.0))); + expect(!isNegativeInf(f16(-0.0))); + expect(!isNegativeInf(f32(0.0))); + expect(!isNegativeInf(f32(-0.0))); + expect(!isNegativeInf(f64(0.0))); + expect(!isNegativeInf(f64(-0.0))); + expect(!isNegativeInf(f128(0.0))); + expect(!isNegativeInf(f128(-0.0))); + expect(!isNegativeInf(math.inf(f16))); + expect(isNegativeInf(-math.inf(f16))); + expect(!isNegativeInf(math.inf(f32))); + expect(isNegativeInf(-math.inf(f32))); + expect(!isNegativeInf(math.inf(f64))); + expect(isNegativeInf(-math.inf(f64))); + expect(!isNegativeInf(math.inf(f128))); + expect(isNegativeInf(-math.inf(f128))); +} diff --git a/lib/std/math/isnan.zig b/lib/std/math/isnan.zig new file mode 100644 index 0000000000..cf8cd2e1c2 --- /dev/null +++ b/lib/std/math/isnan.zig @@ -0,0 +1,27 @@ +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns whether x is a nan. +pub fn isNan(x: var) bool { + return x != x; +} + +/// Returns whether x is a signalling nan. +pub fn isSignalNan(x: var) bool { + // Note: A signalling nan is identical to a standard nan right now but may have a different bit + // representation in the future when required. + return isNan(x); +} + +test "math.isNan" { + expect(isNan(math.nan(f16))); + expect(isNan(math.nan(f32))); + expect(isNan(math.nan(f64))); + expect(isNan(math.nan(f128))); + expect(!isNan(f16(1.0))); + expect(!isNan(f32(1.0))); + expect(!isNan(f64(1.0))); + expect(!isNan(f128(1.0))); +} diff --git a/lib/std/math/isnormal.zig b/lib/std/math/isnormal.zig new file mode 100644 index 0000000000..f8611ef805 --- /dev/null +++ b/lib/std/math/isnormal.zig @@ -0,0 +1,38 @@ +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +// Returns whether x has a normalized representation (i.e. integer part of mantissa is 1). +pub fn isNormal(x: var) bool { + const T = @typeOf(x); + switch (T) { + f16 => { + const bits = @bitCast(u16, x); + return (bits + 1024) & 0x7FFF >= 2048; + }, + f32 => { + const bits = @bitCast(u32, x); + return (bits + 0x00800000) & 0x7FFFFFFF >= 0x01000000; + }, + f64 => { + const bits = @bitCast(u64, x); + return (bits + (1 << 52)) & (maxInt(u64) >> 1) >= (1 << 53); + }, + else => { + @compileError("isNormal not implemented for " ++ @typeName(T)); + }, + } +} + +test "math.isNormal" { + expect(!isNormal(math.nan(f16))); + expect(!isNormal(math.nan(f32))); + expect(!isNormal(math.nan(f64))); + expect(!isNormal(f16(0))); + expect(!isNormal(f32(0))); + expect(!isNormal(f64(0))); + expect(isNormal(f16(1.0))); + expect(isNormal(f32(1.0))); + expect(isNormal(f64(1.0))); +} diff --git a/lib/std/math/ln.zig b/lib/std/math/ln.zig new file mode 100644 index 0000000000..c5d4c9ff25 --- /dev/null +++ b/lib/std/math/ln.zig @@ -0,0 +1,190 @@ +// 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/math/lnf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/ln.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const builtin = @import("builtin"); +const TypeId = builtin.TypeId; + +/// Returns the natural logarithm of x. +/// +/// Special Cases: +/// - ln(+inf) = +inf +/// - ln(0) = -inf +/// - ln(x) = nan if x < 0 +/// - ln(nan) = nan +pub fn ln(x: var) @typeOf(x) { + const T = @typeOf(x); + switch (@typeId(T)) { + TypeId.ComptimeFloat => { + return @typeOf(1.0)(ln_64(x)); + }, + TypeId.Float => { + return switch (T) { + f32 => ln_32(x), + f64 => ln_64(x), + else => @compileError("ln not implemented for " ++ @typeName(T)), + }; + }, + TypeId.ComptimeInt => { + return @typeOf(1)(math.floor(ln_64(f64(x)))); + }, + TypeId.Int => { + return T(math.floor(ln_64(f64(x)))); + }, + else => @compileError("ln not implemented for " ++ @typeName(T)), + } +} + +pub fn ln_32(x_: f32) f32 { + const ln2_hi: f32 = 6.9313812256e-01; + const ln2_lo: f32 = 9.0580006145e-06; + const Lg1: f32 = 0xaaaaaa.0p-24; + const Lg2: f32 = 0xccce13.0p-25; + const Lg3: f32 = 0x91e9ee.0p-25; + const Lg4: f32 = 0xf89e26.0p-26; + + var x = x_; + var ix = @bitCast(u32, x); + var k: i32 = 0; + + // x < 2^(-126) + if (ix < 0x00800000 or ix >> 31 != 0) { + // log(+-0) = -inf + if (ix << 1 == 0) { + return -math.inf(f32); + } + // log(-#) = nan + if (ix >> 31 != 0) { + return math.nan(f32); + } + + // subnormal, scale x + k -= 25; + x *= 0x1.0p25; + ix = @bitCast(u32, x); + } else if (ix >= 0x7F800000) { + return x; + } else if (ix == 0x3F800000) { + return 0; + } + + // x into [sqrt(2) / 2, sqrt(2)] + ix += 0x3F800000 - 0x3F3504F3; + k += @intCast(i32, ix >> 23) - 0x7F; + ix = (ix & 0x007FFFFF) + 0x3F3504F3; + x = @bitCast(f32, ix); + + const f = x - 1.0; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * Lg4); + const t2 = z * (Lg1 + w * Lg3); + const R = t2 + t1; + const hfsq = 0.5 * f * f; + const dk = @intToFloat(f32, k); + + return s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi; +} + +pub fn ln_64(x_: f64) f64 { + const ln2_hi: f64 = 6.93147180369123816490e-01; + const ln2_lo: f64 = 1.90821492927058770002e-10; + const Lg1: f64 = 6.666666666666735130e-01; + const Lg2: f64 = 3.999999999940941908e-01; + const Lg3: f64 = 2.857142874366239149e-01; + const Lg4: f64 = 2.222219843214978396e-01; + const Lg5: f64 = 1.818357216161805012e-01; + const Lg6: f64 = 1.531383769920937332e-01; + const Lg7: f64 = 1.479819860511658591e-01; + + var x = x_; + var ix = @bitCast(u64, x); + var hx = @intCast(u32, ix >> 32); + var k: i32 = 0; + + if (hx < 0x00100000 or hx >> 31 != 0) { + // log(+-0) = -inf + if (ix << 1 == 0) { + return -math.inf(f64); + } + // log(-#) = nan + if (hx >> 31 != 0) { + return math.nan(f64); + } + + // subnormal, scale x + k -= 54; + x *= 0x1.0p54; + hx = @intCast(u32, @bitCast(u64, ix) >> 32); + } else if (hx >= 0x7FF00000) { + return x; + } else if (hx == 0x3FF00000 and ix << 32 == 0) { + return 0; + } + + // x into [sqrt(2) / 2, sqrt(2)] + hx += 0x3FF00000 - 0x3FE6A09E; + k += @intCast(i32, hx >> 20) - 0x3FF; + hx = (hx & 0x000FFFFF) + 0x3FE6A09E; + ix = (u64(hx) << 32) | (ix & 0xFFFFFFFF); + x = @bitCast(f64, ix); + + const f = x - 1.0; + const hfsq = 0.5 * f * f; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + const R = t2 + t1; + const dk = @intToFloat(f64, k); + + return s * (hfsq + R) + dk * ln2_lo - hfsq + f + dk * ln2_hi; +} + +test "math.ln" { + expect(ln(f32(0.2)) == ln_32(0.2)); + expect(ln(f64(0.2)) == ln_64(0.2)); +} + +test "math.ln32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, ln_32(0.2), -1.609438, epsilon)); + expect(math.approxEq(f32, ln_32(0.8923), -0.113953, epsilon)); + expect(math.approxEq(f32, ln_32(1.5), 0.405465, epsilon)); + expect(math.approxEq(f32, ln_32(37.45), 3.623007, epsilon)); + expect(math.approxEq(f32, ln_32(89.123), 4.490017, epsilon)); + expect(math.approxEq(f32, ln_32(123123.234375), 11.720941, epsilon)); +} + +test "math.ln64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, ln_64(0.2), -1.609438, epsilon)); + expect(math.approxEq(f64, ln_64(0.8923), -0.113953, epsilon)); + expect(math.approxEq(f64, ln_64(1.5), 0.405465, epsilon)); + expect(math.approxEq(f64, ln_64(37.45), 3.623007, epsilon)); + expect(math.approxEq(f64, ln_64(89.123), 4.490017, epsilon)); + expect(math.approxEq(f64, ln_64(123123.234375), 11.720941, epsilon)); +} + +test "math.ln32.special" { + expect(math.isPositiveInf(ln_32(math.inf(f32)))); + expect(math.isNegativeInf(ln_32(0.0))); + expect(math.isNan(ln_32(-1.0))); + expect(math.isNan(ln_32(math.nan(f32)))); +} + +test "math.ln64.special" { + expect(math.isPositiveInf(ln_64(math.inf(f64)))); + expect(math.isNegativeInf(ln_64(0.0))); + expect(math.isNan(ln_64(-1.0))); + expect(math.isNan(ln_64(math.nan(f64)))); +} diff --git a/lib/std/math/log.zig b/lib/std/math/log.zig new file mode 100644 index 0000000000..77f3639fd2 --- /dev/null +++ b/lib/std/math/log.zig @@ -0,0 +1,72 @@ +// 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/math/logf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log.c + +const std = @import("../std.zig"); +const math = std.math; +const builtin = @import("builtin"); +const TypeId = builtin.TypeId; +const expect = std.testing.expect; + +/// Returns the logarithm of x for the provided base. +pub fn log(comptime T: type, base: T, x: T) T { + if (base == 2) { + return math.log2(x); + } else if (base == 10) { + return math.log10(x); + } else if ((@typeId(T) == TypeId.Float or @typeId(T) == TypeId.ComptimeFloat) and base == math.e) { + return math.ln(x); + } + + const float_base = math.lossyCast(f64, base); + switch (@typeId(T)) { + TypeId.ComptimeFloat => { + return @typeOf(1.0)(math.ln(f64(x)) / math.ln(float_base)); + }, + TypeId.ComptimeInt => { + return @typeOf(1)(math.floor(math.ln(f64(x)) / math.ln(float_base))); + }, + builtin.TypeId.Int => { + // TODO implement integer log without using float math + return @floatToInt(T, math.floor(math.ln(@intToFloat(f64, x)) / math.ln(float_base))); + }, + + builtin.TypeId.Float => { + switch (T) { + f32 => return @floatCast(f32, math.ln(f64(x)) / math.ln(float_base)), + f64 => return math.ln(x) / math.ln(float_base), + else => @compileError("log not implemented for " ++ @typeName(T)), + } + }, + + else => { + @compileError("log expects integer or float, found '" ++ @typeName(T) ++ "'"); + }, + } +} + +test "math.log integer" { + expect(log(u8, 2, 0x1) == 0); + expect(log(u8, 2, 0x2) == 1); + expect(log(i16, 2, 0x72) == 6); + expect(log(u32, 2, 0xFFFFFF) == 23); + expect(log(u64, 2, 0x7FF0123456789ABC) == 62); +} + +test "math.log float" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, log(f32, 6, 0.23947), -0.797723, epsilon)); + expect(math.approxEq(f32, log(f32, 89, 0.23947), -0.318432, epsilon)); + expect(math.approxEq(f64, log(f64, 123897, 12389216414), 1.981724596, epsilon)); +} + +test "math.log float_special" { + expect(log(f32, 2, 0.2301974) == math.log2(f32(0.2301974))); + expect(log(f32, 10, 0.2301974) == math.log10(f32(0.2301974))); + + expect(log(f64, 2, 213.23019799993) == math.log2(f64(213.23019799993))); + expect(log(f64, 10, 213.23019799993) == math.log10(f64(213.23019799993))); +} diff --git a/lib/std/math/log10.zig b/lib/std/math/log10.zig new file mode 100644 index 0000000000..9b0bc3ac52 --- /dev/null +++ b/lib/std/math/log10.zig @@ -0,0 +1,218 @@ +// 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/math/log10f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log10.c + +const std = @import("../std.zig"); +const math = std.math; +const testing = std.testing; +const builtin = @import("builtin"); +const TypeId = builtin.TypeId; +const maxInt = std.math.maxInt; + +/// Returns the base-10 logarithm of x. +/// +/// Special Cases: +/// - log10(+inf) = +inf +/// - log10(0) = -inf +/// - log10(x) = nan if x < 0 +/// - log10(nan) = nan +pub fn log10(x: var) @typeOf(x) { + const T = @typeOf(x); + switch (@typeId(T)) { + TypeId.ComptimeFloat => { + return @typeOf(1.0)(log10_64(x)); + }, + TypeId.Float => { + return switch (T) { + f32 => log10_32(x), + f64 => log10_64(x), + else => @compileError("log10 not implemented for " ++ @typeName(T)), + }; + }, + TypeId.ComptimeInt => { + return @typeOf(1)(math.floor(log10_64(f64(x)))); + }, + TypeId.Int => { + return @floatToInt(T, math.floor(log10_64(@intToFloat(f64, x)))); + }, + else => @compileError("log10 not implemented for " ++ @typeName(T)), + } +} + +pub fn log10_32(x_: f32) f32 { + const ivln10hi: f32 = 4.3432617188e-01; + const ivln10lo: f32 = -3.1689971365e-05; + const log10_2hi: f32 = 3.0102920532e-01; + const log10_2lo: f32 = 7.9034151668e-07; + const Lg1: f32 = 0xaaaaaa.0p-24; + const Lg2: f32 = 0xccce13.0p-25; + const Lg3: f32 = 0x91e9ee.0p-25; + const Lg4: f32 = 0xf89e26.0p-26; + + var x = x_; + var u = @bitCast(u32, x); + var ix = u; + var k: i32 = 0; + + // x < 2^(-126) + if (ix < 0x00800000 or ix >> 31 != 0) { + // log(+-0) = -inf + if (ix << 1 == 0) { + return -math.inf(f32); + } + // log(-#) = nan + if (ix >> 31 != 0) { + return math.nan(f32); + } + + k -= 25; + x *= 0x1.0p25; + ix = @bitCast(u32, x); + } else if (ix >= 0x7F800000) { + return x; + } else if (ix == 0x3F800000) { + return 0; + } + + // x into [sqrt(2) / 2, sqrt(2)] + ix += 0x3F800000 - 0x3F3504F3; + k += @intCast(i32, ix >> 23) - 0x7F; + ix = (ix & 0x007FFFFF) + 0x3F3504F3; + x = @bitCast(f32, ix); + + const f = x - 1.0; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * Lg4); + const t2 = z * (Lg1 + w * Lg3); + const R = t2 + t1; + const hfsq = 0.5 * f * f; + + var hi = f - hfsq; + u = @bitCast(u32, hi); + u &= 0xFFFFF000; + hi = @bitCast(f32, u); + const lo = f - hi - hfsq + s * (hfsq + R); + const dk = @intToFloat(f32, k); + + return dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi + hi * ivln10hi + dk * log10_2hi; +} + +pub fn log10_64(x_: f64) f64 { + const ivln10hi: f64 = 4.34294481878168880939e-01; + const ivln10lo: f64 = 2.50829467116452752298e-11; + const log10_2hi: f64 = 3.01029995663611771306e-01; + const log10_2lo: f64 = 3.69423907715893078616e-13; + const Lg1: f64 = 6.666666666666735130e-01; + const Lg2: f64 = 3.999999999940941908e-01; + const Lg3: f64 = 2.857142874366239149e-01; + const Lg4: f64 = 2.222219843214978396e-01; + const Lg5: f64 = 1.818357216161805012e-01; + const Lg6: f64 = 1.531383769920937332e-01; + const Lg7: f64 = 1.479819860511658591e-01; + + var x = x_; + var ix = @bitCast(u64, x); + var hx = @intCast(u32, ix >> 32); + var k: i32 = 0; + + if (hx < 0x00100000 or hx >> 31 != 0) { + // log(+-0) = -inf + if (ix << 1 == 0) { + return -math.inf(f32); + } + // log(-#) = nan + if (hx >> 31 != 0) { + return math.nan(f32); + } + + // subnormal, scale x + k -= 54; + x *= 0x1.0p54; + hx = @intCast(u32, @bitCast(u64, x) >> 32); + } else if (hx >= 0x7FF00000) { + return x; + } else if (hx == 0x3FF00000 and ix << 32 == 0) { + return 0; + } + + // x into [sqrt(2) / 2, sqrt(2)] + hx += 0x3FF00000 - 0x3FE6A09E; + k += @intCast(i32, hx >> 20) - 0x3FF; + hx = (hx & 0x000FFFFF) + 0x3FE6A09E; + ix = (u64(hx) << 32) | (ix & 0xFFFFFFFF); + x = @bitCast(f64, ix); + + const f = x - 1.0; + const hfsq = 0.5 * f * f; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + const R = t2 + t1; + + // hi + lo = f - hfsq + s * (hfsq + R) ~ log(1 + f) + var hi = f - hfsq; + var hii = @bitCast(u64, hi); + hii &= u64(maxInt(u64)) << 32; + hi = @bitCast(f64, hii); + const lo = f - hi - hfsq + s * (hfsq + R); + + // val_hi + val_lo ~ log10(1 + f) + k * log10(2) + var val_hi = hi * ivln10hi; + const dk = @intToFloat(f64, k); + const y = dk * log10_2hi; + var val_lo = dk * log10_2lo + (lo + hi) * ivln10lo + lo * ivln10hi; + + // Extra precision multiplication + const ww = y + val_hi; + val_lo += (y - ww) + val_hi; + val_hi = ww; + + return val_lo + val_hi; +} + +test "math.log10" { + testing.expect(log10(f32(0.2)) == log10_32(0.2)); + testing.expect(log10(f64(0.2)) == log10_64(0.2)); +} + +test "math.log10_32" { + const epsilon = 0.000001; + + testing.expect(math.approxEq(f32, log10_32(0.2), -0.698970, epsilon)); + testing.expect(math.approxEq(f32, log10_32(0.8923), -0.049489, epsilon)); + testing.expect(math.approxEq(f32, log10_32(1.5), 0.176091, epsilon)); + testing.expect(math.approxEq(f32, log10_32(37.45), 1.573452, epsilon)); + testing.expect(math.approxEq(f32, log10_32(89.123), 1.94999, epsilon)); + testing.expect(math.approxEq(f32, log10_32(123123.234375), 5.09034, epsilon)); +} + +test "math.log10_64" { + const epsilon = 0.000001; + + testing.expect(math.approxEq(f64, log10_64(0.2), -0.698970, epsilon)); + testing.expect(math.approxEq(f64, log10_64(0.8923), -0.049489, epsilon)); + testing.expect(math.approxEq(f64, log10_64(1.5), 0.176091, epsilon)); + testing.expect(math.approxEq(f64, log10_64(37.45), 1.573452, epsilon)); + testing.expect(math.approxEq(f64, log10_64(89.123), 1.94999, epsilon)); + testing.expect(math.approxEq(f64, log10_64(123123.234375), 5.09034, epsilon)); +} + +test "math.log10_32.special" { + testing.expect(math.isPositiveInf(log10_32(math.inf(f32)))); + testing.expect(math.isNegativeInf(log10_32(0.0))); + testing.expect(math.isNan(log10_32(-1.0))); + testing.expect(math.isNan(log10_32(math.nan(f32)))); +} + +test "math.log10_64.special" { + testing.expect(math.isPositiveInf(log10_64(math.inf(f64)))); + testing.expect(math.isNegativeInf(log10_64(0.0))); + testing.expect(math.isNan(log10_64(-1.0))); + testing.expect(math.isNan(log10_64(math.nan(f64)))); +} diff --git a/lib/std/math/log1p.zig b/lib/std/math/log1p.zig new file mode 100644 index 0000000000..bae6deb536 --- /dev/null +++ b/lib/std/math/log1p.zig @@ -0,0 +1,230 @@ +// 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/math/log1pf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log1p.c + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the natural logarithm of 1 + x with greater accuracy when x is near zero. +/// +/// Special Cases: +/// - log1p(+inf) = +inf +/// - log1p(+-0) = +-0 +/// - log1p(-1) = -inf +/// - log1p(x) = nan if x < -1 +/// - log1p(nan) = nan +pub fn log1p(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => log1p_32(x), + f64 => log1p_64(x), + else => @compileError("log1p not implemented for " ++ @typeName(T)), + }; +} + +fn log1p_32(x: f32) f32 { + const ln2_hi = 6.9313812256e-01; + const ln2_lo = 9.0580006145e-06; + const Lg1: f32 = 0xaaaaaa.0p-24; + const Lg2: f32 = 0xccce13.0p-25; + const Lg3: f32 = 0x91e9ee.0p-25; + const Lg4: f32 = 0xf89e26.0p-26; + + const u = @bitCast(u32, x); + var ix = u; + var k: i32 = 1; + var f: f32 = undefined; + var c: f32 = undefined; + + // 1 + x < sqrt(2)+ + if (ix < 0x3ED413D0 or ix >> 31 != 0) { + // x <= -1.0 + if (ix >= 0xBF800000) { + // log1p(-1) = -inf + if (x == -1.0) { + return -math.inf(f32); + } + // log1p(x < -1) = nan + else { + return math.nan(f32); + } + } + // |x| < 2^(-24) + if ((ix << 1) < (0x33800000 << 1)) { + // underflow if subnormal + if (ix & 0x7F800000 == 0) { + math.forceEval(x * x); + } + return x; + } + // sqrt(2) / 2- <= 1 + x < sqrt(2)+ + if (ix <= 0xBE95F619) { + k = 0; + c = 0; + f = x; + } + } else if (ix >= 0x7F800000) { + return x; + } + + if (k != 0) { + const uf = 1 + x; + var iu = @bitCast(u32, uf); + iu += 0x3F800000 - 0x3F3504F3; + k = @intCast(i32, iu >> 23) - 0x7F; + + // correction to avoid underflow in c / u + if (k < 25) { + c = if (k >= 2) 1 - (uf - x) else x - (uf - 1); + c /= uf; + } else { + c = 0; + } + + // u into [sqrt(2)/2, sqrt(2)] + iu = (iu & 0x007FFFFF) + 0x3F3504F3; + f = @bitCast(f32, iu) - 1; + } + + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * Lg4); + const t2 = z * (Lg1 + w * Lg3); + const R = t2 + t1; + const hfsq = 0.5 * f * f; + const dk = @intToFloat(f32, k); + + return s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi; +} + +fn log1p_64(x: f64) f64 { + const ln2_hi: f64 = 6.93147180369123816490e-01; + const ln2_lo: f64 = 1.90821492927058770002e-10; + const Lg1: f64 = 6.666666666666735130e-01; + const Lg2: f64 = 3.999999999940941908e-01; + const Lg3: f64 = 2.857142874366239149e-01; + const Lg4: f64 = 2.222219843214978396e-01; + const Lg5: f64 = 1.818357216161805012e-01; + const Lg6: f64 = 1.531383769920937332e-01; + const Lg7: f64 = 1.479819860511658591e-01; + + var ix = @bitCast(u64, x); + var hx = @intCast(u32, ix >> 32); + var k: i32 = 1; + var c: f64 = undefined; + var f: f64 = undefined; + + // 1 + x < sqrt(2) + if (hx < 0x3FDA827A or hx >> 31 != 0) { + // x <= -1.0 + if (hx >= 0xBFF00000) { + // log1p(-1) = -inf + if (x == -1.0) { + return -math.inf(f64); + } + // log1p(x < -1) = nan + else { + return math.nan(f64); + } + } + // |x| < 2^(-53) + if ((hx << 1) < (0x3CA00000 << 1)) { + if ((hx & 0x7FF00000) == 0) { + math.raiseUnderflow(); + } + return x; + } + // sqrt(2) / 2- <= 1 + x < sqrt(2)+ + if (hx <= 0xBFD2BEC4) { + k = 0; + c = 0; + f = x; + } + } else if (hx >= 0x7FF00000) { + return x; + } + + if (k != 0) { + const uf = 1 + x; + const hu = @bitCast(u64, uf); + var iu = @intCast(u32, hu >> 32); + iu += 0x3FF00000 - 0x3FE6A09E; + k = @intCast(i32, iu >> 20) - 0x3FF; + + // correction to avoid underflow in c / u + if (k < 54) { + c = if (k >= 2) 1 - (uf - x) else x - (uf - 1); + c /= uf; + } else { + c = 0; + } + + // u into [sqrt(2)/2, sqrt(2)] + iu = (iu & 0x000FFFFF) + 0x3FE6A09E; + const iq = (u64(iu) << 32) | (hu & 0xFFFFFFFF); + f = @bitCast(f64, iq) - 1; + } + + const hfsq = 0.5 * f * f; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + const R = t2 + t1; + const dk = @intToFloat(f64, k); + + return s * (hfsq + R) + (dk * ln2_lo + c) - hfsq + f + dk * ln2_hi; +} + +test "math.log1p" { + expect(log1p(f32(0.0)) == log1p_32(0.0)); + expect(log1p(f64(0.0)) == log1p_64(0.0)); +} + +test "math.log1p_32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, log1p_32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, log1p_32(0.2), 0.182322, epsilon)); + expect(math.approxEq(f32, log1p_32(0.8923), 0.637793, epsilon)); + expect(math.approxEq(f32, log1p_32(1.5), 0.916291, epsilon)); + expect(math.approxEq(f32, log1p_32(37.45), 3.649359, epsilon)); + expect(math.approxEq(f32, log1p_32(89.123), 4.501175, epsilon)); + expect(math.approxEq(f32, log1p_32(123123.234375), 11.720949, epsilon)); +} + +test "math.log1p_64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, log1p_64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, log1p_64(0.2), 0.182322, epsilon)); + expect(math.approxEq(f64, log1p_64(0.8923), 0.637793, epsilon)); + expect(math.approxEq(f64, log1p_64(1.5), 0.916291, epsilon)); + expect(math.approxEq(f64, log1p_64(37.45), 3.649359, epsilon)); + expect(math.approxEq(f64, log1p_64(89.123), 4.501175, epsilon)); + expect(math.approxEq(f64, log1p_64(123123.234375), 11.720949, epsilon)); +} + +test "math.log1p_32.special" { + expect(math.isPositiveInf(log1p_32(math.inf(f32)))); + expect(log1p_32(0.0) == 0.0); + expect(log1p_32(-0.0) == -0.0); + expect(math.isNegativeInf(log1p_32(-1.0))); + expect(math.isNan(log1p_32(-2.0))); + expect(math.isNan(log1p_32(math.nan(f32)))); +} + +test "math.log1p_64.special" { + expect(math.isPositiveInf(log1p_64(math.inf(f64)))); + expect(log1p_64(0.0) == 0.0); + expect(log1p_64(-0.0) == -0.0); + expect(math.isNegativeInf(log1p_64(-1.0))); + expect(math.isNan(log1p_64(-2.0))); + expect(math.isNan(log1p_64(math.nan(f64)))); +} diff --git a/lib/std/math/log2.zig b/lib/std/math/log2.zig new file mode 100644 index 0000000000..88450a7ffd --- /dev/null +++ b/lib/std/math/log2.zig @@ -0,0 +1,214 @@ +// 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/math/log2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log2.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const builtin = @import("builtin"); +const TypeId = builtin.TypeId; +const maxInt = std.math.maxInt; + +/// Returns the base-2 logarithm of x. +/// +/// Special Cases: +/// - log2(+inf) = +inf +/// - log2(0) = -inf +/// - log2(x) = nan if x < 0 +/// - log2(nan) = nan +pub fn log2(x: var) @typeOf(x) { + const T = @typeOf(x); + switch (@typeId(T)) { + TypeId.ComptimeFloat => { + return @typeOf(1.0)(log2_64(x)); + }, + TypeId.Float => { + return switch (T) { + f32 => log2_32(x), + f64 => log2_64(x), + else => @compileError("log2 not implemented for " ++ @typeName(T)), + }; + }, + TypeId.ComptimeInt => comptime { + var result = 0; + var x_shifted = x; + while (b: { + x_shifted >>= 1; + break :b x_shifted != 0; + }) : (result += 1) {} + return result; + }, + TypeId.Int => { + return math.log2_int(T, x); + }, + else => @compileError("log2 not implemented for " ++ @typeName(T)), + } +} + +pub fn log2_32(x_: f32) f32 { + const ivln2hi: f32 = 1.4428710938e+00; + const ivln2lo: f32 = -1.7605285393e-04; + const Lg1: f32 = 0xaaaaaa.0p-24; + const Lg2: f32 = 0xccce13.0p-25; + const Lg3: f32 = 0x91e9ee.0p-25; + const Lg4: f32 = 0xf89e26.0p-26; + + var x = x_; + var u = @bitCast(u32, x); + var ix = u; + var k: i32 = 0; + + // x < 2^(-126) + if (ix < 0x00800000 or ix >> 31 != 0) { + // log(+-0) = -inf + if (ix << 1 == 0) { + return -math.inf(f32); + } + // log(-#) = nan + if (ix >> 31 != 0) { + return math.nan(f32); + } + + k -= 25; + x *= 0x1.0p25; + ix = @bitCast(u32, x); + } else if (ix >= 0x7F800000) { + return x; + } else if (ix == 0x3F800000) { + return 0; + } + + // x into [sqrt(2) / 2, sqrt(2)] + ix += 0x3F800000 - 0x3F3504F3; + k += @intCast(i32, ix >> 23) - 0x7F; + ix = (ix & 0x007FFFFF) + 0x3F3504F3; + x = @bitCast(f32, ix); + + const f = x - 1.0; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * Lg4); + const t2 = z * (Lg1 + w * Lg3); + const R = t2 + t1; + const hfsq = 0.5 * f * f; + + var hi = f - hfsq; + u = @bitCast(u32, hi); + u &= 0xFFFFF000; + hi = @bitCast(f32, u); + const lo = f - hi - hfsq + s * (hfsq + R); + return (lo + hi) * ivln2lo + lo * ivln2hi + hi * ivln2hi + @intToFloat(f32, k); +} + +pub fn log2_64(x_: f64) f64 { + const ivln2hi: f64 = 1.44269504072144627571e+00; + const ivln2lo: f64 = 1.67517131648865118353e-10; + const Lg1: f64 = 6.666666666666735130e-01; + const Lg2: f64 = 3.999999999940941908e-01; + const Lg3: f64 = 2.857142874366239149e-01; + const Lg4: f64 = 2.222219843214978396e-01; + const Lg5: f64 = 1.818357216161805012e-01; + const Lg6: f64 = 1.531383769920937332e-01; + const Lg7: f64 = 1.479819860511658591e-01; + + var x = x_; + var ix = @bitCast(u64, x); + var hx = @intCast(u32, ix >> 32); + var k: i32 = 0; + + if (hx < 0x00100000 or hx >> 31 != 0) { + // log(+-0) = -inf + if (ix << 1 == 0) { + return -math.inf(f64); + } + // log(-#) = nan + if (hx >> 31 != 0) { + return math.nan(f64); + } + + // subnormal, scale x + k -= 54; + x *= 0x1.0p54; + hx = @intCast(u32, @bitCast(u64, x) >> 32); + } else if (hx >= 0x7FF00000) { + return x; + } else if (hx == 0x3FF00000 and ix << 32 == 0) { + return 0; + } + + // x into [sqrt(2) / 2, sqrt(2)] + hx += 0x3FF00000 - 0x3FE6A09E; + k += @intCast(i32, hx >> 20) - 0x3FF; + hx = (hx & 0x000FFFFF) + 0x3FE6A09E; + ix = (u64(hx) << 32) | (ix & 0xFFFFFFFF); + x = @bitCast(f64, ix); + + const f = x - 1.0; + const hfsq = 0.5 * f * f; + const s = f / (2.0 + f); + const z = s * s; + const w = z * z; + const t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + const R = t2 + t1; + + // hi + lo = f - hfsq + s * (hfsq + R) ~ log(1 + f) + var hi = f - hfsq; + var hii = @bitCast(u64, hi); + hii &= u64(maxInt(u64)) << 32; + hi = @bitCast(f64, hii); + const lo = f - hi - hfsq + s * (hfsq + R); + + var val_hi = hi * ivln2hi; + var val_lo = (lo + hi) * ivln2lo + lo * ivln2hi; + + // spadd(val_hi, val_lo, y) + const y = @intToFloat(f64, k); + const ww = y + val_hi; + val_lo += (y - ww) + val_hi; + val_hi = ww; + + return val_lo + val_hi; +} + +test "math.log2" { + expect(log2(f32(0.2)) == log2_32(0.2)); + expect(log2(f64(0.2)) == log2_64(0.2)); +} + +test "math.log2_32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, log2_32(0.2), -2.321928, epsilon)); + expect(math.approxEq(f32, log2_32(0.8923), -0.164399, epsilon)); + expect(math.approxEq(f32, log2_32(1.5), 0.584962, epsilon)); + expect(math.approxEq(f32, log2_32(37.45), 5.226894, epsilon)); + expect(math.approxEq(f32, log2_32(123123.234375), 16.909744, epsilon)); +} + +test "math.log2_64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, log2_64(0.2), -2.321928, epsilon)); + expect(math.approxEq(f64, log2_64(0.8923), -0.164399, epsilon)); + expect(math.approxEq(f64, log2_64(1.5), 0.584962, epsilon)); + expect(math.approxEq(f64, log2_64(37.45), 5.226894, epsilon)); + expect(math.approxEq(f64, log2_64(123123.234375), 16.909744, epsilon)); +} + +test "math.log2_32.special" { + expect(math.isPositiveInf(log2_32(math.inf(f32)))); + expect(math.isNegativeInf(log2_32(0.0))); + expect(math.isNan(log2_32(-1.0))); + expect(math.isNan(log2_32(math.nan(f32)))); +} + +test "math.log2_64.special" { + expect(math.isPositiveInf(log2_64(math.inf(f64)))); + expect(math.isNegativeInf(log2_64(0.0))); + expect(math.isNan(log2_64(-1.0))); + expect(math.isNan(log2_64(math.nan(f64)))); +} diff --git a/lib/std/math/modf.zig b/lib/std/math/modf.zig new file mode 100644 index 0000000000..92194d4c75 --- /dev/null +++ b/lib/std/math/modf.zig @@ -0,0 +1,210 @@ +// 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/math/modff.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/modf.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +fn modf_result(comptime T: type) type { + return struct { + fpart: T, + ipart: T, + }; +} +pub const modf32_result = modf_result(f32); +pub const modf64_result = modf_result(f64); + +/// Returns the integer and fractional floating-point numbers that sum to x. The sign of each +/// result is the same as the sign of x. +/// +/// Special Cases: +/// - modf(+-inf) = +-inf, nan +/// - modf(nan) = nan, nan +pub fn modf(x: var) modf_result(@typeOf(x)) { + const T = @typeOf(x); + return switch (T) { + f32 => modf32(x), + f64 => modf64(x), + else => @compileError("modf not implemented for " ++ @typeName(T)), + }; +} + +fn modf32(x: f32) modf32_result { + var result: modf32_result = undefined; + + const u = @bitCast(u32, x); + const e = @intCast(i32, (u >> 23) & 0xFF) - 0x7F; + const us = u & 0x80000000; + + // TODO: Shouldn't need this. + if (math.isInf(x)) { + result.ipart = x; + result.fpart = math.nan(f32); + return result; + } + + // no fractional part + if (e >= 23) { + result.ipart = x; + if (e == 0x80 and u << 9 != 0) { // nan + result.fpart = x; + } else { + result.fpart = @bitCast(f32, us); + } + return result; + } + + // no integral part + if (e < 0) { + result.ipart = @bitCast(f32, us); + result.fpart = x; + return result; + } + + const mask = u32(0x007FFFFF) >> @intCast(u5, e); + if (u & mask == 0) { + result.ipart = x; + result.fpart = @bitCast(f32, us); + return result; + } + + const uf = @bitCast(f32, u & ~mask); + result.ipart = uf; + result.fpart = x - uf; + return result; +} + +fn modf64(x: f64) modf64_result { + var result: modf64_result = undefined; + + const u = @bitCast(u64, x); + const e = @intCast(i32, (u >> 52) & 0x7FF) - 0x3FF; + const us = u & (1 << 63); + + if (math.isInf(x)) { + result.ipart = x; + result.fpart = math.nan(f64); + return result; + } + + // no fractional part + if (e >= 52) { + result.ipart = x; + if (e == 0x400 and u << 12 != 0) { // nan + result.fpart = x; + } else { + result.fpart = @bitCast(f64, us); + } + return result; + } + + // no integral part + if (e < 0) { + result.ipart = @bitCast(f64, us); + result.fpart = x; + return result; + } + + const mask = u64(maxInt(u64) >> 12) >> @intCast(u6, e); + if (u & mask == 0) { + result.ipart = x; + result.fpart = @bitCast(f64, us); + return result; + } + + const uf = @bitCast(f64, u & ~mask); + result.ipart = uf; + result.fpart = x - uf; + return result; +} + +test "math.modf" { + const a = modf(f32(1.0)); + const b = modf32(1.0); + // NOTE: No struct comparison on generic return type function? non-named, makes sense, but still. + expect(a.ipart == b.ipart and a.fpart == b.fpart); + + const c = modf(f64(1.0)); + const d = modf64(1.0); + expect(a.ipart == b.ipart and a.fpart == b.fpart); +} + +test "math.modf32" { + const epsilon = 0.000001; + var r: modf32_result = undefined; + + r = modf32(1.0); + expect(math.approxEq(f32, r.ipart, 1.0, epsilon)); + expect(math.approxEq(f32, r.fpart, 0.0, epsilon)); + + r = modf32(2.545); + expect(math.approxEq(f32, r.ipart, 2.0, epsilon)); + expect(math.approxEq(f32, r.fpart, 0.545, epsilon)); + + r = modf32(3.978123); + expect(math.approxEq(f32, r.ipart, 3.0, epsilon)); + expect(math.approxEq(f32, r.fpart, 0.978123, epsilon)); + + r = modf32(43874.3); + expect(math.approxEq(f32, r.ipart, 43874, epsilon)); + expect(math.approxEq(f32, r.fpart, 0.300781, epsilon)); + + r = modf32(1234.340780); + expect(math.approxEq(f32, r.ipart, 1234, epsilon)); + expect(math.approxEq(f32, r.fpart, 0.340820, epsilon)); +} + +test "math.modf64" { + const epsilon = 0.000001; + var r: modf64_result = undefined; + + r = modf64(1.0); + expect(math.approxEq(f64, r.ipart, 1.0, epsilon)); + expect(math.approxEq(f64, r.fpart, 0.0, epsilon)); + + r = modf64(2.545); + expect(math.approxEq(f64, r.ipart, 2.0, epsilon)); + expect(math.approxEq(f64, r.fpart, 0.545, epsilon)); + + r = modf64(3.978123); + expect(math.approxEq(f64, r.ipart, 3.0, epsilon)); + expect(math.approxEq(f64, r.fpart, 0.978123, epsilon)); + + r = modf64(43874.3); + expect(math.approxEq(f64, r.ipart, 43874, epsilon)); + expect(math.approxEq(f64, r.fpart, 0.3, epsilon)); + + r = modf64(1234.340780); + expect(math.approxEq(f64, r.ipart, 1234, epsilon)); + expect(math.approxEq(f64, r.fpart, 0.340780, epsilon)); +} + +test "math.modf32.special" { + var r: modf32_result = undefined; + + r = modf32(math.inf(f32)); + expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart)); + + r = modf32(-math.inf(f32)); + expect(math.isNegativeInf(r.ipart) and math.isNan(r.fpart)); + + r = modf32(math.nan(f32)); + expect(math.isNan(r.ipart) and math.isNan(r.fpart)); +} + +test "math.modf64.special" { + var r: modf64_result = undefined; + + r = modf64(math.inf(f64)); + expect(math.isPositiveInf(r.ipart) and math.isNan(r.fpart)); + + r = modf64(-math.inf(f64)); + expect(math.isNegativeInf(r.ipart) and math.isNan(r.fpart)); + + r = modf64(math.nan(f64)); + expect(math.isNan(r.ipart) and math.isNan(r.fpart)); +} diff --git a/lib/std/math/nan.zig b/lib/std/math/nan.zig new file mode 100644 index 0000000000..5a01a5b3bd --- /dev/null +++ b/lib/std/math/nan.zig @@ -0,0 +1,24 @@ +const math = @import("../math.zig"); + +/// Returns the nan representation for type T. +pub fn nan(comptime T: type) T { + return switch (T) { + f16 => math.nan_f16, + f32 => math.nan_f32, + f64 => math.nan_f64, + f128 => math.nan_f128, + else => @compileError("nan not implemented for " ++ @typeName(T)), + }; +} + +/// Returns the signalling nan representation for type T. +pub fn snan(comptime T: type) T { + // Note: A signalling nan is identical to a standard right now by may have a different bit + // representation in the future when required. + return switch (T) { + f16 => @bitCast(f16, math.nan_u16), + f32 => @bitCast(f32, math.nan_u32), + f64 => @bitCast(f64, math.nan_u64), + else => @compileError("snan not implemented for " ++ @typeName(T)), + }; +} diff --git a/lib/std/math/pow.zig b/lib/std/math/pow.zig new file mode 100644 index 0000000000..c3a7792137 --- /dev/null +++ b/lib/std/math/pow.zig @@ -0,0 +1,264 @@ +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE +// +// https://golang.org/src/math/pow.go + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns x raised to the power of y (x^y). +/// +/// Special Cases: +/// - pow(x, +-0) = 1 for any x +/// - pow(1, y) = 1 for any y +/// - pow(x, 1) = x for any x +/// - pow(nan, y) = nan +/// - pow(x, nan) = nan +/// - pow(+-0, y) = +-inf for y an odd integer < 0 +/// - pow(+-0, -inf) = +inf +/// - pow(+-0, +inf) = +0 +/// - pow(+-0, y) = +inf for finite y < 0 and not an odd integer +/// - pow(+-0, y) = +-0 for y an odd integer > 0 +/// - pow(+-0, y) = +0 for finite y > 0 and not an odd integer +/// - pow(-1, +-inf) = 1 +/// - pow(x, +inf) = +inf for |x| > 1 +/// - pow(x, -inf) = +0 for |x| > 1 +/// - pow(x, +inf) = +0 for |x| < 1 +/// - pow(x, -inf) = +inf for |x| < 1 +/// - pow(+inf, y) = +inf for y > 0 +/// - pow(+inf, y) = +0 for y < 0 +/// - pow(-inf, y) = pow(-0, -y) +/// - pow(x, y) = nan for finite x < 0 and finite non-integer y +pub fn pow(comptime T: type, x: T, y: T) T { + if (@typeInfo(T) == builtin.TypeId.Int) { + return math.powi(T, x, y) catch unreachable; + } + + if (T != f32 and T != f64) { + @compileError("pow not implemented for " ++ @typeName(T)); + } + + // pow(x, +-0) = 1 for all x + // pow(1, y) = 1 for all y + if (y == 0 or x == 1) { + return 1; + } + + // pow(nan, y) = nan for all y + // pow(x, nan) = nan for all x + if (math.isNan(x) or math.isNan(y)) { + return math.nan(T); + } + + // pow(x, 1) = x for all x + if (y == 1) { + return x; + } + + if (x == 0) { + if (y < 0) { + // pow(+-0, y) = +- 0 for y an odd integer + if (isOddInteger(y)) { + return math.copysign(T, math.inf(T), x); + } + // pow(+-0, y) = +inf for y an even integer + else { + return math.inf(T); + } + } else { + if (isOddInteger(y)) { + return x; + } else { + return 0; + } + } + } + + if (math.isInf(y)) { + // pow(-1, inf) = 1 for all x + if (x == -1) { + return 1.0; + } + // pow(x, +inf) = +0 for |x| < 1 + // pow(x, -inf) = +0 for |x| > 1 + else if ((math.fabs(x) < 1) == math.isPositiveInf(y)) { + return 0; + } + // pow(x, -inf) = +inf for |x| < 1 + // pow(x, +inf) = +inf for |x| > 1 + else { + return math.inf(T); + } + } + + if (math.isInf(x)) { + if (math.isNegativeInf(x)) { + return pow(T, 1 / x, -y); + } + // pow(+inf, y) = +0 for y < 0 + else if (y < 0) { + return 0; + } + // pow(+inf, y) = +0 for y > 0 + else if (y > 0) { + return math.inf(T); + } + } + + // special case sqrt + if (y == 0.5) { + return math.sqrt(x); + } + + if (y == -0.5) { + return 1 / math.sqrt(x); + } + + const r1 = math.modf(math.fabs(y)); + var yi = r1.ipart; + var yf = r1.fpart; + + if (yf != 0 and x < 0) { + return math.nan(T); + } + if (yi >= 1 << (T.bit_count - 1)) { + return math.exp(y * math.ln(x)); + } + + // a = a1 * 2^ae + var a1: T = 1.0; + var ae: i32 = 0; + + // a *= x^yf + if (yf != 0) { + if (yf > 0.5) { + yf -= 1; + yi += 1; + } + a1 = math.exp(yf * math.ln(x)); + } + + // a *= x^yi + const r2 = math.frexp(x); + var xe = r2.exponent; + var x1 = r2.significand; + + var i = @floatToInt(@IntType(true, T.bit_count), yi); + while (i != 0) : (i >>= 1) { + const overflow_shift = math.floatExponentBits(T) + 1; + if (xe < -(1 << overflow_shift) or (1 << overflow_shift) < xe) { + // catch xe before it overflows the left shift below + // Since i != 0 it has at least one bit still set, so ae will accumulate xe + // on at least one more iteration, ae += xe is a lower bound on ae + // the lower bound on ae exceeds the size of a float exp + // so the final call to Ldexp will produce under/overflow (0/Inf) + ae += xe; + break; + } + if (i & 1 == 1) { + a1 *= x1; + ae += xe; + } + x1 *= x1; + xe <<= 1; + if (x1 < 0.5) { + x1 += x1; + xe -= 1; + } + } + + // a *= a1 * 2^ae + if (y < 0) { + a1 = 1 / a1; + ae = -ae; + } + + return math.scalbn(a1, ae); +} + +fn isOddInteger(x: f64) bool { + const r = math.modf(x); + return r.fpart == 0.0 and @floatToInt(i64, r.ipart) & 1 == 1; +} + +test "math.pow" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const epsilon = 0.000001; + + expect(math.approxEq(f32, pow(f32, 0.0, 3.3), 0.0, epsilon)); + expect(math.approxEq(f32, pow(f32, 0.8923, 3.3), 0.686572, epsilon)); + expect(math.approxEq(f32, pow(f32, 0.2, 3.3), 0.004936, epsilon)); + expect(math.approxEq(f32, pow(f32, 1.5, 3.3), 3.811546, epsilon)); + expect(math.approxEq(f32, pow(f32, 37.45, 3.3), 155736.703125, epsilon)); + expect(math.approxEq(f32, pow(f32, 89.123, 3.3), 2722489.5, epsilon)); + + expect(math.approxEq(f64, pow(f64, 0.0, 3.3), 0.0, epsilon)); + expect(math.approxEq(f64, pow(f64, 0.8923, 3.3), 0.686572, epsilon)); + expect(math.approxEq(f64, pow(f64, 0.2, 3.3), 0.004936, epsilon)); + expect(math.approxEq(f64, pow(f64, 1.5, 3.3), 3.811546, epsilon)); + expect(math.approxEq(f64, pow(f64, 37.45, 3.3), 155736.7160616, epsilon)); + expect(math.approxEq(f64, pow(f64, 89.123, 3.3), 2722490.231436, epsilon)); +} + +test "math.pow.special" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const epsilon = 0.000001; + + expect(pow(f32, 4, 0.0) == 1.0); + expect(pow(f32, 7, -0.0) == 1.0); + expect(pow(f32, 45, 1.0) == 45); + expect(pow(f32, -45, 1.0) == -45); + expect(math.isNan(pow(f32, math.nan(f32), 5.0))); + expect(math.isPositiveInf(pow(f32, -math.inf(f32), 0.5))); + expect(math.isPositiveInf(pow(f32, -0, -0.5))); + expect(pow(f32, -0, 0.5) == 0); + expect(math.isNan(pow(f32, 5.0, math.nan(f32)))); + expect(math.isPositiveInf(pow(f32, 0.0, -1.0))); + //expect(math.isNegativeInf(pow(f32, -0.0, -3.0))); TODO is this required? + expect(math.isPositiveInf(pow(f32, 0.0, -math.inf(f32)))); + expect(math.isPositiveInf(pow(f32, -0.0, -math.inf(f32)))); + expect(pow(f32, 0.0, math.inf(f32)) == 0.0); + expect(pow(f32, -0.0, math.inf(f32)) == 0.0); + expect(math.isPositiveInf(pow(f32, 0.0, -2.0))); + expect(math.isPositiveInf(pow(f32, -0.0, -2.0))); + expect(pow(f32, 0.0, 1.0) == 0.0); + expect(pow(f32, -0.0, 1.0) == -0.0); + expect(pow(f32, 0.0, 2.0) == 0.0); + expect(pow(f32, -0.0, 2.0) == 0.0); + expect(math.approxEq(f32, pow(f32, -1.0, math.inf(f32)), 1.0, epsilon)); + expect(math.approxEq(f32, pow(f32, -1.0, -math.inf(f32)), 1.0, epsilon)); + expect(math.isPositiveInf(pow(f32, 1.2, math.inf(f32)))); + expect(math.isPositiveInf(pow(f32, -1.2, math.inf(f32)))); + expect(pow(f32, 1.2, -math.inf(f32)) == 0.0); + expect(pow(f32, -1.2, -math.inf(f32)) == 0.0); + expect(pow(f32, 0.2, math.inf(f32)) == 0.0); + expect(pow(f32, -0.2, math.inf(f32)) == 0.0); + expect(math.isPositiveInf(pow(f32, 0.2, -math.inf(f32)))); + expect(math.isPositiveInf(pow(f32, -0.2, -math.inf(f32)))); + expect(math.isPositiveInf(pow(f32, math.inf(f32), 1.0))); + expect(pow(f32, math.inf(f32), -1.0) == 0.0); + //expect(pow(f32, -math.inf(f32), 5.0) == pow(f32, -0.0, -5.0)); TODO support negative 0? + expect(pow(f32, -math.inf(f32), -5.2) == pow(f32, -0.0, 5.2)); + expect(math.isNan(pow(f32, -1.0, 1.2))); + expect(math.isNan(pow(f32, -12.4, 78.5))); +} + +test "math.pow.overflow" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + expect(math.isPositiveInf(pow(f64, 2, 1 << 32))); + expect(pow(f64, 2, -(1 << 32)) == 0); + expect(math.isNegativeInf(pow(f64, -2, (1 << 32) + 1))); + expect(pow(f64, 0.5, 1 << 45) == 0); + expect(math.isPositiveInf(pow(f64, 0.5, -(1 << 45)))); +} diff --git a/lib/std/math/powi.zig b/lib/std/math/powi.zig new file mode 100644 index 0000000000..d80700e5cd --- /dev/null +++ b/lib/std/math/powi.zig @@ -0,0 +1,188 @@ +// Based on Rust, which is licensed under the MIT license. +// https://github.com/rust-lang/rust/blob/360432f1e8794de58cd94f34c9c17ad65871e5b5/LICENSE-MIT +// +// https://github.com/rust-lang/rust/blob/360432f1e8794de58cd94f34c9c17ad65871e5b5/src/libcore/num/mod.rs#L3423 + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const assert = std.debug.assert; +const testing = std.testing; + +/// Returns the power of x raised by the integer y (x^y). +/// +/// Special Cases: +/// - powi(x, +-0) = 1 for any x +/// - powi(0, y) = 0 for any y +/// - powi(1, y) = 1 for any y +/// - powi(-1, y) = -1 for y an odd integer +/// - powi(-1, y) = 1 for y an even integer +/// - powi(x, y) = Overflow for y >= @sizeOf(x) - 1 or y > 0 +/// - powi(x, y) = Underflow for y > @sizeOf(x) - 1 or y < 0 +pub fn powi(comptime T: type, x: T, y: T) (error{ + Overflow, + Underflow, +}!T) { + const info = @typeInfo(T); + + comptime assert(@typeInfo(T) == builtin.TypeId.Int); + + // powi(x, +-0) = 1 for any x + if (y == 0 or y == -0) { + return 1; + } + + switch (x) { + // powi(0, y) = 0 for any y + 0 => return 0, + + // powi(1, y) = 1 for any y + 1 => return 1, + + else => { + // powi(x, y) = Overflow for for y >= @sizeOf(x) - 1 y > 0 + // powi(x, y) = Underflow for for y > @sizeOf(x) - 1 y < 0 + const bit_size = @sizeOf(T) * 8; + if (info.Int.is_signed) { + if (x == -1) { + // powi(-1, y) = -1 for for y an odd integer + // powi(-1, y) = 1 for for y an even integer + if (@mod(y, 2) == 0) { + return 1; + } else { + return -1; + } + } + + if (x > 0 and y >= bit_size - 1) { + return error.Overflow; + } else if (x < 0 and y > bit_size - 1) { + return error.Underflow; + } + } else { + if (y >= bit_size) { + return error.Overflow; + } + } + + var base = x; + var exp = y; + var acc: T = 1; + + while (exp > 1) { + if (exp & 1 == 1) { + if (@mulWithOverflow(T, acc, base, &acc)) { + if (x > 0) { + return error.Overflow; + } else { + return error.Underflow; + } + } + } + + exp >>= 1; + + if (@mulWithOverflow(T, base, base, &base)) { + if (x > 0) { + return error.Overflow; + } else { + return error.Underflow; + } + } + } + + if (exp == 1) { + if (@mulWithOverflow(T, acc, base, &acc)) { + if (x > 0) { + return error.Overflow; + } else { + return error.Underflow; + } + } + } + + return acc; + }, + } +} + +test "math.powi" { + testing.expectError(error.Underflow, powi(i8, -66, 6)); + testing.expectError(error.Underflow, powi(i16, -13, 13)); + testing.expectError(error.Underflow, powi(i32, -32, 21)); + testing.expectError(error.Underflow, powi(i64, -24, 61)); + testing.expectError(error.Underflow, powi(i17, -15, 15)); + testing.expectError(error.Underflow, powi(i42, -6, 40)); + + testing.expect((try powi(i8, -5, 3)) == -125); + testing.expect((try powi(i16, -16, 3)) == -4096); + testing.expect((try powi(i32, -91, 3)) == -753571); + testing.expect((try powi(i64, -36, 6)) == 2176782336); + testing.expect((try powi(i17, -2, 15)) == -32768); + testing.expect((try powi(i42, -5, 7)) == -78125); + + testing.expect((try powi(u8, 6, 2)) == 36); + testing.expect((try powi(u16, 5, 4)) == 625); + testing.expect((try powi(u32, 12, 6)) == 2985984); + testing.expect((try powi(u64, 34, 2)) == 1156); + testing.expect((try powi(u17, 16, 3)) == 4096); + testing.expect((try powi(u42, 34, 6)) == 1544804416); + + testing.expectError(error.Overflow, powi(i8, 120, 7)); + testing.expectError(error.Overflow, powi(i16, 73, 15)); + testing.expectError(error.Overflow, powi(i32, 23, 31)); + testing.expectError(error.Overflow, powi(i64, 68, 61)); + testing.expectError(error.Overflow, powi(i17, 15, 15)); + testing.expectError(error.Overflow, powi(i42, 121312, 41)); + + testing.expectError(error.Overflow, powi(u8, 123, 7)); + testing.expectError(error.Overflow, powi(u16, 2313, 15)); + testing.expectError(error.Overflow, powi(u32, 8968, 31)); + testing.expectError(error.Overflow, powi(u64, 2342, 63)); + testing.expectError(error.Overflow, powi(u17, 2723, 16)); + testing.expectError(error.Overflow, powi(u42, 8234, 41)); +} + +test "math.powi.special" { + testing.expectError(error.Underflow, powi(i8, -2, 8)); + testing.expectError(error.Underflow, powi(i16, -2, 16)); + testing.expectError(error.Underflow, powi(i32, -2, 32)); + testing.expectError(error.Underflow, powi(i64, -2, 64)); + testing.expectError(error.Underflow, powi(i17, -2, 17)); + testing.expectError(error.Underflow, powi(i42, -2, 42)); + + testing.expect((try powi(i8, -1, 3)) == -1); + testing.expect((try powi(i16, -1, 2)) == 1); + testing.expect((try powi(i32, -1, 16)) == 1); + testing.expect((try powi(i64, -1, 6)) == 1); + testing.expect((try powi(i17, -1, 15)) == -1); + testing.expect((try powi(i42, -1, 7)) == -1); + + testing.expect((try powi(u8, 1, 2)) == 1); + testing.expect((try powi(u16, 1, 4)) == 1); + testing.expect((try powi(u32, 1, 6)) == 1); + testing.expect((try powi(u64, 1, 2)) == 1); + testing.expect((try powi(u17, 1, 3)) == 1); + testing.expect((try powi(u42, 1, 6)) == 1); + + testing.expectError(error.Overflow, powi(i8, 2, 7)); + testing.expectError(error.Overflow, powi(i16, 2, 15)); + testing.expectError(error.Overflow, powi(i32, 2, 31)); + testing.expectError(error.Overflow, powi(i64, 2, 63)); + testing.expectError(error.Overflow, powi(i17, 2, 16)); + testing.expectError(error.Overflow, powi(i42, 2, 41)); + + testing.expectError(error.Overflow, powi(u8, 2, 8)); + testing.expectError(error.Overflow, powi(u16, 2, 16)); + testing.expectError(error.Overflow, powi(u32, 2, 32)); + testing.expectError(error.Overflow, powi(u64, 2, 64)); + testing.expectError(error.Overflow, powi(u17, 2, 17)); + testing.expectError(error.Overflow, powi(u42, 2, 42)); + + testing.expect((try powi(u8, 6, 0)) == 1); + testing.expect((try powi(u16, 5, 0)) == 1); + testing.expect((try powi(u32, 12, 0)) == 1); + testing.expect((try powi(u64, 34, 0)) == 1); + testing.expect((try powi(u17, 16, 0)) == 1); + testing.expect((try powi(u42, 34, 0)) == 1); +} diff --git a/lib/std/math/round.zig b/lib/std/math/round.zig new file mode 100644 index 0000000000..0b80a46ce5 --- /dev/null +++ b/lib/std/math/round.zig @@ -0,0 +1,126 @@ +// 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/math/roundf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/round.c + +const builtin = @import("builtin"); +const expect = std.testing.expect; +const std = @import("../std.zig"); +const math = std.math; + +/// Returns x rounded to the nearest integer, rounding half away from zero. +/// +/// Special Cases: +/// - round(+-0) = +-0 +/// - round(+-inf) = +-inf +/// - round(nan) = nan +pub fn round(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => round32(x), + f64 => round64(x), + else => @compileError("round not implemented for " ++ @typeName(T)), + }; +} + +fn round32(x_: f32) f32 { + var x = x_; + const u = @bitCast(u32, x); + const e = (u >> 23) & 0xFF; + var y: f32 = undefined; + + if (e >= 0x7F + 23) { + return x; + } + if (u >> 31 != 0) { + x = -x; + } + if (e < 0x7F - 1) { + math.forceEval(x + math.f32_toint); + return 0 * @bitCast(f32, u); + } + + y = x + math.f32_toint - math.f32_toint - x; + if (y > 0.5) { + y = y + x - 1; + } else if (y <= -0.5) { + y = y + x + 1; + } else { + y = y + x; + } + + if (u >> 31 != 0) { + return -y; + } else { + return y; + } +} + +fn round64(x_: f64) f64 { + var x = x_; + const u = @bitCast(u64, x); + const e = (u >> 52) & 0x7FF; + var y: f64 = undefined; + + if (e >= 0x3FF + 52) { + return x; + } + if (u >> 63 != 0) { + x = -x; + } + if (e < 0x3ff - 1) { + math.forceEval(x + math.f64_toint); + return 0 * @bitCast(f64, u); + } + + y = x + math.f64_toint - math.f64_toint - x; + if (y > 0.5) { + y = y + x - 1; + } else if (y <= -0.5) { + y = y + x + 1; + } else { + y = y + x; + } + + if (u >> 63 != 0) { + return -y; + } else { + return y; + } +} + +test "math.round" { + expect(round(f32(1.3)) == round32(1.3)); + expect(round(f64(1.3)) == round64(1.3)); +} + +test "math.round32" { + expect(round32(1.3) == 1.0); + expect(round32(-1.3) == -1.0); + expect(round32(0.2) == 0.0); + expect(round32(1.8) == 2.0); +} + +test "math.round64" { + expect(round64(1.3) == 1.0); + expect(round64(-1.3) == -1.0); + expect(round64(0.2) == 0.0); + expect(round64(1.8) == 2.0); +} + +test "math.round32.special" { + expect(round32(0.0) == 0.0); + expect(round32(-0.0) == -0.0); + expect(math.isPositiveInf(round32(math.inf(f32)))); + expect(math.isNegativeInf(round32(-math.inf(f32)))); + expect(math.isNan(round32(math.nan(f32)))); +} + +test "math.round64.special" { + expect(round64(0.0) == 0.0); + expect(round64(-0.0) == -0.0); + expect(math.isPositiveInf(round64(math.inf(f64)))); + expect(math.isNegativeInf(round64(-math.inf(f64)))); + expect(math.isNan(round64(math.nan(f64)))); +} diff --git a/lib/std/math/scalbn.zig b/lib/std/math/scalbn.zig new file mode 100644 index 0000000000..d5716d621c --- /dev/null +++ b/lib/std/math/scalbn.zig @@ -0,0 +1,92 @@ +// 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/math/scalbnf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns x * 2^n. +pub fn scalbn(x: var, n: i32) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => scalbn32(x, n), + f64 => scalbn64(x, n), + else => @compileError("scalbn not implemented for " ++ @typeName(T)), + }; +} + +fn scalbn32(x: f32, n_: i32) f32 { + var y = x; + var n = n_; + + if (n > 127) { + y *= 0x1.0p127; + n -= 127; + if (n > 1023) { + y *= 0x1.0p127; + n -= 127; + if (n > 127) { + n = 127; + } + } + } else if (n < -126) { + y *= 0x1.0p-126 * 0x1.0p24; + n += 126 - 24; + if (n < -126) { + y *= 0x1.0p-126 * 0x1.0p24; + n += 126 - 24; + if (n < -126) { + n = -126; + } + } + } + + const u = @intCast(u32, n +% 0x7F) << 23; + return y * @bitCast(f32, u); +} + +fn scalbn64(x: f64, n_: i32) f64 { + var y = x; + var n = n_; + + if (n > 1023) { + y *= 0x1.0p1023; + n -= 1023; + if (n > 1023) { + y *= 0x1.0p1023; + n -= 1023; + if (n > 1023) { + n = 1023; + } + } + } else if (n < -1022) { + y *= 0x1.0p-1022 * 0x1.0p53; + n += 1022 - 53; + if (n < -1022) { + y *= 0x1.0p-1022 * 0x1.0p53; + n += 1022 - 53; + if (n < -1022) { + n = -1022; + } + } + } + + const u = @intCast(u64, n +% 0x3FF) << 52; + return y * @bitCast(f64, u); +} + +test "math.scalbn" { + expect(scalbn(f32(1.5), 4) == scalbn32(1.5, 4)); + expect(scalbn(f64(1.5), 4) == scalbn64(1.5, 4)); +} + +test "math.scalbn32" { + expect(scalbn32(1.5, 4) == 24.0); +} + +test "math.scalbn64" { + expect(scalbn64(1.5, 4) == 24.0); +} diff --git a/lib/std/math/signbit.zig b/lib/std/math/signbit.zig new file mode 100644 index 0000000000..e5c5909292 --- /dev/null +++ b/lib/std/math/signbit.zig @@ -0,0 +1,50 @@ +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns whether x is negative or negative 0. +pub fn signbit(x: var) bool { + const T = @typeOf(x); + return switch (T) { + f16 => signbit16(x), + f32 => signbit32(x), + f64 => signbit64(x), + else => @compileError("signbit not implemented for " ++ @typeName(T)), + }; +} + +fn signbit16(x: f16) bool { + const bits = @bitCast(u16, x); + return bits >> 15 != 0; +} + +fn signbit32(x: f32) bool { + const bits = @bitCast(u32, x); + return bits >> 31 != 0; +} + +fn signbit64(x: f64) bool { + const bits = @bitCast(u64, x); + return bits >> 63 != 0; +} + +test "math.signbit" { + expect(signbit(f16(4.0)) == signbit16(4.0)); + expect(signbit(f32(4.0)) == signbit32(4.0)); + expect(signbit(f64(4.0)) == signbit64(4.0)); +} + +test "math.signbit16" { + expect(!signbit16(4.0)); + expect(signbit16(-3.0)); +} + +test "math.signbit32" { + expect(!signbit32(4.0)); + expect(signbit32(-3.0)); +} + +test "math.signbit64" { + expect(!signbit64(4.0)); + expect(signbit64(-3.0)); +} diff --git a/lib/std/math/sin.zig b/lib/std/math/sin.zig new file mode 100644 index 0000000000..ee07b4f85e --- /dev/null +++ b/lib/std/math/sin.zig @@ -0,0 +1,150 @@ +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE +// +// https://golang.org/src/math/sin.go + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the sine of the radian value x. +/// +/// Special Cases: +/// - sin(+-0) = +-0 +/// - sin(+-inf) = nan +/// - sin(nan) = nan +pub fn sin(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => sin_(T, x), + f64 => sin_(T, x), + else => @compileError("sin not implemented for " ++ @typeName(T)), + }; +} + +// sin polynomial coefficients +const S0 = 1.58962301576546568060E-10; +const S1 = -2.50507477628578072866E-8; +const S2 = 2.75573136213857245213E-6; +const S3 = -1.98412698295895385996E-4; +const S4 = 8.33333333332211858878E-3; +const S5 = -1.66666666666666307295E-1; + +// cos polynomial coeffiecients +const C0 = -1.13585365213876817300E-11; +const C1 = 2.08757008419747316778E-9; +const C2 = -2.75573141792967388112E-7; +const C3 = 2.48015872888517045348E-5; +const C4 = -1.38888888888730564116E-3; +const C5 = 4.16666666666665929218E-2; + +const pi4a = 7.85398125648498535156e-1; +const pi4b = 3.77489470793079817668E-8; +const pi4c = 2.69515142907905952645E-15; +const m4pi = 1.273239544735162542821171882678754627704620361328125; + +fn sin_(comptime T: type, x_: T) T { + const I = @IntType(true, T.bit_count); + + var x = x_; + if (x == 0 or math.isNan(x)) { + return x; + } + if (math.isInf(x)) { + return math.nan(T); + } + + var sign = x < 0; + x = math.fabs(x); + + var y = math.floor(x * m4pi); + var j = @floatToInt(I, y); + + if (j & 1 == 1) { + j += 1; + y += 1; + } + + j &= 7; + if (j > 3) { + j -= 4; + sign = !sign; + } + + const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; + const w = z * z; + + const r = if (j == 1 or j == 2) + 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))) + else + z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))); + + return if (sign) -r else r; +} + +test "math.sin" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + expect(sin(f32(0.0)) == sin_(f32, 0.0)); + expect(sin(f64(0.0)) == sin_(f64, 0.0)); + expect(comptime (math.sin(f64(2))) == math.sin(f64(2))); +} + +test "math.sin32" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const epsilon = 0.000001; + + expect(math.approxEq(f32, sin_(f32, 0.0), 0.0, epsilon)); + expect(math.approxEq(f32, sin_(f32, 0.2), 0.198669, epsilon)); + expect(math.approxEq(f32, sin_(f32, 0.8923), 0.778517, epsilon)); + expect(math.approxEq(f32, sin_(f32, 1.5), 0.997495, epsilon)); + expect(math.approxEq(f32, sin_(f32, -1.5), -0.997495, epsilon)); + expect(math.approxEq(f32, sin_(f32, 37.45), -0.246544, epsilon)); + expect(math.approxEq(f32, sin_(f32, 89.123), 0.916166, epsilon)); +} + +test "math.sin64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const epsilon = 0.000001; + + expect(math.approxEq(f64, sin_(f64, 0.0), 0.0, epsilon)); + expect(math.approxEq(f64, sin_(f64, 0.2), 0.198669, epsilon)); + expect(math.approxEq(f64, sin_(f64, 0.8923), 0.778517, epsilon)); + expect(math.approxEq(f64, sin_(f64, 1.5), 0.997495, epsilon)); + expect(math.approxEq(f64, sin_(f64, -1.5), -0.997495, epsilon)); + expect(math.approxEq(f64, sin_(f64, 37.45), -0.246543, epsilon)); + expect(math.approxEq(f64, sin_(f64, 89.123), 0.916166, epsilon)); +} + +test "math.sin32.special" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + expect(sin_(f32, 0.0) == 0.0); + expect(sin_(f32, -0.0) == -0.0); + expect(math.isNan(sin_(f32, math.inf(f32)))); + expect(math.isNan(sin_(f32, -math.inf(f32)))); + expect(math.isNan(sin_(f32, math.nan(f32)))); +} + +test "math.sin64.special" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + expect(sin_(f64, 0.0) == 0.0); + expect(sin_(f64, -0.0) == -0.0); + expect(math.isNan(sin_(f64, math.inf(f64)))); + expect(math.isNan(sin_(f64, -math.inf(f64)))); + expect(math.isNan(sin_(f64, math.nan(f64)))); +} diff --git a/lib/std/math/sinh.zig b/lib/std/math/sinh.zig new file mode 100644 index 0000000000..73ee65ea6f --- /dev/null +++ b/lib/std/math/sinh.zig @@ -0,0 +1,132 @@ +// 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/math/sinhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/sinh.c + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const expo2 = @import("expo2.zig").expo2; +const maxInt = std.math.maxInt; + +/// Returns the hyperbolic sine of x. +/// +/// Special Cases: +/// - sinh(+-0) = +-0 +/// - sinh(+-inf) = +-inf +/// - sinh(nan) = nan +pub fn sinh(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => sinh32(x), + f64 => sinh64(x), + else => @compileError("sinh not implemented for " ++ @typeName(T)), + }; +} + +// sinh(x) = (exp(x) - 1 / exp(x)) / 2 +// = (exp(x) - 1 + (exp(x) - 1) / exp(x)) / 2 +// = x + x^3 / 6 + o(x^5) +fn sinh32(x: f32) f32 { + const u = @bitCast(u32, x); + const ux = u & 0x7FFFFFFF; + const ax = @bitCast(f32, ux); + + if (x == 0.0 or math.isNan(x)) { + return x; + } + + var h: f32 = 0.5; + if (u >> 31 != 0) { + h = -h; + } + + // |x| < log(FLT_MAX) + if (ux < 0x42B17217) { + const t = math.expm1(ax); + if (ux < 0x3F800000) { + if (ux < 0x3F800000 - (12 << 23)) { + return x; + } else { + return h * (2 * t - t * t / (t + 1)); + } + } + return h * (t + t / (t + 1)); + } + + // |x| > log(FLT_MAX) or nan + return 2 * h * expo2(ax); +} + +fn sinh64(x: f64) f64 { + const u = @bitCast(u64, x); + const w = @intCast(u32, u >> 32); + const ax = @bitCast(f64, u & (maxInt(u64) >> 1)); + + if (x == 0.0 or math.isNan(x)) { + return x; + } + + var h: f32 = 0.5; + if (u >> 63 != 0) { + h = -h; + } + + // |x| < log(FLT_MAX) + if (w < 0x40862E42) { + const t = math.expm1(ax); + if (w < 0x3FF00000) { + if (w < 0x3FF00000 - (26 << 20)) { + return x; + } else { + return h * (2 * t - t * t / (t + 1)); + } + } + // NOTE: |x| > log(0x1p26) + eps could be h * exp(x) + return h * (t + t / (t + 1)); + } + + // |x| > log(DBL_MAX) or nan + return 2 * h * expo2(ax); +} + +test "math.sinh" { + expect(sinh(f32(1.5)) == sinh32(1.5)); + expect(sinh(f64(1.5)) == sinh64(1.5)); +} + +test "math.sinh32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, sinh32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, sinh32(0.2), 0.201336, epsilon)); + expect(math.approxEq(f32, sinh32(0.8923), 1.015512, epsilon)); + expect(math.approxEq(f32, sinh32(1.5), 2.129279, epsilon)); +} + +test "math.sinh64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, sinh64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, sinh64(0.2), 0.201336, epsilon)); + expect(math.approxEq(f64, sinh64(0.8923), 1.015512, epsilon)); + expect(math.approxEq(f64, sinh64(1.5), 2.129279, epsilon)); +} + +test "math.sinh32.special" { + expect(sinh32(0.0) == 0.0); + expect(sinh32(-0.0) == -0.0); + expect(math.isPositiveInf(sinh32(math.inf(f32)))); + expect(math.isNegativeInf(sinh32(-math.inf(f32)))); + expect(math.isNan(sinh32(math.nan(f32)))); +} + +test "math.sinh64.special" { + expect(sinh64(0.0) == 0.0); + expect(sinh64(-0.0) == -0.0); + expect(math.isPositiveInf(sinh64(math.inf(f64)))); + expect(math.isNegativeInf(sinh64(-math.inf(f64)))); + expect(math.isNan(sinh64(math.nan(f64)))); +} diff --git a/lib/std/math/sqrt.zig b/lib/std/math/sqrt.zig new file mode 100644 index 0000000000..30af5915d4 --- /dev/null +++ b/lib/std/math/sqrt.zig @@ -0,0 +1,136 @@ +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const builtin = @import("builtin"); +const TypeId = builtin.TypeId; +const maxInt = std.math.maxInt; + +/// Returns the square root of x. +/// +/// Special Cases: +/// - sqrt(+inf) = +inf +/// - sqrt(+-0) = +-0 +/// - sqrt(x) = nan if x < 0 +/// - sqrt(nan) = nan +pub fn sqrt(x: var) (if (@typeId(@typeOf(x)) == TypeId.Int) @IntType(false, @typeOf(x).bit_count / 2) else @typeOf(x)) { + const T = @typeOf(x); + switch (@typeId(T)) { + TypeId.ComptimeFloat => return T(@sqrt(f64, x)), // TODO upgrade to f128 + TypeId.Float => return @sqrt(T, x), + TypeId.ComptimeInt => comptime { + if (x > maxInt(u128)) { + @compileError("sqrt not implemented for comptime_int greater than 128 bits"); + } + if (x < 0) { + @compileError("sqrt on negative number"); + } + return T(sqrt_int(u128, x)); + }, + TypeId.Int => return sqrt_int(T, x), + else => @compileError("sqrt not implemented for " ++ @typeName(T)), + } +} + +test "math.sqrt" { + expect(sqrt(f16(0.0)) == @sqrt(f16, 0.0)); + expect(sqrt(f32(0.0)) == @sqrt(f32, 0.0)); + expect(sqrt(f64(0.0)) == @sqrt(f64, 0.0)); +} + +test "math.sqrt16" { + const epsilon = 0.000001; + + expect(@sqrt(f16, 0.0) == 0.0); + expect(math.approxEq(f16, @sqrt(f16, 2.0), 1.414214, epsilon)); + expect(math.approxEq(f16, @sqrt(f16, 3.6), 1.897367, epsilon)); + expect(@sqrt(f16, 4.0) == 2.0); + expect(math.approxEq(f16, @sqrt(f16, 7.539840), 2.745877, epsilon)); + expect(math.approxEq(f16, @sqrt(f16, 19.230934), 4.385309, epsilon)); + expect(@sqrt(f16, 64.0) == 8.0); + expect(math.approxEq(f16, @sqrt(f16, 64.1), 8.006248, epsilon)); + expect(math.approxEq(f16, @sqrt(f16, 8942.230469), 94.563370, epsilon)); +} + +test "math.sqrt32" { + const epsilon = 0.000001; + + expect(@sqrt(f32, 0.0) == 0.0); + expect(math.approxEq(f32, @sqrt(f32, 2.0), 1.414214, epsilon)); + expect(math.approxEq(f32, @sqrt(f32, 3.6), 1.897367, epsilon)); + expect(@sqrt(f32, 4.0) == 2.0); + expect(math.approxEq(f32, @sqrt(f32, 7.539840), 2.745877, epsilon)); + expect(math.approxEq(f32, @sqrt(f32, 19.230934), 4.385309, epsilon)); + expect(@sqrt(f32, 64.0) == 8.0); + expect(math.approxEq(f32, @sqrt(f32, 64.1), 8.006248, epsilon)); + expect(math.approxEq(f32, @sqrt(f32, 8942.230469), 94.563370, epsilon)); +} + +test "math.sqrt64" { + const epsilon = 0.000001; + + expect(@sqrt(f64, 0.0) == 0.0); + expect(math.approxEq(f64, @sqrt(f64, 2.0), 1.414214, epsilon)); + expect(math.approxEq(f64, @sqrt(f64, 3.6), 1.897367, epsilon)); + expect(@sqrt(f64, 4.0) == 2.0); + expect(math.approxEq(f64, @sqrt(f64, 7.539840), 2.745877, epsilon)); + expect(math.approxEq(f64, @sqrt(f64, 19.230934), 4.385309, epsilon)); + expect(@sqrt(f64, 64.0) == 8.0); + expect(math.approxEq(f64, @sqrt(f64, 64.1), 8.006248, epsilon)); + expect(math.approxEq(f64, @sqrt(f64, 8942.230469), 94.563367, epsilon)); +} + +test "math.sqrt16.special" { + expect(math.isPositiveInf(@sqrt(f16, math.inf(f16)))); + expect(@sqrt(f16, 0.0) == 0.0); + expect(@sqrt(f16, -0.0) == -0.0); + expect(math.isNan(@sqrt(f16, -1.0))); + expect(math.isNan(@sqrt(f16, math.nan(f16)))); +} + +test "math.sqrt32.special" { + expect(math.isPositiveInf(@sqrt(f32, math.inf(f32)))); + expect(@sqrt(f32, 0.0) == 0.0); + expect(@sqrt(f32, -0.0) == -0.0); + expect(math.isNan(@sqrt(f32, -1.0))); + expect(math.isNan(@sqrt(f32, math.nan(f32)))); +} + +test "math.sqrt64.special" { + expect(math.isPositiveInf(@sqrt(f64, math.inf(f64)))); + expect(@sqrt(f64, 0.0) == 0.0); + expect(@sqrt(f64, -0.0) == -0.0); + expect(math.isNan(@sqrt(f64, -1.0))); + expect(math.isNan(@sqrt(f64, math.nan(f64)))); +} + +fn sqrt_int(comptime T: type, value: T) @IntType(false, T.bit_count / 2) { + var op = value; + var res: T = 0; + var one: T = 1 << (T.bit_count - 2); + + // "one" starts at the highest power of four <= than the argument. + while (one > op) { + one >>= 2; + } + + while (one != 0) { + if (op >= res + one) { + op -= res + one; + res += 2 * one; + } + res >>= 1; + one >>= 2; + } + + const ResultType = @IntType(false, T.bit_count / 2); + return @intCast(ResultType, res); +} + +test "math.sqrt_int" { + expect(sqrt_int(u32, 3) == 1); + expect(sqrt_int(u32, 4) == 2); + expect(sqrt_int(u32, 5) == 2); + expect(sqrt_int(u32, 8) == 2); + expect(sqrt_int(u32, 9) == 3); + expect(sqrt_int(u32, 10) == 3); +} diff --git a/lib/std/math/tan.zig b/lib/std/math/tan.zig new file mode 100644 index 0000000000..049c85df12 --- /dev/null +++ b/lib/std/math/tan.zig @@ -0,0 +1,122 @@ +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE +// +// https://golang.org/src/math/tan.go + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; + +/// Returns the tangent of the radian value x. +/// +/// Special Cases: +/// - tan(+-0) = +-0 +/// - tan(+-inf) = nan +/// - tan(nan) = nan +pub fn tan(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => tan_(f32, x), + f64 => tan_(f64, x), + else => @compileError("tan not implemented for " ++ @typeName(T)), + }; +} + +const Tp0 = -1.30936939181383777646E4; +const Tp1 = 1.15351664838587416140E6; +const Tp2 = -1.79565251976484877988E7; + +const Tq1 = 1.36812963470692954678E4; +const Tq2 = -1.32089234440210967447E6; +const Tq3 = 2.50083801823357915839E7; +const Tq4 = -5.38695755929454629881E7; + +const pi4a = 7.85398125648498535156e-1; +const pi4b = 3.77489470793079817668E-8; +const pi4c = 2.69515142907905952645E-15; +const m4pi = 1.273239544735162542821171882678754627704620361328125; + +fn tan_(comptime T: type, x_: T) T { + const I = @IntType(true, T.bit_count); + + var x = x_; + if (x == 0 or math.isNan(x)) { + return x; + } + if (math.isInf(x)) { + return math.nan(T); + } + + var sign = x < 0; + x = math.fabs(x); + + var y = math.floor(x * m4pi); + var j = @floatToInt(I, y); + + if (j & 1 == 1) { + j += 1; + y += 1; + } + + const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; + const w = z * z; + + var r = if (w > 1e-14) + z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4)) + else + z; + + if (j & 2 == 2) { + r = -1 / r; + } + + return if (sign) -r else r; +} + +test "math.tan" { + expect(tan(f32(0.0)) == tan_(f32, 0.0)); + expect(tan(f64(0.0)) == tan_(f64, 0.0)); +} + +test "math.tan32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, tan_(f32, 0.0), 0.0, epsilon)); + expect(math.approxEq(f32, tan_(f32, 0.2), 0.202710, epsilon)); + expect(math.approxEq(f32, tan_(f32, 0.8923), 1.240422, epsilon)); + expect(math.approxEq(f32, tan_(f32, 1.5), 14.101420, epsilon)); + expect(math.approxEq(f32, tan_(f32, 37.45), -0.254397, epsilon)); + expect(math.approxEq(f32, tan_(f32, 89.123), 2.285852, epsilon)); +} + +test "math.tan64" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + const epsilon = 0.000001; + + expect(math.approxEq(f64, tan_(f64, 0.0), 0.0, epsilon)); + expect(math.approxEq(f64, tan_(f64, 0.2), 0.202710, epsilon)); + expect(math.approxEq(f64, tan_(f64, 0.8923), 1.240422, epsilon)); + expect(math.approxEq(f64, tan_(f64, 1.5), 14.101420, epsilon)); + expect(math.approxEq(f64, tan_(f64, 37.45), -0.254397, epsilon)); + expect(math.approxEq(f64, tan_(f64, 89.123), 2.2858376, epsilon)); +} + +test "math.tan32.special" { + expect(tan_(f32, 0.0) == 0.0); + expect(tan_(f32, -0.0) == -0.0); + expect(math.isNan(tan_(f32, math.inf(f32)))); + expect(math.isNan(tan_(f32, -math.inf(f32)))); + expect(math.isNan(tan_(f32, math.nan(f32)))); +} + +test "math.tan64.special" { + expect(tan_(f64, 0.0) == 0.0); + expect(tan_(f64, -0.0) == -0.0); + expect(math.isNan(tan_(f64, math.inf(f64)))); + expect(math.isNan(tan_(f64, -math.inf(f64)))); + expect(math.isNan(tan_(f64, math.nan(f64)))); +} diff --git a/lib/std/math/tanh.zig b/lib/std/math/tanh.zig new file mode 100644 index 0000000000..48d26d091e --- /dev/null +++ b/lib/std/math/tanh.zig @@ -0,0 +1,160 @@ +// 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/math/tanhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/tanh.c + +const builtin = @import("builtin"); +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const expo2 = @import("expo2.zig").expo2; +const maxInt = std.math.maxInt; + +/// Returns the hyperbolic tangent of x. +/// +/// Special Cases: +/// - sinh(+-0) = +-0 +/// - sinh(+-inf) = +-1 +/// - sinh(nan) = nan +pub fn tanh(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => tanh32(x), + f64 => tanh64(x), + else => @compileError("tanh not implemented for " ++ @typeName(T)), + }; +} + +// tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)) +// = (exp(2x) - 1) / (exp(2x) - 1 + 2) +// = (1 - exp(-2x)) / (exp(-2x) - 1 + 2) +fn tanh32(x: f32) f32 { + const u = @bitCast(u32, x); + const ux = u & 0x7FFFFFFF; + const ax = @bitCast(f32, ux); + + var t: f32 = undefined; + + if (x == 0.0 or math.isNan(x)) { + return x; + } + + // |x| < log(3) / 2 ~= 0.5493 or nan + if (ux > 0x3F0C9F54) { + // |x| > 10 + if (ux > 0x41200000) { + t = 1.0; + } else { + t = math.expm1(2 * x); + t = 1 - 2 / (t + 2); + } + } + // |x| > log(5 / 3) / 2 ~= 0.2554 + else if (ux > 0x3E82C578) { + t = math.expm1(2 * x); + t = t / (t + 2); + } + // |x| >= 0x1.0p-126 + else if (ux >= 0x00800000) { + t = math.expm1(-2 * x); + t = -t / (t + 2); + } + // |x| is subnormal + else { + math.forceEval(x * x); + t = x; + } + + if (u >> 31 != 0) { + return -t; + } else { + return t; + } +} + +fn tanh64(x: f64) f64 { + const u = @bitCast(u64, x); + const w = @intCast(u32, u >> 32); + const ax = @bitCast(f64, u & (maxInt(u64) >> 1)); + + var t: f64 = undefined; + + // TODO: Shouldn't need these checks. + if (x == 0.0 or math.isNan(x)) { + return x; + } + + // |x| < log(3) / 2 ~= 0.5493 or nan + if (w > 0x3FE193EA) { + // |x| > 20 or nan + if (w > 0x40340000) { + t = 1.0; + } else { + t = math.expm1(2 * x); + t = 1 - 2 / (t + 2); + } + } + // |x| > log(5 / 3) / 2 ~= 0.2554 + else if (w > 0x3FD058AE) { + t = math.expm1(2 * x); + t = t / (t + 2); + } + // |x| >= 0x1.0p-1022 + else if (w >= 0x00100000) { + t = math.expm1(-2 * x); + t = -t / (t + 2); + } + // |x| is subnormal + else { + math.forceEval(@floatCast(f32, x)); + t = x; + } + + if (u >> 63 != 0) { + return -t; + } else { + return t; + } +} + +test "math.tanh" { + expect(tanh(f32(1.5)) == tanh32(1.5)); + expect(tanh(f64(1.5)) == tanh64(1.5)); +} + +test "math.tanh32" { + const epsilon = 0.000001; + + expect(math.approxEq(f32, tanh32(0.0), 0.0, epsilon)); + expect(math.approxEq(f32, tanh32(0.2), 0.197375, epsilon)); + expect(math.approxEq(f32, tanh32(0.8923), 0.712528, epsilon)); + expect(math.approxEq(f32, tanh32(1.5), 0.905148, epsilon)); + expect(math.approxEq(f32, tanh32(37.45), 1.0, epsilon)); +} + +test "math.tanh64" { + const epsilon = 0.000001; + + expect(math.approxEq(f64, tanh64(0.0), 0.0, epsilon)); + expect(math.approxEq(f64, tanh64(0.2), 0.197375, epsilon)); + expect(math.approxEq(f64, tanh64(0.8923), 0.712528, epsilon)); + expect(math.approxEq(f64, tanh64(1.5), 0.905148, epsilon)); + expect(math.approxEq(f64, tanh64(37.45), 1.0, epsilon)); +} + +test "math.tanh32.special" { + expect(tanh32(0.0) == 0.0); + expect(tanh32(-0.0) == -0.0); + expect(tanh32(math.inf(f32)) == 1.0); + expect(tanh32(-math.inf(f32)) == -1.0); + expect(math.isNan(tanh32(math.nan(f32)))); +} + +test "math.tanh64.special" { + expect(tanh64(0.0) == 0.0); + expect(tanh64(-0.0) == -0.0); + expect(tanh64(math.inf(f64)) == 1.0); + expect(tanh64(-math.inf(f64)) == -1.0); + expect(math.isNan(tanh64(math.nan(f64)))); +} diff --git a/lib/std/math/trunc.zig b/lib/std/math/trunc.zig new file mode 100644 index 0000000000..219bcd4914 --- /dev/null +++ b/lib/std/math/trunc.zig @@ -0,0 +1,100 @@ +// 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/math/truncf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/trunc.c + +const std = @import("../std.zig"); +const math = std.math; +const expect = std.testing.expect; +const maxInt = std.math.maxInt; + +/// Returns the integer value of x. +/// +/// Special Cases: +/// - trunc(+-0) = +-0 +/// - trunc(+-inf) = +-inf +/// - trunc(nan) = nan +pub fn trunc(x: var) @typeOf(x) { + const T = @typeOf(x); + return switch (T) { + f32 => trunc32(x), + f64 => trunc64(x), + else => @compileError("trunc not implemented for " ++ @typeName(T)), + }; +} + +fn trunc32(x: f32) f32 { + const u = @bitCast(u32, x); + var e = @intCast(i32, ((u >> 23) & 0xFF)) - 0x7F + 9; + var m: u32 = undefined; + + if (e >= 23 + 9) { + return x; + } + if (e < 9) { + e = 1; + } + + m = u32(maxInt(u32)) >> @intCast(u5, e); + if (u & m == 0) { + return x; + } else { + math.forceEval(x + 0x1p120); + return @bitCast(f32, u & ~m); + } +} + +fn trunc64(x: f64) f64 { + const u = @bitCast(u64, x); + var e = @intCast(i32, ((u >> 52) & 0x7FF)) - 0x3FF + 12; + var m: u64 = undefined; + + if (e >= 52 + 12) { + return x; + } + if (e < 12) { + e = 1; + } + + m = u64(maxInt(u64)) >> @intCast(u6, e); + if (u & m == 0) { + return x; + } else { + math.forceEval(x + 0x1p120); + return @bitCast(f64, u & ~m); + } +} + +test "math.trunc" { + expect(trunc(f32(1.3)) == trunc32(1.3)); + expect(trunc(f64(1.3)) == trunc64(1.3)); +} + +test "math.trunc32" { + expect(trunc32(1.3) == 1.0); + expect(trunc32(-1.3) == -1.0); + expect(trunc32(0.2) == 0.0); +} + +test "math.trunc64" { + expect(trunc64(1.3) == 1.0); + expect(trunc64(-1.3) == -1.0); + expect(trunc64(0.2) == 0.0); +} + +test "math.trunc32.special" { + expect(trunc32(0.0) == 0.0); // 0x3F800000 + expect(trunc32(-0.0) == -0.0); + expect(math.isPositiveInf(trunc32(math.inf(f32)))); + expect(math.isNegativeInf(trunc32(-math.inf(f32)))); + expect(math.isNan(trunc32(math.nan(f32)))); +} + +test "math.trunc64.special" { + expect(trunc64(0.0) == 0.0); + expect(trunc64(-0.0) == -0.0); + expect(math.isPositiveInf(trunc64(math.inf(f64)))); + expect(math.isNegativeInf(trunc64(-math.inf(f64)))); + expect(math.isNan(trunc64(math.nan(f64)))); +} |
