Это не официальный сайт wikipedia.org 01.01.2023

Алгоритм Барретта — Википедия

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

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

c = a mod n

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

Основная идеяПравить

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

a mod n = a a s n ,  

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

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

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

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

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

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

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

m 2 k = 1 n m = 2 k n  

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

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

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

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

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

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

e = 1 n m 2 k  

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

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

ПримерПравить

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

Наименьшее имеющее смысл значение k   равно k = 7  , поскольку при 2 k < n   приведение будет верно для значений, которые уже минимальны! Для значения семь m = 2 k / 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 / 2 k = 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  . Однако заметим, что промежуточное значение a x   при вычислениях приведёт к переполнению 16-битного значения, когда 810 a  , так что k = 7   в этой ситуации работает лучше.

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

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

  • q = m a 2 k   и
  • r = a q n  .

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

a mod n = { r если  r < n r n иначе  

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

Если a < 2 k  , то

a 2 k ( 2 k mod n ) < n  

Поскольку n m a mod 2 k 2 k < n   независимо от a  , отсюда следует, что

a ( 2 k mod n ) 2 k + n m a mod 2 k 2 k < 2 n  
a ( a a ( 2 k mod n ) 2 k ) + n ( m a mod 2 k ) 2 k < 2 n  
a a 2 k ( 2 k ( 2 k mod n ) ) + n ( m a mod 2 k ) 2 k < 2 n  
a n a 2 k ( 2 k ( 2 k mod n ) n ) + n ( m a mod 2 k ) 2 k < 2 n  
a n a 2 k 2 k n   + n ( m a mod 2 k ) 2 k < 2 n  
a n m a 2 k + n ( m a mod 2 k ) 2 k < 2 n  
a ( m a ( m a mod 2 k ) 2 k ) n < 2 n  
a m a 2 k n < 2 n  
a q n < 2 n  
r < 2 n  

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

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

См. такжеПравить

ПримечанияПравить

ЛитератураПравить