Алгоритм деления

Материал из Википедии — свободной энциклопедии
Это старая версия этой страницы, сохранённая Arthur Viole (обсуждение | вклад) в 07:15, 27 марта 2022. Она может серьёзно отличаться от текущей версии.
Перейти к навигации Перейти к поиску

Алгоритм деления — это алгоритм, который для двух данных целых чисел N и D вычисляет их частное и/или остаток, результат деления с остатком. Некоторые из алгоритмов предназначены для вычислений вручную, другие реализованы в цифровых схемах и программном обеспечении.

Алгоритмы деления разбиваются на две большие категории: медленное деление и быстрое деление. Алгоритмы медленного деления дают по одному знаку результата за итерацию. Примерами медленного деления служат алгоритмы деления с восстановлением, без восстановления и SRT. Методы быстрого деления начинаются с аппроксимации конечного частного и дают вдвое больше знаков в конечном результате на каждой итерации. Алгоритмы Ньютона – Рапсона и Гольдшмидта попадают в эту категорию.

Варианты этих алгоритмов позволяют использовать быстрые алгоритмы умножения. В результате этого для больших целых чисел время вычисления, необходимое для деления, будет тем же самым (с точностью до постоянного множителя), что и время, необходимое для выполнения умножения, какой бы алгоритм из перечисленных не был применён.

Обсуждение будет использовать обозначения , где

  • — числитель (делимое)
  • — знаменатель (делитель)

являются входными числами, а

  • — частное
  • — остаток

являются выходными данными.

Деление путём вычитаний

Простейший алгоритм, исторически встроенный в алгоритм поиска наибольшего общего делителя и представленный в Началах Евклида, Книга VII Предложение 1, находит остаток деления двух положительных целых чисел с помощью только вычитания и сравнения:

R := N
while R >= D do
  R := R  D
end
return R

Доказательство, что частное и остаток существуют и единственные (описано в статье Деление с остатком) дают полный алгоритм деления, основанный на сложениях, вычитаниях и сравнениях:

function divide(N, D)
  if D = 0 then error(DivisionByZero) end
  if D < 0 then (Q, R) := divide(N, D); return (Q, R) end
  if N < 0 then
    (Q,R) := divide(N, D)
    if R = 0 then return (Q, 0)
    else return (Q  1, D  R) end
  end
  -- Здесь N >= 0 и D >= 0
  return divide_unsigned(N, D)
end

function divide_unsigned(N, D)
  Q := 0; R := N
  while R >= D do
    Q := Q + 1
    R := R  D
  end
  return (Q, R)
end

Эта процедура всегда даёт . Будучи очень простым, алгоритм требует шагов, а потому экспоненциально медленнее, чем алгоритмы наподобие длинного деления. Алгоритм полезен, если известно, что (шагов) мало (будучи зависимым от объёма вывода).

Деление столбиком

Деление столбиком является стандартным алгоритмом многозначных чисел в десятичной системе счисления, используемых для деления с помощью карандаша и бумаги. Он сдвигается постепенно слева направо от делимого, вычитая наибольшее возможное кратное делителя на каждом шаге. Множитель становится цифрой в частном, а конечная разность становится остатком от деления.

Когда алгоритм используется по основанию 2, этот метод образует базис для деления натуральных чисел с остатком. Короткое деление[англ.] является вариантом деления в столбик, пригодным для деления на одну цифру. Алгоритм уменьшения[англ.], известный также как метод неполных частных, является менее эффективным видом деления столбиком, но который проще понять. Разрешение вычитать большее кратное число, чем делается на каждом шаге, даёт больше свободы для создания вариантов деления в столбик.

Деление целых чисел (без знака) с остатком

Приведённый алгоритм, двоичная версия известного деления в столбик, делит на , помещая частное в и остаток в . Все значения трактуются как целые числа без знака.

if D = 0 then error(DivisionByZeroException) end -- Деление на ноль
Q := 0                  -- Начальные значения частного и остатка полагаем равны 0
R := 0                     
for i := n  1 .. 0 do  -- Здесь n равно числу бит в N
  R := R << 1           -- Сдвиг влево числа R на 1 бит
  R(0) := N(i)          -- Полагаем младший бит R равным биту i делимого
  if R >= D then
    R := R  D
    Q(i) := 1
  end
