С# Decimal.GetHashCode() и Double.GetHashCode() равны

Почему
17m.GetHashCode() == 17d.GetHashCode()
(m=decimal, d=double)
Кроме того, как и ожидалось,
17f.GetHashCode() != 17d.GetHashCode()
(f=float)
Похоже, это верно для обоих net3 .5 и net4.0.

Как я понимаю, внутренние битовые представления этих типов сильно различаются. Так почему же хеш-коды типов decimal и double равны для одинаковых значений инициализации? Происходит ли какое-то преобразование перед вычислением хэша?

Я обнаружил, что исходный код Double.GetHashCode() таков:

//The hashcode for a double is the absolute value of the integer representation 
//of that double. 
//  
[System.Security.SecuritySafeCritical]  // auto-generated 
public unsafe override int GetHashCode() {  
    double d = m_value;  
    if (d == 0) { 
        // Ensure that 0 and -0 have the same hash code  
        return 0; 
    } 
    long value = *(long*)(&d); 
    return unchecked((int)value) ^ ((int)(value >> 32));  
} 

Я проверил, что этот код возвращает желаемое значение. Но я не нашел исходный код для Decimal.GetHashCode(). Я пытался использовать метод

public static unsafe int GetHashCode(decimal m_value) {  
    decimal d = m_value;  
    if (d == 0) { 
        // Ensure that 0 and -0 have the same hash code  
        return 0; 
    } 
    int* value = (int*)(&d);
    return unchecked(value[0] ^ value[1] ^ value[2] ^ value[3]);  
} 

Но это не дало желаемых результатов (возвратил хеш, соответствующий типу int, что также ожидается с учетом внутренний макет десятичного числа). Так что реализация Decimal.GetHashCode() пока мне неизвестна.


person Roland Pihlakas    schedule 02.09.2012    source источник
comment
Необязательно отличаться; может быть совпадением, может быть преднамеренным особым случаем при обработке десятичных знаков, которые также являются целыми числами... Думаю, у меня нет ответа - я просто говорю, что он не требуется - действительно, реализация может изменяться между фреймворками/платформами/версиями вполне законно.   -  person Marc Gravell    schedule 02.09.2012
comment
Почему вы говорите, что 17f.GetHashCode() != 17d.GetHashCode() соответствует ожиданиям?   -  person Magnus    schedule 02.09.2012
comment
Магнус, я ожидал, что хэши с двойной и одинарной точностью будут разными, поскольку длина части экспоненты внутреннего представления с плавающей запятой различна для этих типов, и поэтому мантиссы имеют разное битовое смещение: см. здесь   -  person Roland Pihlakas    schedule 02.09.2012
comment
@RolandPihlakas Функция GetHashCode пытается быть уникальной только в пределах одного типа. Генерирует ли double и float один и тот же хэш-код или нет, это не имеет значения.   -  person Magnus    schedule 03.09.2012
comment
@Magnus, это было уместно в желании понять внутреннюю работу вычисления хэша. Например, если десятичные числа преобразуются в двойные, то младшие биты десятичного числа могут быть потеряны во время вычисления хэша, если также установлены старшие биты, и поэтому все похожие значения будут сжаты в одно хеш-ведро. По аналогии, двойные числа не преобразуются в числа с плавающей запятой, а их младшие биты отбрасываются перед вычислением хэша. Так что это актуально в смысле качественных свойств гашиша. А также возможно в смысле производительности вычисления хеша. Конечно, для этих дизайнерских решений тоже была какая-то причина.   -  person Roland Pihlakas    schedule 03.09.2012


Ответы (1)


Метод Decimal.GetHashCode() реализован в среде CLR. Вы можете ознакомиться с возможной реализацией из исходного кода SSCLI20, clr/vm/comdecimal.cpp:

double dbl;
VarR8FromDec(d, &dbl);
if (dbl == 0.0) {
    // Ensure 0 and -0 have the same hash code
    return 0;
}
return ((int *)&dbl)[0] ^ ((int *)&dbl)[1];

В остальном это точный эквивалент реализации Double.GetHashCode(). на C#, но написан на C++, поэтому совпадение не является неожиданным. VarR8FromDec() — это автоматизация COM. вспомогательная функция, преобразующая COM DECIMAL в double.

Конечно, никогда не полагайтесь на такое совпадение.


ОБНОВЛЕНИЕ: теперь он выглядит так же, поскольку CLR имеет открытый исходный код, видимый в этот файл github. Одна проблема заключается в том, что VarR8FromDec() — это функция Windows, которая недоступна в Linux или OSX, она была повторно реализован в PAL.

person Hans Passant    schedule 02.09.2012
comment
Поскольку источник Decimal.GetHashCode() недоступен в Reflector, и эта информация, возможно, ближе всего к тому, что мы можем получить, я считаю это принятым ответом. Что касается изменений в реализации - они не могут быть слишком большими, поскольку большинство десятичных знаков дают одинаковый хэш-код как для net3.5, так и для net4.0. - person Roland Pihlakas; 10.09.2012