From 387b0ac4f1c54cb2f83792299aa628a316e17d88 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Fri, 18 Aug 2023 07:07:49 +0100 Subject: [PATCH] Make NaNs quiet by default and other NaN tidy-up (#16826) * Generalise NaN handling and make std.math.nan() give quiet NaNs * Address uses of std.math.qnan_* and std.math.nan_* consts * Comment out failing test due to issues with signalling NaN * Fix issue in c_builtins.zig where we need qnan_u32 --- CMakeLists.txt | 1 - lib/compiler_rt/divtf3_test.zig | 4 +- lib/compiler_rt/divxf3_test.zig | 4 +- lib/compiler_rt/log10.zig | 4 +- lib/compiler_rt/sqrt.zig | 8 +-- lib/std/fmt.zig | 8 +-- lib/std/math.zig | 90 ++++++++++--------------------- lib/std/math/acos.zig | 2 +- lib/std/math/acosh.zig | 6 +-- lib/std/math/atanh.zig | 8 +-- lib/std/math/float.zig | 65 ++++++++++++++++++++++ lib/std/math/isnan.zig | 41 +++++++++----- lib/std/math/nan.zig | 20 ------- lib/std/zig/c_builtins.zig | 2 +- src/Sema.zig | 4 +- src/codegen/c.zig | 6 +-- test/behavior/bitcast.zig | 57 +++++++++++--------- test/behavior/bugs/14198.zig | 33 +++++++++--- test/behavior/maximum_minimum.zig | 8 +-- 19 files changed, 205 insertions(+), 166 deletions(-) delete mode 100644 lib/std/math/nan.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 6816e26ff5..de4fdfac96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,7 +276,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/math/log.zig" "${CMAKE_SOURCE_DIR}/lib/std/math/log10.zig" "${CMAKE_SOURCE_DIR}/lib/std/math/log2.zig" - "${CMAKE_SOURCE_DIR}/lib/std/math/nan.zig" "${CMAKE_SOURCE_DIR}/lib/std/math/signbit.zig" "${CMAKE_SOURCE_DIR}/lib/std/math/sqrt.zig" "${CMAKE_SOURCE_DIR}/lib/std/mem.zig" diff --git a/lib/compiler_rt/divtf3_test.zig b/lib/compiler_rt/divtf3_test.zig index 8ac687b0b5..7639c7219d 100644 --- a/lib/compiler_rt/divtf3_test.zig +++ b/lib/compiler_rt/divtf3_test.zig @@ -30,10 +30,8 @@ fn test__divtf3(a: f128, b: f128, expectedHi: u64, expectedLo: u64) !void { } test "divtf3" { - // qNaN / any = qNaN - try test__divtf3(math.qnan_f128, 0x1.23456789abcdefp+5, 0x7fff800000000000, 0); // NaN / any = NaN - try test__divtf3(math.nan_f128, 0x1.23456789abcdefp+5, 0x7fff800000000000, 0); + try test__divtf3(math.nan(f128), 0x1.23456789abcdefp+5, 0x7fff800000000000, 0); // inf / any(except inf and nan) = inf try test__divtf3(math.inf(f128), 0x1.23456789abcdefp+5, 0x7fff000000000000, 0); // inf / inf = nan diff --git a/lib/compiler_rt/divxf3_test.zig b/lib/compiler_rt/divxf3_test.zig index 1e0a304956..98118602fd 100644 --- a/lib/compiler_rt/divxf3_test.zig +++ b/lib/compiler_rt/divxf3_test.zig @@ -39,10 +39,8 @@ fn test__divxf3(a: f80, b: f80) !void { } test "divxf3" { - // qNaN / any = qNaN - try expect__divxf3_result(math.qnan_f80, 0x1.23456789abcdefp+5, 0x7fffC000000000000000); // NaN / any = NaN - try expect__divxf3_result(math.nan_f80, 0x1.23456789abcdefp+5, 0x7fffC000000000000000); + try expect__divxf3_result(math.nan(f80), 0x1.23456789abcdefp+5, 0x7fffC000000000000000); // inf / any(except inf and nan) = inf try expect__divxf3_result(math.inf(f80), 0x1.23456789abcdefp+5, 0x7fff8000000000000000); // inf / inf = nan diff --git a/lib/compiler_rt/log10.zig b/lib/compiler_rt/log10.zig index 59ad3d860d..57ddbcf323 100644 --- a/lib/compiler_rt/log10.zig +++ b/lib/compiler_rt/log10.zig @@ -112,11 +112,11 @@ pub fn log10(x_: f64) callconv(.C) f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf if (ix << 1 == 0) { - return -math.inf(f32); + return -math.inf(f64); } // log(-#) = nan if (hx >> 31 != 0) { - return math.nan(f32); + return math.nan(f64); } // subnormal, scale x diff --git a/lib/compiler_rt/sqrt.zig b/lib/compiler_rt/sqrt.zig index e0017eb661..9996ac0582 100644 --- a/lib/compiler_rt/sqrt.zig +++ b/lib/compiler_rt/sqrt.zig @@ -29,7 +29,7 @@ pub fn sqrtf(x: f32) callconv(.C) f32 { var ix: i32 = @as(i32, @bitCast(x)); if ((ix & 0x7F800000) == 0x7F800000) { - return x * x + x; // sqrt(nan) = nan, sqrt(+inf) = +inf, sqrt(-inf) = snan + return x * x + x; // sqrt(nan) = nan, sqrt(+inf) = +inf, sqrt(-inf) = nan } // zero @@ -38,7 +38,7 @@ pub fn sqrtf(x: f32) callconv(.C) f32 { return x; // sqrt (+-0) = +-0 } if (ix < 0) { - return math.snan(f32); + return math.nan(f32); } } @@ -119,9 +119,9 @@ pub fn sqrt(x: f64) callconv(.C) f64 { if (x == 0.0) { return x; } - // sqrt(-ve) = snan + // sqrt(-ve) = nan if (ix0 & sign != 0) { - return math.snan(f64); + return math.nan(f64); } // normalize x diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 7af21c86df..551e96c975 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -2384,22 +2384,22 @@ test "float.scientific.precision" { } test "float.special" { - try expectFmt("f64: nan", "f64: {}", .{math.nan_f64}); + try expectFmt("f64: nan", "f64: {}", .{math.nan(f64)}); // negative nan is not defined by IEE 754, // and ARM thus normalizes it to positive nan if (builtin.target.cpu.arch != .arm) { - try expectFmt("f64: -nan", "f64: {}", .{-math.nan_f64}); + try expectFmt("f64: -nan", "f64: {}", .{-math.nan(f64)}); } try expectFmt("f64: inf", "f64: {}", .{math.inf(f64)}); try expectFmt("f64: -inf", "f64: {}", .{-math.inf(f64)}); } test "float.hexadecimal.special" { - try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64}); + try expectFmt("f64: nan", "f64: {x}", .{math.nan(f64)}); // negative nan is not defined by IEE 754, // and ARM thus normalizes it to positive nan if (builtin.target.cpu.arch != .arm) { - try expectFmt("f64: -nan", "f64: {x}", .{-math.nan_f64}); + try expectFmt("f64: -nan", "f64: {x}", .{-math.nan(f64)}); } try expectFmt("f64: inf", "f64: {x}", .{math.inf(f64)}); try expectFmt("f64: -inf", "f64: {x}", .{-math.inf(f64)}); diff --git a/lib/std/math.zig b/lib/std/math.zig index 6d5faeb7ee..558629b743 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -47,6 +47,8 @@ pub const floatMin = @import("math/float.zig").floatMin; pub const floatMax = @import("math/float.zig").floatMax; pub const floatEps = @import("math/float.zig").floatEps; pub const inf = @import("math/float.zig").inf; +pub const nan = @import("math/float.zig").nan; +pub const snan = @import("math/float.zig").snan; pub const f16_true_min = @compileError("Deprecated: use `floatTrueMin(f16)` instead"); pub const f32_true_min = @compileError("Deprecated: use `floatTrueMin(f32)` instead"); @@ -73,47 +75,38 @@ pub const f32_toint = @compileError("Deprecated: use `1.0 / floatEps(f32)` inste pub const f64_toint = @compileError("Deprecated: use `1.0 / floatEps(f64)` instead"); pub const f80_toint = @compileError("Deprecated: use `1.0 / floatEps(f80)` instead"); pub const f128_toint = @compileError("Deprecated: use `1.0 / floatEps(f128)` instead"); -pub const inf_u16 = @compileError("Deprecated: use `@bitCast(u16, inf(f16))` instead"); +pub const inf_u16 = @compileError("Deprecated: use `@as(u16, @bitCast(inf(f16)))` instead"); pub const inf_f16 = @compileError("Deprecated: use `inf(f16)` instead"); -pub const inf_u32 = @compileError("Deprecated: use `@bitCast(u32, inf(f32))` instead"); +pub const inf_u32 = @compileError("Deprecated: use `@as(u32, @bitCast(inf(f32)))` instead"); pub const inf_f32 = @compileError("Deprecated: use `inf(f32)` instead"); -pub const inf_u64 = @compileError("Deprecated: use `@bitCast(u64, inf(f64))` instead"); +pub const inf_u64 = @compileError("Deprecated: use `@as(u64, @bitCast(inf(f64)))` instead"); pub const inf_f64 = @compileError("Deprecated: use `inf(f64)` instead"); +pub const inf_u80 = @compileError("Deprecated: use `@as(u80, @bitCast(inf(f80)))` instead"); pub const inf_f80 = @compileError("Deprecated: use `inf(f80)` instead"); -pub const inf_u128 = @compileError("Deprecated: use `@bitCast(u128, inf(f128))` instead"); +pub const inf_u128 = @compileError("Deprecated: use `@as(u128, @bitCast(inf(f128)))` instead"); pub const inf_f128 = @compileError("Deprecated: use `inf(f128)` instead"); +pub const nan_u16 = @compileError("Deprecated: use `@as(u16, @bitCast(nan(f16)))` instead"); +pub const nan_f16 = @compileError("Deprecated: use `nan(f16)` instead"); +pub const nan_u32 = @compileError("Deprecated: use `@as(u32, @bitCast(nan(f32)))` instead"); +pub const nan_f32 = @compileError("Deprecated: use `nan(f32)` instead"); +pub const nan_u64 = @compileError("Deprecated: use `@as(u64, @bitCast(nan(f64)))` instead"); +pub const nan_f64 = @compileError("Deprecated: use `nan(f64)` instead"); +pub const nan_u80 = @compileError("Deprecated: use `@as(u80, @bitCast(nan(f80)))` instead"); +pub const nan_f80 = @compileError("Deprecated: use `nan(f80)` instead"); +pub const nan_u128 = @compileError("Deprecated: use `@as(u128, @bitCast(nan(f128)))` instead"); +pub const nan_f128 = @compileError("Deprecated: use `nan(f128)` instead"); +pub const qnan_u16 = @compileError("Deprecated: use `@as(u16, @bitCast(nan(f16)))` instead"); +pub const qnan_f16 = @compileError("Deprecated: use `nan(f16)` instead"); +pub const qnan_u32 = @compileError("Deprecated: use `@as(u32, @bitCast(nan(f32)))` instead"); +pub const qnan_f32 = @compileError("Deprecated: use `nan(f32)` instead"); +pub const qnan_u64 = @compileError("Deprecated: use `@as(u64, @bitCast(nan(f64)))` instead"); +pub const qnan_f64 = @compileError("Deprecated: use `nan(f64)` instead"); +pub const qnan_u80 = @compileError("Deprecated: use `@as(u80, @bitCast(nan(f80)))` instead"); +pub const qnan_f80 = @compileError("Deprecated: use `nan(f80)` instead"); +pub const qnan_u128 = @compileError("Deprecated: use `@as(u128, @bitCast(nan(f128)))` instead"); +pub const qnan_f128 = @compileError("Deprecated: use `nan(f128)` instead"); pub const epsilon = @compileError("Deprecated: use `floatEps` instead"); -pub const nan_u16 = @as(u16, 0x7C01); -pub const nan_f16 = @as(f16, @bitCast(nan_u16)); - -pub const qnan_u16 = @as(u16, 0x7E00); -pub const qnan_f16 = @as(f16, @bitCast(qnan_u16)); - -pub const nan_u32 = @as(u32, 0x7F800001); -pub const nan_f32 = @as(f32, @bitCast(nan_u32)); - -pub const qnan_u32 = @as(u32, 0x7FC00000); -pub const qnan_f32 = @as(f32, @bitCast(qnan_u32)); - -pub const nan_u64 = @as(u64, 0x7FF << 52) | 1; -pub const nan_f64 = @as(f64, @bitCast(nan_u64)); - -pub const qnan_u64 = @as(u64, 0x7ff8000000000000); -pub const qnan_f64 = @as(f64, @bitCast(qnan_u64)); - -pub const nan_f80 = make_f80(F80{ .fraction = 0xA000000000000000, .exp = 0x7fff }); -pub const qnan_f80 = make_f80(F80{ .fraction = 0xC000000000000000, .exp = 0x7fff }); - -pub const nan_u128 = @as(u128, 0x7fff0000000000000000000000000001); -pub const nan_f128 = @as(f128, @bitCast(nan_u128)); - -pub const qnan_u128 = @as(u128, 0x7fff8000000000000000000000000000); -pub const qnan_f128 = @as(f128, @bitCast(qnan_u128)); - -pub const nan = @import("math/nan.zig").nan; -pub const snan = @import("math/nan.zig").snan; - /// Performs an approximate comparison of two floating point values `x` and `y`. /// Returns true if the absolute difference between them is less or equal than /// the specified tolerance. @@ -336,37 +329,8 @@ test { _ = floatMax; _ = floatEps; _ = inf; - - _ = nan_u16; - _ = nan_f16; - - _ = qnan_u16; - _ = qnan_f16; - - _ = nan_u32; - _ = nan_f32; - - _ = qnan_u32; - _ = qnan_f32; - - _ = nan_u64; - _ = nan_f64; - - _ = qnan_u64; - _ = qnan_f64; - - _ = nan_f80; - _ = qnan_f80; - - _ = nan_u128; - _ = nan_f128; - - _ = qnan_u128; - _ = qnan_f128; - _ = nan; _ = snan; - _ = isNan; _ = isSignalNan; _ = frexp; diff --git a/lib/std/math/acos.zig b/lib/std/math/acos.zig index 1a29ca7b54..b451664885 100644 --- a/lib/std/math/acos.zig +++ b/lib/std/math/acos.zig @@ -117,7 +117,7 @@ fn acos64(x: f64) f64 { } } - return math.nan(f32); + return math.nan(f64); } // |x| < 0.5 diff --git a/lib/std/math/acosh.zig b/lib/std/math/acosh.zig index 0c6de9933e..5ffb6e141c 100644 --- a/lib/std/math/acosh.zig +++ b/lib/std/math/acosh.zig @@ -11,7 +11,7 @@ const expect = std.testing.expect; /// Returns the hyperbolic arc-cosine of x. /// /// Special cases: -/// - acosh(x) = snan if x < 1 +/// - acosh(x) = nan if x < 1 /// - acosh(nan) = nan pub fn acosh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); @@ -84,10 +84,10 @@ test "math.acosh64" { test "math.acosh32.special" { try expect(math.isNan(acosh32(math.nan(f32)))); - try expect(math.isSignalNan(acosh32(0.5))); + try expect(math.isNan(acosh32(0.5))); } test "math.acosh64.special" { try expect(math.isNan(acosh64(math.nan(f64)))); - try expect(math.isSignalNan(acosh64(0.5))); + try expect(math.isNan(acosh64(0.5))); } diff --git a/lib/std/math/atanh.zig b/lib/std/math/atanh.zig index c0f24a5944..0da2e46ffa 100644 --- a/lib/std/math/atanh.zig +++ b/lib/std/math/atanh.zig @@ -107,15 +107,15 @@ test "math.atanh_64" { test "math.atanh32.special" { try expect(math.isPositiveInf(atanh_32(1))); try expect(math.isNegativeInf(atanh_32(-1))); - try expect(math.isSignalNan(atanh_32(1.5))); - try expect(math.isSignalNan(atanh_32(-1.5))); + try expect(math.isNan(atanh_32(1.5))); + try expect(math.isNan(atanh_32(-1.5))); try expect(math.isNan(atanh_32(math.nan(f32)))); } test "math.atanh64.special" { try expect(math.isPositiveInf(atanh_64(1))); try expect(math.isNegativeInf(atanh_64(-1))); - try expect(math.isSignalNan(atanh_64(1.5))); - try expect(math.isSignalNan(atanh_64(-1.5))); + try expect(math.isNan(atanh_64(1.5))); + try expect(math.isNan(atanh_64(-1.5))); try expect(math.isNan(atanh_64(math.nan(f64)))); } diff --git a/lib/std/math/float.zig b/lib/std/math/float.zig index 5552ec5c9c..a8de2dd720 100644 --- a/lib/std/math/float.zig +++ b/lib/std/math/float.zig @@ -1,6 +1,8 @@ const std = @import("../std.zig"); +const builtin = @import("builtin"); const assert = std.debug.assert; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; /// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic. inline fn mantissaOne(comptime T: type) comptime_int { @@ -97,6 +99,27 @@ pub inline fn inf(comptime T: type) T { return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T)); } +/// Returns the canonical quiet NaN representation for floating point type T. +pub inline fn nan(comptime T: type) T { + return reconstructFloat( + T, + floatExponentMax(T) + 1, + mantissaOne(T) | 1 << (floatFractionalBits(T) - 1), + ); +} + +/// Returns a signalling NaN representation for floating point type T. +/// +/// TODO: LLVM is known to miscompile on some architectures to quiet NaN - +/// this is tracked by https://github.com/ziglang/zig/issues/14366 +pub inline fn snan(comptime T: type) T { + return reconstructFloat( + T, + floatExponentMax(T) + 1, + mantissaOne(T) | 1 << (floatFractionalBits(T) - 2), + ); +} + test "float bits" { inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { // (1 +) for the sign bit, since it is separate from the other bits @@ -108,3 +131,45 @@ test "float bits" { try expect(-floatFractionalBits(T) <= floatExponentMax(T)); } } + +test "math.inf" { + const inf_u16: u16 = 0x7C00; + const inf_u32: u32 = 0x7F800000; + const inf_u64: u64 = 0x7FF0000000000000; + const inf_u80: u80 = 0x7FFF8000000000000000; + const inf_u128: u128 = 0x7FFF0000000000000000000000000000; + try expectEqual(inf_u16, @bitCast(inf(f16))); + try expectEqual(inf_u32, @bitCast(inf(f32))); + try expectEqual(inf_u64, @bitCast(inf(f64))); + try expectEqual(inf_u80, @bitCast(inf(f80))); + try expectEqual(inf_u128, @bitCast(inf(f128))); +} + +test "math.nan" { + const qnan_u16: u16 = 0x7E00; + const qnan_u32: u32 = 0x7FC00000; + const qnan_u64: u64 = 0x7FF8000000000000; + const qnan_u80: u80 = 0x7FFFC000000000000000; + const qnan_u128: u128 = 0x7FFF8000000000000000000000000000; + try expectEqual(qnan_u16, @bitCast(nan(f16))); + try expectEqual(qnan_u32, @bitCast(nan(f32))); + try expectEqual(qnan_u64, @bitCast(nan(f64))); + try expectEqual(qnan_u80, @bitCast(nan(f80))); + try expectEqual(qnan_u128, @bitCast(nan(f128))); +} + +test "math.snan" { + // TODO: https://github.com/ziglang/zig/issues/14366 + if (builtin.zig_backend == .stage2_llvm and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest; + + const snan_u16: u16 = 0x7D00; + const snan_u32: u32 = 0x7FA00000; + const snan_u64: u64 = 0x7FF4000000000000; + const snan_u80: u80 = 0x7FFFA000000000000000; + const snan_u128: u128 = 0x7FFF4000000000000000000000000000; + try expectEqual(snan_u16, @bitCast(snan(f16))); + try expectEqual(snan_u32, @bitCast(snan(f32))); + try expectEqual(snan_u64, @bitCast(snan(f64))); + try expectEqual(snan_u80, @bitCast(snan(f80))); + try expectEqual(snan_u128, @bitCast(snan(f128))); +} diff --git a/lib/std/math/isnan.zig b/lib/std/math/isnan.zig index f28eb9ce8d..d4666f38a5 100644 --- a/lib/std/math/isnan.zig +++ b/lib/std/math/isnan.zig @@ -1,27 +1,40 @@ const std = @import("../std.zig"); +const builtin = @import("builtin"); const math = std.math; +const meta = std.meta; const expect = std.testing.expect; -const maxInt = std.math.maxInt; -/// Returns whether x is a nan. pub fn isNan(x: anytype) bool { return x != x; } -/// Returns whether x is a signalling nan. +/// TODO: LLVM is known to miscompile on some architectures to quiet NaN - +/// this is tracked by https://github.com/ziglang/zig/issues/14366 pub fn isSignalNan(x: anytype) 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); + const T = @TypeOf(x); + const U = meta.Int(.unsigned, @bitSizeOf(T)); + const quiet_signal_bit_mask = 1 << (math.floatFractionalBits(T) - 1); + return isNan(x) and (@as(U, @bitCast(x)) & quiet_signal_bit_mask == 0); } test "math.isNan" { - try expect(isNan(math.nan(f16))); - try expect(isNan(math.nan(f32))); - try expect(isNan(math.nan(f64))); - try expect(isNan(math.nan(f128))); - try expect(!isNan(@as(f16, 1.0))); - try expect(!isNan(@as(f32, 1.0))); - try expect(!isNan(@as(f64, 1.0))); - try expect(!isNan(@as(f128, 1.0))); + inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { + try expect(isNan(math.nan(T))); + try expect(isNan(-math.nan(T))); + try expect(isNan(math.snan(T))); + try expect(!isNan(@as(T, 1.0))); + try expect(!isNan(@as(T, math.inf(T)))); + } +} + +test "math.isSignalNan" { + inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { + // TODO: Signalling NaN values get converted to quiet NaN values in + // some cases where they shouldn't such that this can fail. + // See https://github.com/ziglang/zig/issues/14366 + // try expect(isSignalNan(math.snan(T))); + try expect(!isSignalNan(math.nan(T))); + try expect(!isSignalNan(@as(T, 1.0))); + try expect(!isSignalNan(math.inf(T))); + } } diff --git a/lib/std/math/nan.zig b/lib/std/math/nan.zig deleted file mode 100644 index 8a27937242..0000000000 --- a/lib/std/math/nan.zig +++ /dev/null @@ -1,20 +0,0 @@ -const math = @import("../math.zig"); - -/// Returns the nan representation for type T. -pub inline fn nan(comptime T: type) T { - return switch (@typeInfo(T).Float.bits) { - 16 => math.nan_f16, - 32 => math.nan_f32, - 64 => math.nan_f64, - 80 => math.nan_f80, - 128 => math.nan_f128, - else => @compileError("unreachable"), - }; -} - -/// Returns the signalling nan representation for 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. -pub inline fn snan(comptime T: type) T { - return nan(T); -} diff --git a/lib/std/zig/c_builtins.zig b/lib/std/zig/c_builtins.zig index ab28395296..33336543fa 100644 --- a/lib/std/zig/c_builtins.zig +++ b/lib/std/zig/c_builtins.zig @@ -207,7 +207,7 @@ pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long { pub inline fn __builtin_nanf(tagp: []const u8) f32 { const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits - return @bitCast(@as(u32, bits) | std.math.qnan_u32); + return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); } pub inline fn __builtin_huge_valf() f32 { diff --git a/src/Sema.zig b/src/Sema.zig index d62cf3ef0c..be5242db2b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -15593,7 +15593,7 @@ fn analyzeArithmetic( return Air.internedToRef(rhs_val.toIntern()); } if (rhs_val.isInf(mod)) { - return Air.internedToRef((try mod.floatValue(resolved_type, std.math.nan_f128)).toIntern()); + return Air.internedToRef((try mod.floatValue(resolved_type, std.math.nan(f128))).toIntern()); } } else if (resolved_type.isAnyFloat()) { break :lz; @@ -15621,7 +15621,7 @@ fn analyzeArithmetic( if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema)) rz: { if (maybe_lhs_val) |lhs_val| { if (lhs_val.isInf(mod)) { - return Air.internedToRef((try mod.floatValue(resolved_type, std.math.nan_f128)).toIntern()); + return Air.internedToRef((try mod.floatValue(resolved_type, std.math.nan(f128))).toIntern()); } } else if (resolved_type.isAnyFloat()) { break :rz; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 39b4165635..bea3c02985 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1087,7 +1087,7 @@ pub const DeclGen = struct { // MSVC doesn't have a way to define a custom or signaling NaN value in a constant expression // TODO: Re-enable this check, otherwise we're writing qnan bit patterns on msvc incorrectly - // if (std.math.isNan(f128_val) and f128_val != std.math.qnan_f128) + // if (std.math.isNan(f128_val) and f128_val != std.math.nan(f128)) // return dg.fail("Only quiet nans are supported in global variable initializers", .{}); } @@ -6704,13 +6704,13 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { .Min => switch (scalar_ty.zigTypeTag(mod)) { .Bool => Value.true, .Int => try scalar_ty.maxIntScalar(mod, scalar_ty), - .Float => try mod.floatValue(scalar_ty, std.math.nan_f128), + .Float => try mod.floatValue(scalar_ty, std.math.nan(f128)), else => unreachable, }, .Max => switch (scalar_ty.zigTypeTag(mod)) { .Bool => Value.false, .Int => try scalar_ty.minIntScalar(mod, scalar_ty), - .Float => try mod.floatValue(scalar_ty, std.math.nan_f128), + .Float => try mod.floatValue(scalar_ty, std.math.nan(f128)), else => unreachable, }, }, .Initializer); diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig index 3833fe1b31..e47d9f2f0b 100644 --- a/test/behavior/bitcast.zig +++ b/test/behavior/bitcast.zig @@ -413,7 +413,7 @@ fn bitCastWrapper64(x: f64) u64 { fn bitCastWrapper128(x: f128) u128 { return @as(u128, @bitCast(x)); } -test "bitcast nan float does modify signaling bit" { +test "bitcast nan float does not modify signaling bit" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO @@ -423,41 +423,46 @@ test "bitcast nan float does modify signaling bit" { // TODO: https://github.com/ziglang/zig/issues/14366 if (builtin.zig_backend == .stage2_llvm and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest; - // 16 bit - const snan_f16_const = math.nan_f16; - try expectEqual(math.nan_u16, @as(u16, @bitCast(snan_f16_const))); - try expectEqual(math.nan_u16, bitCastWrapper16(snan_f16_const)); + const snan_u16: u16 = 0x7D00; + const snan_u32: u32 = 0x7FA00000; + const snan_u64: u64 = 0x7FF4000000000000; + const snan_u128: u128 = 0x7FFF4000000000000000000000000000; - var snan_f16_var = math.nan_f16; - try expectEqual(math.nan_u16, @as(u16, @bitCast(snan_f16_var))); - try expectEqual(math.nan_u16, bitCastWrapper16(snan_f16_var)); + // 16 bit + const snan_f16_const = math.snan(f16); + try expectEqual(snan_u16, @as(u16, @bitCast(snan_f16_const))); + try expectEqual(snan_u16, bitCastWrapper16(snan_f16_const)); + + var snan_f16_var = math.snan(f16); + try expectEqual(snan_u16, @as(u16, @bitCast(snan_f16_var))); + try expectEqual(snan_u16, bitCastWrapper16(snan_f16_var)); // 32 bit - const snan_f32_const = math.nan_f32; - try expectEqual(math.nan_u32, @as(u32, @bitCast(snan_f32_const))); - try expectEqual(math.nan_u32, bitCastWrapper32(snan_f32_const)); + const snan_f32_const = math.snan(f32); + try expectEqual(snan_u32, @as(u32, @bitCast(snan_f32_const))); + try expectEqual(snan_u32, bitCastWrapper32(snan_f32_const)); - var snan_f32_var = math.nan_f32; - try expectEqual(math.nan_u32, @as(u32, @bitCast(snan_f32_var))); - try expectEqual(math.nan_u32, bitCastWrapper32(snan_f32_var)); + var snan_f32_var = math.snan(f32); + try expectEqual(snan_u32, @as(u32, @bitCast(snan_f32_var))); + try expectEqual(snan_u32, bitCastWrapper32(snan_f32_var)); // 64 bit - const snan_f64_const = math.nan_f64; - try expectEqual(math.nan_u64, @as(u64, @bitCast(snan_f64_const))); - try expectEqual(math.nan_u64, bitCastWrapper64(snan_f64_const)); + const snan_f64_const = math.snan(f64); + try expectEqual(snan_u64, @as(u64, @bitCast(snan_f64_const))); + try expectEqual(snan_u64, bitCastWrapper64(snan_f64_const)); - var snan_f64_var = math.nan_f64; - try expectEqual(math.nan_u64, @as(u64, @bitCast(snan_f64_var))); - try expectEqual(math.nan_u64, bitCastWrapper64(snan_f64_var)); + var snan_f64_var = math.snan(f64); + try expectEqual(snan_u64, @as(u64, @bitCast(snan_f64_var))); + try expectEqual(snan_u64, bitCastWrapper64(snan_f64_var)); // 128 bit - const snan_f128_const = math.nan_f128; - try expectEqual(math.nan_u128, @as(u128, @bitCast(snan_f128_const))); - try expectEqual(math.nan_u128, bitCastWrapper128(snan_f128_const)); + const snan_f128_const = math.snan(f128); + try expectEqual(snan_u128, @as(u128, @bitCast(snan_f128_const))); + try expectEqual(snan_u128, bitCastWrapper128(snan_f128_const)); - var snan_f128_var = math.nan_f128; - try expectEqual(math.nan_u128, @as(u128, @bitCast(snan_f128_var))); - try expectEqual(math.nan_u128, bitCastWrapper128(snan_f128_var)); + var snan_f128_var = math.snan(f128); + try expectEqual(snan_u128, @as(u128, @bitCast(snan_f128_var))); + try expectEqual(snan_u128, bitCastWrapper128(snan_f128_var)); } test "@bitCast of packed struct of bools all true" { diff --git a/test/behavior/bugs/14198.zig b/test/behavior/bugs/14198.zig index 92a33f4589..5a4a515810 100644 --- a/test/behavior/bugs/14198.zig +++ b/test/behavior/bugs/14198.zig @@ -3,16 +3,33 @@ const math = std.math; const mem = std.mem; const testing = std.testing; +const qnan_u16: u16 = 0x7E00; +const snan_u16: u16 = 0x7D00; +const qnan_u32: u32 = 0x7FC00000; +const snan_u32: u32 = 0x7FA00000; +const qnan_u64: u64 = 0x7FF8000000000000; +const snan_u64: u64 = 0x7FF4000000000000; +const qnan_u128: u128 = 0x7FFF8000000000000000000000000000; +const snan_u128: u128 = 0x7FFF4000000000000000000000000000; +const qnan_f16: f16 = math.nan(f16); +const snan_f16: f16 = math.snan(f16); +const qnan_f32: f32 = math.nan(f32); +const snan_f32: f32 = math.snan(f32); +const qnan_f64: f64 = math.nan(f64); +const snan_f64: f64 = math.snan(f64); +const qnan_f128: f128 = math.nan(f128); +const snan_f128: f128 = math.snan(f128); + test "nan memory equality" { // signaled - try testing.expect(mem.eql(u8, mem.asBytes(&math.nan_u16), mem.asBytes(&math.nan_f16))); - try testing.expect(mem.eql(u8, mem.asBytes(&math.nan_u32), mem.asBytes(&math.nan_f32))); - try testing.expect(mem.eql(u8, mem.asBytes(&math.nan_u64), mem.asBytes(&math.nan_f64))); - try testing.expect(mem.eql(u8, mem.asBytes(&math.nan_u128), mem.asBytes(&math.nan_f128))); + try testing.expect(mem.eql(u8, mem.asBytes(&snan_u16), mem.asBytes(&snan_f16))); + try testing.expect(mem.eql(u8, mem.asBytes(&snan_u32), mem.asBytes(&snan_f32))); + try testing.expect(mem.eql(u8, mem.asBytes(&snan_u64), mem.asBytes(&snan_f64))); + try testing.expect(mem.eql(u8, mem.asBytes(&snan_u128), mem.asBytes(&snan_f128))); // quiet - try testing.expect(mem.eql(u8, mem.asBytes(&math.qnan_u16), mem.asBytes(&math.qnan_f16))); - try testing.expect(mem.eql(u8, mem.asBytes(&math.qnan_u32), mem.asBytes(&math.qnan_f32))); - try testing.expect(mem.eql(u8, mem.asBytes(&math.qnan_u64), mem.asBytes(&math.qnan_f64))); - try testing.expect(mem.eql(u8, mem.asBytes(&math.qnan_u128), mem.asBytes(&math.qnan_f128))); + try testing.expect(mem.eql(u8, mem.asBytes(&qnan_u16), mem.asBytes(&qnan_f16))); + try testing.expect(mem.eql(u8, mem.asBytes(&qnan_u32), mem.asBytes(&qnan_f32))); + try testing.expect(mem.eql(u8, mem.asBytes(&qnan_u64), mem.asBytes(&qnan_f64))); + try testing.expect(mem.eql(u8, mem.asBytes(&qnan_u128), mem.asBytes(&qnan_f128))); } diff --git a/test/behavior/maximum_minimum.zig b/test/behavior/maximum_minimum.zig index 609ce6e225..260bdaa355 100644 --- a/test/behavior/maximum_minimum.zig +++ b/test/behavior/maximum_minimum.zig @@ -44,8 +44,8 @@ test "@max on vectors" { var y = @max(c, d); try expect(mem.eql(f32, &@as([4]f32, y), &[4]f32{ 0, 0.42, -0.64, 7.8 })); - var e: @Vector(2, f32) = [2]f32{ 0, std.math.qnan_f32 }; - var f: @Vector(2, f32) = [2]f32{ std.math.qnan_f32, 0 }; + var e: @Vector(2, f32) = [2]f32{ 0, std.math.nan(f32) }; + var f: @Vector(2, f32) = [2]f32{ std.math.nan(f32), 0 }; var z = @max(e, f); try expect(mem.eql(f32, &@as([2]f32, z), &[2]f32{ 0, 0 })); } @@ -93,8 +93,8 @@ test "@min for vectors" { var y = @min(c, d); try expect(mem.eql(f32, &@as([4]f32, y), &[4]f32{ -0.23, 0.4, -2.4, 0.9 })); - var e: @Vector(2, f32) = [2]f32{ 0, std.math.qnan_f32 }; - var f: @Vector(2, f32) = [2]f32{ std.math.qnan_f32, 0 }; + var e: @Vector(2, f32) = [2]f32{ 0, std.math.nan(f32) }; + var f: @Vector(2, f32) = [2]f32{ std.math.nan(f32), 0 }; var z = @max(e, f); try expect(mem.eql(f32, &@as([2]f32, z), &[2]f32{ 0, 0 })); }