std::hash
的{{1}}(例如double
或float
s)的浮点专精化是否可靠almost-equality?也就是说,如果两个值(例如(1./std::sqrt(5.)/std::sqrt(5.))
和.2
)应该相等但不会与==
运算符进行比较,那么std::hash
将如何表现?
那么,我可以依靠double
作为std::unordered_map
键来按预期工作吗?
我看过“Hashing floating point values”,但是要求提升;我在询问C ++ 11的保证。
答案 0 :(得分:9)
std::hash
对所有类型都有相同的保证
实例化:如果两个对象相等,它们的哈希码将是
等于。否则,它们的可能性非常大
惯于。因此,您可以依赖double
作为关键字
unordered_map
按预期工作:如果没有两个双打
相等(由==
定义),它们可能会有所不同
哈希(即使它们没有,它们也是不同的键,因为
unordered_map
也检查是否相等。
显然,如果你的价值观是不准确的结果
计算,它们不是unordered_map
的合适键
(也许不是任何地图)。
答案 1 :(得分:7)
这个问题存在多个问题:
你的两个表达式不比较相等的原因并不是有两个二进制表达式为0.2,而是没有0.2
的完全(有限)二进制表示,或{{ 1}}!实际上,虽然sqrt(5)
和(1./std::sqrt(5.)/std::sqrt(5.))
在代数上应该是相同的,但在计算机精度算术中它们可能不一样。 (它们甚至不具有有限精度的纸笔算法。假设您使用小数点后的10位数。用10位数写出.2
并计算您的第一个表达式。它不会是{ {1}}。)
当然,你有两个数字接近的明智概念。事实上,你至少有两个:一个绝对(sqrt(5)
),一个亲戚。但这并没有转化为明智的哈希。如果您希望彼此的.2
内的所有数字具有相同的散列,则|a-b| < eps
将具有相同的散列,因此,所有数字将具有相同的散列。这是一个有效但无用的哈希函数。但它是唯一一个满足您将附近值映射到同一个哈希值的要求!
答案 2 :(得分:3)
没有严格的“几乎平等”的概念。所以原则上不能保证行为。如果你想定义你自己的“几乎相等”的概念并构造一个哈希函数,使得两个“几乎相等”的浮点数具有相同的哈希值,你可以。但是,只有你的“几乎相同”浮动的概念才会出现这种情况。
答案 3 :(得分:2)
在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;
}
};
基本上通过将union的值设置为源值然后只获得一个大size_t
的片段来构建散列。
所以你得到一些填充或者你的值被截断,但这并不重要,因为你可以看到数字的原始位用于计算哈希值,这意味着它的工作原理与==
运营商。两个浮动数字,具有相同的散列(不包括由截断给出的碰撞),必须是相同的值。