На этом шаге мы рассмотрим задачу перемножения длинных целых чисел без знака.
Длинные целые числа используются в арифметике высокой точности (Д. Кнут. Т. 2), криптографическом кодировании сообщений (шифровании). При этом длина чисел (т.е. количество цифр в записи числа) может достигать нескольких сотен и более. Арифметические операции (сложение, вычитание, умножение, деление) над длинными числами не могут выполняться компьютером за один шаг. Любая такая операция должна реализовываться специально написанной процедурой.
Наиболее естественным является простое расширение стандартного формата: если для записи числа требуется n цифр, то память для них выделяется в виде непрерывной области соответствующего размера.
Предположим, что перемножается два числа U = unun-1un-2 ... u3u2u1 и V = vnvn-1vn-2 ... v3v2v1. Результат имеет суммарную длину W = wnwn-1wn-2 ... w3w2w1. Числа записаны в позиционной системе счисления с основанием (базой) β: 0 <= ui, vi, wi < b. При этом индекс единица соответствует младшей цифре в записи числа.
Этот алгоритм работает подобно тому, как производится умножение на бумаге. Числа представлены массивами цифр. Основание системы счисления b может быть как двоичным, b = 2, так и сколь угодно большим, например, занимающим машинное слово. Важно, чтобы перемножить или сложить цифры можно было одной машинной операцией (или малым их числом). Для представления цифр процедура использует некоторый тип Digit.
procedure SimpleMultiplication (U, V: superlongpositive; var W: superlongpositive; n, m: integer); var i, j: integer; S: DoubleDigit; {Место для формирования двух цифр.} C: Digit; {Разряд переноса.} begin for i := 1 to n do W[i] := 0; {Подготовка места для результата.} for j := 1 to m do {Пo цифрам числа V.} begin С := 0; {Сначала переноса нет.} for i := 1 to n do {Пo цифрам числа U.} begin S := U[i]*V[j]+W[i+j-1]+C; W[i+j-1] := S mod P; {Временное значение очередной цифры.} С := S div P; {Формирование разряда переноса.} end; W[j + n] := С; {Старшая цифра частичного произведения.} end; end;
Непосредственно видно, что сложность этого алгоритма равна O(nm) или в случае сомножителей одинаковой длины - O(n2).
Разделим задачу на подзадачи, разделив запись каждого из сомножителей на две части: U= U2U1, V=V2V1. При n-разрядных сомножителях длины Ui, Vi будут равны n/2. Индексу 1 соответствует младшая половина, а индексу 2 - старшая. Иначе можно записать U = U200..00 + U1 = U2βn/2 + U1.
Произведение UV = (U2βn/2 + U1)(V2βn/2 + V1) = UVβn + U2V1βn/2 + U1V2βn/2 + U1V1. Вычисление произведения предлагаемым способом требует четырех умножений чисел половинной длины, трех сложений и трех умножений на величину βq. Умножения на степень β равносильны сдвигу и требуют времени O(n). То же можно сказать о сложности аддитивных операций.
Четырех умножений чисел оказывается слишком много: log24 = 2, сложность рекурсивного алгоритма будет равна O(n2).
Но можно немного преобразовать расчетную формулу:
UV = U2V2(βn + βn/2) + (U2 - U1)(V1-V2)βn/2 + U1V1(βn/2+1)
Она содержит только три умножения. Очевидный алгоритм, строящийся по этой формуле, описывается уравнением для функции сложности:
Т(n) = 3Т(n/2) + βn,
его решение Т(n) = O(nlog3) = O(n1,58), что значительно лучше классического алгоритма.
На следующем шаге мы рассмотрим возведение целого числа без знака в положительную целую степень.