Есть ли простой способ сделать модуль 2 ^ 32 - 1 операции? - PullRequest
4 голосов
/ 25 марта 2012

Я только что слышал о том, что x mod (2^32-1) и x / (2^32-1) будет легко, но как?

для расчета по формуле:

x n = (x n-1 + x n-1 / b) мод b.

Для b = 2^32, это просто, x%(2^32) == x & (2^32-1); и x / (2^32) == x >> 32. (^ здесь не XOR). Как это сделать, когда b = 2 ^ 32 - 1.

На странице https://en.wikipedia.org/wiki/Multiply-with-carry. Они говорят "arithmetic for modulus 2^32 − 1 requires only a simple adjustment from that for 2^32". Так что же такое «простая настройка»?

Ответы [ 4 ]

6 голосов
/ 25 марта 2012

(Этот ответ обрабатывает только случай mod.)

Я предполагаю, что тип данных x больше 32 бит (этот ответ будет работать с любым положительным целым числом) и чтоон является положительным (отрицательный случай - просто -(-x mod 2^32-1)), поскольку, если он не более 32 бит, на вопрос можно ответить

x mod (2^32-1) = 0 if x == 2^32-1, x otherwise
x / (2^32 - 1) = 1 if x == 2^32-1, 0 otherwise

Мы можем написать x в базе 2 ^ 32, сцифры x0, x1, ..., xn.Итак,

  x = x0 + 2^32 * x1 + (2^32)^2 * x2 + ... + (2^32)^n * xn

Это проясняет ответ, когда мы делаем модуль, начиная с 2^32 == 1 mod 2^32-1.То есть

  x == x0 + 1 * x1 + 1^2 * x2 + ... + 1^n * xn (mod 2^32-1)
    == x0 + x1 + ... + xn (mod 2^32-1)

x mod 2^32-1 совпадает с суммой базовых 2 ^ 32 цифр!(мы не можем бросить мод 2 ^ 32-1 еще).Сейчас у нас есть два случая: либо сумма находится между 0 и 2 ^ 32-1, либо она больше.В первом мы закончили;позже мы можем просто повторяться, пока не получим значение от 0 до 2 ^ 32-1.Получение цифр в базе 2 ^ 32 быстрое, поскольку мы можем использовать побитовые операции.В Python (это не обрабатывает отрицательные числа):

def mod_2to32sub1(x):
    s = 0 # the sum

    while x > 0: # get the digits
        s += x & (2**32-1)
        x >>= 32

    if s > 2**32-1:
        return mod_2to32sub1(s)
    elif s == 2**32-1:
        return 0
    else:
        return s

(Это очень легко обобщить до x mod 2^n-1, фактически вы просто заменяете любое вхождение 32 на n в этом ответе.)

(EDIT: добавлено предложение elif, чтобы избежать бесконечного цикла при mod_2to32sub1(2**32-1). EDIT2: заменено ^ на ** ... oops.)

2 голосов
/ 25 марта 2012

Таким образом, вы вычисляете с «правилом» 2 32 = 1. В общем, 2 32 + x = 2 x . Вы можете упростить 2 a , взяв экспоненту по модулю 32. Пример: 2 66 = 2 2 .

Вы можете выразить любое число в двоичном виде, а затем уменьшить показатели. Пример: число 2 40 + 2 38 + 2 20 + 2 + 1 может быть упрощено до 2 8 + 2 6 + 2 20 + 2 + 1.

Как правило, вы можете группировать экспоненты каждые 32 степени по 2 и "понижать" все показатели по модулю 32.

Для 64-битных слов число может быть выражено как

2 32 A + B

, где 0 <= A, B <= 2 <sup>32 -1. Получение A и B легко с побитовыми операциями.

Таким образом, вы можете упростить это до A + B, который намного меньше: максимум 2 33 . Затем проверьте, является ли это число не менее 2 32 -1, и вычтите 2 32 - 1 в этом случае.

Это позволяет избежать дорогостоящего прямого деления.

1 голос
/ 25 марта 2012

Модуль уже объяснен, тем не менее, давайте повторим.

Чтобы найти остаток от k по модулю 2^n-1, напишите

k = a + 2^n*b,  0 <= a < 2^n

Затем

