Algoritmo de división

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 6 de febrero de 2022; las comprobaciones requieren 13 ediciones .

El algoritmo de división es un algoritmo que calcula para dos enteros dados y su cociente y/o resto, el resultado de la división con un resto de . Algunos de los algoritmos están diseñados para cálculos manuales, otros se implementan en circuitos digitales y software.

Los algoritmos de división se dividen en dos grandes categorías: división lenta y división rápida. Los algoritmos de división lenta producen un dígito del resultado por iteración. Ejemplos de división lenta son los algoritmos de división con recuperación , sin recuperación y SRT . Los métodos de división rápida comienzan aproximando el cociente final y producen el doble de dígitos en el resultado final en cada iteración. Los algoritmos de Newton-Rapson y Goldschmidt entran en esta categoría.

Las variantes de estos algoritmos permiten el uso de algoritmos de multiplicación rápida . Como resultado, para números enteros grandes, el tiempo de cálculo requerido para la división será el mismo (hasta un factor constante) que el tiempo requerido para realizar la multiplicación, cualquiera que sea el algoritmo de los enumerados que no se aplique.

La discusión utilizará la notación donde

son números de entrada, y

son la salida.

División por resta

El algoritmo más simple, históricamente integrado en el algoritmo del máximo común divisor y presentado en los Principia de Euclides , Libro VII Proposición 1, encuentra el resto de una división de dos números enteros positivos usando solo la resta y la comparación:

R : = N mientras que R >= D hacer R : = R D final retorno R

La prueba de que el cociente y el resto existen y son únicos (descrito en el artículo División con resto ) da un algoritmo de división completo basado en sumas, restas y comparaciones:

función dividir ( N ​​, D ) si D = 0 entonces error ( DivisionByZero ) terminar si D < 0 entonces ( Q , R ) : = dividir ( N ​​, D ); volver ( Q , R ) terminar si N < 0 entonces ( Q , R ) : = dividir ( N , D ) si R = 0 entonces volver ( Q , 0 ) si no volver ( Q 1 , D R ) end end -- Aquí N >= 0 y D >= 0 devuelven divide_unsigned ( N , D ) end función divide_unsigned ( N , D ) Q : = 0 ; R : = N while R >= D do Q : = Q + 1 R : = R D fin volver ( Q , R ) fin

Este procedimiento siempre da . Al ser muy simple, el algoritmo requiere pasos y, por lo tanto, es exponencialmente más lento que algoritmos como la división larga. El algoritmo es útil si se sabe que (los pasos) son pocos (dependiendo del volumen de salida).

División de columnas

La división larga es un algoritmo estándar de números decimales de varios dígitos que se utiliza para la división con lápiz y papel. Se desplaza gradualmente de izquierda a derecha desde el dividendo, restando el mayor múltiplo posible del divisor en cada paso. El multiplicador se convierte en un cociente y la diferencia final se convierte en el resto de la división.

Cuando el algoritmo se usa en base 2, este método forma la base para dividir números naturales con un resto. La división corta es una variante de la división larga, adecuada para dividir por un solo dígito. El algoritmo de decremento , también conocido como el método del cociente fraccionario, es un tipo de división por columna menos eficiente, pero es más fácil de entender. Permitir que se reste un múltiplo más grande que el que se hace en cada paso permite más libertad para crear variantes de división larga.

División de enteros (sin signo) con resto

El algoritmo anterior, una versión binaria de la conocida división en longitud , divide colocando el cociente en y el resto en . Todos los valores se tratan como enteros sin signo.

si D = 0 entonces error ( DivisionByZeroException ) end -- División por cero Q : = 0 -- Los valores iniciales del cociente y resto son 0 R : = 0 para i : = n 1 .. 0 do -- Aquí n es un bit numérico en N R : = R << 1 -- Desplazamiento a la izquierda de R en 1 bit R ( 0 ) : = N ( i ) -- Iguala el bit menos significativo de R al bit i del dividendo si R >= D entonces R : = R D Q ( i ) : = 1 fin fin Ejemplo

Toma ( ) y ( )

Paso 1 : Establecer R = 0 y Q = 0
Paso 2 : Tomar i = 3 (uno menos que el número de bits en N)
Paso 3 : R = 00 (cambiar 1 a la izquierda)
Paso 4 : R = 01 (poner R (0 ) igual a N(i))
Paso 5 : R < D, omita el comando

Paso 2 : Establecer i = 2
Paso 3 : R = 010
Paso 4 : R = 011
Paso 5 : R < D, omitir comando

Paso 2 : Establecer i = 1
Paso 3 : R = 0110
Paso 4 : R = 0110
Paso 5 : R >= D, se ejecuta el comando
Paso 5b : R = 10 (R−D)
Paso 5c : Q = 10 (establecer Q(i) igual a 1)

Paso 2 : Establecer i = 0
Paso 3 : R = 100
Paso 4 : R = 100
Paso 5 : R >= D, se ejecuta el comando
Paso 5b : R = 0 (R−D)
Paso 5c : Q = 11 (establecer Q(i) igual a 1)

Fin ( ) y .

Métodos de división lenta

Todos los métodos de división lenta se basan en la relación de recurrencia estándar [1]

dónde:

  • es el j-ésimo resto parcial de la división
  • B es la base del número (generalmente igual a 2 en computadoras y calculadoras)
  • es el dígito del cociente en la posición , donde las posiciones de los dígitos se numeran desde los dígitos menos significativos (0) hasta los dígitos más significativos ( )
  • − número de caracteres en privado
  • − divisor

División de Restauración

La división reconstructiva funciona con números fraccionarios de punto flotante y depende de una suposición .

Los números privados se forman a partir del conjunto .

Algoritmo de recuperación básico para binario (base 2):

R : = N D : = D << n -- R y D deben ser palabras dos veces más largas que N y Q para i : = n 1 .. 0 do -- Por ejemplo, 31..0 para R de 32 bits : = 2 * R D -- Resta de prueba del valor desplazado (multiplicar por 2 es un cambio en la interpretación binaria) si R >= 0 entonces q ( i ) : = 1 -- El resultado es el bit 1 sino q ( i ) : = 0 -- El resultado es el bit 0 R : = R + D -- El nuevo resto parcial es igual al valor desplazado (restaurado) end end -- Aquí: N = dividendo, D = divisor, n = número de bits, R = resto parcial, q(i) = bit #i del cociente

La variante del algoritmo que no implementa lo retiene explícitamente y, por lo tanto , no necesita volver a agregarse en el caso de .

División no restauradora

La división sin restauración usa un conjunto de dígitos para los dígitos del cociente en lugar de . El algoritmo es más complejo, pero tiene la ventaja cuando se implementa en chips de que solo hay una decisión y una suma/resta por bit de cociente. No hay un paso de recuperación después de la resta, lo que puede reducir el número de operaciones a la mitad y permite que el algoritmo se ejecute más rápido [2] . Algoritmo de división sin restauración para números positivos binarios (base 2):

R : = N D : = D << n -- R y D deben ser palabras dos veces más largas que N y Q para i = n 1 .. 0 do -- Por ejemplo, 31..0 para 32 bits si R > = 0 entonces q [ i ] : = + 1 R : = 2 * R D otra cosa q [ i ] : = 1 R : = 2 * R + D final si final -- Aquí: N = dividendo, D = divisor, n = número de bits, R = resto parcial, q(i) = bit #i del cociente.

Si seguimos este algoritmo, obtenemos el cociente en un formato no estándar, que consta de los números -1 y +1. Este formulario debe convertirse a formato binario. Ejemplo:

Convertir cociente a dígitos :
Comienzo:
1. Formamos un término positivo:
2. Formamos un término negativo:
3. Reste: :
El término negativo se representa en inversa binaria , no en complemento a dos.

Si los caracteres −1 se almacenan como ceros (0), como en la representación habitual, entonces el cálculo es trivial: realiza un complemento bit a bit (un bit se reemplaza por el complemento de un bit) del original .

Q : = Q bit . bnot ( Q ) -- Si los dígitos −1 en Q se representan como ceros.

Finalmente, los cocientes calculados por este algoritmo siempre son impares y el resto R está dentro de . Por ejemplo, . Para llevarlo a un resto positivo, tomamos un paso de recuperación después de que se haya reducido de una forma no estándar a una estándar:

si R < 0 entonces Q : = Q 1 R : = R + D termina si

El verdadero resto es R >> n. Al igual que con la recuperación, los bits menos significativos se usan en el mismo orden en que se forman los bits del cociente , y tiene sentido usar un solo cambio de registro para ambos números al mismo tiempo.

División de la SRT

La división lleva el nombre de las primeras letras de los nombres de los creadores (Sweeney, Robertson, Tocher). La división SRT es un método de división popular en muchos microprocesadores [3] [4] . La división es similar a la división sin recuperación, pero usa una tabla de búsqueda basada en el dividendo y el divisor para determinar el cociente.

La diferencia más significativa es que se utiliza una representación redundante para lo privado. Por ejemplo, si se implementa la división en base 4 de SRT, cada dígito del cociente se elige entre cinco posibilidades : . En vista de esto, la elección del dígito del cociente no tiene por qué ser exacta. Más tarde, las cifras del cociente se pueden corregir. Por ejemplo, pares de dígitos y son equivalentes porque . Esta tolerancia le permite seleccionar los dígitos del cociente basándose solo en los pocos bits más significativos del dividendo y el divisor, en lugar de restar por la longitud total. Esta simplificación, a su vez, permite el uso de una base para números mayores de 2.

Al igual que la división sin restauración, los pasos finales son restar la longitud total de los números para obtener el último bit del cociente y convertir el cociente a binario estándar.

El infame error de división flotante en los procesadores Intel Pentium fue causado una tabla de búsqueda codificada incorrectamente. Cinco de las 1066 celdas de la tabla se omitieron por error [5] [6] .

Métodos de división rápida

División de Newton-Rapson

La división de Newton-Rapson usa el método de Newton para encontrar el recíproco de un número y multiplica ese recíproco por para encontrar el cociente resultante .

Pasos de división de Newton-Rapson:

  1. Calculamos la aproximación por el recíproco del divisor .
  2. Calculamos sucesivamente aproximaciones más precisas del recíproco. Este es el lugar donde, de hecho, se utiliza el método de Newton-Rapson.
  3. Calculamos el cociente multiplicando el dividendo por el inverso del divisor: .

Para aplicar el método de Newton para encontrar el recíproco de un número, necesitas encontrar una función que tenga cero en el punto . Obviamente, esta función es , pero las iteraciones de Newton-Rapson no son exitosas para ella, ya que no se pueden realizar sin conocer el recíproco del número (además, el método intenta calcular el recíproco exacto en un solo paso, y no hacer mejoras iterativas ). La función que funciona es aquella para la que la iteración de Newton-Rapson da

que se puede calcular usando solo una multiplicación y una resta o dos multiplicaciones- sumas .

Desde un punto de vista computacional, las expresiones y no son equivalentes. Para obtener una precisión de 2n bits utilizando la segunda expresión, debe calcular el producto entre y con doble precisión hasta la precisión especificada ( n bits). Por el contrario, el producto debe calcularse solo con una precisión de n bits, ya que los n bits iniciales (después del punto binario) son cero.

Si el error se define como , entonces

Este error cuadrático en cada paso de iteración (la llamada convergencia cuadrática del método de Newton-Rapson) tiene el efecto de que el número de dígitos correctos en el resultado se duplica aproximadamente para cada iteración , una propiedad que se vuelve extremadamente importante cuando los números encontrados tienen muchos dígitos. . Pero también significa que la convergencia inicial del método puede ser relativamente lenta, especialmente si el valor se elige mal.

Para el subproblema de elegir una estimación inicial, es conveniente aplicar un desplazamiento al divisor para que quede entre , mientras que al dividendo se le aplica el mismo desplazamiento para que el cociente no cambie. Entonces uno puede usar la Aproximación Lineal en la forma

para inicializar el método de Newton-Rapson. Para minimizar el error absoluto máximo de esta aproximación en el intervalo , se debe usar

Los coeficientes de aproximación lineal se definen como sigue. El valor absoluto del error es . El mínimo del valor absoluto máximo del error se determina de acuerdo con el teorema de oscilación equivalente de Chebyshev aplicado a . El mínimo local de la función estará en el punto donde , que tiene solución . La función en este mínimo debe tener un valor con signo opuesto con respecto a los puntos extremos del intervalo, a saber, . Dos igualdades en dos incógnitas dan una única solución y , y el error máximo es . Al usar esta aproximación, el valor absoluto del error del valor inicial es menor que

Es posible formar un polinomio de grado superior a 1 calculando los coeficientes según el algoritmo de Remez . Luego, la aproximación inicial requiere más cálculo, lo que puede compensarse con un número menor de iteraciones de Newton-Rapson.

Dado que la convergencia de este método es exactamente cuadrática, se sigue que es suficiente

pasos para calcular el valor hasta lugares binarios. Esto es equivalente a 3 para IEEE sencillos y 4 para dobles y dobles extendidos .

Pseudocódigo

El siguiente pseudocódigo calcula el cociente de N y D , hasta P dígitos binarios:

