О целочисленном умножении, переполнении и потери информации

Я читаю Глава 3 Джошуа Блоха Эффективная Java. В пункте 8: всегда переопределяйте hashCode при переопределении равных, автор использует следующий комбинирующий шаг в своей хэш-функции:

result = 37 * result + c;

Затем он объясняет, почему выбрано 37 (выделено мной):

Множитель 37 был выбран потому, что он является нечетным простым. Если бы это было ровно и умножение переполнено, информация будет потеряна, поскольку умножение на два эквивалентно сдвигу. Преимущества использования простого числа меньше ясно, но для этой цели традиционно использовать простые числа.

Мой вопрос, почему имеет значение, что фактор объединения (37) нечетный? Разве переполнение не приведет к потере информации независимо от того, был ли этот фактор нечетным или даже?

Ответы

Ответ 1

Рассмотрим, что происходит, когда положительное значение многократно умножается на два в представлении base-2 - все установленные бит в конце концов заканчиваются, оставляя вас с нулем.

Четный множитель приведет к хэш-кодам с меньшим разнесением.

Нечетные числа, с другой стороны, могут привести к переполнению, но без потери разнообразия.

Ответ 2

Цель хэш-кода - иметь случайные биты на основе ввода (особенно нижние биты, поскольку они часто используются больше)

Когда вы несколько на два, младший бит может быть равен 0, что не имеет случайности. Если вы несколько по нечетному числу, младший бит может быть нечетным или четным.


Аналогичный вопрос - вот что вы здесь делаете

public static void main(String... args) {
    System.out.println(factorial(66));
}

public static long factorial(int n) {
    long product = 1;
    for (; n > 1; n--)
        product *= n;
    return product;
}

печатает

0

Каждое второе число является четным, а каждый четвертый - кратным 4 и т.д.

Ответ 3

Решение лежит в теории чисел и Самый низкий общий знаменатель вашего множителя и вашего номера по модулю.

Пример может помочь. Скажем, вместо 32bit вы получили только 2 бит для представления числа. Итак, вы получили 4 номера (классы). 0, 1, 2 и 3

Переполнение в CPU такое же, как операция с модулем

Class - x2 - mod 4 - x2 - mod 4

0       0      0     0     0

1       2      2     4     0

2       4      0     0     0

3       6      2     4     0

После двух операций вы получили только 1 возможное число (класс). Таким образом, у вас есть "потерянная" информация.

Class - x3 - mod 4 - x3 - mod 4 ...

0       0      0     0     0

1       3      3     9     1

2       6      2     6     2

3       9      1     3     3

Это может продолжаться вечно, и у вас все еще есть 4 класса. Поэтому вы не теряете информацию.

Ключ в том, что ЖК-дисплей вашего мультипликатора и ваш класс modulo равны 1. Это справедливо для всех нечетных чисел, потому что ваше число по модулю в настоящее время всегда имеет силу 2. Они не должны быть штрихами, и они не должны 37. Но потеря информации - это всего лишь один критерий, по которому 37 выбраны другие критерии: распределение значений и т.д.

Ответ 4

Не-математическая простая версия почему...

Для хэширования используются простые числа для сохранения разнообразия.

Возможно, разнообразие важнее из-за реализаций Set и Map. Эти реализации используют последние биты хэш-номеров объектов для индексации внутренних массивов записей.

Например, в HashMap с внутренней таблицей (массивом) для записей с размером 8 он будет использовать последние 3 бита хеш-номеров для входа в таблицу.

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

На самом деле это не так, но если бы объект Integer имел

    hash = 4 * number;

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

Я полагаю, что главной заботой Джошуа Блоха было распределение целых чисел хеширования, насколько это возможно, для оптимизации производительности коллекций путем равномерного распределения объектов в Maps и Sets. Простые числа интуитивно кажутся хорошим фактором распределения.

Ответ 5

Для обеспечения разнообразия не требуются простые цифры; что необходимо, чтобы множитель был взаимно простым с модулем.

Поскольку модуль для двоичной арифметики всегда равен степени два, любое нечетное число является относительно простым, и этого будет достаточно. Если бы вы использовали модуль, отличный от переполнения, то простое число продолжало бы обеспечивать разнообразие (при условии, что вы не выбрали один и тот же простой...).