end

Пример

Возьмём () и ()

Шаг 1: Set R = 0 and Q = 0
Шаг 2: Take i = 3 (на единицу меньше числа бит в N)
Шаг 3: R = 00 (сдвиг влево на 1)
Шаг 4: R = 01 (полагаем R(0) равным N(i))
Шаг 5: R < D, так что пропускаем команду

Шаг 2: Set i = 2
Шаг 3: R = 010
Шаг 4: R = 011
Шаг 5: R < D, пропускаем команду

Шаг 2: Set i = 1
Шаг 3: R = 0110
Шаг 4: R = 0110
Шаг 5: R >= D, команда выполняется
Шаг 5b: R = 10 (R−D)
Шаг 5c: Q = 10 (полагаем Q(i) равным 1)

Шаг 2: Set i = 0
Шаг 3: R = 100
Шаг 4: R = 100
Шаг 5: R >= D, команда выполняется
Шаг 5b: R = 0 (R−D)
Шаг 5c: Q = 11 (полагаем Q(i) равным 1)

Конец
() и .

Методы медленного деления

Все методы медленного деления основываются на стандартном рекуррентном отношении[1]

где:

  • является j-м частичным остатком деления
  • B − основание счисления (равно обычно 2 в компьютерах и калькуляторах)
  • является цифрой частного в позиции , где позиции цифр пронумерованы от менее значащих цифр (0) к более значащим ()
  • − число знаков в частном
  • − делитель

Восстанавливающее деление

Восстанавливающее деление работает с дробными числами числами с плавающей запятой и зависит от предположения .

Цифры частного формируются из набора .

Основной восстанавливающий алгоритм для двоичного (по основанию 2):

R := N
D := D << n             -- R и D должны быть словами вдвое длиннее, чем N и Q
for i := n  1 .. 0 do  -- Например, 31..0 для 32 бит
  R := 2 * R  D        -- Пробное вычитание из сдвинутого значения (умножение на 2 есть сдвиг в бинарной интерпретации)
  if R >= 0 then
    q(i) := 1          -- Результат - бит 1
  else
    q(i) := 0          -- Результат - бит 0
    R := R + D         -- Новый частичный остаток равен (восстановленному) сдвинутому значению
  end
end

-- Здесь: N = делимое, D = делитель, n = число бит, R = частичный остаток, q(i) = бит № i частного

Вариант алгоритма, не осуществляющий явно , сохраняет его, а потому не нужно добавлять обратно в случае .

Невосстанавливающее деление

Невосстанавливающее деление использует набор цифр для цифр частного вместо . Алгоритм более сложен, но имеет преимущество, когда реализуется в микросхемах, что имеется всего одно принятие решения и одно сложение/вычитание на бит частного. Нет шага восстановления после вычитания, что может сократить число операций вдвое, и позволяет выполнить алгоритм быстрее[2]. Алгоритм невосстанавливающего деления для двоичных (по основанию 2) положительных чисел:

R := N
D := D << n            -- R и D должны быть словами вдвое длиннее, чем N и Q
for i = n  1 .. 0 do  -- Например, 31..0 для 32 бит
  if R >= 0 then
    q[i] := +1
    R := 2 * R  D
  else
    q[i] := 1
    R := 2 * R + D
  end if
end
 
-- Здесь: N = делимое, D = делитель, n = число бит, R = частичный остаток, q(i) = бит №i частного.

Если следовать этому алгоритму, получаем частное в нестандартном формате, состоящем из цифр -1 и +1. Этот вид нужно преобразовывать в двоичную форму. Пример:

Преобразование частного к цифрам :
Старт:
1. Образуем положительный член:
2. Образуем отрицательный член:
3. Вычитаем: :
Отрицательный член представлен в бинарном обратном коде, не в дополнительном коде

Если −1 знаки запомнены как нули (0), как в обычном представлении, то является и вычисление тривиально: осуществляет побитное дополнение (бит заменяется на дополнение бита) исходного .

