Swift - hashValue для десятичного числа одинаков для X == -X, его нельзя использовать для сравнения значений hashValue - PullRequest
2 голосов
/ 14 июня 2019

Мы выяснили, что вы не можете различить два десятичных знака по их значению hashValue, если один является отрицательным от другого. Мы используем десятичные числа как поле в структуре, и эта структура реализует 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?

1 Ответ

2 голосов
/ 14 июня 2019

Одинаковые объекты должны иметь одинаковое значение хеш-функции, но не наоборот: отдельные объекты могут иметь одинаковое значение хеш-функции.Проверка на равенство должна выполняться с == и , никогда не полагаться только на одно хеш-значение.

В этом конкретном случае обратите внимание, что существует более 2 64 Decimal значений, так что на самом деле было бы невозможно присвоить разные значения хеш-функции всем им.(Аналогично для строк, массивов, словарей, ...).

Если у вас есть пользовательская структура, содержащая Decimal (и, возможно, другие) свойства, то реализация протокола Equatable и Hashable должнавыглядит следующим образом:

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 (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.) Но вышеизложенное все еще применимо: всегда могут быть коллизии, и нельзя полагаться на разные значения, имеющие разные хэши.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...