Вот два способа подойти к этой проблеме. Первый, использующий общую технику бит-скручивания, и, если тщательно оптимизирован, может побить аппаратное разделение. Другой заменяет умножение для деления, аналогично оптимизации, выполняемой gcc
, и является самым быстрым. Суть в том, что нет смысла пытаться избежать оператора , если второй аргумент постоянный, потому что gcc
его покрыл. (И, вероятно, другие компиляторы тоже.)
Следующая функция основана на том, что x
- это то же самое (mod 31) как сумма базовых 32 цифр x
. Это верно, потому что 32
- 1 mod 31
, и, следовательно, любая мощность 32
равна 1 mod 31
. Таким образом, каждая позиция «цифры» в номере базы-32 вносит цифру * 1 в сумму мод 31. И легко получить представление base-32: мы просто берем биты по пять за раз.
(Как и все остальные функции этого ответа, он будет работать только для неотрицательных x
).
unsigned mod31(unsigned x) {
unsigned tmp;
for (tmp = 0; x; x >>= 5) {
tmp += x & 31;
}
// Here we assume that there are at most 160 bits in x
tmp = (tmp >> 5) + (tmp & 31);
return tmp >= 31 ? tmp - 31 : tmp;
}
Для конкретного целочисленного размера вы можете развернуть цикл и, вполне возможно, разбить деление. (И см @chux's answer способ, чтобы преобразовать контур в O(log bits)
операций вместо O(bits)
Труднее бить gcc
, что позволяет избежать деления, когда делимое постоянная известно во время компиляции.
В очень быстром тесте с использованием знака 32-битные целые числа, наивный развернутый цикл занял 19 секунд, а версия, основанная на ответе @ chux, заняла всего 13 секунд, но gcc x%31
заняла 9,7 секунды. Принуждение gcc к использованию аппаратного разрыва (путем создания деления непостоянно) заняло 23,4 секунды , а код, показанный выше, занял 25,6 секунды. Эти цифры должны быть взяты с несколькими зернами соли. Время для вычисления i%31
для всех возможных значений i
на моем ноутбуке с использованием -O3 -march=native
.
gcc
избегает 32-разрядного деления на константу, заменив его тем, что по существу является 64-разрядным умножением на инверсию константы, за которой следует правый сдвиг. (Фактический алгоритм делает немного больше работы, чтобы избежать переполнения.) Эта процедура была реализована более 20 лет назад в gcc v2.6
, а документ, описывающий алгоритм, доступен на gmp site. (GMP также использует этот трюк.)
Вот упрощенная версия: Скажем, мы хотим вычислить n // 31
для некоторого беззнаковое 32-битное целое число n
(с использованием вещий //
указать усеченный целочисленное деление). Мы используем «магическую константу» m = 232 // 31
, которая составляет 138547332
. Теперь стало ясно, что для любого n
:
m * n <= 232 * n/31 < m * n + n ⇒ m * n // 232 <= n//31 <= (m * n + n) // 232
(Здесь мы используем тот факт, что если a < b
затем floor(a) <= floor(b)
.)
Кроме того, поскольку n < 232
, m * n // 232
и (m * n + n) // 232
либо одного целого или два последовательных целых числа. Следовательно, один (или оба) этих двух является фактическим значением n//31
.
Теперь мы действительно хотим вычислить n%31
. Поэтому нам нужно умножить (предполагаемое) значение на 31 и вычесть из n
. Если мы будем использовать меньшее из двух возможных дробей, может оказаться, что вычисленное значение по модулю слишком велика, но она может быть слишком большой по 31.
Или, выражаясь в коде:
static unsigned long long magic = 138547332;
unsigned mod31g(unsigned x) {
unsigned q = (x * magic) >> 32;
// To multiply by 31, we multiply by 32 and subtract
unsigned mod = x - ((q << 5) - q);
return mod < 31 ? mod : mod - 31;
}
Фактический алгоритм, используемый gcc, избегает теста в конце, используя несколько более точные вычисления, основанные на умножении на 237//31 + 1
. Это всегда дает правильный коэффициент, но за счет некоторых дополнительных сдвигов и добавляет, чтобы избежать переполнения целых чисел. Как оказалось, версия выше немного быстрее - в том же тесте, что и выше, потребовалось всего 6,3 секунды.
Другие протестированные функции, для полноты:
Наивные развернутый петли
unsigned mod31b(unsigned x) {
unsigned tmp = x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31; x >>= 5;
tmp += x & 31;
tmp = (tmp >> 5) + (tmp & 31);
return tmp >= 31 ? tmp - 31 : tmp;
}
@ улучшение chux в, слегка Оптимизированные
static const unsigned mask1 = (31U << 0) | (31U << 10) | (31U << 20) | (31U << 30);
static const unsigned mask2 = (31U << 5) | (31U << 15) | (31U << 25);
unsigned mod31c(unsigned x) {
x = (x & mask1) + ((x & mask2) >> 5);
x += x >> 20;
x += x >> 10;
x = (x & 31) + ((x >> 5) & 31);
return x >= 31 ? x - 31: x;
}
Это можно сделать, но не легко - вам это не понравится. Ты все еще интересуешься? – harold
Ради вопроса, почему бы и нет? Однако я мог бы отредактировать его по причине. – MaiaVictor
Дубликат этого? : http://stackoverflow.com/questions/3072665/bitwise-and-in-place-of-modulus-operator – Chris