¿Cómo puedo multiplicar y dividir usando solo el cambio de bits y la suma?
c
assembly
bit-manipulation
division
multiplication
Spidfire
fuente
fuente
Respuestas:
Para multiplicar en términos de suma y desplazamiento, desea descomponer uno de los números por potencias de dos, así:
21 * 5 = 10101_2 * 101_2 (Initial step) = 10101_2 * (1 * 2^2 + 0 * 2^1 + 1 * 2^0) = 10101_2 * 2^2 + 10101_2 * 2^0 = 10101_2 << 2 + 10101_2 << 0 (Decomposed) = 10101_2 * 4 + 10101_2 * 1 = 10101_2 * 5 = 21 * 5 (Same as initial expression)
(
_2
significa base 2)Como puede ver, la multiplicación se puede descomponer en sumas y cambios y viceversa. Esta es también la razón por la que la multiplicación lleva más tiempo que los cambios de bits o la suma: es O (n ^ 2) en lugar de O (n) en el número de bits. Los sistemas informáticos reales (a diferencia de los sistemas informáticos teóricos) tienen un número finito de bits, por lo que la multiplicación requiere un múltiplo constante de tiempo en comparación con la suma y el desplazamiento. Si mal no recuerdo, los procesadores modernos, si se canalizan correctamente, pueden hacer una multiplicación casi tan rápido como una suma, jugando con la utilización de las ALU (unidades aritméticas) en el procesador.
fuente
La respuesta de Andrew Toulouse se puede extender a la división.
La división por constantes enteras se considera en detalle en el libro "Hacker's Delight" de Henry S. Warren (ISBN 9780201914658).
La primera idea para implementar la división es escribir el valor inverso del denominador en base dos.
P.ej,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....
Entonces,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
para la aritmética de 32 bits.Combinando los términos de una manera obvia podemos reducir el número de operaciones:
b = (a >> 2) + (a >> 4)
b += (b >> 4)
b += (b >> 8)
b += (b >> 16)
Hay formas más interesantes de calcular la división y los residuos.
EDIT1:
Si OP significa multiplicación y división de números arbitrarios, no la división por un número constante, entonces este hilo podría ser útil: https://stackoverflow.com/a/12699549/1182653
EDIT2:
Una de las formas más rápidas de dividir por constantes enteras es explotar la aritmética modular y la reducción de Montgomery: ¿Cuál es la forma más rápida de dividir un número entero entre 3?
fuente
b += r * 11 >> 5
conr = a - q * 3
. Enlace: hackersdelight.org/divcMore.pdf página 2+.X * 2 = desplazamiento de 1 bit a la izquierda
X / 2 = desplazamiento de 1 bit a la derecha
X * 3 = desplazamiento de 1 bit a la izquierda y luego agregar X
fuente
add X
a ese último?x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k
Puede utilizar estos cambios para realizar cualquier operación de multiplicación. Por ejemplo:
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)
Para dividir un número por un no poder de dos, no conozco ninguna forma fácil, a menos que desee implementar alguna lógica de bajo nivel, usar otras operaciones binarias y usar alguna forma de iteración.
fuente
fuente
Traduje el código de Python a C. El ejemplo dado tenía un pequeño defecto. Si el valor del dividendo ocupaba todos los 32 bits, el cambio fallaría. Solo usé variables de 64 bits internamente para solucionar el problema:
int No_divide(int nDivisor, int nDividend, int *nRemainder) { int nQuotient = 0; int nPos = -1; unsigned long long ullDivisor = nDivisor; unsigned long long ullDividend = nDividend; while (ullDivisor < ullDividend) { ullDivisor <<= 1; nPos ++; } ullDivisor >>= 1; while (nPos > -1) { if (ullDividend >= ullDivisor) { nQuotient += (1 << nPos); ullDividend -= ullDivisor; } ullDivisor >>= 1; nPos -= 1; } *nRemainder = (int) ullDividend; return nQuotient; }
fuente
ullDivisor >>= 1
antes delwhile
bucle? Además, ¿no funcionaránPos >= 0
?Un procedimiento para dividir números enteros que usa turnos y sumas se puede derivar de manera sencilla a partir de la división decimal a mano como se enseña en la escuela primaria. La selección de cada dígito del cociente se simplifica, ya que el dígito es 0 y 1: si el resto actual es mayor o igual que el divisor, el bit menos significativo del cociente parcial es 1.
Al igual que con la división decimal a mano, los dígitos del dividendo se consideran del más significativo al menos significativo, un dígito a la vez. Esto se logra fácilmente mediante un desplazamiento a la izquierda en la división binaria. Además, los bits del cociente se recopilan desplazando a la izquierda los bits del cociente actual en una posición y luego agregando el nuevo bit del cociente.
En una disposición clásica, estos dos desplazamientos a la izquierda se combinan en un desplazamiento a la izquierda de un par de registros. La mitad superior contiene el resto actual, la mitad inferior inicial contiene el dividendo. A medida que los bits de dividendo se transfieren al registro restante mediante un desplazamiento a la izquierda, los bits menos significativos no utilizados de la mitad inferior se utilizan para acumular los bits del cociente.
A continuación se muestra el lenguaje ensamblador x86 y las implementaciones C de este algoritmo. Esta variante particular de una división de cambio y suma a veces se denomina variante "sin rendimiento", ya que la resta del divisor del resto actual no se realiza a menos que el resto sea mayor o igual que el divisor. En C, no hay noción de bandera de acarreo utilizada por la versión de ensamblaje en el desplazamiento a la izquierda del par de registros. En cambio, se emula, basándose en la observación de que el resultado de una suma módulo 2 n puede ser menor que cualquiera de los dos sumandos solo si hubo una ejecución.
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #define USE_ASM 0 #if USE_ASM uint32_t bitwise_division (uint32_t dividend, uint32_t divisor) { uint32_t quot; __asm { mov eax, [dividend];// quot = dividend mov ecx, [divisor]; // divisor mov edx, 32; // bits_left mov ebx, 0; // rem $div_loop: add eax, eax; // (rem:quot) << 1 adc ebx, ebx; // ... cmp ebx, ecx; // rem >= divisor ? jb $quot_bit_is_0; // if (rem < divisor) $quot_bit_is_1: // sub ebx, ecx; // rem = rem - divisor add eax, 1; // quot++ $quot_bit_is_0: dec edx; // bits_left-- jnz $div_loop; // while (bits_left) mov [quot], eax; // quot } return quot; } #else uint32_t bitwise_division (uint32_t dividend, uint32_t divisor) { uint32_t quot, rem, t; int bits_left = CHAR_BIT * sizeof (uint32_t); quot = dividend; rem = 0; do { // (rem:quot) << 1 t = quot; quot = quot + quot; rem = rem + rem + (quot < t); if (rem >= divisor) { rem = rem - divisor; quot = quot + 1; } bits_left--; } while (bits_left); return quot; } #endif
fuente
Tome dos números, digamos 9 y 10, escríbalos como binarios: 1001 y 1010.
Comience con un resultado, R, de 0.
Tome uno de los números, 1010 en este caso, lo llamaremos A, y lo desplazaremos un bit hacia la derecha, si saca uno, agregue el primer número, lo llamaremos B, a R.
Ahora cambie B a la izquierda un bit y repita hasta que todos los bits se hayan desplazado fuera de A.
Es más fácil ver lo que está pasando si lo ve escrito, este es el ejemplo:
0 0000 0 10010 1 000000 0 1001000 1 ------ 1011010
fuente
Tomado de aqui .
Esto es solo para la división:
int add(int a, int b) { int partialSum, carry; do { partialSum = a ^ b; carry = (a & b) << 1; a = partialSum; b = carry; } while (carry != 0); return partialSum; } int subtract(int a, int b) { return add(a, add(~b, 1)); } int division(int dividend, int divisor) { boolean negative = false; if ((dividend & (1 << 31)) == (1 << 31)) { // Check for signed bit negative = !negative; dividend = add(~dividend, 1); // Negation } if ((divisor & (1 << 31)) == (1 << 31)) { negative = !negative; divisor = add(~divisor, 1); // Negation } int quotient = 0; long r; for (int i = 30; i >= 0; i = subtract(i, 1)) { r = (divisor << i); // Left shift divisor until it's smaller than dividend if (r < Integer.MAX_VALUE && r >= 0) { // Avoid cases where comparison between long and int doesn't make sense if (r <= dividend) { quotient |= (1 << i); dividend = subtract(dividend, (int) r); } } } if (negative) { quotient = add(~quotient, 1); } return quotient; }
fuente
es básicamente multiplicar y dividir con la potencia base 2
desplazar a la izquierda = x * 2 ^ y
desplazar a la derecha = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
fuente
eax
no puede contener un valor fraccionario como1/4
. (A menos que esté usando un punto fijo en lugar de un número entero, pero no lo especificó)Esto debería funcionar para la multiplicación:
.data .text .globl main main: # $4 * $5 = $2 addi $4, $0, 0x9 addi $5, $0, 0x6 add $2, $0, $0 # initialize product to zero Loop: beq $5, $0, Exit # if multiplier is 0,terminate loop andi $3, $5, 1 # mask out the 0th bit in multiplier beq $3, $0, Shift # if the bit is 0, skip add addu $2, $2, $4 # add (shifted) multiplicand to product Shift: sll $4, $4, 1 # shift up the multiplicand 1 bit srl $5, $5, 1 # shift down the multiplier 1 bit j Loop # go for next Exit: # EXIT: li $v0,10 syscall
fuente
El siguiente método es la implementación de la división binaria considerando que ambos números son positivos. Si la resta es una preocupación, podemos implementarla también usando operadores binarios.
Código
-(int)binaryDivide:(int)numerator with:(int)denominator { if (numerator == 0 || denominator == 1) { return numerator; } if (denominator == 0) { #ifdef DEBUG NSAssert(denominator==0, @"denominator should be greater then 0"); #endif return INFINITY; } // if (numerator <0) { // numerator = abs(numerator); // } int maxBitDenom = [self getMaxBit:denominator]; int maxBitNumerator = [self getMaxBit:numerator]; int msbNumber = [self getMSB:maxBitDenom ofNumber:numerator]; int qoutient = 0; int subResult = 0; int remainingBits = maxBitNumerator-maxBitDenom; if (msbNumber >= denominator) { qoutient |=1; subResult = msbNumber - denominator; } else { subResult = msbNumber; } while (remainingBits > 0) { int msbBit = (numerator & (1 << (remainingBits-1)))>0?1:0; subResult = (subResult << 1) | msbBit; if(subResult >= denominator) { subResult = subResult - denominator; qoutient= (qoutient << 1) | 1; } else{ qoutient = qoutient << 1; } remainingBits--; } return qoutient; } -(int)getMaxBit:(int)inputNumber { int maxBit = 0; BOOL isMaxBitSet = NO; for (int i=0; i<sizeof(inputNumber)*8; i++) { if (inputNumber & (1<<i)) { maxBit = i; isMaxBitSet=YES; } } if (isMaxBitSet) { maxBit+=1; } return maxBit; } -(int)getMSB:(int)bits ofNumber:(int)number { int numbeMaxBit = [self getMaxBit:number]; return number >> (numbeMaxBit - bits); }
Para multiplicar:
-(int)multiplyNumber:(int)num1 withNumber:(int)num2 { int mulResult = 0; int ithBit; BOOL isNegativeSign = (num1<0 && num2>0) || (num1>0 && num2<0); num1 = abs(num1); num2 = abs(num2); for (int i=0; i<sizeof(num2)*8; i++) { ithBit = num2 & (1<<i); if (ithBit>0) { mulResult += (num1 << i); } } if (isNegativeSign) { mulResult = ((~mulResult)+1); } return mulResult; }
fuente
-(int)multiplyNumber:(int)num1 withNumber:(int)num2
?Para cualquier persona interesada en una solución x86 de 16 bits, hay un fragmento de código de JasonKnight aquí 1 (también incluye un fragmento de multiplicación firmado, que no he probado). Sin embargo, ese código tiene problemas con entradas grandes, donde la parte "agregar bx, bx" se desbordaría.
La versión fija:
softwareMultiply: ; INPUT CX,BX ; OUTPUT DX:AX - 32 bits ; CLOBBERS BX,CX,DI xor ax,ax ; cheap way to zero a reg mov dx,ax ; 1 clock faster than xor mov di,cx or di,bx ; cheap way to test for zero on both regs jz @done mov di,ax ; DI used for reg,reg adc @loop: shr cx,1 ; divide by two, bottom bit moved to carry flag jnc @skipAddToResult add ax,bx adc dx,di ; reg,reg is faster than reg,imm16 @skipAddToResult: add bx,bx ; faster than shift or mul adc di,di or cx,cx ; fast zero check jnz @loop @done: ret
O lo mismo en el ensamblaje en línea de GCC:
asm("mov $0,%%ax\n\t" "mov $0,%%dx\n\t" "mov %%cx,%%di\n\t" "or %%bx,%%di\n\t" "jz done\n\t" "mov %%ax,%%di\n\t" "loop:\n\t" "shr $1,%%cx\n\t" "jnc skipAddToResult\n\t" "add %%bx,%%ax\n\t" "adc %%di,%%dx\n\t" "skipAddToResult:\n\t" "add %%bx,%%bx\n\t" "adc %%di,%%di\n\t" "or %%cx,%%cx\n\t" "jnz loop\n\t" "done:\n\t" : "=d" (dx), "=a" (ax) : "b" (bx), "c" (cx) : "ecx", "edi" );
fuente
Prueba esto. https://gist.github.com/swguru/5219592
import sys # implement divide operation without using built-in divide operator def divAndMod_slow(y,x, debug=0): r = 0 while y >= x: r += 1 y -= x return r,y # implement divide operation without using built-in divide operator def divAndMod(y,x, debug=0): ## find the highest position of positive bit of the ratio pos = -1 while y >= x: pos += 1 x <<= 1 x >>= 1 if debug: print "y=%d, x=%d, pos=%d" % (y,x,pos) if pos == -1: return 0, y r = 0 while pos >= 0: if y >= x: r += (1 << pos) y -= x if debug: print "y=%d, x=%d, r=%d, pos=%d" % (y,x,r,pos) x >>= 1 pos -= 1 return r, y if __name__ =="__main__": if len(sys.argv) == 3: y = int(sys.argv[1]) x = int(sys.argv[2]) else: y = 313271356 x = 7 print "=== Slow Version ...." res = divAndMod_slow( y, x) print "%d = %d * %d + %d" % (y, x, res[0], res[1]) print "=== Fast Version ...." res = divAndMod( y, x, debug=1) print "%d = %d * %d + %d" % (y, x, res[0], res[1])
fuente