19

我正在考虑使用 Double 作为 HashMap 的键,但我知道浮点比较是不安全的,这让我开始思考。Double 类的 equals 方法是否也不安全?如果是这样,那将意味着 hashCode 方法可能也不正确。这意味着使用 Double 作为 HashMap 的键会导致不可预知的行为。

谁能在这里证实我的任何猜测?

4

8 回答 8

17

简短的回答:不要这样做

长答案:这是计算密钥的方式:

实际的键将是一个java.lang.Double对象,因为键必须是对象。这是它的hashCode()方法:

public int hashCode() {
  long bits = doubleToLongBits(value);
  return (int)(bits ^ (bits >>> 32));
}

doubleToLongBits()方法基本上占用 8 个字节并将它们表示为尽可能长。因此,这意味着 double 计算中的微小变化可能意味着很大,并且您将有关键失误。

如果您可以在点后满足给定数量的点 - 乘以 10^(点后的位数)并转换为 int (例如 - 2 位数乘以 100)。

会安全很多。

于 2009-07-02T14:47:48.953 回答
8

我想你是对的。虽然双打的散列是整数,但双打可能会弄乱散列。这就是为什么正如 Josh Bloch 在 Effective Java 中提到的那样,当您使用 double 作为哈希函数的输入时,您应该使用doubleToLongBits()。同样,将 floatToIntBits 用于浮点数。

特别是,要按照 Josh Bloch 的食谱使用 double 作为哈希,您可以:

public int hashCode() {
  int result = 17;
  long temp = Double.doubleToLongBits(the_double_field);
  result = 37 * result + ((int) (temp ^ (temp >>> 32)));
  return result;
}

这是来自 Effective Java 的第 8 条,“当你覆盖 equals 时总是覆盖 hashCode”。可以在本书章节的 pdf 中找到它。

希望这可以帮助。

于 2009-07-02T14:44:17.103 回答
6

这取决于您将如何使用它。

如果您对只能基于完全相同的位模式(或可能等效的位模式,例如 +/- 0 和各种 NaN)找到值感到满意,那么它可能没问题。

特别是,所有的 NaN 最终都会被认为是相等的,但 +0 和 -0 会被认为是不同的。从文档中Double.equals

请注意,在大多数情况下,对于 Double 类的两个实例 d1 和 d2,d1.equals(d2) 的值当且仅当

d1.doubleValue() == d2.doubleValue() 的值也为 true。但是,有两个例外:

  • 如果 d1 和 d2 都表示 Double.NaN,则 equals 方法返回 true,即使 Double.NaN==Double.NaN 的值为 false。
  • 如果 d1 表示 +0.0 而 d2 表示 -0.0,或者反之亦然,则相等测试的值为 false,即使 +0.0==-0.0 的值为 true。

此定义允许哈希表正常运行。

不过,您很可能对“非常接近键的数字”感兴趣,这使得它的可行性大大降低。特别是如果您要进行一组计算以获取一次密钥,然后进行另一组计算以第二次获取密钥,您将遇到问题。

于 2009-07-02T14:44:11.130 回答
5

问题不在于哈希码,而在于双精度。这会导致一些奇怪的结果。例子:

    double x = 371.4;
    double y = 61.9;
    double key = x + y;    // expected 433.3

    Map<Double, String> map = new HashMap<Double, String>();
    map.put(key, "Sum of " + x + " and " + y);

    System.out.println(map.get(433.3));  // prints null

计算值(键)是“433.29999999999995”,它不等于 433.3,因此您在 Map 中找不到条目(哈希码可能也不同,但这不是主要问题)。

如果你使用

map.get(key)

它应该找到条目... []]

于 2009-07-02T15:19:54.473 回答
3

简短的回答:它可能行不通。

诚实的回答:这一切都取决于。

更长的答案:哈希码不是问题,而是浮点相等比较的本质。正如 Nalandial 和他帖子中的评论者所指出的那样,最终任何与哈希表的匹配最终仍会使用 equals 来选择正确的值。

所以问题是,你的双打是以你知道等于真的等于等于的方式生成的吗?如果您读取或计算一个值,将其存储在哈希表中,然后使用完全相同的计算读取或计算该值,则 Double.equals 将起作用。但否则它是不可靠的:1.2 + 2.3 不一定等于 3.5,它可能等于 3.4999995 或其他。(不是一个真实的例子,我只是编造的,但这就是发生的事情。)您可以合理可靠地比较浮点数和双精度数,以用于小于或大于,但不能用于等于。

于 2009-07-02T17:35:15.297 回答
2

也许BigDecimal可以带你去你想去的地方?

于 2009-07-02T15:45:33.253 回答
0

使用双精度的哈希,而不是双精度本身。

编辑:谢谢,乔恩,我实际上并不知道。

我对此不确定(您应该只查看 Double 对象的源代码),但我认为浮点比较的任何问题都会为您解决。

于 2009-07-02T14:39:57.097 回答
0

这取决于您存储和访问地图的方式,是的,相似的值最终可能会略有不同,因此不会散列到相同的值。

private static final double key1 = 1.1+1.3-1.6;
private static final double key2 = 123321;
...
map.get(key1);

一切都会好的,但是

map.put(1.1+2.3, value);
...
map.get(5.0 - 1.6);

会很危险

于 2009-07-02T14:44:26.027 回答