简体   繁体   English

HashMap中的Double

[英]Double in HashMap

I was thinking of using a Double as the key to a HashMap but I know floating point comparisons are unsafe, that got me thinking. 我当时想将Double用作HashMap的键,但是我知道浮点比较是不安全的,这引起了我的思考。 Is the equals method on the Double class also unsafe? Double类上的equals方法是否还不安全? If it is then that would mean the hashCode method is probably also incorrect. 如果是,那将意味着hashCode方法可能也是不正确的。 This would mean that using Double as the key to a HashMap would lead to unpredictable behavior. 这意味着使用Double作为HashMap的键将导致不可预测的行为。

Can anyone confirm any of my speculation here? 有人可以在这里确认我的任何猜测吗?

Short answer: Don't do it 简短答案:请勿这样做

Long answer: Here is how the key is going to be computed: 长答案:这是密钥的计算方式:

The actual key will be a java.lang.Double object, since keys must be objects. 实际的键将是java.lang.Double对象,因为键必须是对象。 Here is its hashCode() method: 这是其hashCode()方法:

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

The doubleToLongBits() method basically takes the 8 bytes and represent them as long. doubleToLongBits()方法基本上采用8个字节并将它们表示为长。 So it means that small changes in the computation of double can mean a great deal and you will have key misses. 因此,这意味着double的计算中的微小变化可能会带来很大的影响,您将有很多关键失误。

If you can settle for a given number of points after the dot - multiply by 10^(number of digits after the dot) and convert to int (for example - for 2 digits multiply by 100). 如果您可以在点后找到给定数量的点,请乘以10 ^(点后的位数),然后转换为int(例如-2位数乘以100)。

It will be much safer. 这样会更安全。

I think you are right. 我想你是对的。 Although the hash of the doubles are ints, the double could mess up the hash. 尽管双精度型的哈希值是整数,但双精度型可能会弄乱哈希值。 That is why, as Josh Bloch mentions in Effective Java, when you use a double as an input to a hash function, you should use doubleToLongBits() . 这就是为什么,正如Josh Bloch在Effective Java中提到的那样,当您将double用作哈希函数的输入时,应该使用doubleToLongBits() Similarly, use floatToIntBits for floats. 同样,对float使用floatToIntBits。

In particular, to use a double as your hash, following Josh Bloch's recipe, you would do: 特别是,要按照乔希·布洛赫(Josh Bloch)的食谱将双精度数用作哈希,您可以执行以下操作:

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

This is from Item 8 of Effective Java, "Always override hashCode when you override equals". 这来自有效Java的第8项,“当您覆盖等于时始终覆盖hashCode”。 It can be found in this pdf of the chapter from the book . 可以在本书章节的pdf中找到。

Hope this helps. 希望这可以帮助。

It depends on how you would be using it. 这取决于您将如何使用它。

If you're happy with only being able to find the value based on the exact same bit pattern (or potentially an equivalent one, such as +/- 0 and various NaNs) then it might be okay. 如果只对能够基于完全相同的位模式 (或可能等效的位模式 ,例如+/- 0和各种NaN)找到值感到满意,那就可以了。

In particular, all NaNs would end up being considered equal, but +0 and -0 would be considered different. 特别是,所有NaN最终将被视为相等,但+0和-0将被视为不同。 From the docs for Double.equals : Double.equals的文档中:

Note that in most cases, for two instances of class Double, d1 and d2, the value of d1.equals(d2) is true if and only if 请注意,在大多数情况下,对于Double类的两个实例d1和d2,当且仅当d1.equals(d2)的值为true。

d1.doubleValue() == d2.doubleValue() also has the value true. d1.doubleValue()== d2.doubleValue()也具有true值。 However, there are two exceptions: 但是,有两个例外:

  • If d1 and d2 both represent Double.NaN, then the equals method returns true, even though Double.NaN==Double.NaN has the value false. 如果d1和d2都表示Double.NaN,则即使Double.NaN == Double.NaN的值为false,equals方法也将返回true。
  • If d1 represents +0.0 while d2 represents -0.0, or vice versa, the equal test has the value false, even though +0.0==-0.0 has the value true. 如果d1表示+0.0,而d2表示-0.0,反之亦然,即使+0.0 ==-0.0的值为true,相等测试的值为false。

This definition allows hash tables to operate properly. 此定义允许哈希表正常运行。

Most likely you're interested in "numbers very close to the key" though, which makes it a lot less viable. 不过,最有可能您对“数字非常接近密钥”感兴趣,这使它的可行性大大降低。 In particular if you're going to do one set of calculations to get the key once, then a different set of calculations to get the key the second time, you'll have problems. 特别是,如果您要进行一组计算以获取一次密钥,然后再次进行另一组计算以获取密钥,则会遇到问题。

The problem is not the hash code but the precision of the doubles. 问题不是哈希码,而是双精度。 This will cause some strange results. 这将导致一些奇怪的结果。 Example: 例:

    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

The calculated value (key) is "433.29999999999995" which is not EQUALS to 433.3 and so you don't find the entry in the Map (the hash code probably is also different, but that is not the main problem). 计算得出的值(键)为“ 433.29999999999995”,与433.3不相等,因此您在Map中找不到该条目(哈希码也可能不同,但这不是主要问题)。

If you use 如果您使用

map.get(key)

it should find the entry... []] 它应该找到条目... []]

Short answer: It probably won't work. 简短的回答:可能行不通。

Honest answer: It all depends. 诚实的回答:这一切都取决于。

Longer answer: The hash code isn't the issue, it's the nature of equal comparisons on floating point. 更长的答案:哈希码不是问题,这是浮点数相等比较的本质。 As Nalandial and the commenters on his post point out, ultimately any match against a hash table still ends up using equals to pick the right value. 正如Nalandial和他的帖子中的评论者所指出的,最终与哈希表的任何匹配最终都将使用equals来选择正确的值。

So the question is, are your doubles generated in such a way that you know that equals really means equals? 所以问题是,您的双打是否以这样的方式生成,即您知道等于真的意味着等于? If you read or compute a value, store it in the hash table, and then later read or compute the value using exactly the same computation, then Double.equals will work. 如果您读取或计算一个值,将其存储在哈希表中,然后再使用完全相同的计算来读取或计算该值,则Double.equals将起作用。 But otherwise it's unreliable: 1.2 + 2.3 does not necessarily equal 3.5, it might equal 3.4999995 or whatever. 但是,否则它是不可靠的:1.2 + 2.3不一定等于3.5,它可能等于3.4999995或其他。 (Not a real example, I just made that up, but that's the sort of thing that happens.) You can compare floats and doubles reasonably reliably for less or greater, but not for equals. (这不是一个真实的例子,我只是弥补了这一点,但这就是发生的事情。)您可以比较可靠地比较浮点数和双精度数,无论是大还是小,但不等于。

也许BigDecimal能让您到达想要去的地方?

The hash of the double is used, not the double itself. 使用double的哈希,而不使用double本身。

Edit: Thanks, Jon, I actually didn't know that. 编辑:谢谢,乔恩,我实际上不知道。

I'm not sure about this (you should just look at the source code of the Double object) but I would think any issues with floating point comparisons would be taken care of for you. 我对此不确定(您应该只看Double对象的源代码),但是我认为浮点比较的任何问题都将为您解决。

It depends on how you store and access you map, yes similar values could end up being slightly different and therefore not hash to the same value. 这取决于您存储和访问映射的方式,是的,相似的值最终可能会略有不同,因此不能哈希为相同的值。

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

would be all good, however 会很好,但是

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

would be dangerous 会很危险

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM