swift - Swift - X == -X 的 Decimal 的 hashValue 相同,不能用于比较 hashValues
问题描述
我们发现,如果一个是另一个的负数,则无法通过它们的 hashValue 区分两个小数。我们使用 Decimals 作为结构中的字段,并且该结构实现了 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
。
解决方法是直接比较小数,如果其他部分需要,可以选择将其包含在 hashValue 中。
这种行为是有意的吗?尾数是相同的,所以我猜他们不被认为是相等的原因是因为符号不包含在十进制的 hashValue 中?
解决方案
相同的对象必须具有相同的哈希值,但不能相反:不同的对象可以具有相同的哈希值。必须对相等性进行测试,==
并且永远不要单独依赖哈希值。
在这种特殊情况下,请注意有超过 2 64 个 Decimal
值,因此实际上不可能为所有这些值分配不同的哈希值。(同样适用于字符串、数组、字典……)。
如果您有一个包含Decimal
(可能还有其他)属性的自定义结构,那么Equatable
andHashable
协议的实现应该如下所示:
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
}
备注:我假设行为是从 Foundation 类型继承的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 开始,哈希值是随机的。)但上述内容仍然适用:总是可能存在冲突,并且不能依赖具有不同哈希值的不同值。
推荐阅读
- sql-server - EF Core Code First - 没有 Id 的一对多关系
- python - Python 3.9.1 - 使用 StandardScaler() 缩放单行浮点数
- javascript - Storybook react-native 检查 `CellRenderer` 的渲染方法
- swift - 更改焦点元素的颜色 (Picker) SwiftUI - WatchOS
- php - 将数据连接到谷歌数据工作室
- bubble.io - 如何找出我的 Bubble.io 插件有什么问题
- react-native - 如何使用 react-native 应用程序在 SQLite 中更快地插入
- javascript - 处理文件然后上传:nodejs / formidable
- angular - ERROR 错误:FIREBASE 致命错误:无法确定 Firebase 数据库 URL
- android - 如何更改 TextInputLayout 中的浮动标签背景?