Swift - 十进制的哈希值对于 X == -X 是相同的,不能用于比较哈希值



我们发现,如果一个是另一个的负数,则无法通过它们的哈希值来区分两个小数。我们使用小数作为结构中的字段,并且该结构实现了 Hashable 以便能够放入集合中。然后,我们的业务逻辑要求所有字段都是唯一的,因此所有字段和组合为hashValue。这意味着两个结构,其中我们的十进制字段是另一个的负数,其余字段实际上是相等的,那么整个结构被认为是相等的。这不是我们想要的。

游乐场代码:

for i in 0..<10 {
let randomNumber: Int = Int.random(in: 0..<10000000)
let lhs = Decimal(integerLiteral: randomNumber)
let rhs = Decimal(integerLiteral: -randomNumber)
print("Are (lhs) and (rhs)'s hashValues equal? (lhs.hashValue == rhs.hashValue)")
print("Are (randomNumber) and (-randomNumber)'s hashValues equal? (randomNumber.hashValue == (-randomNumber).hashValue)n")
}

使用doubleLiteral而不是integerLiteral进行测试时也会发生同样的情况。

解决方法是直接比较小数,如果其他部分需要,可以选择将其包含在哈希值中。

这种行为是有意的吗?尾数是一样的,所以我想它们不被认为是相等的原因是因为符号不包含在十进制的哈希值中?

相同的对象必须具有相同的哈希值,但不能相反:不同的对象可以具有相同的哈希值。相等性测试必须使用==完成,切勿仅依赖哈希值。

在这种特殊情况下,请注意有超过 264Decimal值,因此实际上不可能为所有值分配不同的哈希值。(同样适用于字符串、数组、字典等)。

如果您有一个包含Decimal(可能还有其他)属性的自定义结构,则EquatableHashable协议的实现应如下所示:

struct Foo: Hashable {
let value: Decimal
let otherValue: Int
static func == (lhs: Foo, rhs: Foo) -> Bool {
return lhs.value == rhs.value && lhs.otherValue == rhs.otherValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(value)
hasher.combine(otherValue)
}
}

请注意,如果所有存储的属性都Hashable则编译器可以自动合成这些方法,并且声明一致性就足够了:

struct Foo: Hashable {
let value: Decimal
let otherValue: Int
}

备注:我假设该行为继承自基金会类型NSDecimalNumber。使用 Xcode 11 beta (Swift 5.1)x-x具有不同的哈希值作为Decimal,但哈希值与NSDecimalNumber相同:

let d1: Decimal = 123
let d2: Decimal = -123
print(d1.hashValue) // 1891002061093723710
print(d2.hashValue) // -6669334682005615919
print(NSDecimalNumber(decimal: d1).hashValue) // 326495598603
print(NSDecimalNumber(decimal: d2).hashValue) // 326495598603

(您的值可能会有所不同,因为哈希值从 Swift 4.2 开始是随机的。但上述内容仍然适用:总是可以发生冲突,并且不能依赖具有不同哈希值的不同值。

最新更新