authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-06-30 00:54:03-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-06-30 00:54:03-07:00
logc8531faaf56612773ef860d341f8eebb36af4bf3
treeb966d3a249d388be2f0591c4fef98d1e67071c33
parent6bc6e47b1582a4538078c8f4e9b3dae386854d07

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".

1 files changed, 35 insertions(+), 15 deletions(-)

src/value.zig+35-15
...@@ -2189,12 +2189,25 @@ pub const Value = extern union {...@@ -2189,12 +2189,25 @@ pub const Value = extern union {
2189 return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0;2189 return ty.isTupleOrAnonStruct() and ty.structFieldCount() != 0;
2190 },2190 },
2191 .Float => {2191 .Float => {
2192 const a_nan = a.isNan();2192 switch (ty.floatBits(target)) {
2193 const b_nan = b.isNan();2193 16 => return @bitCast(u16, a.toFloat(f16)) == @bitCast(u16, b.toFloat(f16)),
2194 if (a_nan or b_nan) {2194 32 => return @bitCast(u32, a.toFloat(f32)) == @bitCast(u32, b.toFloat(f32)),
2195 return a_nan and b_nan;2195 64 => return @bitCast(u64, a.toFloat(f64)) == @bitCast(u64, b.toFloat(f64)),
2196 80 => return @bitCast(u80, a.toFloat(f80)) == @bitCast(u80, b.toFloat(f80)),
2197 128 => return @bitCast(u128, a.toFloat(f128)) == @bitCast(u128, b.toFloat(f128)),
2198 else => unreachable,
2196 }2199 }
2197 return order(a, b, target).compare(.eq);2200 },
2201 .ComptimeFloat => {
2202 const a_float = a.toFloat(f128);
2203 const b_float = b.toFloat(f128);
2204
2205 const a_nan = std.math.isNan(a_float);
2206 const b_nan = std.math.isNan(b_float);
2207 if (a_nan != b_nan) return false;
2208 if (std.math.signbit(a_float) != std.math.signbit(b_float)) return false;
2209 if (a_nan) return true;
2210 return a_float == b_float;
2198 },2211 },
2199 .Optional => {2212 .Optional => {
2200 if (a.tag() != .opt_payload and b.tag() == .opt_payload) {2213 if (a.tag() != .opt_payload and b.tag() == .opt_payload) {
...@@ -2231,18 +2244,25 @@ pub const Value = extern union {...@@ -2231,18 +2244,25 @@ pub const Value = extern union {
2231 var buf: ToTypeBuffer = undefined;2244 var buf: ToTypeBuffer = undefined;
2232 return val.toType(&buf).hashWithHasher(hasher, mod);2245 return val.toType(&buf).hashWithHasher(hasher, mod);
2233 },2246 },
2234 .Float, .ComptimeFloat => {2247 .Float => {
2235 // Normalize the float here because this hash must match eql semantics.2248 // For hash/eql purposes, we treat floats as their IEEE integer representation.
2236 // These functions are used for hash maps so we want NaN to equal itself,2249 switch (ty.floatBits(mod.getTarget())) {
2237 // and -0.0 to equal +0.0.2250 16 => std.hash.autoHash(hasher, @bitCast(u16, val.toFloat(f16))),
2251 32 => std.hash.autoHash(hasher, @bitCast(u32, val.toFloat(f32))),
2252 64 => std.hash.autoHash(hasher, @bitCast(u64, val.toFloat(f64))),
2253 80 => std.hash.autoHash(hasher, @bitCast(u80, val.toFloat(f80))),
2254 128 => std.hash.autoHash(hasher, @bitCast(u128, val.toFloat(f128))),
2255 else => unreachable,
2256 }
2257 },
2258 .ComptimeFloat => {
2238 const float = val.toFloat(f128);2259 const float = val.toFloat(f128);
2239 if (std.math.isNan(float)) {2260 const is_nan = std.math.isNan(float);
2240 std.hash.autoHash(hasher, std.math.nan_u128);2261 std.hash.autoHash(hasher, is_nan);
2241 } else if (float == 0.0) {2262 if (!is_nan) {
2242 var normalized_zero: f128 = 0.0;
2243 std.hash.autoHash(hasher, @bitCast(u128, normalized_zero));
2244 } else {
2245 std.hash.autoHash(hasher, @bitCast(u128, float));2263 std.hash.autoHash(hasher, @bitCast(u128, float));
2264 } else {
2265 std.hash.autoHash(hasher, std.math.signbit(float));
2246 }2266 }
2247 },2267 },
2248 .Bool, .Int, .ComptimeInt, .Pointer => switch (val.tag()) {2268 .Bool, .Int, .ComptimeInt, .Pointer => switch (val.tag()) {