Алгоритм умножения — это алгоритм (или метод) умножения двух чисел. В зависимости от размера чисел, разные алгоритмы более эффективны, чем другие. Известно множество алгоритмов, и по этой теме было проведено много исследований.
Самый старый и простой метод, известный с античности как длинное умножение или умножение в начальной школе , состоит из умножения каждой цифры в первом числе на каждую цифру во втором и сложения результатов. Это имеет временную сложность , где n — количество цифр. При ручном выполнении это также можно перефразировать как умножение методом сетки или решетчатое умножение . В программном обеспечении это может называться «сдвиг и сложение», поскольку сдвиги битов и сложение являются единственными двумя необходимыми операциями.
В 1960 году Анатолий Карацуба открыл умножение Карацубы , вызвав поток исследований в области быстрых алгоритмов умножения. Этот метод использует три умножения вместо четырех для умножения двух двузначных чисел. (Вариант этого также может быть использован для быстрого умножения комплексных чисел .) Рекурсивно это имеет временную сложность . Разбиение чисел на более чем две части приводит к умножению Тума-Кука ; например, использование трех частей приводит к алгоритму Тума-3 . Использование многих частей может установить показатель степени произвольно близко к 1, но постоянный множитель также растет, что делает его непрактичным.
В 1968 году был открыт алгоритм Шёнхаге-Штрассена , который использует преобразование Фурье по модулю . Его временная сложность составляет . В 2007 году Мартин Фюрер предложил алгоритм со сложностью . В 2014 году Харви, Йорис ван дер Хувен и Лесерф предложили алгоритм со сложностью , таким образом сделав неявную константу явной; он был улучшен до в 2018 году. Наконец, в 2019 году Харви и ван дер Хувен придумали галактический алгоритм со сложностью . Это соответствует предположению Шёнхаге и Штрассена о том, что это будет оптимальная граница, хотя сегодня это остается гипотезой .
Алгоритмы целочисленного умножения можно также использовать для умножения многочленов с помощью метода подстановки Кронекера .
Если используется позиционная система счисления , естественный способ умножения чисел преподается в школах как умножение в столбик , иногда называемое умножением начальной школы , иногда называемым Стандартным алгоритмом : умножить множимое на каждую цифру множителя, а затем сложить все правильно сдвинутые результаты. Это требует запоминания таблицы умножения для однозначных чисел.
Это обычный алгоритм умножения больших чисел вручную в десятичной системе счисления. Человек, выполняющий умножение в столбик на бумаге, записывает все результаты, а затем складывает их; пользователь счет суммирует результаты сразу после вычисления каждого из них.
В этом примере используется умножение в столбик для умножения 23 958 233 (множимое) на 5 830 (множитель) и получается результат 139 676 498 390 (произведение).
23958233× 5830——————————————— 00000000 ( = 23,958,233 × 0) 71874699 (= 23 958 233 × 30) 191665864 (= 23 958 233 × 800)+ 119791165 (= 23 958 233 × 5 000)——————————————— 139676498390 (= 139 676 498 390)
В некоторых странах, например, в Германии , указанное выше умножение изображается аналогичным образом, но исходное произведение располагается горизонтально, а вычисление начинается с первой цифры множителя: [1]
23958233 · 5830——————————————— 119791165 191665864 71874699 000000099——————————————— 139676498390
Ниже приведен псевдокод, описывающий процесс умножения выше. Он сохраняет только одну строку для сохранения суммы, которая в конечном итоге становится результатом. Обратите внимание, что оператор '+=' используется для обозначения суммы к существующему значению и операции сохранения (подобно таким языкам, как Java и C) для компактности.
умножить ( a [ 1 .. p ] , b [ 1 .. q ] , основание ) // Операнды, содержащие самые правые цифры в индексе 1 product = [ 1 .. p + q ] // Выделяем место для результата для b_i = 1 до q // для всех цифр в b перенос = 0 для a_i = 1 до p // для всех цифр в a произведение [ a_i + b_i - 1 ] += перенос + a [ a_i ] * b [ b_i ] перенос = произведение [ a_i + b_i - 1 ] / основание продукт [ a_i + b_i - 1 ] = продукт [ a_i + b_i - 1 ] mod base продукт [ b_i + p ] = перенос // последняя цифра берется из конечного переноса возврат товара
Некоторые чипы реализуют длинное умножение, на аппаратном уровне или в микрокоде , для различных размеров целых и плавающих слов. В арифметике произвольной точности обычно используют длинное умножение с основанием, равным 2 w , где w — количество бит в слове, для умножения относительно небольших чисел. Чтобы умножить два числа с n цифрами с помощью этого метода, требуется около n 2 операций. Более формально, умножение двух n -значных чисел с помощью длинного умножения требует Θ ( n 2 ) однозначных операций (сложений и умножений).
При программной реализации алгоритмы длинного умножения должны иметь дело с переполнением во время сложений, что может быть затратным. Типичное решение — представить число в малой базе, b , так что, например, 8 b является представимым машинным целым числом. Затем можно выполнить несколько сложений, прежде чем произойдет переполнение. Когда число становится слишком большим, мы добавляем его часть к результату или переносим и отображаем оставшуюся часть обратно в число, которое меньше b . Этот процесс называется нормализацией . Ричард Брент использовал этот подход в своем пакете Fortran, MP. [2]
Первоначально компьютеры использовали очень похожий алгоритм длинного умножения в двоичной системе счисления, но современные процессоры оптимизировали схемы для быстрого умножения, используя более эффективные алгоритмы, ценой более сложной аппаратной реализации. [ необходима цитата ] В двоичной системе счисления длинное умножение иногда называют «сдвигом и сложением» , потому что алгоритм упрощается и состоит только из сдвига влево (умножение на степени двойки) и сложения. Большинство доступных в настоящее время микропроцессоров реализуют этот или другие похожие алгоритмы (например, кодирование Бута ) для различных целочисленных и плавающих размеров в аппаратных умножителях или в микрокоде . [ необходима цитата ]
На доступных в настоящее время процессорах инструкция побитового сдвига обычно (но не всегда) быстрее инструкции умножения и может использоваться для умножения (сдвига влево) и деления (сдвига вправо) на степени двойки. Умножение на константу и деление на константу можно реализовать с помощью последовательности сдвигов и сложений или вычитаний. Например, существует несколько способов умножения на 10, используя только побитовый сдвиг и сложение.
(( x << 2 ) + x ) << 1 # Здесь 10*x вычисляется как (x*2^2 + x)*2 ( x << 3 ) + ( x << 1 ) # Здесь 10*x вычисляется как x*2^3 + x*2
В некоторых случаях такие последовательности сдвигов и сложений или вычитаний будут превосходить аппаратные умножители и особенно делители. Деление на число формы или часто может быть преобразовано в такую короткую последовательность.
В дополнение к стандартному длинному умножению, есть несколько других методов, используемых для выполнения умножения вручную. Такие алгоритмы могут быть разработаны для скорости, простоты вычислений или образовательной ценности, особенно когда компьютеры или таблицы умножения недоступны.
Метод сетки (или метод ящика) — это вводный метод для умножения многозначных чисел, который часто преподается ученикам начальной школы или начальной школы . Он является стандартной частью национальной программы по математике начальной школы в Англии и Уэльсе с конца 1990-х годов. [3]
Оба множителя разбиваются («разделяются») на сотни, десятки и единицы, а затем произведения частей вычисляются явно на относительно простом этапе умножения, после чего эти вклады суммируются для получения окончательного ответа на отдельном этапе сложения.
Например, вычисление 34 × 13 можно выполнить с помощью сетки:
300 40 90 + 12 ———— 442
затем складываем, чтобы получить 442, либо в виде единой суммы (см. справа), либо путем формирования итогов по строкам (300 + 40) + (90 + 12) = 340 + 102 = 442.
Этот подход к вычислениям (хотя и не обязательно с явным расположением сетки) также известен как алгоритм частичных произведений . Его суть заключается в вычислении простых умножений по отдельности, при этом все сложение откладывается на заключительный этап сбора.
Метод сетки в принципе может быть применен к факторам любого размера, хотя количество подпродуктов становится громоздким по мере увеличения количества цифр. Тем не менее, он рассматривается как полезный явный метод для введения идеи многозначных умножений; и в эпоху, когда большинство вычислений умножения выполняются с помощью калькулятора или электронной таблицы, на практике это может быть единственным алгоритмом умножения, который когда-либо понадобится некоторым ученикам.
Решеточное или сито, умножение алгоритмически эквивалентно длинному умножению. Оно требует подготовки решетки (сетки, нарисованной на бумаге), которая направляет вычисления и отделяет все умножения от сложений . Оно было введено в Европу в 1202 году в Liber Abaci Фибоначчи . Фибоначчи описал операцию как умственную, используя свою правую и левую руки для выполнения промежуточных вычислений. Матракчи Насух представил 6 различных вариантов этого метода в этой книге 16-го века Umdet-ul Hisab. Он широко использовался в школах Эндерун по всей Османской империи. [4] Кости Напьера или стержни Напьера также использовали этот метод, как опубликовано Напьером в 1617 году, в год его смерти.
Как показано в примере, множимое и множитель записаны выше и справа от решетки или решета. Это встречается в «Арифметике» Мухаммада ибн Мусы аль-Хорезми , одном из источников Леонардо, упомянутом Сиглером, автором «Fibonacci's Liber Abaci», 2002. [ необходима цитата ]
На рисунках справа показано, как вычислить 345 × 12 с помощью решетчатого умножения. В качестве более сложного примера рассмотрим рисунок ниже, на котором показано вычисление 23 958 233, умноженного на 5 830 (множитель); результат равен 139 676 498 390. Обратите внимание, что 23 958 233 находится вдоль верхней части решетки, а 5 830 — вдоль правой стороны. Произведения заполняют решетку, а сумма этих произведений (по диагонали) находится вдоль левой и нижней сторон. Затем эти суммы суммируются, как показано.
Двоичный метод также известен как крестьянское умножение, потому что он широко использовался людьми, которые классифицируются как крестьяне и, таким образом, не запомнили таблицу умножения, необходимую для умножения в столбик. [5] [ не удалось проверить ] Алгоритм использовался в Древнем Египте. [6] Его основные преимущества в том, что ему можно быстро обучиться, он не требует запоминания и может быть выполнен с использованием жетонов, таких как фишки для покера , если бумага и карандаш недоступны. Недостатком является то, что он требует больше шагов, чем длинное умножение, поэтому он может быть громоздким для больших чисел.
На бумаге запишите в один столбец числа, которые вы получаете, когда вы многократно делите множитель пополам, игнорируя остаток; в столбце рядом с ним многократно удваивайте множимое. Вычеркните каждую строку, в которой последняя цифра первого числа четная, и сложите оставшиеся числа во втором столбце, чтобы получить произведение.
В этом примере используется крестьянский метод умножения для умножения 11 на 3, в результате чего получается 33.
Десятичный: Двоичный:11 3 1011 115 6 101 1102121011001 24 1 11000 —— —————— 33 100001
Подробно описываем шаги:
Метод работает, поскольку умножение является распределительным , поэтому:
Более сложный пример, использующий цифры из предыдущих примеров (23 958 233 и 5 830):
Десятичный: Двоичный:583023958233101101100011010110110110010010110110012915 47916466 101101100011 101101101100100101101100101457 95832932 10110110001 10110110110010010110110010072819166586410110110001011011011001001011011001000364383331728101101100101101101100100101101100100001827666634561011011010110110110010010110110010000091 1533326912 1011011 101101101100100101101100100000045 3066653824 101101 101101101100100101101100100000002261333076481011010110110110010010110110010000000011 12266615296 1011 10110110110010010110110010000000005 24533230592 101 10110110110010010110110010000000000249066461184101011011011001001011011001000000000001 98132922368 1 1011011011001001011011001000000000000 ————————————— 1022143253354344244353353243222210110 (до переноса) 139676498390 10000010000101010111100011100111010110
Эту формулу можно использовать в некоторых случаях для облегчения выполнения задач на умножение:
В случае, когда и являются целыми числами, имеем, что
потому что и либо оба четные, либо оба нечетные. Это означает, что
и достаточно (предварительно) вычислить целую часть квадратов, деленных на 4, как в следующем примере.
Ниже приведена таблица соответствия квадратов четвертей с отброшенным остатком для цифр от 0 до 18; это позволяет умножать числа до 9×9 .
Если, например, вы хотите умножить 9 на 3, вы увидите, что сумма и разность составляют 12 и 6 соответственно. Поиск обоих этих значений в таблице дает 36 и 9, разность которых составляет 27, что является произведением 9 и 3.
В доисторические времена умножение на четверть квадрата включало функцию возведения в степень ; некоторые источники [7] [8] приписывают это вавилонской математике (2000–1600 гг. до н.э.).
Антуан Вуазен опубликовал таблицу четвертных квадратов от 1 до 1000 в 1817 году в качестве помощи при умножении. Более крупная таблица четвертных квадратов от 1 до 100000 была опубликована Сэмюэлем Лонди в 1856 году, [9] а таблица от 1 до 200000 — Джозефом Блатером в 1888 году. [10]
Умножители четверти квадрата использовались в аналоговых компьютерах для формирования аналогового сигнала , который был произведением двух аналоговых входных сигналов. В этом приложении сумма и разность двух входных напряжений формируются с помощью операционных усилителей . Квадрат каждого из них аппроксимируется с помощью кусочно-линейных цепей. Наконец, разность двух квадратов формируется и масштабируется с коэффициентом одной четверти с помощью еще одного операционного усилителя.
В 1980 году Эверетт Л. Джонсон предложил использовать метод четверти квадрата в цифровом умножителе. [11] Например, чтобы сформировать произведение двух 8-битных целых чисел, цифровое устройство формирует сумму и разность, ищет обе величины в таблице квадратов, берет разность результатов и делит на четыре, сдвигая два бита вправо. Для 8-битных целых чисел таблица четвертных квадратов будет иметь 2 9 −1=511 записей (одна запись для всего диапазона 0..510 возможных сумм, разности используют только первые 256 записей в диапазоне 0..255) или 2 9 −1=511 записей (для отрицательных разностей используется техника 2-дополнения и 9-битного маскирования, которая позволяет избежать проверки знака разностей), каждая запись имеет ширину 16 бит (значения записей находятся в диапазоне от (0²/4)=0 до (510²/4)=65025).
Техника множителя четверть квадрата принесла пользу 8-битным системам, которые не поддерживают аппаратный множитель. Чарльз Патни реализовал это для 6502. [12 ]
Направление исследований в области теоретической информатики посвящено числу однобитовых арифметических операций, необходимых для умножения двухбитовых целых чисел. Это известно как вычислительная сложность умножения. Обычные алгоритмы, выполняемые вручную, имеют асимптотическую сложность , но в 1960 году Анатолий Карацуба обнаружил, что возможна и лучшая сложность (с помощью алгоритма Карацубы ).
В настоящее время алгоритмом с лучшей вычислительной сложностью является алгоритм 2019 года Дэвида Харви и Йориса ван дер Хувена , который использует стратегии использования теоретико-числовых преобразований, введенных в алгоритме Шёнхаге–Штрассена, для умножения целых чисел с использованием только операций. [13] Предполагается, что это наилучший возможный алгоритм, но нижние границы неизвестны.
Умножение Карацубы — это алгоритм «разделяй и властвуй» с трудоёмкостью O( n log 2 3 ) ≈ O( n 1,585 ), который использует рекурсию для объединения подвычислений.
Переписывая формулу, можно сделать подвычисления / рекурсию. Выполняя рекурсию, можно решить это быстро.
Пусть и будут представлены как -значные строки в некотором основании . Для любого положительного целого числа, меньшего , можно записать два заданных числа как
где и меньше . Произведение тогда равно
где
Эти формулы требуют четырех умножений и были известны Чарльзу Бэббиджу . [14] Карацуба заметил, что можно вычислить всего за три умножения, за счет нескольких дополнительных сложений. С и как и прежде можно заметить, что
Из-за накладных расходов на рекурсию умножение Карацубы выполняется медленнее, чем длинное умножение для малых значений n ; поэтому типичные реализации переключаются на длинное умножение для малых значений n .
Исследуя закономерности после расширения, можно увидеть следующее:
Каждое слагаемое связано с уникальным двоичным числом от 0 до , например , и т. д. Кроме того, в этой двоичной строке B возводится в степень числа 1, умноженного на m.
Если выразить это меньшим количеством терминов, то получим:
, где означает цифру в числе i на позиции j. Обратите внимание, что
Алгоритм Карацубы был первым известным алгоритмом умножения, который асимптотически быстрее, чем длинное умножение [15] , и поэтому его можно рассматривать как отправную точку для теории быстрых умножений.
Другой метод умножения называется Toom–Cook или Toom-3. Метод Toom–Cook разбивает каждое число, которое нужно умножить, на несколько частей. Метод Toom–Cook является одним из обобщений метода Карацубы. Трехходовой метод Toom–Cook может выполнить умножение размера 3N за стоимость пяти умножений размера N. Это ускоряет операцию в 9/5 раз, в то время как метод Карацубы ускоряет ее в 4/3.
Хотя использование все большего количества частей может еще больше сократить время, затрачиваемое на рекурсивные умножения, накладные расходы на сложения и управление цифрами также растут. По этой причине метод преобразований Фурье обычно быстрее для чисел с несколькими тысячами цифр и асимптотически быстрее для еще больших чисел.
Каждое число в системе счисления B можно записать в виде многочлена:
Более того, умножение двух чисел можно рассматривать как произведение двух многочленов:
Потому что для : у нас есть свертка.
Используя быстрое преобразование Фурье (БПФ) с правилом свертки, мы можем получить
● . То есть; ● , где — соответствующий коэффициент в пространстве Фурье. Это также можно записать как: fft(a * b) = fft(a) ● fft(b).
У нас есть тот же коэффициент из-за линейности при преобразовании Фурье, а также потому, что эти полиномы состоят только из одного уникального члена на коэффициент:
и
Правило свертки: ●
Мы свели нашу задачу свертки к задаче произведения с помощью БПФ.
Находя ifft (полиномиальную интерполяцию) для каждого , получаем искомые коэффициенты.
Алгоритм использует стратегию «разделяй и властвуй», чтобы разделить проблему на подзадачи.
Его временная сложность составляет O( n log( n ) log(log( n ))).
Алгоритм был изобретен Штрассеном (1968). Он был реализован на практике, а теоретические гарантии были предоставлены в 1971 году Шёнхаге и Штрассеном, что привело к появлению алгоритма Шёнхаге–Штрассена . [16]
В 2007 году швейцарский математик Мартин Фюрер из Университета штата Пенсильвания улучшил асимптотическую сложность целочисленного умножения до n log( n ) 2 Θ( log * ( n )) с помощью преобразований Фурье над комплексными числами , [17] где log * обозначает итерированный логарифм . Аниндья Де, Чандан Саха, Пиюш Курур и Рампрасад Саптариши в 2008 году предложили аналогичный алгоритм с использованием модульной арифметики, достигнув того же времени выполнения. [18] В контексте вышеизложенного материала, то, чего достигли эти последние авторы, заключается в том, что они нашли N, намного меньшее, чем 2 3 k + 1, так что Z / NZ имеет корень степени (2 m ) из единицы. Это ускоряет вычисления и снижает временную сложность. Однако эти последние алгоритмы быстрее, чем алгоритм Шёнхаге–Штрассена, только для непрактично больших входных данных.
В 2014 году Харви, Йорис ван дер Хувен и Лесерф [19] предложили новый алгоритм, который достигает времени выполнения , делая явной подразумеваемую константу в показателе степени. Они также предложили вариант своего алгоритма, который достигает , но справедливость которого основана на стандартных предположениях о распределении простых чисел Мерсенна . В 2016 году Кованов и Томе предложили алгоритм целочисленного умножения, основанный на обобщении простых чисел Ферма , который предположительно достигает границы сложности . Это соответствует условному результату Харви, ван дер Хувена и Лесерф 2015 года, но использует другой алгоритм и опирается на другую гипотезу. [20] В 2018 году Харви и ван дер Хувен использовали подход, основанный на существовании коротких векторов решетки, гарантированных теоремой Минковского, чтобы доказать безусловную границу сложности . [21]
В марте 2019 года Дэвид Харви и Йорис ван дер Хувен объявили об открытии алгоритма умножения O ( n log n ) . [22] Он был опубликован в Annals of Mathematics в 2021 году. [23] Поскольку Шёнхаге и Штрассен предсказали, что n log( n ) является «наилучшим возможным» результатом, Харви сказал: «... ожидается, что наша работа станет концом пути для этой проблемы, хотя мы пока не знаем, как это строго доказать». [24]
Существует тривиальная нижняя граница Ω ( n ) для умножения двух n -битных чисел на одном процессоре; неизвестен ни один алгоритм сопоставления (на обычных машинах, то есть на машинах, эквивалентных Тьюрингу), ни какая-либо более точная нижняя граница. Умножение лежит за пределами AC 0 [ p ] для любого простого числа p , что означает, что не существует семейства схем постоянной глубины, полиномиального (или даже субэкспоненциального) размера, использующих вентили AND, OR, NOT и MOD p , которые могут вычислять произведение. Это следует из сведения MOD q к умножению с постоянной глубиной. [25] Нижние границы для умножения также известны для некоторых классов ветвящихся программ . [26]
Сложное умножение обычно включает четыре умножения и два сложения.
Или
Как заметил Питер Унгар в 1963 году, можно сократить количество умножений до трех, используя по сути те же вычисления, что и в алгоритме Карацубы . [27] Произведение ( a + bi ) · ( c + di ) можно вычислить следующим образом.
Этот алгоритм использует только три умножения вместо четырех и пять сложений или вычитаний вместо двух. Если умножение обходится дороже, чем три сложения или вычитания, как при ручном вычислении, то есть выигрыш в скорости. На современных компьютерах умножение и сложение могут занимать примерно одинаковое время, поэтому выигрыша в скорости может не быть. Есть компромисс в том, что может быть некоторая потеря точности при использовании плавающей точки.
Для быстрых преобразований Фурье (БПФ) (или любого линейного преобразования ) комплексные умножения производятся на постоянные коэффициенты c + di (называемые вращательными факторами в БПФ), в этом случае два сложения ( d − c и c + d ) могут быть предварительно вычислены. Следовательно, требуется только три умножения и три сложения. [28] Однако, замена умножения на сложение таким образом может быть невыгодной с современными блоками с плавающей точкой . [29]
Все вышеперечисленные алгоритмы умножения также могут быть расширены для умножения многочленов . В качестве альтернативы можно использовать технику подстановки Кронекера для преобразования задачи умножения многочленов в одно двоичное умножение. [30]
Методы длинного умножения можно обобщить, чтобы обеспечить умножение алгебраических формул:
14ac - 3ab + 2 умножить на ac - ab + 1
14ac -3ab 2 ac-ab 1 ———————————————————— 14а 2 в 2 -3а 2 бв 2ак -14а 2 бс 3 а 2 б 2 -2аб 14ac -3ab 2 ——————————————————————————————————————— 14а 2 в 2 -17а 2 бв 16ак 3а 2 б 2 -5аб +2 ====================================== [31]
В качестве еще одного примера умножения столбиком рассмотрим умножение 23 длинных тонн (т), 12 центнеров (ц) и 2 квартеров (квартеров) на 47. В этом примере используются меры эвердьюпойс : 1 т = 20 ц, 1 ц = 4 квартера.
т цвт кварта 23 12 2 47 х ———————————————— 141 94 94 940 470 29 23 ———————————————— 1110 587 94 ———————————————— 1110 7 2 ================= Ответ: 1110 тонн 7 центнеров 2 кварты
Сначала умножьте четверти на 47, результат 94 записывается в первую рабочую область. Затем умножьте cwt 12*47 = (2 + 10)*47, но пока не складывайте частичные результаты (94, 470). Аналогично умножьте 23 на 47, что дает (141, 940). Столбец quarters суммируется, и результат помещается во вторую рабочую область (тривиальный ход в этом случае). 94 quarters — это 23 cwt и 2 qtr, поэтому поместите 2 в ответ, а 23 — в следующий столбец слева. Теперь сложите три записи в столбце cwt, что дает 587. Это 29 t 7 cwt, поэтому запишите 7 в ответ, а 29 — в столбец слева. Теперь сложите столбец tons. Никаких корректировок вносить не нужно, поэтому результат просто копируется вниз.
Эту же схему и методы можно использовать для любых традиционных единиц измерения и недесятичных валют, таких как старая британская система фунтов стерлингов (£sd) .