Вопрос

Я подумывал использовать Double в качестве ключа к HashMap, но знаю, что сравнения с плавающей запятой небезопасны, и это заставило меня задуматься.Является ли метод равенства в классе Double также небезопасным?Если это так, это будет означать, что метод hashCode, вероятно, также неверен.Это означало бы, что использование Double в качестве ключа к HashMap приведет к непредсказуемому поведению.

Может ли кто-нибудь подтвердить мои предположения здесь?

Это было полезно?

Решение

Короткий ответ: Не делай этого

Длинный ответ: Вот как будет вычисляться ключ:

Фактический ключ будет java.lang.Double объект, поскольку ключи должны быть объектами.Вот его hashCode() метод:

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

В doubleToLongBits() метод в основном принимает 8 байтов и представляет их так долго.Это означает, что небольшие изменения в вычислении double могут иметь большое значение, и вы будете допускать ключевые промахи.

Если вы можете согласиться на заданное количество точек после точки - умножьте на 10^(количество цифр после точки) и преобразуйте в целое число (например - для 2 цифр умножьте на 100).

Это будет намного безопаснее.

Другие советы

Я думаю ты прав.Хотя хэш двойных чисел является целым числом, двойник может испортить хеш.Вот почему, как упоминает Джош Блох в книге «Эффективная Java», когда вы используете двойное число в качестве входных данных для хеш-функции, вам следует использовать ДаблТоЛонгБитс().Аналогично используйте floatToIntBits для чисел с плавающей запятой.

В частности, чтобы использовать двойное значение в качестве хеша, следуя рецепту Джоша Блоха, вы должны сделать:

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

Это из пункта 8 книги «Эффективная Java»: «Всегда переопределять hashCode при переопределении равенства».Его можно найти в это pdf главы из книги.

Надеюсь это поможет.

Это зависит от того, как вы будете его использовать.

Если вас устраивает возможность найти значение только на основе точно такая же битовая комбинация (или потенциально эквивалентный, например +/- 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.

Это определение позволяет правильно работать хэш-таблицам.

Скорее всего, вас интересуют «числа, очень близкие к ключу», что делает его гораздо менее жизнеспособным.В частности, если вы собираетесь выполнить один набор вычислений, чтобы получить ключ один раз, а затем другой набор вычислений, чтобы получить ключ второй раз, у вас возникнут проблемы.

Проблема не в хэш-коде, а в точности двойников.Это приведет к некоторым странным результатам.Пример:

    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.get(key)

он должен найти запись...[]]

Короткий ответ:Вероятно, это не сработает.

Честный ответ:Все это зависит.

Более длинный ответ:Проблема не в хеш-коде, а в природе равных сравнений с плавающей запятой.Как отмечают Наландиал и комментаторы его поста, в конечном итоге любое совпадение с хеш-таблицей все равно заканчивается использованием равенства для выбора правильного значения.

Итак, вопрос в том, генерируются ли ваши двойники таким образом, что вы знаете, что «равно» на самом деле означает «равно»?Если вы читаете или вычисляете значение, сохраняете его в хеш-таблице, а затем позже читаете или вычисляете значение, используя точно такие же вычисления, то Double.equals будет работать.Но в остальном это ненадежно:1,2 + 2,3 не обязательно равно 3,5, оно может равняться 3,4999995 или чему-то еще.(Это не настоящий пример, я только что это придумал, но такое случается.) Вы можете достаточно надежно сравнивать числа с плавающей запятой и двойные значения для меньшего или большего, но не для равных.

Может быть BigDecimal доставить тебя туда, куда ты хочешь?

Используется хэш двойника, а не сам двойник.

Редактировать: Спасибо, Джон, я правда этого не знал.

Я не уверен в этом (вам следует просто посмотреть исходный код объекта Double), но я думаю, что любые проблемы со сравнением с плавающей запятой будут решены за вас.

Это зависит от того, как вы храните карту и получаете к ней доступ, да, похожие значения могут немного отличаться и, следовательно, не иметь одного и того же значения.

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);

было бы опасно

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top