Expresar D como donde (representación de punto flotante estándar) // convertir a un valor entre 0,5 y 1, lo que se puede hacer desplazando bits / restando el exponente // constantes precalculadas con la misma precisión que los tiempos de repetición de D // se pueden calcular en anticipo para una P fija devolución final

Por ejemplo, para la división de punto flotante de doble precisión, este método utiliza 10 multiplicaciones, 9 sumas y 2 desplazamientos.

Variantes de la división de Newton-Rapson

El método de división de Newton-Rapson se puede modificar para que sea un poco más rápido. Después de cambiar N y D para que D esté en el intervalo [0.5, 1.0], inicializamos con

.

Esta es la mejor aproximación cuadrática y da un valor de error absoluto de como máximo . Los parámetros se eligen de forma que se elija un error igual al valor del tercer orden del polinomio de Chebyshev de primera especie. Los coeficientes deben calcularse previamente y codificarse en el método.

Luego, en el ciclo, usamos una iteración que eleva el error a un cubo.

Si el bucle se ejecuta hasta que se acerca a los bits iniciales, entonces el número de iteraciones será como máximo

que es igual al número de veces 99 al cubo para obtener . Después

es igual al cociente hasta bits.

El uso de polinomios de mayor grado en inicializaciones o iteraciones da como resultado un impacto en el rendimiento, ya que sería mejor utilizar multiplicaciones adicionales para hacer más iteraciones.

División de Goldschmidt

La división de Goldschmidt [7] (Robert Elliott Goldschmidt) usa un proceso iterativo de multiplicar repetidamente el dividendo y el divisor por el mismo factor , elegido para que el divisor converja a 1. Esto hace que el dividendo converja al cociente :

Pasos de la división de Goldschmidt:

  1. Generamos una aproximación para el multiplicador .
  2. Multiplica el dividendo y el divisor por .
  3. Si el divisor está lo suficientemente cerca de 1, devuelve el dividendo; de lo contrario, vuelve al paso 1.

Suponga escalado al valor , cada uno basado en :

Multiplicamos el dividendo y el divisor por el factor y obtenemos:

Después de un número suficiente de iteraciones .

El método Goldschmidt se utiliza en los procesadores AMD Athlon y posteriores [8] [9] . También se conoce como el algoritmo Anderson Earle Goldschmidt Powers (AEGP) y se implementa en varios procesadores de IBM [10] [11] . Aunque la convergencia del método es la misma que la de la implementación de Newton-Rapmon, una de las ventajas del método de Goldschmidt es que las multiplicaciones en el numerador y el denominador se pueden realizar en paralelo [11] .

Teorema del binomio

El método de Goldschmidt se puede utilizar con factores que permitan la simplificación con el binomio de Newton . Supongamos que N/D se multiplica por una potencia de dos de modo que . Elegimos y . Esto da

.

Después de los pasos , el denominador se puede redondear con un error relativo

,

que tiene un valor máximo en , proporcionando una precisión mínima en dígitos binarios.

Métodos para números grandes

Los métodos destinados a ser implementados en hardware no suelen contar con números enteros con miles o millones de dígitos decimales, lo cual es común, por ejemplo, en cálculos de módulo en criptografía . Para estos números grandes, los algoritmos más eficientes convierten el problema en el uso de una pequeña cantidad de multiplicaciones, lo que se puede hacer con algoritmos de multiplicación asintóticamente eficientes , como el algoritmo de Karatsuba , la multiplicación de Tum-Cook o el algoritmo de Schönhage-Strassen . Como resultado , la complejidad computacional de la división será del mismo orden (hasta la multiplicación por una constante) que la multiplicación. Los ejemplos incluyen la reducción a la multiplicación de Newton como se describe anteriormente [12] , así como los algoritmos ligeramente más rápidos de la división de Bournickel-Ziegler [ 13] , Baret y Montgomery [14] . El método de Newton, en particular, es efectivo en escenarios donde se deben realizar múltiples divisiones por un número determinado, ya que después de encontrar inicialmente el recíproco, solo se necesita una multiplicación (reducida) para cada multiplicación.

División por una constante

Dividir por una constante es equivalente a multiplicar por su recíproco . Como el denominador es constante, el recíproco también es constante . Luego puede calcular el valor una vez y durante los cálculos realizamos la multiplicación en lugar de la división . En la aritmética de coma flotante, el uso causa un pequeño problema relacionado con la optimización del código por parte de los compiladores [a] , pero en la aritmética de enteros , el resto siempre será cero, siempre que .

