Алгоритм Барретта

Материал из testwiki
Перейти к навигации Перейти к поиску

Алгоритм Барретта — это алгоритм приведения, который в 1986 году предложил П. Д. БарреттШаблон:Sfn. Обычный способ вычисления

c=amodn

использовал бы быстрый алгоритм деления. Приведение Баррета разработано для оптимизации этой операции путём замены делений на умножения в предположении, что n является постоянной величиной, а a<n2.

Основная идея

Пусть s=1/n будет обратным числом для n как число с плавающей запятой. Тогда

amodn=a−⌊as⌋n,

где ⌊x⌋ означает округление до ближайшего целого в сторону уменьшения. Результат будет точным, если s вычислено с достаточной точностью.

Приведение Барретта для одного слова

Барретт первоначально рассматривал целочисленную версию алгоритма выше, когда значения помещаются в машинное слово.

При вычислении amodn с помощью вышеуказанного метода, но с целыми числами, очевидной аналогией было бы деление на n:

func reduce(a uint) uint {
    q := a / n  // Деление в неявной форме возвращает результат, округлённый до ближайшего целого вниз.
    return a - q * n
}

Однако деление может стоить дорого и в условиях задач криптографии может не иметь постоянного времени выполнения на некоторых ЦПУ. В этом случае приведение Барретта аппроксимирует 1/n значением m/2k, поскольку деление на 2k является просто сдвигом вправо и выполняется быстро.

Чтобы вычислить лучшее значение величины m для заданного 2k, рассмотрим

m2k=1n⟺m=2kn

Чтобы m было целым, нам нужно округлить как-то 2k/n. Округление до ближайшего целого даст лучшее приближение, но может привести к тому, что m/2k будет больше 1/n, что может привести к обнулению. Поэтому обычно используется m=⌊2k/n⌋.

Теперь мы можем аппроксимировать функцию выше так:

func reduce(a uint) uint {
    q := (a * m) >> k // ">> k" означает сдвиг  на k позиций.
    return a - q * n
}

Однако, поскольку m/2k⩽1/n, значение q в этой функции может оказаться слишком мало, а тогда a гарантированно будет только в интервале [0,2n), а не в [0,n), как обычно требуется. Вычитание по условию может исправить ситуацию:

func reduce(a uint) uint {
    q := (a * m) >> k
    a -= q * n
    if n <= a {
        a -= n
    }
  return a
}

Поскольку m/2k является лишь приближением, следует рассматривать правильные границы a. Ошибка приближения к 1/n равна

e=1n−m2k

Тогда ошибка значения q равна ae. Поскольку ae<1, то приведение будет верным, поскольку a<1/e. Функция приведения может не сразу дать неверный ответ при a⩾1/e, но границы a следует соблюдать, чтобы обеспечить правильный ответ в общем случае.

При выборе бо́льших значений k область значений a, для которых приведение верно, может быть увеличена, но бо́льшие значения k могут вызвать проблемы переполнения в другом месте.

Пример

Рассмотрим случай n=101 при работе с 16-битными целыми числами.

Наименьшее имеющее смысл значение k равно k=7, поскольку при 2k<n приведение будет верно для значений, которые уже минимальны! Для значения семь m=⌊2k/n⌋=⌊128/101⌋=1. Для значения восемь m=⌊256/101⌋=2. Тогда значение k=8 не даёт никаких преимуществ, поскольку приближение 1/101 в этом случае (2/256) будет тем же самым, что и для 1/128. Для k=9 мы получаем m=⌊512/101⌋=5, что является лучшим приближением.

Теперь рассмотрим границы входных данных для k=7 и k=9. В первом случае e=1/n−m/2k=1/101−1/128=27/12928, так что из a<1/e вытекает a<478,81. Поскольку число a целое, эффективное максимальное значение равно 478. (На самом деле функция будет работать со значениями вплоть до 504.)

Если мы возьмём k=9, то e=1/101−5/512=7/51712 и тогда максимальное значение a равно 7387. (Функция будет работать до значения 7473.)

Следующее значение k, которое приводит к лучшему приближению, равно 13, что даёт 81/8192. Однако заметим, что промежуточное значение ax при вычислениях приведёт к переполнению 16-битного значения, когда 810⩽a, так что k=7 в этой ситуации работает лучше.

Доказательство для границ для конкретного k

Пусть k0 будет наименьшим целым, таким что 2k0>n. Возьмём k0+1 в качестве приемлемого значения k в равенствах выше. Как и в выше приведённом коде, положим

  • q=⌊ma2k⌋ и
  • r=a−qn.

Поскольку осуществляется округление до целого вниз, q является целым числом и r≡a(modn). Также, если a<2k, то r<2n. В этом случае переписываем отрывок кода выше:

amodn={rесли r<nr−nиначе

Доказательство неравенства r<2n:

Если a<2k, то

a2k⋅(2kmodn)<n

Поскольку n⋅mamod2k2k<n независимо от a, отсюда следует, что

a⋅(2kmodn)2k+n⋅mamod2k2k<2n
a−(a−a⋅(2kmodn)2k)+n⋅(mamod2k)2k<2n
a−a2k⋅(2k−(2kmodn))+n⋅(mamod2k)2k<2n
a−na2k⋅(2k−(2kmodn)n)+n⋅(mamod2k)2k<2n
a−na2k⋅⌊2kn⌋ +n⋅(mamod2k)2k<2n
a−nma2k+n⋅(mamod2k)2k<2n
a−(ma−(mamod2k)2k)⋅n<2n
a−⌊ma2k⌋⋅n<2n
a−qn<2n
r<2n

Приведение Барретта к нескольким машинным словам

Основной причиной для Баррета обратиться к приведению была работа с реализацией алгоритма RSA, где значения чисел почти наверняка выйдут за границы машинного слова. В этой ситуации Барретт представил алгоритм, который аппроксимирует числа, размещённые в нескольких машинных словах. Детали см. в разделе 14.3.3 книги Handbook of Applied CryptographyШаблон:Sfn.

См. также

Примечания

Шаблон:Примечания

Литература

Шаблон:Refbegin

Шаблон:Refend

Шаблон:Rq