Q := Q  bit.bnot(Q)      -- Если цифры −1 в Q представлены как нули.

Наконец, частные, вычисляемые этим алгоритмом, всегда нечётные, а остаток R находится в пределах . Например, . Для приведения к положительному остатку делаем один шаг восстановления после того, как приведена из нестандартного вида к стандартному:

if R < 0 then
  Q := Q  1
  R := R + D  
end if

Истинный остаток равен R >> n. Как и в варианте с восстановлением, младшие биты используются в том же порядке, как образуются биты частного , и имеет смысл использовать единый сдвиг регистра для обоих чисел одновременно.

Деление SRT

Деление названо по первым буквам фамилий создателей (Sweeney, Robertson, Tocher). Деление SRT является популярным методом деления во многих микропроцессорах[3][4]. Деление подобно делению без восстановления, но использует таблицу поиска на основе делимого и делителя для определения цифры частного.

Наиболее существенная разница в том, что используется избыточное представление для частного. Например, если реализуется деление SRT по основанию 4, каждая цифра частного выбирается из пяти возможностей: . Ввиду этого выбор цифры частного не обязательно должен быть точным. Позднее цифры частного можно откорректировать. Например, пары цифр и эквивалентны, поскольку . Это допустимое отклонение позволяет выбирать цифры частного, исходя только из нескольких наиболее значащих бит делимого и делителя, вместо вычитания по полной длине. Это упрощение, в свою очередь, позволяет применять основание для чисел, большее 2.

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

Печально известный баг плавающего деления в процессорах Intel Pentium[англ.] был вызван неправильно закодированной таблицей поиска. Пять из 1066 ячеек таблицы были ошибочно опущены[5][6].

Методы быстрого деления

Деление Ньютона – Рапсона

Деление Ньютона – Рапсона использует метод Ньютона для нахождения обратного к числа и умножает это обратное на , чтобы найти результирующее частное .

Шаги деления Ньютона – Рапсона:

  1. Вычисляем приближение для обратного числа делителя .
  2. Последовательно вычисляем более точные приближения обратного числа. Это место, где, собственно, и используется метод Ньютона – Рапсона.
  3. Вычисляем частное путём умножения делимого на обратное к делителю: .

Чтобы применить метод Ньютона для нахождения обратного к числа, необходимо найти функцию , имеющую нуль в точке . Очевидно, что такой функцией является , но итерации Ньютона – Рапсона для неё неуспешны, поскольку не могут быть осуществлены без знания обратного к числа (более того, метод пытается вычислить точное обратное за один шаг, а не делать итеративные улучшения). Функция, которая работает – это , для которой итерация Ньютона – Рапсона даёт

что можно вычислить из , используя только умножение и вычитание или два умножения-сложения.

С точки зрения вычислений выражения и не эквивалентны. Чтобы получить точность в 2n бит при использовании второго выражения, нужно вычислить произведение между и с двойной точностью к заданной точности (n бит). В отличие от этого произведение и нужно вычислять только с точностью n бит, поскольку ведущие n бит (после двоичной точки) числа нулевые.

Если ошибка определяется как , то

Эта ошибка в квадрате на каждом шаге итерации (так называемая квадратичная сходимость метода Ньютона – Рапсона) имеет эффект такой, что число верных знаков в результате, грубо говоря, удваивается для каждой итерации, свойство, которое становится крайне важным, когда встречающиеся числа имеют много знаков. Но это также означает, что начальная сходимость метода может оказаться сравнительно медленной, особенно если значение выбрано плохо.

Для подзадачи выбора начальной оценки удобно применить сдвиг делителя , чтобы он оказался между , с применением того же самого сдвига к делимому , чтобы частное не изменилось. Тогда можно использовать линейную Аппроксимацию в виде

для инициализации метода Ньютона – Рапсона. Чтобы минимизировать максимальную абсолютную ошибку этого приближения на интервале , следует использовать