k = a + ((2^n-1) + 1) * b
  = (a + b) + (2^n-1)*b
  ≡ (a + b) (mod 2^n-1)

Если a + b >= 2^n, повторять до тех пор, пока остаток не станет меньше 2^n, а если это приведет вас к a + b = 2^n-1, заменить его на 0. Каждое "смещение вправо на n и добавьте к последнему n bits "перемещает первый установленный бит вправо на n или n-1 мест (если только k < 2^(2*n-1), когда первый установленный бит после сдвига и добавления может быть 2^n бит).Поэтому, если ширина типа велика по сравнению с n, для этого потребуется много смен - рассмотрим 128-битный тип и n = 3, для больших k вам потребуется более 40 смен.Чтобы уменьшить количество требуемых смен, вы можете использовать тот факт, что

2^(m*n) - 1 = (2^n - 1) * (2^((m-1)*n) + 2^((m-2)*n) + ... + 2^(2*n) + 2^n + 1),

, из которых мы будем использовать только то, что 2^n - 1 делит 2^(m*n) - 1 на все m > 0.Затем вы сдвигаетесь на значения, кратные n, которые примерно равны половине максимальной длины в битах, которую может иметь значение на этом шаге.Для приведенного выше примера 128-битного типа и остатка по модулю 7 (2^3 - 1) ближайшие кратные от 3 до 128/2 равны 63 и 66, первое смещение на 63 бита

r_1 = (k & (2^63 - 1)) + (k >> 63) // r_1 < 2^63 + 2^(128-63) < 2^66

дополучить число максимум с 66 битами, затем сдвинуть на 66/2 = 33 бита

r_2 = (r_1 & (2^33 - 1)) + (r_1 >> 33) // r_2 < 2^33 + 2^(66-33) = 2^34

, чтобы достичь не более 34 бит.Следующий сдвиг на 18 бит, затем 9, 6, 3

r_3 = (r_2 & (2^18 - 1)) + (r_2 >> 18) // r_3 < 2^18 + 2^(34-18) < 2^19
r_4 = (r_3 & (2^9 - 1)) + (r_3 >> 9)   // r_4 < 2^9 + 2^(19-9) < 2^11
r_5 = (r_4 & (2^6 - 1)) + (r_4 >> 6)   // r_5 < 2^6 + 2^(11-6) < 2^7
r_6 = (r_5 & (2^3 - 1)) + (r_5 >> 3)   // r_6 < 2^3 + 2^(7-3) < 2^5
r_7 = (r_6 & (2^3 - 1)) + (r_6 >> 3)   // r_7 < 2^3 + 2^(5-3) < 2^4

Теперь достаточно одного вычитания, если r_7 >= 2^3 - 1 достаточно.Чтобы вычислить k % (2^n -1) в b-битном типе, необходимы сдвиги O (log 2 (b / n)).

Коэффициент получается аналогично, снова пишем

k = a + 2^n*b,  0 <= a < 2^n
  = a + ((2^n-1) + 1)*b
  = (2^n-1)*b + (a+b),

, поэтому k/(2^n-1) = b + (a+b)/(2^n-1), и мы продолжаем пока a+b > 2^n-1.Здесь, к сожалению, мы не можем уменьшить работу, сдвигая и маскируя примерно половину ширины, поэтому этот метод эффективен только тогда, когда n не намного меньше ширины типа.

Код для быстрых случаев, когда n не слишком мало:

unsigned long long modulus_2n1(unsigned n, unsigned long long k) {
    unsigned long long mask = (1ULL << n) - 1ULL;
    while(k > mask) {
        k = (k & mask) + (k >> n);
    }
    return k == mask ? 0 : k;
}

unsigned long long quotient_2n1(unsigned n, unsigned long long k) {
    unsigned long long mask = (1ULL << n) - 1ULL, quotient = 0;
    while(k > mask) {
        quotient += k >> n;
        k = (k & mask) + (k >> n);
    }
    return k == mask ? quotient + 1 : quotient;
}

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

0 голосов
/ 25 марта 2012

Это не так.Что вы слышали, так это то, что x mod 2 ^ n и x / 2 ^ n проще.x / 2 ^ n можно выполнить как x >> n, а x mod 2 ^ n, сделать x & (1 <

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...