Double.GetHashCode()的C#缺陷

时间:2018-07-23 19:00:11

标签: c# hash double

双精度的位格式将符号存储在第一位。用于double的C#哈希算法是上下32位的二进制异或。

这样,当您对双精度A及其负数-A进行哈希运算时,哈希值的唯一区别是在第一位。

要散列多个字段,大多数参考文献建议使用如下所示的内容:

public override int GetHashCode()
{
    unchecked
    {
        int hash = 17;

        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        return hash;
    }
}

最后,考虑两个分别具有两个双精度的对象,如下所示:

对象1:{A,-B} 对象2:{-A,B}

对于A和B中的任何两个双精度值,这两个对象似乎总是散列为相同的值(使用上面的方法,而不管种子和乘数如何)。大致的理由是,熵存储在最高有效位中,并且该位溢出会丢失重要信息。

我希望对具有类似性能但具有出色熵的双打使用不同的哈希值。有什么建议吗?

编辑:请不要写/评论碰撞的必然性。

2 个答案:

答案 0 :(得分:1)

您可以在long上进行哈希处理。将int hash替换为long hash,例如:

public override int GetHashCode()
{
    unchecked
    {
        long hash = 17;

        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();

        return hash.GetHashCode();
    }
}

现在,您消除了溢出问题。通过我已经完成的测试(在double上进行了测试:-)),就解决了您的问题。

答案 1 :(得分:0)

您刚刚指出了为什么简单的XOR并不是组合哈希的最佳实践。

作为参考,以下是System.Tuple<T1,T2>结合2个散列的方式:

// From System.Web.Util.HashCodeCombiner
internal static int CombineHashCodes(int h1, int h2)
{
    return (((h1 << 5) + h1) ^ h2);
}

https://referencesource.microsoft.com/#mscorlib/system/tuple.cs,1806cf6634f5a371

用GetHashCode()实现替换组件类型确实很棘手,但是在组合哈希之后,您可能会引入一个附加术语。

例如,在组件符号位的串联中进行XOR,以确保Hash(d,-d)!= Hash(-d,d)。

    internal static int GetSign(double d)
    {
        return d >= 0 ? 1 : 0;
    }
    public override int GetHashCode()
    {
        var signs = GetSign(d2) << 1 | GetSign(d1);
        var h = CombineHashCodes(d1.GetHashCode(), d2.GetHashCode());
        return h ^ signs;
    }