Коэффициенты линейного приближения определяются следующим образом. Абсолютное значение ошибки равно . Минимум максимального абсолютного значения ошибки определяется согласно теореме Чебышева об эквивалентных колебаниях[англ.], применённой к . Локальный минимум функции будет в точке, где , что имеет решение . Функция в этом минимуме должна иметь значение с противоположным знаком по отношению к крайним точкам интервала, а именно, . Два равенства от двух неизвестных дают единственное решение и , а максимальная ошибка равна . При использовании этого приближения абсолютное значение ошибки начального значения меньше, чем

Можно образовать многочлен со степенью большей 1, вычислив коэффициенты согласно алгоритму Ремеза. Тогда начальное приближение требует больших вычислений, что может компенсироваться меньшим числом итерация Ньютона – Рапсона.

Поскольку сходимость этого метода в точности квадратичная, отсюда следует, что достаточно

шагов для вычисления значения с точностью до двоичных мест. Это эквивалентно 3 для IEEE чисел одинарной точности и 4 для чисел двойной точности и чисел расширенной двойной точности.

Псевдокод

Следующий псевдокод вычисляет частное от деления N и D с точностью до P двоичных знаков:

Выражаем D как , где  (стандартное представление  с плавающей запятой)
   // приводим к значению между 0,5 и 1, что можно выполнить битовым сдвигом / вычитанием экспоненты

   // заранее вычисленные константы с той же точностью, что и D
повторить  раз   // может быть вычислено заранее для фиксированного P
    
конец
возвратить 

Например, для деления двойной точности с плавающей запятой этот метод использует 10 умножений, 9 сложений и 2 сдвига.

Варианты деления Ньютона – Рапсона

Метод деления Ньютона – Рапсона можно модифицировать, чтобы сделать чуть быстрее. После сдвига N и D, так что D будет в интервале [0,5, 1,0], инициализируем с

.

Это лучшее квадратичное приближение к и оно даёт значение абсолютной ошибки не больше . Параметры выбраны так, чтобы выбрать ошибку равной значению третьего порядка многочлена Чебышёва первого рода. Коэффициенты должны быть вычислены заранее и жёстко закодированы в методе.

Тогда в цикле используем итерацию, которая возводит ошибку в куб.

Если цикл выполняется пока не приблизится к до ведущих бит, то число итераций будет не более

что равно числу раз возведения 99 в куб, чтобы получить . Тогда

равно частному с точностью до бит.

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

Деление Гольдшмидта

Деление Гольдшмидта[7] (Robert Elliott Goldschmidt) использует итеративный процесс многократного умножения делимого и делителя на один и тот же множитель , выбранный так, что делитель сходится к 1. Это приводит к тому, что делимое сходится к частному :

Шаги деления Гольдшмидта:

  1. Генерируем приближение для множителя .
  2. Умножаем делимое и делитель на .
  3. Если делитель достаточно близок к 1, возвращаем делимое, в противном случае возвращаемся к шагу 1.

Предполагаем, что масштабировано к значению , каждый основан на :

Умножаем делимое и делитель на множитель и получаем:

После достаточного числа итераций .

Метод Гольдшмидта используется в процессорах AMD Athlon и более поздних моделях[8][9]. Он известен также как Anderson Earle Goldschmidt Powers (AEGP) алгоритм и реализован в различных процессорах IBM[10][11]. Хотя сходимость метода такая же, как у реализации Ньютона – Рапмона, одно из преимуществ метода Гольдшмидта в том, что умножения в числителе и знаменателе можно делать параллельно[11].

Биномиальная теорема

Метод Гольдшмидта можно использовать с множителями, которые позволяют упрощение с помощью бинома Ньютона. Предположим, что N/D умножено на степень двойки так, что . Мы выбираем и . Это даёт

.

После шагов знаменатель можно округлить до с относительной ошибкой

,

которая имеет максимальную величину при , обеспечивая минимальную точность в двоичных цифр.

Методы для больших чисел

