From d94328530bd666413bc25c1d04a29c80ed97e7ed Mon Sep 17 00:00:00 2001 From: Christopher Richez Date: Fri, 4 Feb 2022 16:28:59 +0100 Subject: [PATCH 1/2] fixed `BinaryFloatingPoint` hash instructions for zero --- Sources/FowlerNollVo/FNVHashable.swift | 6 +- .../FloatingPointHashingTests.swift | 78 +++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 Tests/FowlerNollVoTests/FloatingPointHashingTests.swift diff --git a/Sources/FowlerNollVo/FNVHashable.swift b/Sources/FowlerNollVo/FNVHashable.swift index a302f29..68625c3 100644 --- a/Sources/FowlerNollVo/FNVHashable.swift +++ b/Sources/FowlerNollVo/FNVHashable.swift @@ -48,13 +48,13 @@ public protocol FNVHashable { extension Double: FNVHashable { public func hash(into hasher: inout Hasher) where Hasher : FNVHasher { - withUnsafeBytes(of: self) { hasher.combine($0) } + hasher.combine(isZero ? Double.zero.bitPattern : bitPattern) } } extension Float: FNVHashable { public func hash(into hasher: inout Hasher) where Hasher : FNVHasher { - withUnsafeBytes(of: self) { hasher.combine($0) } + hasher.combine(isZero ? Float.zero.bitPattern : bitPattern) } } @@ -68,7 +68,7 @@ extension Float: FNVHashable { @available(tvOS 14, *) extension Float16: FNVHashable { public func hash(into hasher: inout Hasher) where Hasher : FNVHasher { - withUnsafeBytes(of: self) { hasher.combine($0) } + hasher.combine(isZero ? Float16.zero.bitPattern : bitPattern) } } #endif diff --git a/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift b/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift new file mode 100644 index 0000000..11fd300 --- /dev/null +++ b/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift @@ -0,0 +1,78 @@ +// +// FloatingPointHashingTests.swift +// +// +// Created by Christopher Richez on 2/3/22. +// + +import FowlerNollVo +import XCTest + +/// This test case asserts different representations of zero still return the same hash value. +class FloatingPointHashingTests: XCTestCase { + func test(type: T.Type, hasher: Hasher.Type) + where T : BinaryFloatingPoint & FNVHashable, Hasher : FNVHasher, Hasher.Digest : Equatable { + // Get both representations of zero + let plusZero = T(sign: .plus, exponent: 1, significand: 0.0) + let minusZero = T(sign: .minus, exponent: 1, significand: 0.0) + + // Hash both values + var plusHasher = Hasher() + plusZero.hash(into: &plusHasher) + var minusHasher = Hasher() + minusZero.hash(into: &minusHasher) + + // Compare their digests + XCTAssertEqual(plusHasher.digest, minusHasher.digest) + } + + func testFloat() { + let type = Float.self + test(type: type, hasher: FNV32.self) + test(type: type, hasher: FNV32a.self) + test(type: type, hasher: FNV64.self) + test(type: type, hasher: FNV64a.self) + test(type: type, hasher: FNV128.self) + test(type: type, hasher: FNV128a.self) + test(type: type, hasher: FNV256.self) + test(type: type, hasher: FNV256a.self) + test(type: type, hasher: FNV512.self) + test(type: type, hasher: FNV512a.self) + test(type: type, hasher: FNV1024.self) + test(type: type, hasher: FNV1024a.self) + } + + func testDouble() { + let type = Double.self + test(type: type, hasher: FNV32.self) + test(type: type, hasher: FNV32a.self) + test(type: type, hasher: FNV64.self) + test(type: type, hasher: FNV64a.self) + test(type: type, hasher: FNV128.self) + test(type: type, hasher: FNV128a.self) + test(type: type, hasher: FNV256.self) + test(type: type, hasher: FNV256a.self) + test(type: type, hasher: FNV512.self) + test(type: type, hasher: FNV512a.self) + test(type: type, hasher: FNV1024.self) + test(type: type, hasher: FNV1024a.self) + } + +#if swift(>=5.4) && !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64)) + func testFloat16() { + let type = Float16.self + test(type: type, hasher: FNV32.self) + test(type: type, hasher: FNV32a.self) + test(type: type, hasher: FNV64.self) + test(type: type, hasher: FNV64a.self) + test(type: type, hasher: FNV128.self) + test(type: type, hasher: FNV128a.self) + test(type: type, hasher: FNV256.self) + test(type: type, hasher: FNV256a.self) + test(type: type, hasher: FNV512.self) + test(type: type, hasher: FNV512a.self) + test(type: type, hasher: FNV1024.self) + test(type: type, hasher: FNV1024a.self) + } +#endif +} From 342574124b4472ec8f3ed8d8e255cd11eae8e019 Mon Sep 17 00:00:00 2001 From: Christopher Richez Date: Fri, 4 Feb 2022 22:47:33 +0100 Subject: [PATCH 2/2] added `Float16` availability statement to `FloatingPointHashingTests` --- Tests/FowlerNollVoTests/FloatingPointHashingTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift b/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift index 11fd300..598d4ac 100644 --- a/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift +++ b/Tests/FowlerNollVoTests/FloatingPointHashingTests.swift @@ -59,6 +59,7 @@ class FloatingPointHashingTests: XCTestCase { } #if swift(>=5.4) && !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64)) + @available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) func testFloat16() { let type = Float16.self test(type: type, hasher: FNV32.self)