No es necesario usar exactamente , puede usar cualquier valor que se reduzca a . Por ejemplo, al dividir por 3 , puede usar fracciones , o . Por lo tanto, cuando es una potencia de dos, el paso de división se puede reducir a un desplazamiento rápido a la derecha. Como efecto, el cálculo de cómo reemplaza la división con la multiplicación y el desplazamiento. Tenga en cuenta que es precisamente este procedimiento el que es esencial aquí, ya que dará como resultado cero.

Sin embargo, si ya es una potencia de dos, no existen y se cumplen las condiciones anteriores. Afortunadamente, da exactamente el mismo resultado en aritmética de enteros que , incluso si no es exactamente igual a , pero "lo suficientemente cerca", de modo que el error introducido por la aproximación está en los bits que desaparecerán después de la operación de desplazamiento [15] [ 16 ] [17] [segundo]

Como ejemplo específico de aritmética de punto fijo , para enteros sin signo de 32 bits, la división por 3 se puede reemplazar por multiplicar por , es decir, multiplicar por 2863311531 ( hexadecimal 0xAAAAAAAB) seguido de un desplazamiento a la derecha de 33 bits. El valor 2863311531 se calcula como , luego se redondea hacia arriba. De manera similar, una división por 10 se puede expresar como una multiplicación por 3435973837 (0xCCCCCCCD) seguida de una división por (o un desplazamiento a la derecha de 35 bits) [19] . OEIS da una secuencia de constantes para la multiplicación como A346495 y para el desplazamiento a la derecha como A346496 .

Para una división de enteros sin signo de bits comunes donde el divisor no es una potencia de dos, la siguiente identidad transforma la división en sumas/restas de dos bits, una multiplicación de números de bit por bit (donde solo la mitad superior del resultado se utiliza), y varios turnos, precálculo y :

dónde

En algunos casos, la división por una constante se puede hacer incluso en menos tiempo reemplazando la "multiplicación por una constante" con una serie de cambios y sumas o restas [20] . De particular interés es la división por 10, para la cual se obtiene el cociente exacto con un resto si es necesario [21] .

Véase también

Notas

  1. A pesar de cuán "pequeño" es el problema causado por la optimización, esta optimización generalmente se oculta detrás del indicador "matemáticas rápidas" en los compiladores modernos .
  2. Los compiladores modernos normalmente implementan esta optimización de multiplicación y desplazamiento de enteros. Para constantes conocidas solo en tiempo de ejecución, el programa debe implementar la optimización por sí mismo [18]
  1. Morris, Iniewski, 2017 .
  2. Flynn Stanford EE486 (División Aritmética Informática Avanzada) - Folleto del Capítulo 5 (División) . Universidad de Stanford . Consultado el 10 de enero de 2022. Archivado desde el original el 18 de abril de 2022.
  3. Harris, Oberman, Horowitz, 1998 .
  4. McCann, Pippenger, 2005 , pág. 1279-1301.
  5. Análisis estadístico de defectos de punto flotante . Corporación Intel (1994). Consultado el 22 de octubre de 2013. Archivado desde el original el 23 de octubre de 2013.
  6. Oberman, Flynn, 1995 .
  7. Goldschmidt, 1964 .
  8. Oberman, 1999 , pág. 106-115.
  9. Soderquist, Leeser, 1997 , pág. 56-66.
  10. Anderson, Earle, Goldschmidt, Powers, 1997 .
  11. 1 2 Guy, Peter, Ferguson, 2005 , p. 118–139.
  12. Hasselstrom, 2003 .
  13. Burnikel, Ziegler, 1998 .
  14. Barrett, 1987 , pág. 311-323.
  15. Granlund, Montgomery, 1994 , pág. 61–72.
  16. Möller, Granlund, 2011 , pág. 165–175.
  17. ridículo_pez. "Labor of Division (Episode III): Faster Unsigned Division by Constants" Archivado el 8 de enero de 2022 en Wayback Machine . 2011.
  18. ridiculous_fish libdivide, división de enteros optimizada . Consultado el 6 de julio de 2021. Archivado desde el original el 23 de noviembre de 2021.
  19. Warren Jr., 2013 , pág. 230-234.
  20. La Budde, Robert A.; Golovchenko, Nikolái; Newton, James; Parker, David; Massmind: Binary Division by a Constant Archivado el 9 de enero de 2022 en Wayback Machine .
  21. Vocales, 1992 , p. 81–85.

Literatura

Lectura para leer más