From 5bbec42a4edb3f6758a9d67fcf763c660ac3f204 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 20 Jun 2017 23:01:04 +1200 Subject: [PATCH] Add math special case tests and general fixes - Should cover special case inputs for most functions - Fixed a number of runtime panicking behaviour reliant on shift overflow/division by zero etc. --- std/math/acos.zig | 18 +++++++- std/math/acosh.zig | 15 +++++++ std/math/asin.zig | 23 +++++++++- std/math/asinh.zig | 31 +++++++++++++ std/math/atan.zig | 23 ++++++++++ std/math/atan2.zig | 68 ++++++++++++++++++++++++++++ std/math/atanh.zig | 32 +++++++++++++- std/math/cbrt.zig | 22 +++++++++ std/math/ceil.zig | 27 ++++++++++++ std/math/cos.zig | 18 +++++++- std/math/cosh.zig | 29 +++++++++++- std/math/exp.zig | 16 +++++++ std/math/exp2.zig | 21 ++++++++- std/math/expm1.zig | 37 ++++++++++++++-- std/math/{_expo2.zig => expo2.zig} | 0 std/math/fabs.zig | 17 +++++++ std/math/floor.zig | 27 ++++++++++++ std/math/frexp.zig | 45 +++++++++++++++++++ std/math/hypot.zig | 25 +++++++++++ std/math/ilogb.zig | 33 +++++++++++++- std/math/index.zig | 2 + std/math/isnan.zig | 6 +++ std/math/ln.zig | 33 +++++++++++--- std/math/log10.zig | 33 +++++++++++--- std/math/log1p.zig | 44 ++++++++++++++---- std/math/log2.zig | 33 +++++++++++--- std/math/modf.zig | 48 +++++++++++++++++++- std/math/nan.zig | 12 +++++ std/math/pow.zig | 71 ++++++++++++++++++++++++++++-- std/math/round.zig | 22 +++++++++ std/math/sin.zig | 22 +++++++++ std/math/sinh.zig | 27 +++++++++++- std/math/sqrt.zig | 29 ++++++++++-- std/math/tan.zig | 22 +++++++++ std/math/tanh.zig | 37 ++++++++++++++-- std/math/trunc.zig | 22 +++++++++ 36 files changed, 936 insertions(+), 54 deletions(-) rename std/math/{_expo2.zig => expo2.zig} (100%) diff --git a/std/math/acos.zig b/std/math/acos.zig index 27764b3019..9d09b628cb 100644 --- a/std/math/acos.zig +++ b/std/math/acos.zig @@ -1,3 +1,7 @@ +// Special Cases: +// +// - acos(x) = nan if x < -1 or x > 1 + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -40,7 +44,7 @@ fn acos32(x: f32) -> f32 { return 0; } } else { - return 0 / (x - x); + return math.nan(f32); } } @@ -109,7 +113,7 @@ fn acos64(x: f64) -> f64 { } } - return 0 / (x - x); + return math.nan(f32); } // |x| < 0.5 @@ -166,3 +170,13 @@ test "math.acos64" { assert(math.approxEq(f64, acos64(0.8923), 0.468382, epsilon)); assert(math.approxEq(f64, acos64(-0.2), 1.772154, epsilon)); } + +test "math.acos32.special" { + assert(math.isNan(acos32(-2))); + assert(math.isNan(acos32(1.5))); +} + +test "math.acos64.special" { + assert(math.isNan(acos64(-2))); + assert(math.isNan(acos64(1.5))); +} diff --git a/std/math/acosh.zig b/std/math/acosh.zig index 96d77261a4..16c869f5f6 100644 --- a/std/math/acosh.zig +++ b/std/math/acosh.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - acosh(x) = snan if x < 1 +// - acosh(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -72,3 +77,13 @@ test "math.acosh64" { assert(math.approxEq(f64, acosh64(89.123), 5.183133, epsilon)); assert(math.approxEq(f64, acosh64(123123.234375), 12.414088, epsilon)); } + +test "math.acosh32.special" { + assert(math.isNan(acosh32(math.nan(f32)))); + assert(math.isSignalNan(acosh32(0.5))); +} + +test "math.acosh64.special" { + assert(math.isNan(acosh64(math.nan(f64)))); + assert(math.isSignalNan(acosh64(0.5))); +} diff --git a/std/math/asin.zig b/std/math/asin.zig index be8d006cac..3f705a9cf7 100644 --- a/std/math/asin.zig +++ b/std/math/asin.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - asin(+-0) = +-0 +// - asin(x) = nan if x < -1 or x > 1 + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -36,7 +41,7 @@ fn asin32(x: f32) -> f32 { if (ix == 0x3F800000) { return x * pio2 + 0x1.0p-120; // asin(+-1) = +-pi/2 with inexact } else { - return 0 / (x - x); // asin(|x| > 1) is nan + return math.nan(f32); // asin(|x| > 1) is nan } } @@ -95,7 +100,7 @@ fn asin64(x: f64) -> f64 { if ((ix - 0x3FF00000) | lx == 0) { return x * pio2_hi + 0x1.0p-120; } else { - return 0/ (x - x); + return math.nan(f64); } } @@ -158,3 +163,17 @@ test "math.asin64" { assert(math.approxEq(f64, asin64(0.5), 0.523599, epsilon)); assert(math.approxEq(f64, asin64(0.8923), 1.102415, epsilon)); } + +test "math.asin32.special" { + assert(asin32(0.0) == 0.0); + assert(asin32(-0.0) == -0.0); + assert(math.isNan(asin32(-2))); + assert(math.isNan(asin32(1.5))); +} + +test "math.asin64.special" { + assert(asin64(0.0) == 0.0); + assert(asin64(-0.0) == -0.0); + assert(math.isNan(asin64(-2))); + assert(math.isNan(asin64(1.5))); +} diff --git a/std/math/asinh.zig b/std/math/asinh.zig index 587d1927b9..27394e9ce4 100644 --- a/std/math/asinh.zig +++ b/std/math/asinh.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - asinh(+-0) = +-0 +// - asinh(+-inf) = +-inf +// - asinh(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -21,6 +27,11 @@ fn asinh32(x: f32) -> f32 { 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; @@ -48,6 +59,10 @@ fn asinh64(x: f64) -> f64 { var rx = @bitCast(f64, u & (@maxValue(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; @@ -96,3 +111,19 @@ test "math.asinh64" { assert(math.approxEq(f64, asinh64(89.123), 5.183196, epsilon)); assert(math.approxEq(f64, asinh64(123123.234375), 12.414088, epsilon)); } + +test "math.asinh32.special" { + assert(asinh32(0.0) == 0.0); + assert(asinh32(-0.0) == -0.0); + assert(math.isPositiveInf(asinh32(math.inf(f32)))); + assert(math.isNegativeInf(asinh32(-math.inf(f32)))); + assert(math.isNan(asinh32(math.nan(f32)))); +} + +test "math.asinh64.special" { + assert(asinh64(0.0) == 0.0); + assert(asinh64(-0.0) == -0.0); + assert(math.isPositiveInf(asinh64(math.inf(f64)))); + assert(math.isNegativeInf(asinh64(-math.inf(f64)))); + assert(math.isNan(asinh64(math.nan(f64)))); +} diff --git a/std/math/atan.zig b/std/math/atan.zig index 3e67b47ca0..1cd1f570b6 100644 --- a/std/math/atan.zig +++ b/std/math/atan.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - atan(+-0) = +-0 +// - atan(+-inf) = +-pi/2 + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -228,3 +233,21 @@ test "math.atan64" { assert(math.approxEq(f64, atan64(0.8923), 0.728545, epsilon)); assert(math.approxEq(f64, atan64(1.5), 0.982794, epsilon)); } + +test "math.atan32.special" { + const epsilon = 0.000001; + + assert(atan32(0.0) == 0.0); + assert(atan32(-0.0) == -0.0); + assert(math.approxEq(f32, atan32(math.inf(f32)), math.pi_2, epsilon)); + assert(math.approxEq(f32, atan32(-math.inf(f32)), -math.pi_2, epsilon)); +} + +test "math.atan64.special" { + const epsilon = 0.000001; + + assert(atan64(0.0) == 0.0); + assert(atan64(-0.0) == -0.0); + assert(math.approxEq(f64, atan64(math.inf(f64)), math.pi_2, epsilon)); + assert(math.approxEq(f64, atan64(-math.inf(f64)), -math.pi_2, epsilon)); +} diff --git a/std/math/atan2.zig b/std/math/atan2.zig index ac1fe30da2..7abb5a2742 100644 --- a/std/math/atan2.zig +++ b/std/math/atan2.zig @@ -1,3 +1,23 @@ +// 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 + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -215,3 +235,51 @@ test "math.atan2_64" { assert(math.approxEq(f64, atan2_64(0.34, -0.4), 2.437099, epsilon)); assert(math.approxEq(f64, atan2_64(0.34, 1.243), 0.267001, epsilon)); } + +test "math.atan2_32.special" { + const epsilon = 0.000001; + + assert(math.isNan(atan2_32(1.0, math.nan(f32)))); + assert(math.isNan(atan2_32(math.nan(f32), 1.0))); + assert(atan2_32(0.0, 5.0) == 0.0); + assert(atan2_32(-0.0, 5.0) == -0.0); + assert(math.approxEq(f32, atan2_32(0.0, -5.0), math.pi, epsilon)); + assert(math.approxEq(f32, atan2_32(-0.0, -5.0), -math.pi, epsilon)); + assert(math.approxEq(f32, atan2_32(1.0, 0.0), math.pi_2, epsilon)); + assert(math.approxEq(f32, atan2_32(1.0, -0.0), math.pi_2, epsilon)); + assert(math.approxEq(f32, atan2_32(-1.0, 0.0), -math.pi_2, epsilon)); + assert(math.approxEq(f32, atan2_32(-1.0, -0.0), -math.pi_2, epsilon)); + assert(math.approxEq(f32, atan2_32(math.inf(f32), math.inf(f32)), math.pi_4, epsilon)); + assert(math.approxEq(f32, atan2_32(-math.inf(f32), math.inf(f32)), -math.pi_4, epsilon)); + assert(math.approxEq(f32, atan2_32(math.inf(f32), -math.inf(f32)), 3.0 * math.pi_4, epsilon)); + assert(math.approxEq(f32, atan2_32(-math.inf(f32), -math.inf(f32)), -3.0 * math.pi_4, epsilon)); + assert(atan2_32(1.0, math.inf(f32)) == 0.0); + assert(math.approxEq(f32, atan2_32(1.0, -math.inf(f32)), math.pi, epsilon)); + assert(math.approxEq(f32, atan2_32(-1.0, -math.inf(f32)), -math.pi, epsilon)); + assert(math.approxEq(f32, atan2_32(math.inf(f32), 1.0), math.pi_2, epsilon)); + assert(math.approxEq(f32, atan2_32(-math.inf(f32), 1.0), -math.pi_2, epsilon)); +} + +test "math.atan2_64.special" { + const epsilon = 0.000001; + + assert(math.isNan(atan2_64(1.0, math.nan(f64)))); + assert(math.isNan(atan2_64(math.nan(f64), 1.0))); + assert(atan2_64(0.0, 5.0) == 0.0); + assert(atan2_64(-0.0, 5.0) == -0.0); + assert(math.approxEq(f64, atan2_64(0.0, -5.0), math.pi, epsilon)); + assert(math.approxEq(f64, atan2_64(-0.0, -5.0), -math.pi, epsilon)); + assert(math.approxEq(f64, atan2_64(1.0, 0.0), math.pi_2, epsilon)); + assert(math.approxEq(f64, atan2_64(1.0, -0.0), math.pi_2, epsilon)); + assert(math.approxEq(f64, atan2_64(-1.0, 0.0), -math.pi_2, epsilon)); + assert(math.approxEq(f64, atan2_64(-1.0, -0.0), -math.pi_2, epsilon)); + assert(math.approxEq(f64, atan2_64(math.inf(f64), math.inf(f64)), math.pi_4, epsilon)); + assert(math.approxEq(f64, atan2_64(-math.inf(f64), math.inf(f64)), -math.pi_4, epsilon)); + assert(math.approxEq(f64, atan2_64(math.inf(f64), -math.inf(f64)), 3.0 * math.pi_4, epsilon)); + assert(math.approxEq(f64, atan2_64(-math.inf(f64), -math.inf(f64)), -3.0 * math.pi_4, epsilon)); + assert(atan2_64(1.0, math.inf(f64)) == 0.0); + assert(math.approxEq(f64, atan2_64(1.0, -math.inf(f64)), math.pi, epsilon)); + assert(math.approxEq(f64, atan2_64(-1.0, -math.inf(f64)), -math.pi, epsilon)); + assert(math.approxEq(f64, atan2_64(math.inf(f64), 1.0), math.pi_2, epsilon)); + assert(math.approxEq(f64, atan2_64(-math.inf(f64), 1.0), -math.pi_2, epsilon)); +} diff --git a/std/math/atanh.zig b/std/math/atanh.zig index 25540d2996..7b1f3d0989 100644 --- a/std/math/atanh.zig +++ b/std/math/atanh.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - atanh(+-1) = +-inf with signal +// - atanh(x) = nan if |x| > 1 with signal +// - atanh(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -21,6 +27,10 @@ fn atanh_32(x: f32) -> f32 { 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 @@ -33,7 +43,6 @@ fn atanh_32(x: f32) -> f32 { y = 0.5 * math.log1p(2 * y + 2 * y * y / (1 - y)); } } else { - // avoid overflow y = 0.5 * math.log1p(2 * (y / (1 - y))); } @@ -47,6 +56,10 @@ fn atanh_64(x: f64) -> f64 { var y = @bitCast(f64, u & (@maxValue(u64) >> 1)); // |x| + if (y == 1.0) { + return math.copysign(f64, math.inf(f64), x); + } + if (e < 0x3FF - 1) { if (e < 0x3FF - 32) { // underflow @@ -59,7 +72,6 @@ fn atanh_64(x: f64) -> f64 { y = 0.5 * math.log1p(2 * y + 2 * y * y / (1 - y)); } } else { - // avoid overflow y = 0.5 * math.log1p(2 * (y / (1 - y))); } @@ -86,3 +98,19 @@ test "math.atanh_64" { assert(math.approxEq(f64, atanh_64(0.2), 0.202733, epsilon)); assert(math.approxEq(f64, atanh_64(0.8923), 1.433099, epsilon)); } + +test "math.atanh32.special" { + assert(math.isPositiveInf(atanh_32(1))); + assert(math.isNegativeInf(atanh_32(-1))); + assert(math.isSignalNan(atanh_32(1.5))); + assert(math.isSignalNan(atanh_32(-1.5))); + assert(math.isNan(atanh_32(math.nan(f32)))); +} + +test "math.atanh64.special" { + assert(math.isPositiveInf(atanh_64(1))); + assert(math.isNegativeInf(atanh_64(-1))); + assert(math.isSignalNan(atanh_64(1.5))); + assert(math.isSignalNan(atanh_64(-1.5))); + assert(math.isNan(atanh_64(math.nan(f64)))); +} diff --git a/std/math/cbrt.zig b/std/math/cbrt.zig index ac6e90576e..3674f10c34 100644 --- a/std/math/cbrt.zig +++ b/std/math/cbrt.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - cbrt(+-0) = +-0 +// - cbrt(+-inf) = +-inf +// - cbrt(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -135,3 +141,19 @@ test "math.cbrt64" { assert(math.approxEq(f64, cbrt64(37.45), 3.345676, epsilon)); assert(math.approxEq(f64, cbrt64(123123.234375), 49.748501, epsilon)); } + +test "math.cbrt.special" { + assert(cbrt32(0.0) == 0.0); + assert(cbrt32(-0.0) == -0.0); + assert(math.isPositiveInf(cbrt32(math.inf(f32)))); + assert(math.isNegativeInf(cbrt32(-math.inf(f32)))); + assert(math.isNan(cbrt32(math.nan(f32)))); +} + +test "math.cbrt64.special" { + assert(cbrt64(0.0) == 0.0); + assert(cbrt64(-0.0) == -0.0); + assert(math.isPositiveInf(cbrt64(math.inf(f64)))); + assert(math.isNegativeInf(cbrt64(-math.inf(f64)))); + assert(math.isNan(cbrt64(math.nan(f64)))); +} diff --git a/std/math/ceil.zig b/std/math/ceil.zig index f5bc22500a..933182e771 100644 --- a/std/math/ceil.zig +++ b/std/math/ceil.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - ceil(+-0) = +-0 +// - ceil(+-inf) = +-inf +// - ceil(nan) = nan + const builtin = @import("builtin"); const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -19,6 +25,11 @@ fn ceil32(x: f32) -> f32 { var e = 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; } @@ -90,3 +101,19 @@ test "math.ceil64" { assert(ceil64(-1.3) == -1.0); assert(ceil64(0.2) == 1.0); } + +test "math.ceil32.special" { + assert(ceil32(0.0) == 0.0); + assert(ceil32(-0.0) == -0.0); + assert(math.isPositiveInf(ceil32(math.inf(f32)))); + assert(math.isNegativeInf(ceil32(-math.inf(f32)))); + assert(math.isNan(ceil32(math.nan(f32)))); +} + +test "math.ceil64.special" { + assert(ceil64(0.0) == 0.0); + assert(ceil64(-0.0) == -0.0); + assert(math.isPositiveInf(ceil64(math.inf(f64)))); + assert(math.isNegativeInf(ceil64(-math.inf(f64)))); + assert(math.isNan(ceil64(math.nan(f64)))); +} diff --git a/std/math/cos.zig b/std/math/cos.zig index 8b5a1211e3..8bd2a275f7 100644 --- a/std/math/cos.zig +++ b/std/math/cos.zig @@ -1,7 +1,11 @@ +// Special Cases: +// +// - cos(+-inf) = nan +// - cos(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; - // TODO issue #393 pub const cos = cos_workaround; @@ -163,3 +167,15 @@ test "math.cos64" { assert(math.approxEq(f64, cos64(37.45), 0.969132, epsilon)); assert(math.approxEq(f64, cos64(89.123), 0.40080, epsilon)); } + +test "math.cos32.special" { + assert(math.isNan(cos32(math.inf(f32)))); + assert(math.isNan(cos32(-math.inf(f32)))); + assert(math.isNan(cos32(math.nan(f32)))); +} + +test "math.cos64.special" { + assert(math.isNan(cos64(math.inf(f64)))); + assert(math.isNan(cos64(-math.inf(f64)))); + assert(math.isNan(cos64(math.nan(f64)))); +} diff --git a/std/math/cosh.zig b/std/math/cosh.zig index 5eba8baa5b..e593485315 100644 --- a/std/math/cosh.zig +++ b/std/math/cosh.zig @@ -1,5 +1,11 @@ +// Special Cases: +// +// - cosh(+-0) = 1 +// - cosh(+-inf) = +inf +// - cosh(nan) = nan + const math = @import("index.zig"); -const expo2 = @import("_expo2.zig").expo2; +const expo2 = @import("expo2.zig").expo2; const assert = @import("../debug.zig").assert; // TODO issue #393 @@ -47,6 +53,11 @@ fn cosh64(x: f64) -> f64 { const w = u32(u >> 32); const ax = @bitCast(f64, u & (@maxValue(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)) { @@ -92,3 +103,19 @@ test "math.cosh64" { assert(math.approxEq(f64, cosh64(0.8923), 1.425225, epsilon)); assert(math.approxEq(f64, cosh64(1.5), 2.352410, epsilon)); } + +test "math.cosh32.special" { + assert(cosh32(0.0) == 1.0); + assert(cosh32(-0.0) == 1.0); + assert(math.isPositiveInf(cosh32(math.inf(f32)))); + assert(math.isPositiveInf(cosh32(-math.inf(f32)))); + assert(math.isNan(cosh32(math.nan(f32)))); +} + +test "math.cosh64.special" { + assert(cosh64(0.0) == 1.0); + assert(cosh64(-0.0) == 1.0); + assert(math.isPositiveInf(cosh64(math.inf(f64)))); + assert(math.isPositiveInf(cosh64(-math.inf(f64)))); + assert(math.isNan(cosh64(math.nan(f64)))); +} diff --git a/std/math/exp.zig b/std/math/exp.zig index 0e35108a97..40eafde39b 100644 --- a/std/math/exp.zig +++ b/std/math/exp.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - exp(+inf) = +inf +// - exp(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -192,3 +197,14 @@ test "math.exp64" { 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" { + // TODO: Error on release (like pow) + assert(math.isPositiveInf(exp64(math.inf(f64)))); + assert(math.isNan(exp64(math.nan(f64)))); +} diff --git a/std/math/exp2.zig b/std/math/exp2.zig index 5b135a7b22..da8168f95d 100644 --- a/std/math/exp2.zig +++ b/std/math/exp2.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - exp2(+inf) = +inf +// - exp2(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -363,6 +368,11 @@ fn exp2_64(x: f64) -> f64 { const ux = @bitCast(u64, x); const ix = 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 @@ -432,5 +442,14 @@ test "math.exp2_64" { assert(math.approxEq(f64, exp2_64(0.2), 1.148698, epsilon)); assert(math.approxEq(f64, exp2_64(0.8923), 1.856133, epsilon)); assert(math.approxEq(f64, exp2_64(1.5), 2.828427, epsilon)); - // assert(math.approxEq(f64, exp2_64(37.45), 18379273786760560.000000, epsilon)); +} + +test "math.exp2_32.special" { + assert(math.isPositiveInf(exp2_32(math.inf(f32)))); + assert(math.isNan(exp2_32(math.nan(f32)))); +} + +test "math.exp2_64.special" { + assert(math.isPositiveInf(exp2_64(math.inf(f64)))); + assert(math.isNan(exp2_64(math.nan(f64)))); } diff --git a/std/math/expm1.zig b/std/math/expm1.zig index c54913299a..327bf27bcd 100644 --- a/std/math/expm1.zig +++ b/std/math/expm1.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - expm1(+inf) = +inf +// - expm1(-inf) = -1 +// - expm1(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -26,6 +32,11 @@ fn expm1_32(x_: f32) -> f32 { 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 @@ -113,7 +124,7 @@ fn expm1_32(x_: f32) -> f32 { } } - const twopk = @bitCast(f32, u32(0x7F + k) << 23); + const twopk = @bitCast(f32, u32((0x7F + k) <<% 23)); if (k < 0 or k > 56) { var y = x - e + 1.0; @@ -150,6 +161,10 @@ fn expm1_64(x_: f64) -> f64 { const hx = u32(ux >> 32) & 0x7FFFFFFF; const sign = hx >> 63; + if (math.isNegativeInf(x)) { + return -1.0; + } + // |x| >= 56 * ln2 if (hx >= 0x4043687A) { // exp1md(nan) = nan @@ -162,7 +177,7 @@ fn expm1_64(x_: f64) -> f64 { } if (x > o_threshold) { math.raiseOverflow(); - return math.nan(f64); + return math.inf(f64); } } @@ -238,7 +253,7 @@ fn expm1_64(x_: f64) -> f64 { } } - const twopk = @bitCast(f64, u64(0x3FF + k) << 52); + const twopk = @bitCast(f64, u64(0x3FF + k) <<% 52); if (k < 0 or k > 56) { var y = x - e + 1.0; @@ -283,3 +298,19 @@ test "math.expm1_64" { assert(math.approxEq(f64, expm1_64(0.8923), 1.440737, epsilon)); assert(math.approxEq(f64, expm1_64(1.5), 3.481689, epsilon)); } + +test "math.expm1_32.special" { + const epsilon = 0.000001; + + assert(math.isPositiveInf(expm1_32(math.inf(f32)))); + assert(expm1_32(-math.inf(f32)) == -1.0); + assert(math.isNan(expm1_32(math.nan(f32)))); +} + +test "math.expm1_64.special" { + const epsilon = 0.000001; + + assert(math.isPositiveInf(expm1_64(math.inf(f64)))); + assert(expm1_64(-math.inf(f64)) == -1.0); + assert(math.isNan(expm1_64(math.nan(f64)))); +} diff --git a/std/math/_expo2.zig b/std/math/expo2.zig similarity index 100% rename from std/math/_expo2.zig rename to std/math/expo2.zig diff --git a/std/math/fabs.zig b/std/math/fabs.zig index 1e8e0b5e10..7030dcd8bb 100644 --- a/std/math/fabs.zig +++ b/std/math/fabs.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - fabs(+-inf) = +inf +// - fabs(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -39,3 +44,15 @@ test "math.fabs64" { assert(fabs64(1.0) == 1.0); assert(fabs64(-1.0) == 1.0); } + +test "math.fabs32.special" { + assert(math.isPositiveInf(fabs(math.inf(f32)))); + assert(math.isPositiveInf(fabs(-math.inf(f32)))); + assert(math.isNan(fabs(math.nan(f32)))); +} + +test "math.fabs64.special" { + assert(math.isPositiveInf(fabs(math.inf(f64)))); + assert(math.isPositiveInf(fabs(-math.inf(f64)))); + assert(math.isNan(fabs(math.nan(f64)))); +} diff --git a/std/math/floor.zig b/std/math/floor.zig index 7c0b6c414a..a561066e91 100644 --- a/std/math/floor.zig +++ b/std/math/floor.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - floor(+-0) = +-0 +// - floor(+-inf) = +-inf +// - floor(nan) = nan + const builtin = @import("builtin"); const assert = @import("../debug.zig").assert; const math = @import("index.zig"); @@ -19,6 +25,11 @@ fn floor32(x: f32) -> f32 { const e = 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; } @@ -90,3 +101,19 @@ test "math.floor64" { assert(floor64(-1.3) == -2.0); assert(floor64(0.2) == 0.0); } + +test "math.floor32.special" { + assert(floor32(0.0) == 0.0); + assert(floor32(-0.0) == -0.0); + assert(math.isPositiveInf(floor32(math.inf(f32)))); + assert(math.isNegativeInf(floor32(-math.inf(f32)))); + assert(math.isNan(floor32(math.nan(f32)))); +} + +test "math.floor64.special" { + assert(floor64(0.0) == 0.0); + assert(floor64(-0.0) == -0.0); + assert(math.isPositiveInf(floor64(math.inf(f64)))); + assert(math.isNegativeInf(floor64(-math.inf(f64)))); + assert(math.isNan(floor64(math.nan(f64)))); +} diff --git a/std/math/frexp.zig b/std/math/frexp.zig index e0c6975e1c..6de126e723 100644 --- a/std/math/frexp.zig +++ b/std/math/frexp.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - frexp(+-0) = +-0, 0 +// - frexp(+-inf) = +-inf, 0 +// - frexp(nan) = nan, 0 + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -114,3 +120,42 @@ test "math.frexp64" { r = frexp64(78.0234); assert(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); + assert(r.significand == 0.0 and r.exponent == 0); + + r = frexp32(-0.0); + assert(r.significand == -0.0 and r.exponent == 0); + + r = frexp32(math.inf(f32)); + assert(math.isPositiveInf(r.significand) and r.exponent == 0); + + r = frexp32(-math.inf(f32)); + assert(math.isNegativeInf(r.significand) and r.exponent == 0); + + r = frexp32(math.nan(f32)); + assert(math.isNan(r.significand) and r.exponent == 0); +} + +test "math.frexp64.special" { + // TODO: Error on release mode (like pow) + var r: frexp64_result = undefined; + + r = frexp64(0.0); + assert(r.significand == 0.0 and r.exponent == 0); + + r = frexp64(-0.0); + assert(r.significand == -0.0 and r.exponent == 0); + + r = frexp64(math.inf(f64)); + assert(math.isPositiveInf(r.significand) and r.exponent == 0); + + r = frexp64(-math.inf(f64)); + assert(math.isNegativeInf(r.significand) and r.exponent == 0); + + r = frexp64(math.nan(f64)); + assert(math.isNan(r.significand) and r.exponent == 0); +} diff --git a/std/math/hypot.zig b/std/math/hypot.zig index 636c1707fb..f864237ba3 100644 --- a/std/math/hypot.zig +++ b/std/math/hypot.zig @@ -1,3 +1,10 @@ +// Special Cases: +// +// - hypot(+-inf, y) = +inf +// - hypot(x, +-inf) = +inf +// - hypot(nan, y) = nan +// - hypot(x, nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -136,3 +143,21 @@ test "math.hypot64" { assert(math.approxEq(f64, hypot64(89.123, 382.028905), 392.286876, epsilon)); assert(math.approxEq(f64, hypot64(123123.234375, 529428.707813), 543556.885247, epsilon)); } + +test "math.hypot32.special" { + assert(math.isPositiveInf(hypot32(math.inf(f32), 0.0))); + assert(math.isPositiveInf(hypot32(-math.inf(f32), 0.0))); + assert(math.isPositiveInf(hypot32(0.0, math.inf(f32)))); + assert(math.isPositiveInf(hypot32(0.0, -math.inf(f32)))); + assert(math.isNan(hypot32(math.nan(f32), 0.0))); + assert(math.isNan(hypot32(0.0, math.nan(f32)))); +} + +test "math.hypot64.special" { + assert(math.isPositiveInf(hypot64(math.inf(f64), 0.0))); + assert(math.isPositiveInf(hypot64(-math.inf(f64), 0.0))); + assert(math.isPositiveInf(hypot64(0.0, math.inf(f64)))); + assert(math.isPositiveInf(hypot64(0.0, -math.inf(f64)))); + assert(math.isNan(hypot64(math.nan(f64), 0.0))); + assert(math.isNan(hypot64(0.0, math.nan(f64)))); +} diff --git a/std/math/ilogb.zig b/std/math/ilogb.zig index f045bb6239..805a14d9ba 100644 --- a/std/math/ilogb.zig +++ b/std/math/ilogb.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - ilogb(+-inf) = @maxValue(i32) +// - ilogb(0) = @maxValue(i32) +// - ilogb(nan) = @maxValue(i32) + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -21,6 +27,11 @@ fn ilogb32(x: f32) -> i32 { var u = @bitCast(u32, x); var e = i32((u >> 23) & 0xFF); + // TODO: We should be able to merge this with the lower check. + if (math.isNan(x)) { + return @maxValue(i32); + } + if (e == 0) { u <<= 9; if (u == 0) { @@ -38,7 +49,7 @@ fn ilogb32(x: f32) -> i32 { if (e == 0xFF) { math.raiseInvalid(); - if (u << 9 != 0) { + if (u <<% 9 != 0) { return fp_ilogbnan; } else { return @maxValue(i32); @@ -52,6 +63,10 @@ fn ilogb64(x: f64) -> i32 { var u = @bitCast(u64, x); var e = i32((u >> 52) & 0x7FF); + if (math.isNan(x)) { + return @maxValue(i32); + } + if (e == 0) { u <<= 12; if (u == 0) { @@ -69,7 +84,7 @@ fn ilogb64(x: f64) -> i32 { if (e == 0x7FF) { math.raiseInvalid(); - if (u << 12 != 0) { + if (u <<% 12 != 0) { return fp_ilogbnan; } else { return @maxValue(i32); @@ -101,3 +116,17 @@ test "math.ilogb64" { assert(ilogb64(-123984) == 16); assert(ilogb64(2398.23) == 11); } + +test "math.ilogb32.special" { + assert(ilogb32(math.inf(f32)) == @maxValue(i32)); + assert(ilogb32(-math.inf(f32)) == @maxValue(i32)); + assert(ilogb32(0.0) == @minValue(i32)); + assert(ilogb32(math.nan(f32)) == @maxValue(i32)); +} + +test "math.ilogb64.special" { + assert(ilogb64(math.inf(f64)) == @maxValue(i32)); + assert(ilogb64(-math.inf(f64)) == @maxValue(i32)); + assert(ilogb64(0.0) == @minValue(i32)); + assert(ilogb64(math.nan(f64)) == @maxValue(i32)); +} diff --git a/std/math/index.zig b/std/math/index.zig index d2babbddbf..c27c2d8ab2 100644 --- a/std/math/index.zig +++ b/std/math/index.zig @@ -42,6 +42,7 @@ pub const inf_u64 = u64(0x7FF << 52); pub const inf_f64 = @bitCast(f64, inf_u64); pub const nan = @import("nan.zig").nan; +pub const snan = @import("nan.zig").snan; pub const inf = @import("inf.zig").inf; pub fn approxEq(comptime T: type, x: T, y: T, epsilon: T) -> bool { @@ -90,6 +91,7 @@ pub fn raiseDivByZero() { } pub const isNan = @import("isnan.zig").isNan; +pub const isSignalNan = @import("isnan.zig").isSignalNan; pub const fabs = @import("fabs.zig").fabs; pub const ceil = @import("ceil.zig").ceil; pub const floor = @import("floor.zig").floor; diff --git a/std/math/isnan.zig b/std/math/isnan.zig index b6fce507c4..8bcb200a6a 100644 --- a/std/math/isnan.zig +++ b/std/math/isnan.zig @@ -18,6 +18,12 @@ pub fn isNan(x: var) -> bool { } } +// Note: A signalling nan is identical to a standard right now by may have a different bit +// representation in the future when required. +pub fn isSignalNan(x: var) -> bool { + isNan(x) +} + test "math.isNan" { assert(isNan(math.nan(f32))); assert(isNan(math.nan(f64))); diff --git a/std/math/ln.zig b/std/math/ln.zig index 9a695d6922..85335ba33f 100644 --- a/std/math/ln.zig +++ b/std/math/ln.zig @@ -1,3 +1,10 @@ +// Special Cases: +// +// - ln(+inf) = +inf +// - ln(0) = -inf +// - ln(x) = nan if x < 0 +// - ln(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -27,12 +34,12 @@ fn lnf(x_: f32) -> f32 { // x < 2^(-126) if (ix < 0x00800000 or ix >> 31 != 0) { // log(+-0) = -inf - if (ix << 1 == 0) { - return -1 / (x * x); + if (ix <<% 1 == 0) { + return -math.inf(f32); } // log(-#) = nan if (ix >> 31 != 0) { - return (x - x) / 0.0 + return math.nan(f32); } // subnormal, scale x @@ -82,12 +89,12 @@ fn lnd(x_: f64) -> f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf - if (ix << 1 == 0) { - return -1 / (x * x); + if (ix <<% 1 == 0) { + return -math.inf(f64); } // log(-#) = nan if (hx >> 31 != 0) { - return (x - x) / 0.0; + return math.nan(f64); } // subnormal, scale x @@ -148,3 +155,17 @@ test "math.ln64" { assert(math.approxEq(f64, lnd(89.123), 4.490017, epsilon)); assert(math.approxEq(f64, lnd(123123.234375), 11.720941, epsilon)); } + +test "math.ln32.special" { + assert(math.isPositiveInf(lnf(math.inf(f32)))); + assert(math.isNegativeInf(lnf(0.0))); + assert(math.isNan(lnf(-1.0))); + assert(math.isNan(lnf(math.nan(f32)))); +} + +test "math.ln64.special" { + assert(math.isPositiveInf(lnd(math.inf(f64)))); + assert(math.isNegativeInf(lnd(0.0))); + assert(math.isNan(lnd(-1.0))); + assert(math.isNan(lnd(math.nan(f64)))); +} diff --git a/std/math/log10.zig b/std/math/log10.zig index c48043834f..50c942a8bc 100644 --- a/std/math/log10.zig +++ b/std/math/log10.zig @@ -1,3 +1,10 @@ +// Special Cases: +// +// - log10(+inf) = +inf +// - log10(0) = -inf +// - log10(x) = nan if x < 0 +// - log10(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -31,12 +38,12 @@ fn log10_32(x_: f32) -> f32 { // x < 2^(-126) if (ix < 0x00800000 or ix >> 31 != 0) { // log(+-0) = -inf - if (ix << 1 == 0) { - return -1 / (x * x); + if (ix <<% 1 == 0) { + return -math.inf(f32); } // log(-#) = nan if (ix >> 31 != 0) { - return (x - x) / 0.0 + return math.nan(f32); } k -= 25; @@ -93,12 +100,12 @@ fn log10_64(x_: f64) -> f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf - if (ix << 1 == 0) { - return -1 / (x * x); + if (ix <<% 1 == 0) { + return -math.inf(f32); } // log(-#) = nan if (hx >> 31 != 0) { - return (x - x) / 0.0; + return math.nan(f32); } // subnormal, scale x @@ -176,3 +183,17 @@ test "math.log10_64" { assert(math.approxEq(f64, log10_64(89.123), 1.94999, epsilon)); assert(math.approxEq(f64, log10_64(123123.234375), 5.09034, epsilon)); } + +test "math.log10_32.special" { + assert(math.isPositiveInf(log10_32(math.inf(f32)))); + assert(math.isNegativeInf(log10_32(0.0))); + assert(math.isNan(log10_32(-1.0))); + assert(math.isNan(log10_32(math.nan(f32)))); +} + +test "math.log10_64.special" { + assert(math.isPositiveInf(log10_64(math.inf(f64)))); + assert(math.isNegativeInf(log10_64(0.0))); + assert(math.isNan(log10_64(-1.0))); + assert(math.isNan(log10_64(math.nan(f64)))); +} diff --git a/std/math/log1p.zig b/std/math/log1p.zig index a4e96c76d7..6b8f9ba27d 100644 --- a/std/math/log1p.zig +++ b/std/math/log1p.zig @@ -1,3 +1,11 @@ +// Special Cases: +// +// - log1p(+inf) = +inf +// - log1p(+-0) = +-0 +// - log1p(-1) = -inf +// - log1p(x) = nan if x < -1 +// - log1p(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -31,17 +39,17 @@ fn log1p_32(x: f32) -> f32 { if (ix < 0x3ED413D0 or ix >> 31 != 0) { // x <= -1.0 if (ix >= 0xBF800000) { - // log1p(-1) = +inf - if (x == -1) { - return x / 0.0; + // log1p(-1) = -inf + if (x == -1.0) { + return -math.inf(f32); } // log1p(x < -1) = nan else { - return (x - x) / 0.0; + return math.nan(f32); } } // |x| < 2^(-24) - if ((ix << 1) < (0x33800000 << 1)) { + if ((ix <<% 1) < (0x33800000 << 1)) { // underflow if subnormal if (ix & 0x7F800000 == 0) { math.forceEval(x * x); @@ -111,16 +119,16 @@ fn log1p_64(x: f64) -> f64 { // x <= -1.0 if (hx >= 0xBFF00000) { // log1p(-1) = -inf - if (x == 1) { - return x / 0.0; + if (x == -1.0) { + return -math.inf(f64); } // log1p(x < -1) = nan else { - return (x - x) / 0.0; + return math.nan(f64); } } // |x| < 2^(-53) - if ((hx << 1) < (0x3CA00000 << 1)) { + if ((hx <<% 1) < (0x3CA00000 << 1)) { if ((hx & 0x7FF00000) == 0) { math.raiseUnderflow(); } @@ -198,3 +206,21 @@ test "math.log1p_64" { assert(math.approxEq(f64, log1p_64(89.123), 4.501175, epsilon)); assert(math.approxEq(f64, log1p_64(123123.234375), 11.720949, epsilon)); } + +test "math.log1p_32.special" { + assert(math.isPositiveInf(log1p_32(math.inf(f32)))); + assert(log1p_32(0.0) == 0.0); + assert(log1p_32(-0.0) == -0.0); + assert(math.isNegativeInf(log1p_32(-1.0))); + assert(math.isNan(log1p_32(-2.0))); + assert(math.isNan(log1p_32(math.nan(f32)))); +} + +test "math.log1p_64.special" { + assert(math.isPositiveInf(log1p_64(math.inf(f64)))); + assert(log1p_64(0.0) == 0.0); + assert(log1p_64(-0.0) == -0.0); + assert(math.isNegativeInf(log1p_64(-1.0))); + assert(math.isNan(log1p_64(-2.0))); + assert(math.isNan(log1p_64(math.nan(f64)))); +} diff --git a/std/math/log2.zig b/std/math/log2.zig index b4c7da5f08..c136c7166c 100644 --- a/std/math/log2.zig +++ b/std/math/log2.zig @@ -1,3 +1,10 @@ +// Special Cases: +// +// - log2(+inf) = +inf +// - log2(0) = -inf +// - log2(x) = nan if x < 0 +// - log2(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -29,12 +36,12 @@ fn log2_32(x_: f32) -> f32 { // x < 2^(-126) if (ix < 0x00800000 or ix >> 31 != 0) { // log(+-0) = -inf - if (ix << 1 == 0) { - return -1 / (x * x); + if (ix <<% 1 == 0) { + return -math.inf(f32); } // log(-#) = nan if (ix >> 31 != 0) { - return (x - x) / 0.0 + return math.nan(f32); } k -= 25; @@ -87,12 +94,12 @@ fn log2_64(x_: f64) -> f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf - if (ix << 1 == 0) { - return -1 / (x * x); + if (ix <<% 1 == 0) { + return -math.inf(f64); } // log(-#) = nan if (hx >> 31 != 0) { - return (x - x) / 0.0; + return math.nan(f64); } // subnormal, scale x @@ -166,3 +173,17 @@ test "math.log2_64" { assert(math.approxEq(f64, log2_64(37.45), 5.226894, epsilon)); assert(math.approxEq(f64, log2_64(123123.234375), 16.909744, epsilon)); } + +test "math.log2_32.special" { + assert(math.isPositiveInf(log2_32(math.inf(f32)))); + assert(math.isNegativeInf(log2_32(0.0))); + assert(math.isNan(log2_32(-1.0))); + assert(math.isNan(log2_32(math.nan(f32)))); +} + +test "math.log2_64.special" { + assert(math.isPositiveInf(log2_64(math.inf(f64)))); + assert(math.isNegativeInf(log2_64(0.0))); + assert(math.isNan(log2_64(-1.0))); + assert(math.isNan(log2_64(math.nan(f64)))); +} diff --git a/std/math/modf.zig b/std/math/modf.zig index 3b9468b1aa..a10703e346 100644 --- a/std/math/modf.zig +++ b/std/math/modf.zig @@ -1,3 +1,8 @@ +// Special Cases: +// +// - modf(+-inf) = +-inf, nan +// - modf(nan) = nan, nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -29,10 +34,17 @@ fn modf32(x: f32) -> modf32_result { const e = 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 + if (e == 0x80 and u <<% 9 != 0) { // nan result.fpart = x; } else { result.fpart = @bitCast(f32, us); @@ -67,10 +79,16 @@ fn modf64(x: f64) -> modf64_result { const e = 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 + if (e == 0x400 and u <<% 12 != 0) { // nan result.fpart = x; } else { result.fpart = @bitCast(f64, us); @@ -158,3 +176,29 @@ test "math.modf64" { assert(math.approxEq(f64, r.ipart, 1234, epsilon)); assert(math.approxEq(f64, r.fpart, 0.340780, epsilon)); } + +test "math.modf32.special" { + var r: modf32_result = undefined; + + r = modf32(math.inf(f32)); + assert(math.isPositiveInf(r.ipart) and math.isNan(r.fpart)); + + r = modf32(-math.inf(f32)); + assert(math.isNegativeInf(r.ipart) and math.isNan(r.fpart)); + + r = modf32(math.nan(f32)); + assert(math.isNan(r.ipart) and math.isNan(r.fpart)); +} + +test "math.modf64.special" { + var r: modf64_result = undefined; + + r = modf64(math.inf(f64)); + assert(math.isPositiveInf(r.ipart) and math.isNan(r.fpart)); + + r = modf64(-math.inf(f64)); + assert(math.isNegativeInf(r.ipart) and math.isNan(r.fpart)); + + r = modf64(math.nan(f64)); + assert(math.isNan(r.ipart) and math.isNan(r.fpart)); +} diff --git a/std/math/nan.zig b/std/math/nan.zig index 7dc175a61b..3c7b33e8c4 100644 --- a/std/math/nan.zig +++ b/std/math/nan.zig @@ -9,3 +9,15 @@ pub fn nan_workaround(comptime T: type) -> T { else => @compileError("nan not implemented for " ++ @typeName(T)), } } + +pub const snan = snan_workaround; + +// Note: A signalling nan is identical to a standard right now by may have a different bit +// representation in the future when required. +pub fn snan_workaround(comptime T: type) -> T { + switch (T) { + f32 => @bitCast(f32, math.nan_u32), + f64 => @bitCast(f64, math.nan_u64), + else => @compileError("snan not implemented for " ++ @typeName(T)), + } +} diff --git a/std/math/pow.zig b/std/math/pow.zig index 1252baf0da..ed851f8b81 100644 --- a/std/math/pow.zig +++ b/std/math/pow.zig @@ -1,3 +1,26 @@ +// 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 + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -59,9 +82,9 @@ pub fn pow_workaround(comptime T: type, x: T, y: T) -> T { } if (math.isInf(y)) { - // pow(-1, inf) = -1 for all x + // pow(-1, inf) = 1 for all x if (x == -1) { - return -1; + return 1.0; } // pow(x, +inf) = +0 for |x| < 1 // pow(x, -inf) = +0 for |x| > 1 @@ -156,17 +179,57 @@ fn isOddInteger(x: f64) -> bool { test "math.pow" { const epsilon = 0.000001; - // assert(math.approxEq(f32, pow(f32, 0.0, 3.3), 0.0, epsilon)); // TODO: Handle div zero + // TODO: Error on release + assert(math.approxEq(f32, pow(f32, 0.0, 3.3), 0.0, epsilon)); assert(math.approxEq(f32, pow(f32, 0.8923, 3.3), 0.686572, epsilon)); assert(math.approxEq(f32, pow(f32, 0.2, 3.3), 0.004936, epsilon)); assert(math.approxEq(f32, pow(f32, 1.5, 3.3), 3.811546, epsilon)); assert(math.approxEq(f32, pow(f32, 37.45, 3.3), 155736.703125, epsilon)); assert(math.approxEq(f32, pow(f32, 89.123, 3.3), 2722489.5, epsilon)); - // assert(math.approxEq(f32, pow(f64, 0.0, 3.3), 0.0, epsilon)); // TODO: Handle div zero + assert(math.approxEq(f64, pow(f64, 0.0, 3.3), 0.0, epsilon)); assert(math.approxEq(f64, pow(f64, 0.8923, 3.3), 0.686572, epsilon)); assert(math.approxEq(f64, pow(f64, 0.2, 3.3), 0.004936, epsilon)); assert(math.approxEq(f64, pow(f64, 1.5, 3.3), 3.811546, epsilon)); assert(math.approxEq(f64, pow(f64, 37.45, 3.3), 155736.7160616, epsilon)); assert(math.approxEq(f64, pow(f64, 89.123, 3.3), 2722490.231436, epsilon)); } + +test "math.pow.special" { + const epsilon = 0.000001; + + assert(pow(f32, 4, 0.0) == 1.0); + assert(pow(f32, 7, -0.0) == 1.0); + assert(pow(f32, 45, 1.0) == 45); + assert(pow(f32, -45, 1.0) == -45); + assert(math.isNan(pow(f32, math.nan(f32), 5.0))); + assert(math.isNan(pow(f32, 5.0, math.nan(f32)))); + assert(math.isPositiveInf(pow(f32, 0.0, -1.0))); + assert(math.isNegativeInf(pow(f32, -0.0, -3.0))); + assert(math.isPositiveInf(pow(f32, 0.0, -math.inf(f32)))); + assert(math.isPositiveInf(pow(f32, -0.0, -math.inf(f32)))); + assert(pow(f32, 0.0, math.inf(f32)) == 0.0); + assert(pow(f32, -0.0, math.inf(f32)) == 0.0); + assert(math.isPositiveInf(pow(f32, 0.0, -2.0))); + assert(math.isPositiveInf(pow(f32, -0.0, -2.0))); + assert(pow(f32, 0.0, 1.0) == 0.0); + assert(pow(f32, -0.0, 1.0) == -0.0); + assert(pow(f32, 0.0, 2.0) == 0.0); + assert(pow(f32, -0.0, 2.0) == 0.0); + assert(math.approxEq(f32, pow(f32, -1.0, math.inf(f32)), 1.0, epsilon)); + assert(math.approxEq(f32, pow(f32, -1.0, -math.inf(f32)), 1.0, epsilon)); + assert(math.isPositiveInf(pow(f32, 1.2, math.inf(f32)))); + assert(math.isPositiveInf(pow(f32, -1.2, math.inf(f32)))); + assert(pow(f32, 1.2, -math.inf(f32)) == 0.0); + assert(pow(f32, -1.2, -math.inf(f32)) == 0.0); + assert(pow(f32, 0.2, math.inf(f32)) == 0.0); + assert(pow(f32, -0.2, math.inf(f32)) == 0.0); + assert(math.isPositiveInf(pow(f32, 0.2, -math.inf(f32)))); + assert(math.isPositiveInf(pow(f32, -0.2, -math.inf(f32)))); + assert(math.isPositiveInf(pow(f32, math.inf(f32), 1.0))); + assert(pow(f32, math.inf(f32), -1.0) == 0.0); + assert(pow(f32, -math.inf(f32), 5.0) == pow(f32, -0.0, -5.0)); + assert(pow(f32, -math.inf(f32), -5.2) == pow(f32, -0.0, 5.2)); + assert(math.isNan(pow(f32, -1.0, 1.2))); + assert(math.isNan(pow(f32, -12.4, 78.5))); +} diff --git a/std/math/round.zig b/std/math/round.zig index dfdce3db9e..6b7cbf1172 100644 --- a/std/math/round.zig +++ b/std/math/round.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - round(+-0) = +-0 +// - round(+-inf) = +-inf +// - round(nan) = nan + const builtin = @import("builtin"); const assert = @import("../debug.zig").assert; const math = @import("index.zig"); @@ -106,3 +112,19 @@ test "math.round64" { assert(round64(0.2) == 0.0); assert(round64(1.8) == 2.0); } + +test "math.round32.special" { + assert(round32(0.0) == 0.0); + assert(round32(-0.0) == -0.0); + assert(math.isPositiveInf(round32(math.inf(f32)))); + assert(math.isNegativeInf(round32(-math.inf(f32)))); + assert(math.isNan(round32(math.nan(f32)))); +} + +test "math.round64.special" { + assert(round64(0.0) == 0.0); + assert(round64(-0.0) == -0.0); + assert(math.isPositiveInf(round64(math.inf(f64)))); + assert(math.isNegativeInf(round64(-math.inf(f64)))); + assert(math.isNan(round64(math.nan(f64)))); +} diff --git a/std/math/sin.zig b/std/math/sin.zig index a1b6c3cada..c35b6022d0 100644 --- a/std/math/sin.zig +++ b/std/math/sin.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - sin(+-0) = +-0 +// - sin(+-inf) = nan +// - sin(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -164,3 +170,19 @@ test "math.sin64" { assert(math.approxEq(f64, sin64(37.45), -0.246543, epsilon)); assert(math.approxEq(f64, sin64(89.123), 0.916166, epsilon)); } + +test "math.sin32.special" { + assert(sin32(0.0) == 0.0); + assert(sin32(-0.0) == -0.0); + assert(math.isNan(sin32(math.inf(f32)))); + assert(math.isNan(sin32(-math.inf(f32)))); + assert(math.isNan(sin32(math.nan(f32)))); +} + +test "math.sin64.special" { + assert(sin64(0.0) == 0.0); + assert(sin64(-0.0) == -0.0); + assert(math.isNan(sin64(math.inf(f64)))); + assert(math.isNan(sin64(-math.inf(f64)))); + assert(math.isNan(sin64(math.nan(f64)))); +} diff --git a/std/math/sinh.zig b/std/math/sinh.zig index 01590c4b1b..019e2f0fbd 100644 --- a/std/math/sinh.zig +++ b/std/math/sinh.zig @@ -1,6 +1,12 @@ +// Special Cases: +// +// - sinh(+-0) = +-0 +// - sinh(+-inf) = +-inf +// - sinh(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; -const expo2 = @import("_expo2.zig").expo2; +const expo2 = @import("expo2.zig").expo2; // TODO issue #393 pub const sinh = sinh_workaround; @@ -45,6 +51,8 @@ fn sinh32(x: f32) -> f32 { } fn sinh64(x: f64) -> f64 { + @setFloatMode(this, @import("builtin").FloatMode.Strict); + const u = @bitCast(u64, x); const w = u32(u >> 32); const ax = @bitCast(f64, u & (@maxValue(u64) >> 1)); @@ -94,3 +102,20 @@ test "math.sinh64" { assert(math.approxEq(f64, sinh64(0.8923), 1.015512, epsilon)); assert(math.approxEq(f64, sinh64(1.5), 2.129279, epsilon)); } + +test "math.sinh32.special" { + assert(sinh32(0.0) == 0.0); + assert(sinh32(-0.0) == -0.0); + assert(math.isPositiveInf(sinh32(math.inf(f32)))); + assert(math.isNegativeInf(sinh32(-math.inf(f32)))); + assert(math.isNan(sinh32(math.nan(f32)))); +} + +test "math.sinh64.special" { + // TODO: Error on release mode (like pow) + assert(sinh64(0.0) == 0.0); + assert(sinh64(-0.0) == -0.0); + assert(math.isPositiveInf(sinh64(math.inf(f64)))); + assert(math.isNegativeInf(sinh64(-math.inf(f64)))); + assert(math.isNan(sinh64(math.nan(f64)))); +} diff --git a/std/math/sqrt.zig b/std/math/sqrt.zig index 1257c0cf61..2d8b0ca7d3 100644 --- a/std/math/sqrt.zig +++ b/std/math/sqrt.zig @@ -1,3 +1,10 @@ +// Special Cases: +// +// - sqrt(+inf) = +inf +// - sqrt(+-0) = +-0 +// - sqrt(x) = nan if x < 0 +// - sqrt(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -28,7 +35,7 @@ fn sqrt32(x: f32) -> f32 { return x; // sqrt (+-0) = +-0 } if (ix < 0) { - return (x - x) / (x - x); // sqrt(-ve) = snan + return math.snan(f32); } } @@ -106,12 +113,12 @@ fn sqrt64(x: f64) -> f64 { } // sqrt(+-0) = +-0 - if ((ix0 & ~sign) | ix0 == 0) { + if (x == 0.0) { return x; } // sqrt(-ve) = snan if (ix0 & sign != 0) { - return (x - x) / (x - x); + return math.snan(f64); } // normalize x @@ -254,3 +261,19 @@ test "math.sqrt64" { assert(math.approxEq(f64, sqrt64(64.1), 8.006248, epsilon)); assert(math.approxEq(f64, sqrt64(8942.230469), 94.563367, epsilon)); } + +test "math.sqrt32.special" { + assert(math.isPositiveInf(sqrt32(math.inf(f32)))); + assert(sqrt32(0.0) == 0.0); + assert(sqrt32(-0.0) == -0.0); + assert(math.isNan(sqrt32(-1.0))); + assert(math.isNan(sqrt32(math.nan(f32)))); +} + +test "math.sqrt64.special" { + assert(math.isPositiveInf(sqrt64(math.inf(f64)))); + assert(sqrt64(0.0) == 0.0); + assert(sqrt64(-0.0) == -0.0); + assert(math.isNan(sqrt64(-1.0))); + assert(math.isNan(sqrt64(math.nan(f64)))); +} diff --git a/std/math/tan.zig b/std/math/tan.zig index a2b7fddfa1..4ef4bf6c73 100644 --- a/std/math/tan.zig +++ b/std/math/tan.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - tan(+-0) = +-0 +// - tan(+-inf) = nan +// - tan(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -150,3 +156,19 @@ test "math.tan64" { assert(math.approxEq(f64, tan64(37.45), -0.254397, epsilon)); assert(math.approxEq(f64, tan64(89.123), 2.2858376, epsilon)); } + +test "math.tan32.special" { + assert(tan32(0.0) == 0.0); + assert(tan32(-0.0) == -0.0); + assert(math.isNan(tan32(math.inf(f32)))); + assert(math.isNan(tan32(-math.inf(f32)))); + assert(math.isNan(tan32(math.nan(f32)))); +} + +test "math.tan64.special" { + assert(tan64(0.0) == 0.0); + assert(tan64(-0.0) == -0.0); + assert(math.isNan(tan64(math.inf(f64)))); + assert(math.isNan(tan64(-math.inf(f64)))); + assert(math.isNan(tan64(math.nan(f64)))); +} diff --git a/std/math/tanh.zig b/std/math/tanh.zig index bf4d86f7e7..a76de9ce22 100644 --- a/std/math/tanh.zig +++ b/std/math/tanh.zig @@ -1,6 +1,12 @@ +// Special Cases: +// +// - sinh(+-0) = +-0 +// - sinh(+-inf) = +-1 +// - sinh(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; -const expo2 = @import("_expo2.zig").expo2; +const expo2 = @import("expo2.zig").expo2; // TODO issue #393 pub const tanh = tanh_workaround; @@ -64,11 +70,19 @@ fn tanh64(x: f64) -> f64 { var t: f64 = undefined; + // TODO: Shouldn't need these checks. + if (x == 0.0) { + return x; + } + if (math.isNan(x)) { + return x; + } + // |x| < log(3) / 2 ~= 0.5493 or nan - if (w > 0x3Fe193EA) { + if (w > 0x3FE193EA) { // |x| > 20 or nan if (w > 0x40340000) { - t = 1.0 + 0 / x; + t = 1.0; // TODO + 0 / x; } else { t = math.expm1(2 * x); t = 1 - 2 / (t + 2); @@ -121,3 +135,20 @@ test "math.tanh64" { assert(math.approxEq(f64, tanh64(1.5), 0.905148, epsilon)); assert(math.approxEq(f64, tanh64(37.45), 1.0, epsilon)); } + +test "math.tanh32.special" { + // TODO: Error on release (like pow) + assert(tanh32(0.0) == 0.0); + assert(tanh32(-0.0) == -0.0); + assert(tanh32(math.inf(f32)) == 1.0); + assert(tanh32(-math.inf(f32)) == -1.0); + assert(math.isNan(tanh32(math.nan(f32)))); +} + +test "math.tanh64.special" { + assert(tanh64(0.0) == 0.0); + assert(tanh64(-0.0) == -0.0); + assert(tanh64(math.inf(f64)) == 1.0); + assert(tanh64(-math.inf(f64)) == -1.0); + assert(math.isNan(tanh64(math.nan(f64)))); +} diff --git a/std/math/trunc.zig b/std/math/trunc.zig index 7311da2f15..33b37967a5 100644 --- a/std/math/trunc.zig +++ b/std/math/trunc.zig @@ -1,3 +1,9 @@ +// Special Cases: +// +// - trunc(+-0) = +-0 +// - trunc(+-inf) = +-inf +// - trunc(nan) = nan + const math = @import("index.zig"); const assert = @import("../debug.zig").assert; @@ -70,3 +76,19 @@ test "math.trunc64" { assert(trunc64(-1.3) == -1.0); assert(trunc64(0.2) == 0.0); } + +test "math.trunc32.special" { + assert(trunc32(0.0) == 0.0); // 0x3F800000 + assert(trunc32(-0.0) == -0.0); + assert(math.isPositiveInf(trunc32(math.inf(f32)))); + assert(math.isNegativeInf(trunc32(-math.inf(f32)))); + assert(math.isNan(trunc32(math.nan(f32)))); +} + +test "math.trunc64.special" { + assert(trunc64(0.0) == 0.0); + assert(trunc64(-0.0) == -0.0); + assert(math.isPositiveInf(trunc64(math.inf(f64)))); + assert(math.isNegativeInf(trunc64(-math.inf(f64)))); + assert(math.isNan(trunc64(math.nan(f64)))); +}