Un algoritmo de división es un algoritmo que, dados dos números enteros N y D (respectivamente el numerador y el denominador), calcula su cociente y/o resto , resultado de la división euclidiana . Algunos se aplican a mano, mientras que otros se emplean mediante software y diseños de circuitos digitales.
Los algoritmos de división se dividen en dos categorías principales: división lenta y división rápida. Los algoritmos de división lenta producen un dígito del cociente final por iteración. Ejemplos de división lenta incluyen restauración, restauración no realizada, no restauración y división SRT. Los métodos de división rápida comienzan con una aproximación cercana al cociente final y producen el doble de dígitos del cociente final en cada iteración. [1] Los algoritmos de Newton-Raphson y Goldschmidt entran en esta categoría.
Las variantes de estos algoritmos permiten utilizar algoritmos de multiplicación rápida . Resulta que, para números enteros grandes, el tiempo de computadora necesario para una división es el mismo, hasta un factor constante, que el tiempo necesario para una multiplicación, cualquiera que sea el algoritmo de multiplicación utilizado.
La discusión se referirá al formulario , donde
es la entrada, y
es la salida.
El algoritmo de división más simple, históricamente incorporado en un algoritmo de máximo común divisor presentado en los Elementos de Euclides , Libro VII, Proposición 1, encuentra el resto dados dos números enteros positivos usando solo restas y comparaciones:
R : = N Q : = 0 mientras R ≥ D hacer R : = R − D Q : = Q + 1 finalizar regresar ( Q , R )
La prueba de que el cociente y el resto existen y son únicos (descrita en División euclidiana ) da lugar a un algoritmo de división completo, aplicable tanto a números negativos como positivos, mediante 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 ); devolver ( − Q , R ) terminar si N < 0 entonces ( Q , R ) : = dividir ( − N , D ) si R = 0 entonces devolver ( − Q , 0 ) en caso contrario devolver ( − Q − 1 , D − R ) end end - En este punto, N ≥ 0 y D > 0 devuelve divide_unsigned ( N , D ) end function divide_unsigned ( N , D ) Q : = 0 ; R : = N mientras R ≥ D hacer Q : = Q + 1 R : = R − D fin regresar ( Q , R ) fin
Este procedimiento siempre produce R ≥ 0. Aunque es muy simple, requiere Ω(Q) pasos y, por lo tanto, es exponencialmente más lento que incluso los algoritmos de división lenta como la división larga. Es útil si se sabe que Q es pequeño (al ser un algoritmo sensible a la salida ) y puede servir como especificación ejecutable.
La división larga es el algoritmo estándar utilizado para la división con lápiz y papel de números de varios dígitos expresados en notación decimal. Se desplaza gradualmente del extremo izquierdo al derecho del dividendo, restando el mayor múltiplo posible del divisor (a nivel de dígitos) en cada etapa; los múltiplos luego se convierten en los dígitos del cociente, y la diferencia final es el resto.
Cuando se utiliza con una base binaria, este método forma la base para la división de enteros (sin signo) con el algoritmo de resto que se muestra a continuación. La división corta es una forma abreviada de división larga adecuada para divisores de un dígito. La fragmentación , también conocida como método de los cocientes parciales o método del ahorcado, es una forma menos eficiente de división larga que puede ser más fácil de entender. Al permitir restar más múltiplos de los que se tienen actualmente en cada etapa, también se puede desarrollar una variante más libre de división larga.
El siguiente algoritmo, la versión binaria de la famosa división larga , dividirá N entre D , colocando el cociente en Q y el resto en R. En el siguiente pseudocódigo, todos los valores se tratan como enteros sin signo.
si D = 0 entonces el error ( DivisionByZeroException ) finaliza Q : = 0 - Inicializa el cociente y el resto en cero R : = 0 para i : = n − 1 .. 0 do - Donde n es el número de bits en N R : = R << 1 - Desplazamiento hacia la izquierda R en 1 bit R ( 0 ) : = N ( i ) -- Establece el bit menos significativo de R igual al bit i del numerador si R ≥ D entonces R : = R − D Q ( i ) : = 1 extremo extremo
Si tomamos N=1100 2 (12 10 ) y D=100 2 (4 10 )
Paso 1 : Establezca R=0 y Q=0
Paso 2 : Tome i=3 (uno menos que el número de bits en N)
Paso 3 : R=00 (desplazado a la izquierda en 1)
Paso 4 : R=01 (configurando R (0) a N(i))
Paso 5 : R < D, así que omita la declaración
Paso 2 : Establecer i=2
Paso 3 : R=010
Paso 4 : R=011
Paso 5 : R < D, declaración omitida
Paso 2 : Establecer i=1
Paso 3 : R=0110
Paso 4 : R=0110
Paso 5 : R>=D, declaración ingresada
Paso 5b : R=10 (R−D)
Paso 5c : Q=10 (configuración Q( i) a 1)
Paso 2 : Establecer i=0
Paso 3 : R=100
Paso 4 : R=100
Paso 5 : R>=D, declaración ingresada
Paso 5b : R=0 (R−D)
Paso 5c : Q=11 (estableciendo Q( i) a 1)
final
Q=11 2 (3 10 ) y R=0.
Todos los métodos de división lenta se basan en una ecuación de recurrencia estándar [2]
dónde:
La restauración de la división opera en números fraccionarios de punto fijo y depende del supuesto 0 < D < N . [ cita necesaria ]
Los dígitos del cociente q se forman a partir del conjunto de dígitos {0,1}.
El algoritmo básico para restaurar la división binaria (base 2) es:
R : = N D : = D << n -- R y D necesitan el doble del ancho de palabra de N y Q para i : = n − 1 .. 0 do -- Por ejemplo 31..0 para 32 bits R : = 2 * R − D -- Resta de prueba del valor desplazado (la multiplicación por 2 es un cambio en la representación binaria) si R >= 0 entonces q ( i ) : = 1 -- Resultado-bit 1 en caso contrario q ( i ) : = 0 -- Bit de resultado 0 R : = R + D -- El nuevo resto parcial es (restablecido) valor desplazado final-- Donde: N = numerador, D = denominador, n = #bits, R = resto parcial, q(i) = bit #i del cociente
La división de restauración no ejecutable es similar a la división de restauración, excepto que el valor de 2R se guarda, por lo que no es necesario volver a agregar D en el caso de R <0.
La división sin restauración utiliza el conjunto de dígitos {−1, 1} para los dígitos del cociente en lugar de {0, 1}. El algoritmo es más complejo, pero tiene la ventaja cuando se implementa en hardware de que solo hay una decisión y una suma/resta por bit de cociente; no hay ningún paso de restauración después de la resta, [3] lo que potencialmente reduce el número de operaciones hasta a la mitad y permite que se ejecute más rápido. [4] El algoritmo básico para la división binaria (base 2) sin restauración de números no negativos es:
R : = N D : = D << n -- R y D necesitan el doble del ancho de palabra de N y Q para i = n − 1 .. 0 -- por ejemplo 31..0 para 32 bits si R >= 0 entonces q ( i ) : = + 1 R : = 2 * R − D else q ( i ) : = − 1 R : = 2 * R + D fin si fin -- Nota: N=numerador, D=denominador, n=#bits, R=resto parcial, q(i)=bit #i del cociente.
Siguiendo este algoritmo, el cociente está en una forma no estándar que consta de dígitos −1 y +1. Esta forma debe convertirse a binaria para formar el cociente final. Ejemplo:
Si los dígitos −1 de se almacenan como ceros (0) como es común, entonces la computación es trivial: realice un complemento a uno (complemento bit a bit) en el original .
Q : = Q - bit . bnot ( Q ) : apropiado si −1 dígitos en Q se representan como ceros, como es común.
Finalmente, los cocientes calculados por este algoritmo siempre son impares y el resto en R está en el rango −D ≤ R < D. Por ejemplo, 5/2 = 3 R −1. Para convertir a un resto positivo, realice un único paso de restauración después de que Q se convierta de una forma no estándar a una forma estándar:
si R < 0 entonces Q : = Q − 1 R : = R + D -- Necesario sólo si el resto es de interés. terminara si
El resto real es R >> n. (Al igual que con la restauración de la división, los bits de orden inferior de R se consumen al mismo ritmo que se producen los bits del cociente Q, y es común utilizar un único registro de desplazamiento para ambos).
La división SRT es un método popular de división en muchas implementaciones de microprocesadores . [5] [6] El algoritmo lleva el nombre de DW Sweeney de IBM , James E. Robertson de la Universidad de Illinois y KD Tocher del Imperial College de Londres . Todos desarrollaron el algoritmo de forma independiente aproximadamente al mismo tiempo (publicado en febrero de 1957, septiembre de 1958 y enero de 1958, respectivamente). [7] [8] [9]
La división SRT es similar a la división sin restauración, pero utiliza una tabla de búsqueda basada en el dividendo y el divisor para determinar cada dígito del cociente.
La diferencia más significativa es que se utiliza una representación redundante para el cociente. Por ejemplo, al implementar la división SRT radix-4, cada dígito del cociente se elige entre cinco posibilidades: { −2, −1, 0, +1, +2 }. Debido a esto, la elección de un dígito cociente no tiene por qué ser perfecta; Los dígitos del cociente posteriores pueden corregir errores leves. (Por ejemplo, los pares de dígitos cocientes (0, +2) y (1, −2) son equivalentes, ya que 0×4+2 = 1×4−2.) Esta tolerancia permite seleccionar los dígitos cocientes utilizando sólo unos pocos bits más significativos del dividendo y el divisor, en lugar de requerir una resta de ancho completo. Esta simplificación, a su vez, permite utilizar una base superior a 2.
Al igual que la división sin restauración, los pasos finales son una resta final de ancho completo para resolver el último bit del cociente y la conversión del cociente a la forma binaria estándar.
El infame error de división de punto flotante del procesador Intel Pentium fue causado por una tabla de búsqueda codificada incorrectamente. Cinco de las 1.066 entradas se habían omitido por error. [10] [11]
Newton-Raphson usa el método de Newton para encontrar el recíproco y multiplicarlo por para encontrar el cociente final .
Los pasos de la división Newton-Raphson son:
Para aplicar el método de Newton para encontrar el recíproco de , es necesario encontrar una función que tenga un cero en . La función obvia es , pero la iteración de Newton-Raphson para esto no es útil, ya que no se puede calcular sin conocer ya el recíproco de (además, intenta calcular el recíproco exacto en un solo paso, en lugar de permitir mejoras iterativas). Una función que sí funciona es , para la cual la iteración de Newton-Raphson da
que se puede calcular usando solo multiplicación y resta, o usando dos multiplicaciones y sumas fusionadas .
Desde el punto de vista computacional, las expresiones y no son equivalentes. Para obtener un resultado con una precisión de 2 n bits mientras se utiliza la segunda expresión, se debe calcular el producto entre y con el doble de la precisión dada de ( n bits). [ cita necesaria ] Por el contrario, el producto entre y solo necesita calcularse con una precisión de n bits, porque los n bits iniciales (después del punto binario) de son ceros.
Si el error se define como , entonces:
Esta elevación al cuadrado del error en cada paso de iteración (la llamada convergencia cuadrática del método de Newton-Raphson) tiene el efecto de que el número de dígitos correctos en el resultado se duplica aproximadamente en cada iteración , una propiedad que se vuelve extremadamente valiosa cuando los números involucrados tener muchos dígitos (por ejemplo, en el dominio de los enteros grandes). Pero también significa que la convergencia inicial del método puede ser comparativamente lenta, especialmente si la estimación inicial se elige mal.
Para el subproblema de elegir una estimación inicial , es conveniente aplicar un desplazamiento de bits al divisor D para escalarlo de modo que 0,5 ≤ D ≤ 1; aplicando el mismo desplazamiento de bits al numerador N , se garantiza que el cociente no cambie. Entonces se podría utilizar una aproximación lineal en la forma
para inicializar Newton-Raphson. Para minimizar el máximo del valor absoluto del error de esta aproximación en el intervalo , se debe utilizar
Los coeficientes de la aproximación lineal se determinan de la siguiente manera. El valor absoluto del error es . El mínimo del valor absoluto máximo del error está determinado por el teorema de equioscilación de Chebyshev aplicado a . El mínimo local de ocurre cuando , que tiene solución . La función en ese mínimo debe ser de signo opuesto a la función en los puntos finales, es decir, . Las dos ecuaciones con las dos incógnitas tienen una solución única y y el error máximo es . Usando esta aproximación, el valor absoluto del error del valor inicial es menor que
Es posible generar un ajuste polinómico de grado mayor que 1, calculando los coeficientes mediante el algoritmo de Remez . La desventaja es que la suposición inicial requiere más ciclos computacionales pero, con suerte, a cambio de menos iteraciones de Newton-Raphson.
Dado que para este método la convergencia es exactamente cuadrática, se deduce que
Los pasos son suficientes para calcular el valor hasta lugares binarios. Esto se evalúa como 3 para precisión simple IEEE y 4 para formatos de doble precisión y doble extensión .
A continuación se calcula el cociente de N y D con una precisión de P lugares binarios:
Exprese D como M × 2 e donde 1 ≤ M < 2 (representación de punto flotante estándar)D' := D / 2 e+1 // escala entre 0,5 y 1, se puede realizar con desplazamiento de bits / resta de exponente N' := N / 2 e+1 X := 48/17 − 32/17 × D' // precalcular constantes con la misma precisión que D tiempos de repetición // se puede precalcular en función de P fijo X := X + X × (1 - D' × X)final retorno N' × X
Por ejemplo, para una división de punto flotante de doble precisión, este método utiliza 10 multiplicaciones, 9 sumas y 2 desplazamientos.
El método de división de Newton-Raphson se puede modificar para que sea un poco más rápido de la siguiente manera. Después de cambiar N y D para que D esté en [0.5, 1.0], inicialice con
Este es el mejor ajuste cuadrático a 1/ D y da un valor absoluto del error menor o igual a 1/99. Se elige hacer que el error sea igual a un polinomio de Chebyshev de tercer orden reescalado de primera especie. Los coeficientes deben calcularse previamente y codificarse.
Luego, en el bucle, utilice una iteración que cubra el error.
El término Y · E es nuevo.
Si el ciclo se realiza hasta que X concuerde con 1/ D en sus P bits iniciales , entonces el número de iteraciones no será mayor que
que es el número de veces que se debe elevar al cubo 99 para llegar a 2 P +1 . Entonces
es el cociente de P bits.
El uso de polinomios de mayor grado, ya sea en la inicialización o en la iteración, da como resultado una degradación del rendimiento porque las multiplicaciones adicionales requeridas se gastarían mejor en realizar más iteraciones.
La división de Goldschmidt [12] (según Robert Elliott Goldschmidt) [13] utiliza un proceso iterativo de multiplicar repetidamente tanto el dividendo como el divisor por un factor común Fi , elegido de manera que el divisor converja a 1. Esto hace que el dividendo converja a cociente buscado Q :
Los pasos para la división Goldschmidt son:
Suponiendo que N / D se ha escalado de modo que 0 < D < 1, cada F i se basa en D :
Multiplicando el dividendo y el divisor por el factor se obtiene:
Después de un número suficiente k de iteraciones .
El método Goldschmidt se utiliza en las CPU AMD Athlon y modelos posteriores. [14] [15] También se conoce como algoritmo Anderson Earle Goldschmidt Powers (AEGP) y se implementa en varios procesadores de IBM. [16] [17] Aunque converge al mismo ritmo que una implementación de Newton-Raphson, una ventaja del método Goldschmidt es que las multiplicaciones en el numerador y el denominador se pueden realizar en paralelo. [17]
El método de Goldschmidt se puede utilizar con factores que permitan simplificaciones mediante el teorema del binomio . Supongamos que ha sido escalado por una potencia de dos tal que . Nosotros elegimos y . Esto produce
Después de n pasos , el denominador se puede redondear a1 con un error relativo
que es máximo en cuando , proporcionando así una precisión mínima de dígitos binarios.
Los métodos diseñados para la implementación de hardware generalmente no se escalan a números enteros con miles o millones de dígitos decimales; Esto ocurre frecuentemente, por ejemplo, en reducciones modulares en criptografía . Para estos números enteros grandes, los algoritmos de división más eficientes transforman el problema para usar una pequeña cantidad de multiplicaciones, que luego se pueden hacer usando un algoritmo de multiplicación asintóticamente eficiente como el algoritmo de Karatsuba , la multiplicación de Toom-Cook o el algoritmo de Schönhage-Strassen . El resultado es que la complejidad computacional de la división es del mismo orden (hasta una constante multiplicativa) que la de la multiplicación. Los ejemplos incluyen la reducción a la multiplicación por el método de Newton como se describe anteriormente, [18] así como la división ligeramente más rápida de Burnikel-Ziegler, [19] la reducción de Barrett y los algoritmos de reducción de Montgomery . [20] [ verificación necesaria ] El método de Newton es particularmente eficiente en escenarios donde uno debe dividir por el mismo divisor muchas veces, ya que después de la inversión inicial de Newton solo se necesita una multiplicación (truncada) para cada división.
La división por una constante D equivale a la multiplicación por su recíproco . Como el denominador es constante, también lo es su recíproco (1/ D ). Por lo tanto, es posible calcular el valor de (1/ D ) una vez en tiempo de compilación y, en tiempo de ejecución, realizar la multiplicación N ·(1/ D ) en lugar de la división N/D . En aritmética de coma flotante el uso de (1/ D ) presenta pocos problemas, [a] pero en aritmética de enteros el recíproco siempre se evaluará como cero (asumiendo | D | > 1).
No es necesario utilizar específicamente (1/ D ); Se puede utilizar cualquier valor ( X / Y ) que se reduzca a (1/ D ). Por ejemplo, para dividir por 3, se podrían utilizar los factores 1/3, 2/6, 3/9 o 194/582. En consecuencia, si Y fuera una potencia de dos, el paso de división se reduciría a un rápido desplazamiento de bit a la derecha. El efecto de calcular N / D como ( N · X )/ Y reemplaza una división con una multiplicación y un desplazamiento. Tenga en cuenta que los paréntesis son importantes, ya que N ·( X / Y ) se evaluará como cero.
Sin embargo, a menos que D sea una potencia de dos, no hay X ni Y que satisfagan las condiciones anteriores. Afortunadamente, ( N · X )/ Y da exactamente el mismo resultado que N / D en aritmética de enteros incluso cuando ( X / Y ) no es exactamente igual a 1/ D , pero está "lo suficientemente cerca" como para que el error introducido por la aproximación sea en los bits que son descartados por la operación de cambio. [21] [22] [23] La reducción de Barrett utiliza potencias de 2 para el valor de Y para hacer que la división por Y sea un simple desplazamiento a la derecha. [b]
Como ejemplo concreto de aritmética de punto fijo , para enteros sin signo de 32 bits, la división por 3 se puede reemplazar con una multiplicación por2863311531/2 33, una multiplicación por 2863311531 ( hexadecimal 0xAAAAAAAB) seguida de un desplazamiento de 33 bits a la derecha. El valor de 2863311531 se calcula como2 33/3, luego redondeado. Asimismo, la división por 10 se puede expresar como una multiplicación por 3435973837 (0xCCCCCCCD) seguida de una división por 2 35 (o desplazamiento de 35 bits a la derecha). [25] : p230-234 OEIS proporciona secuencias de las constantes para la multiplicación como A346495 y para el desplazamiento a la derecha como A346496.
Para una división general de enteros sin signo de x bits donde el divisor D no es una potencia de 2, la siguiente identidad convierte la división en dos sumas/restas de x bits, una multiplicación de x bits por x bits (donde solo la mitad superior de se utiliza el resultado) y varios turnos, después de precalcular y :
En algunos casos, la división por una constante se puede lograr en incluso menos tiempo convirtiendo la "multiplicación por una constante" en una serie de desplazamientos y sumas o restas . [26] De particular interés es la división por 10, de la que se obtiene el cociente exacto, con resto si es necesario. [27]
Las operaciones de división pueden introducir errores de redondeo debido a la precisión limitada .
{{citation}}
: CS1 maint: location missing publisher (link){{citation}}
: CS1 maint: location missing publisher (link)