Я смотрю запись Найти базу данных 2 из N-разрядного целого числа в операциях O (lg (N)) с умножением и поиском от Бит Twiddling hacks.
Я легко вижу, как работает второй алгоритм в этой записи
static const int MultiplyDeBruijnBitPosition2[32] =
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition2[(uint32_t)(v * 0x077CB531U) >> 27];
который вычисляет n = log2 v
, где v
, как известно, является степенью 2. В этом случае 0x077CB531
является обычной последовательностью Де Брейна, а остальное очевидно.
Однако первый алгоритм в этой записи
static const int MultiplyDeBruijnBitPosition[32] =
{
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
};
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
r = MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];
выглядит немного сложнее для меня. Начнем с привязки v
до ближайшего большего значения 2^n - 1
. Это значение 2^n - 1
затем умножается на 0x07C4ACDD
, которое в этом случае действует так же, как и последовательность DeBruijn в предыдущем алгоритме.
Мой вопрос: как мы можем построить эту магическую последовательность 0x07C4ACDD
? То есть как мы можем построить последовательность, которая может быть использована для генерации уникальных индексов при умножении на значение 2^n - 1
? Для множителя 2^n
это просто обычная последовательность Де Брейна, как мы видим выше, поэтому ясно, откуда взялся 0x077CB531
. Но как насчет 2^n - 1
множителя 0x07C4ACDD
? Я чувствую, что мне не хватает чего-то очевидного здесь.
P.S. Чтобы прояснить мой вопрос: я действительно не ищу алгоритм для генерации этих последовательностей. Меня больше интересует более или менее тривиальное свойство (если оно существует), что делает работу 0x07C4ACDD
так, как мы хотим, чтобы она работала. Для 0x077CB531
свойство, которое делает его работу, довольно очевидно: оно содержит все 5-битные комбинации, "сохраненные" в последовательности с 1-битным шагом (что в основном является последовательностью Де Бройна).
С другой стороны, 0x07C4ACDD
не является последовательностью Де Бройна. Итак, к какой собственности они стремились при построении 0x07C4ACDD
(помимо неконструктивного "он должен сделать вышеописанный алгоритм" )? Кто-то как-то придумал этот алгоритм. Поэтому они, вероятно, знали, что этот подход жизнеспособен и что соответствующая последовательность существует. Откуда они это знали?
Например, если бы я должен был построить алгоритм для произвольного v
, я бы сделал
v |= v >> 1;
v |= v >> 2;
...
первый. Тогда я просто сделаю ++v
, чтобы превратить v
в степень 2 (допустим, что он не переполняется). Затем я применил бы первый алгоритм. И, наконец, я бы сделал --r
, чтобы получить окончательный ответ. Однако этим людям удалось оптимизировать его: они исключили ведущие ++v
и завершающие шаги --r
, просто изменив множитель и переставив таблицу. Как они узнали, что это возможно? Какова математика, лежащая в основе этой оптимизации?