C# Decimal.GetHashCode()和Double.GetHashCode()相等

9
为什么
17m.GetHashCode() == 17d.GetHashCode()
(m=decimal, d=double)
此外,如预期的那样
17f.GetHashCode() != 17d.GetHashCode()
(f=float)
这在net3.5和net4.0中都是正确的。

据我了解,这些类型的内部位表示非常不同。那么为什么相等的初始化值的decimaldouble类型的哈希码相等呢?在计算哈希之前是否进行了某种转换?

我发现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()的实现目前仍然未知。

2
不一定要有所不同;可能是巧合,也可能是在处理既是小数又是整数时刻意设置的特殊情况...我猜我没有答案-我只是说没有必要有一个答案-实际上,实现可以合理地在框架/平台/版本之间变化。 - Marc Gravell
为什么你说 17f.GetHashCode() != 17d.GetHashCode() 是预期的结果? - Magnus
Magnus,我本来期望双精度和单精度哈希是不同的,因为浮点数内部表示的指数部分长度对于这些类型是不同的,因此有效数字具有不同的位偏移:请参见此处 - Roland Pihlakas
@RolandPihlakas GetHashCode函数仅在同一类型内尝试保持唯一性。双精度和单精度浮点数是否生成相同的哈希码并不重要。 - Magnus
@Magnus,这与理解哈希计算的内部工作方式有关。例如,如果十进制数被转换为双精度浮点数,则在哈希计算过程中可能会丢失小数的低位,如果高位也被设置,则所有类似的值都将被压缩到一个哈希桶中。类比地说,在哈希计算之前,双精度浮点数不会被转换为浮点数并且其低位被丢弃。因此,这与哈希的质量属性相关。也许还与哈希计算的性能有关。当然,这些设计决策也有一些原因。 - Roland Pihlakas
1个回答

7

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

这基本上是C#中Double.GetHashCode()实现的精确等效版本,但它是用C++编写的,因此匹配是不意外的。 VarR8FromDec()是一个COM自动化助手函数,它将COM DECIMAL转换为double。

当然,永远不要依赖这样的匹配。


更新:现在CLR已开源,可以在this github file中看到。但有一个问题是VarR8FromDec()是一个Windows函数,在Linux或OSX上不可用,因此它已经在PAL中重新实现


由于Reflector中没有Decimal.GetHashCode()的源代码,而这个信息可能是我们能够获得的最接近的信息,因此我认为这是一个被接受的答案。关于实现的更改 - 它们不能太大,因为大多数十进制数在net3.5和net4.0中产生相同的哈希码。 - Roland Pihlakas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接