aboutsummaryrefslogtreecommitdiff
path: root/lib/std/math
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std/math')
-rw-r--r--lib/std/math/acos.zig186
-rw-r--r--lib/std/math/acosh.zig94
-rw-r--r--lib/std/math/asin.zig183
-rw-r--r--lib/std/math/asinh.zig134
-rw-r--r--lib/std/math/atan.zig255
-rw-r--r--lib/std/math/atan2.zig289
-rw-r--r--lib/std/math/atanh.zig121
-rw-r--r--lib/std/math/big.zig7
-rw-r--r--lib/std/math/big/int.zig2314
-rw-r--r--lib/std/math/big/rational.zig946
-rw-r--r--lib/std/math/cbrt.zig163
-rw-r--r--lib/std/math/ceil.zig120
-rw-r--r--lib/std/math/complex.zig183
-rw-r--r--lib/std/math/complex/abs.zig19
-rw-r--r--lib/std/math/complex/acos.zig22
-rw-r--r--lib/std/math/complex/acosh.zig22
-rw-r--r--lib/std/math/complex/arg.zig19
-rw-r--r--lib/std/math/complex/asin.zig28
-rw-r--r--lib/std/math/complex/asinh.zig23
-rw-r--r--lib/std/math/complex/atan.zig142
-rw-r--r--lib/std/math/complex/atanh.zig23
-rw-r--r--lib/std/math/complex/conj.zig18
-rw-r--r--lib/std/math/complex/cos.zig22
-rw-r--r--lib/std/math/complex/cosh.zig177
-rw-r--r--lib/std/math/complex/exp.zig143
-rw-r--r--lib/std/math/complex/ldexp.zig80
-rw-r--r--lib/std/math/complex/log.zig24
-rw-r--r--lib/std/math/complex/pow.zig23
-rw-r--r--lib/std/math/complex/proj.zig25
-rw-r--r--lib/std/math/complex/sin.zig23
-rw-r--r--lib/std/math/complex/sinh.zig176
-rw-r--r--lib/std/math/complex/sqrt.zig146
-rw-r--r--lib/std/math/complex/tan.zig23
-rw-r--r--lib/std/math/complex/tanh.zig125
-rw-r--r--lib/std/math/copysign.zig74
-rw-r--r--lib/std/math/cos.zig128
-rw-r--r--lib/std/math/cosh.zig127
-rw-r--r--lib/std/math/exp.zig218
-rw-r--r--lib/std/math/exp2.zig455
-rw-r--r--lib/std/math/expm1.zig328
-rw-r--r--lib/std/math/expo2.zig35
-rw-r--r--lib/std/math/fabs.zig101
-rw-r--r--lib/std/math/floor.zig171
-rw-r--r--lib/std/math/fma.zig172
-rw-r--r--lib/std/math/frexp.zig178
-rw-r--r--lib/std/math/hypot.zig168
-rw-r--r--lib/std/math/ilogb.zig138
-rw-r--r--lib/std/math/inf.zig13
-rw-r--r--lib/std/math/isfinite.zig41
-rw-r--r--lib/std/math/isinf.zig131
-rw-r--r--lib/std/math/isnan.zig27
-rw-r--r--lib/std/math/isnormal.zig38
-rw-r--r--lib/std/math/ln.zig190
-rw-r--r--lib/std/math/log.zig72
-rw-r--r--lib/std/math/log10.zig218
-rw-r--r--lib/std/math/log1p.zig230
-rw-r--r--lib/std/math/log2.zig214
-rw-r--r--lib/std/math/modf.zig210
-rw-r--r--lib/std/math/nan.zig24
-rw-r--r--lib/std/math/pow.zig264
-rw-r--r--lib/std/math/powi.zig188
-rw-r--r--lib/std/math/round.zig126
-rw-r--r--lib/std/math/scalbn.zig92
-rw-r--r--lib/std/math/signbit.zig50
-rw-r--r--lib/std/math/sin.zig150
-rw-r--r--lib/std/math/sinh.zig132
-rw-r--r--lib/std/math/sqrt.zig136
-rw-r--r--lib/std/math/tan.zig122
-rw-r--r--lib/std/math/tanh.zig160
-rw-r--r--lib/std/math/trunc.zig100
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))));
+}