Методы, предназначенные для аппаратной реализации, обычно не рассчитаны на целые числа с тысячами или миллионами десятичных цифр, что часто встречается, например, при вычислениях по сравнению по модулю в криптографии. Для этих больших чисел более эффективные алгоритмы преобразуют задачу к использованию малого числа умножений, которые можно сделать с помощью асимптотически эффективных алгоритмов умножения[англ.], таких как Алгоритм Карацубы, умножение Тума – Кука[англ.], или алгоритм Шёнхаге — Штрассена. В результате вычислительная сложность деления будет того же порядка (с точностью до умножения на константу) как и умножения. Примеры включают сведение к умножению по методу Ньютона как описанное выше[12], так и чуть более быстрое деление Бурникеля – Циглера[13], алгоритмы Барета[англ.] и Монтгомери[14]. Метод Ньютона, в частности, эффективен в сценариях, где нужно делить на определённое число несколько раз, поскольку после начального нахождения обратного значения только одно (сокращённое) умножение нужно для каждого умножения.

Деление на константу

Деление на константу эквивалентно умножению на её обратную величину. Поскольку знаменатель постоянен, постоянна и обратная величина . Тогда можно вычислить значение один раз и во время вычислений осуществляем умножение вместо деления . В арифметике с плавающей запятой использование вызывают небольшую проблему, связанную с оптимизацией кода компиляторами[a], но в арифметике целых чисел остаток всегда будет равен нулю, при условии .

Нет необходимости использовать именно , можно использовать любое значение которое сводится к . Например, при делении на 3, можно использовать дроби , , или . Следовательно, когда является степенью двойки, шаг деления можно свести к быстрому правому сдвигу. Как эффект, вычисление как заменяет деление на умножение и сдвиг. Заметим, что именно такой порядок действий здесь существеннен, поскольку даст в результате нуль.

Однако, если уже является степенью двойки, нет и , удовлетворяющих условиям выше. К счастью, даёт в точности тот же результат в арифметике целых чисел, что и , даже если не в точности равно , но «достаточно близко», так что ошибка, вносимая при аппроксимации находится в битах, которые уйдут после операции сдвига[15][16][17][b]

В качестве конкретного примера арифметики с фиксированной запятой, для 32-битных беззнаковых целых, деление на 3 может быть заменено на умножение на , то есть умножением на 2863311531 (шестнадцатеричное 0xAAAAAAAB) с последующим сдвигом на 33 бита вправо. Значение 2863311531 вычислено как , затем округлено. Аналогично деление на 10 может быть выражено как умножение на 3435973837 (0xCCCCCCCD) с последующим делением на (или сдвигом вправо на 35 бит)[19]. OEIS даёт последовательность констант для умножения как A346495 и для правого сдвига как A346496.

Для общего деления -битного беззнакового целого деления, где делитель не является степенью двойки, следующее тождество преобразует деление на два -битных сложения/вычитания, одно умножение -битного на -битное чисел (где только старшая половина результата используется) и несколько сдвигов, предварительного вычисления и :

где

В некоторых случаев деление на константу может быть совершено даже за меньшее время путём замены «умножения на константу» в серию сдвигов и сложений или вычитаний[20]. Особый интерес представляет деление на 10, для которого получается точное частное с остатком, если потребуется[21].

См. также

Примечания

  1. Несмотря на то, как «мала» проблема, вызываемая оптимизацией, эта оптимизация обычно спрятана за флаг «быстрая математика» в современных компиляторах.
  2. Современные Компиляторы обычно осуществляют эту оптимизацию целого умножения-и-сдвига. Для констант, известных только в момент выполнения программа должна реализовать оптимизацию сама[18]
  1. Morris, Iniewski, 2017.
  2. Flynn Stanford EE486 (Advanced Computer Arithmetic Division) – Chapter 5 Handout (Division). Stanford University.
  3. Harris, Oberman, Horowitz, 1998.
  4. McCann, Pippenger, 2005, с. 1279–1301.
  5. Statistical Analysis of Floating Point Flaw. Intel Corporation (1994). Дата обращения: 22 октября 2013.
  6. Oberman, Flynn, 1995.
  7. Goldschmidt, 1964.
  8. Oberman, 1999, с. 106-115.
  9. Soderquist, Leeser, 1997, с. 56-66.
  10. Anderson, Earle, Goldschmidt, Powers, 1997.
  11. 1 2 Guy, Peter, Ferguson, 2005, с. 118–139.
  12. Hasselström, 2003.
  13. Burnikel, Ziegler, 1998.
  14. Barrett, 1987, с. 311-323.
  15. Granlund, Montgomery, 1994, с. 61–72.
  16. Möller, Granlund, 2011, с. 165–175.
  17. ridiculous_fish. "Labor of Division (Episode III): Faster Unsigned Division by Constants". 2011.
  18. ridiculous_fish libdivide, optimized integer division. Дата обращения: 6 июля 2021.
  19. Warren Jr., 2013, с. 230-234.
  20. LaBudde, Robert A.; Golovchenko, Nikolai; Newton, James; Parker, David; Massmind: Binary Division by a Constant
  21. Vowels, 1992, с. 81–85.

