From c8531faaf56612773ef860d341f8eebb36af4bf3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 30 Jun 2022 00:54:03 -0700 Subject: [PATCH] stage2: hash/eql of fixed-size floats use bit pattern Zig guarantees the memory layout of f16, f32, f64, f80, and f128 which means for generic function purposes, values of these types need to be compared on the basis of their bits in memory. This means nan-packing can be used with generic functions, for example. For comptime_float, the sign is observable, whether it is nan is observable, but not any more kinds of bit patterns are observable. This fixes the std.fmt tests that check printing "-nan". --- src/value.zig | 50 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/value.zig b/src/value.zig index e5332a72e7..90cdf82834 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2189,12 +2189,25 @@ pub const Value = extern union { return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0; }, .Float => { - const a_nan = a.isNan(); - const b_nan = b.isNan(); - if (a_nan or b_nan) { - return a_nan and b_nan; + switch (ty.floatBits(target)) { + 16 => return @bitCast(u16, a.toFloat(f16)) == @bitCast(u16, b.toFloat(f16)), + 32 => return @bitCast(u32, a.toFloat(f32)) == @bitCast(u32, b.toFloat(f32)), + 64 => return @bitCast(u64, a.toFloat(f64)) == @bitCast(u64, b.toFloat(f64)), + 80 => return @bitCast(u80, a.toFloat(f80)) == @bitCast(u80, b.toFloat(f80)), + 128 => return @bitCast(u128, a.toFloat(f128)) == @bitCast(u128, b.toFloat(f128)), + else => unreachable, } - return order(a, b, target).compare(.eq); + }, + .ComptimeFloat => { + const a_float = a.toFloat(f128); + const b_float = b.toFloat(f128); + + const a_nan = std.math.isNan(a_float); + const b_nan = std.math.isNan(b_float); + if (a_nan != b_nan) return false; + if (std.math.signbit(a_float) != std.math.signbit(b_float)) return false; + if (a_nan) return true; + return a_float == b_float; }, .Optional => { if (a.tag() != .opt_payload and b.tag() == .opt_payload) { @@ -2231,18 +2244,25 @@ pub const Value = extern union { var buf: ToTypeBuffer = undefined; return val.toType(&buf).hashWithHasher(hasher, mod); }, - .Float, .ComptimeFloat => { - // Normalize the float here because this hash must match eql semantics. - // These functions are used for hash maps so we want NaN to equal itself, - // and -0.0 to equal +0.0. + .Float => { + // For hash/eql purposes, we treat floats as their IEEE integer representation. + switch (ty.floatBits(mod.getTarget())) { + 16 => std.hash.autoHash(hasher, @bitCast(u16, val.toFloat(f16))), + 32 => std.hash.autoHash(hasher, @bitCast(u32, val.toFloat(f32))), + 64 => std.hash.autoHash(hasher, @bitCast(u64, val.toFloat(f64))), + 80 => std.hash.autoHash(hasher, @bitCast(u80, val.toFloat(f80))), + 128 => std.hash.autoHash(hasher, @bitCast(u128, val.toFloat(f128))), + else => unreachable, + } + }, + .ComptimeFloat => { const float = val.toFloat(f128); - if (std.math.isNan(float)) { - std.hash.autoHash(hasher, std.math.nan_u128); - } else if (float == 0.0) { - var normalized_zero: f128 = 0.0; - std.hash.autoHash(hasher, @bitCast(u128, normalized_zero)); - } else { + const is_nan = std.math.isNan(float); + std.hash.autoHash(hasher, is_nan); + if (!is_nan) { std.hash.autoHash(hasher, @bitCast(u128, float)); + } else { + std.hash.autoHash(hasher, std.math.signbit(float)); } }, .Bool, .Int, .ComptimeInt, .Pointer => switch (val.tag()) {