Арифметика вычетов
Вы все учили математику вычетов в школе. Иногда ее называли "арифметикой часов". Если Милдред сказ а-ла, что она будет дома к 10:00, и опоздала на 13 часов, то когда она придет домой, и на сколько лет отец лишит ее водительских прав? Это арифметика по модулю 12. Двадцать три по модулю 12 равно 11.
(10 + 13) mod 12 = 23 mod 12 = 11 mod 12
Другим способом записать это является утверждение об эквивалентности 23 и И по модулю 12:
10 + 13 = 11 (mod 12)
В основном, а = Ь (mod и), если а = Ъ + кп для некоторого целого к. Если а неотрицательно и Ъ находится между 0 и и, можно рассматривать Ъ как остаток при делении а на п. Иногда, Ъ называется вычетома по модулю и. Иногда а называется конгруэнтнымЪ по модулю и (знак тройного равенства, =, обозначает конгруэнтность). Одно и то же можно сказать разными способами.
Множество чисел от 0 до и-1 образует то, что называется полным множеством вычетовпо модулю и. Это означает, что для любого целого а, его остаток по модулю и является некоторым числом от 0 до й-1.
Операция a mod и обозначает остаток от а, являющийся некоторым целым числом от 0 до й-1. Эта операция называется приведением по модулю.Например, 5 mod 3 = 2.
Это определение mod может отличаться от принятого в некоторых языках программирования. Например, оператор получения остатка в языке PASCAL иногда возвращает отрицательное число. Он возвращает число между -(и-1) и и-1. В языке С оператор % возвращает остаток от деления первого выражения на второе, оно может быть отрицательным числом, если любой из операндов отрицателен. Для всех алгоритмов в этой книге проверяйте, что вы добавляете и к результату операции получения остатка, если она возвращает отрицательное число.
Арифметика остатков очень похожа на обычную арифметику: она коммутативна, ассоциативна и дистриб у-тивна. Кроме того, приведение каждого промежуточного результата по модулю п дает тот же результат, как и выполнение всего вычисления с последующим приведением конечного результата по модулю п.
{а + Ъ) mod n = ((a mod n) + (b mod и)) mod n
(а - Ъ) mod n = ((a mod n) - (b mod и)) mod n
(а * Ъ) mod n = ((a mod n) * (b mod и)) mod n
(а * ф+с)) mod п == (((a*b) mod и) + ((а*с) mod и)) mod и
Вычисление mod n часто используется в криптографии, так как вычисление дискретных логарифмов и ква д-ратных корней mod n может быть нелегкой проблемой. Арифметика вычетов, к тому же, легче реализуется на компьютерах, поскольку она ограничивает диапазон промежуточных значений и результата. Для ^-битовых вычетов п, промежуточные результаты любого сложения, вычитание или умножения будут не длиннее, чем 2 к бит. Поэтому в арифметике вычетов мы можем выполнить возведение в степень без огромных промежуточных р е-зультатов. Вычисление степени некоторого числа по модулю другого числа,
ахтойп,
представляет собой просто последовательность умножений и делений, но существуют приемы, ускоряющие это действие. Один из таких приемов стремится минимизировать количество умножений по модулю, другой -оптимизировать отдельные умножения по модулю. Так как операции дистрибутивны, быстрее выполнить возв е-дение в степень как поток последовательных умножений, каждый раз получая вычеты. Сейчас вы не чувствуете разницы, но она будет заметна при умножении 200-битовых чисел.
Например, если вы хотите вычислить с? mod n, не выполняйте наивно семь умножений и одно приведение по модулю:
{а * а * а * а * а * а * а * a) mod n
Вместо этого выполните три меньших умножения и три меньших приведения по м одулю:
{{a2 mod nf mod nf mod n
Точно также,
a16 mod n =(((a2 mod nf mod nf mod nf mod n
Вычисление cf, где х не является степенью 2, ненамного труднее. Двоичная запись представляет х в виде суммы степеней 2: 25 - это бинарное 11001, поэтому 25 = 24 + 23 + 20. Поэтому
a25 mod п = (а*а24) mod п = (a* a**ai6) mod n =
= (а*(( а2) 2) 2*((( а2) 2) 2) 2) mod п = (**((( а*а2) 2) 2) 2) mod n
С продуманным сохранением промежуточных результатов вам понадобится только шесть умножений:
(((((((a2 mod nf af mod nf mod nf mod nf mod nf *a) mod n
Такой прием называется цепочкой сложений[863], или методом двоичных квадратов и умножения. Он использует простую и очевидную цепочку сложений, в основе которой лежит двоичное представление числа. На языке С это выглядит следующим образом:
unsigned long qe2(unsigned long x, unsigned long y, unsigned long n) { unsigned long s, t, u; int i;
s=l; t=x; u=y; while (u) {
if(u&l) s=(s*t)%n;
u»l;
t=(t*t)%n; }
return(s) }
А вот другой, рекурсивный, алгоритм:
unsigned long fast_exp(unsigned long x, unsigned long y, unsigned long N) { unsigned long tmp;
if(y==l) return(x % N); if (1л(х&1)) {
tmp= fast_exp(x,y/2,N);
return ((tmp*tmp)%N); else {
tmp = fast_exp(x,(y-1)/2,N);
tmp = (tmp*tmp)%N;
tmp = (tmp*x)%N;
return (tmp); ) )
Этот метод уменьшает количество операций, в среднем, до 1.5* к операций, где к - длина числа х в битах. Найти способ вычисления с наименьшим количеством операций - трудная проблема (было доказано, что поел е-довательность должна содержать не меньше к-\ операций), но нетрудно снизить число операций до 1.1** или даже лучше при больших к.
Эффективным способом много раз выполнять приведение по модулю для одного п является метод Монтгомери[1111]. Другой метод называется алгоритмом Баррета[87]. Эффективность описанного алгоритма и этих двух методов рассматривается в [210]: алгоритм, рассмотренный мною, является наилучшим для едини ч-ного приведения по модулю, алгоритм Баррета - наилучшим для малых аргументов, а метод Монтгомери - на и-лучшим для обычного возведения в степень по модулю. (Метод Монтгомери также использует преимущество малых показателей степени, используя прием, называющийся смешанной арифметикой.)
Операция, обратная возведению в степень по модулю и, вычисляет дискретный логарифм. Ядальше вкратце рассмотрю эту операцию.