Литература

  • R. A. Vowels. Division by 10 // Australian Computer Journal. — 1992. — Т. 24, вып. 3.
  • James E. Morris, Krzysztof Iniewski. Nanoelectronic Device Applications Handbook. — CRC Press, 2017. — ISBN 978-1-351-83197-0.
  • David L. Harris, Stuart F. Oberman, Mark A. Horowitz. SRT Division: Architectures, Models, and Implementations. — Stanford University, 1998.
  • Mark McCann, Nicholas Pippenger. SRT Division Algorithms as Dynamical Systems // SIAM Journal on Computing. — 2005. — Т. 34, вып. 6. — doi:10.1137/S009753970444106X.
  • Stuart F. Oberman, Michael J. Flynn. An Analysis of Division Algorithms and Implementations. — Stanford University, 1995.
  • Robert E. Goldschmidt. Applications of Division by Convergence. — M.I.T., 1964. — (M.Sc. dissertation).
  • Stuart F. Oberman. Floating Point Division and Square Root Algorithms and Implementation in the AMD-K7 Microprocessor // Proceedings of the IEEE Symposium on Computer Arithmetic. — 1999. — С. 106–115. — doi:10.1109/ARITH.1999.762835.
  • Peter Soderquist, Miriam Leeser. Division and Square Root: Choosing the Right Implementation // IEEE Micro. — 1997. — July–August (т. 17, вып. 4). — doi:10.1109/40.612224.
  • S. F. Anderson, J. G. Earle, R. E. Goldschmidt, D. M. Powers. The IBM 360/370 model 91: floating-point execution unit // IBM Journal of Research and Development. — 1997. — Январь.
  • Even Guy, Siedel Peter, Warren Ferguson. A parametric error analysis of Goldschmidt's division algorithm // Journal of Computer and System Sciences. — 2005. — Февраль (т. 70, вып. 1). — doi:10.1016/j.jcss.2004.08.004.
  • Karl Hasselström. Fast Division of Large Integers: A Comparison of Algorithms. — Royal Institute of Technology, 2003. — ((thesis) M.Sc. in Computer Science). Архивировано 8 июля 2017 года.
  • Christoph Burnikel, Joachim Ziegler. Fast Recursive Division. — Max-Planck-Institut für Informatik, 1998.
  • Paul Barrett. Implementing the Rivest Shamir and Adleman public key encryption algorithm on a standard digital signal processor // Proceedings on Advances in cryptology---CRYPTO '86. — London, UK: Springer-Verlag, 1987. — С. 311–323. — ISBN 0-387-18047-8.
  • Torbjörn Granlund, Peter L. Montgomery. Division by Invariant Integers using Multiplication // SIGPLAN Notices. — 1994. — Июнь (т. 29, вып. 6). — doi:10.1145/773473.178249.
  • Niels Möller, Torbjörn Granlund. Improved Division by Invariant Integers // IEEE Transactions on Computers. — 2011. — Февраль (т. 60, вып. 2). — doi:10.1109/TC.2010.143.
  • Henry S. Warren Jr. Hacker's Delight. — 2. — Addison Wesley - Pearson Education, Inc., 2013. — ISBN 978-0-321-84268-8.

Литература для дальнейшего чтения