std::hash 是否保证"equal"浮点数的哈希值相等?

Does std::hash guarantee equal hashes for "equal" floating point numbers?

本文关键字:哈希值 浮点数 equal 是否 std hash      更新时间:2023-10-16

关于几乎相等std::hash的浮点专业化(例如,对于doubles或floats)是否可靠?也就是说,如果两个值(如(1./std::sqrt(5.)/std::sqrt(5.)).2)应该比较相等,但==运算符不会这样做,那么std::hash将如何表现?

那么,我可以依靠double作为std::unordered_map密钥来按预期工作吗?


我看过"哈希浮点值",但这是关于提升的问题;我在问关于C++11的保证。

std::hash对所有类型都有相同的保证被实例化:如果两个对象相等,它们的哈希代码将平等。否则,他们很有可能不会。因此,您可以将double作为unordered_map按预期工作:如果没有两个替身相等(如==所定义),它们可能会有不同的hash(即使它们不是,它们也是不同的密钥,因为unordered_map也检查相等性)。

显然,如果你的价值观是不精确的结果计算,它们不适合unordered_map(也许对于任何地图来说都是如此)。

此问题存在多个问题:

  • 你的两个表达式不相等的原因不是有两个0.2的二进制表达式,而是没有0.2sqrt(5)的精确(有限)二进制表示!因此,事实上,虽然(1./std::sqrt(5.)/std::sqrt(5.)).2在代数上应该是相同的,但在计算机精度算术中它们很可能不相同。(它们甚至不是有限精度的纸上算术。假设你正在使用小数点后的10位数字。用10位数字写出sqrt(5),然后计算你的第一个表达式。它不会是.2。)

  • 当然,你有一个合理的概念,两个数字是接近的。事实上,你至少有两个:一个绝对值(|a-b| < eps),一个相对值。但这并不能转化为合理的话题。如果您希望eps中的所有数字都具有相同的哈希,那么1, 1+eps, 1+2*eps, ...将具有相同的hash,因此,所有数字都将具有相同哈希。这是一个有效但无用的散列函数。但它是唯一一个满足您将附近值映射到同一哈希的要求的!

unordered_map的默认哈希后面有一个std::hash结构,它提供operator()来计算给定值的哈希。

此模板的一组默认专业化可用,包括std::hash<float>std::hash<double>

在我的机器上(LLVM+clang),这些被定义为

template <>
struct hash<float> : public __scalar_hash<float>
{
    size_t operator()(float __v) const _NOEXCEPT
    {
        // -0.0 and 0.0 should return same hash
       if (__v == 0)
           return 0;
        return __scalar_hash<float>::operator()(__v);
    }
};

其中__scalar_hash定义为:

template <class _Tp>
struct __scalar_hash<_Tp, 0> : public unary_function<_Tp, size_t>
{
    size_t operator()(_Tp __v) const _NOEXCEPT
    {
        union
        {
            _Tp    __t;
            size_t __a;
        } __u;
        __u.__a = 0;
        __u.__t = __v;
        return __u.__a;
    }
};

基本上,散列是通过将并集的值设置为源值,然后只获得一个大到size_t的片段来构建的。

所以你得到了一些填充或截断了你的值,但这并不重要,因为正如你所看到的,数字的原始位被用来计算哈希,这意味着它与==运算符完全一样。两个浮点数要具有相同的散列(不包括截断引起的冲突),必须是相同的值。

没有严格的"几乎相等"概念。因此,行为原则上不能得到保证。如果你想定义你自己的"几乎相等"概念,并构造一个散列函数,使两个"几乎相等的"浮点具有相同的散列,你可以。但只有当你认为"几乎相等"的浮动时,这才是真的。