C# Decimal.GetHashCode() and Double.GetHashCode() equal
Asked Answered
F

1

9

Why is it that
17m.GetHashCode() == 17d.GetHashCode()
(m=decimal, d=double)
Additionally, as expected
17f.GetHashCode() != 17d.GetHashCode()
(f=float)
This appears to be true for both net3.5 and net4.0.

As I understand, the internal bit representations of these types are quite different. So how come that the hash codes of decimal and double types equal for equal initialization values? Is there some conversion taking place before calculation of the hash?

I found that the source code for Double.GetHashCode() is this:

//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));  
} 

I verified that this code returns desired value. But I did not found the source code for Decimal.GetHashCode(). I tried using method

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]);  
} 

But this did not match the desired results (it returned the hash corresponding to the int type, which is also expected considering the internal layout of decimal). So the implementation of Decimal.GetHashCode() remains currently unknown to me.

Fustian answered 2/9, 2012 at 13:53 Comment(5)
It is not required to be different; could be coincidence, could be a deliberate special-case when handling decimals that are also integers.... I guess I don't have an answer - I'm just saying there isn't required to be one - indeed, the implementation could change begween frameworks/platforms/versions quite legitimately.Amaya
Why do you say that 17f.GetHashCode() != 17d.GetHashCode() is as expected?Chantellechanter
Magnus, I expected the double and single precision hashes to be different since the exponent part's length of the floating point internal representation is different for these types and therefore the significand has different bit offset: see hereFustian
@RolandPihlakas The GetHashCode function only tries to be unique within the same type. If double and float generates the same hashcode or not is not relevant.Chantellechanter
@Magnus, it was relevant in the desire to understand the inner workings of the hash calculation. For example, if decimals get converted to double then low bits of decimal may get lost during hash calculation if the high bits are also set and so all similar values would get cramped into one hash bucket. As analogy, doubles do not get converted into float and their low bits dropped before hash calculation. So it's relevant in the sense of quality properties of hash. And also perhaps in the sense of performance of hash calculation. Of course there was also some reason for these design decisions.Fustian
W
7

The Decimal.GetHashCode() method is implemented in the CLR. You can get a peek at the possible implementation from the SSCLI20 source code, 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];

This is otherwise the exact equivalent of the Double.GetHashCode() implementation in C# but written in C++ so getting a match is not unexpected. VarR8FromDec() is a COM Automation helper function that converts a COM DECIMAL to double.

Of course, never rely on such a match.


UPDATE: it still looks the same now that the CLR is open-sourced, visible in this github file. One wrinkle is that VarR8FromDec() is a Windows function that isn't available in Linux or OSX, it was re-implemented in the PAL.

Warhol answered 2/9, 2012 at 15:12 Comment(1)
Since the source of Decimal.GetHashCode() is not available in Reflector and this information is perhaps closest of what we can get, I consider this as accepted answer. Regarding the implementation changes - they cannot be too big since most decimals yield same hashcode for both net3.5 and net4.0.Fustian

© 2022 - 2024 — McMap. All rights reserved.