Como posso multiplicar e dividir usando apenas deslocamento e adição de bits?
c
assembly
bit-manipulation
division
multiplication
Spidfire
fonte
fonte
Respostas:
Para multiplicar em termos de adição e deslocamento, você deseja decompor um dos números em potências de dois, assim:
(
_2
significa base 2)Como você pode ver, a multiplicação pode ser decomposta em soma e deslocamento e vice-versa. É também por isso que a multiplicação leva mais tempo do que deslocamentos de bits ou adição - é O (n ^ 2) em vez de O (n) no número de bits. Os sistemas de computador reais (em oposição aos sistemas de computador teóricos) têm um número finito de bits, então a multiplicação leva um múltiplo constante de tempo em comparação com a adição e o deslocamento. Se bem me lembro, os processadores modernos, se canalizados corretamente, podem fazer multiplicação quase tão rápido quanto a adição, mexendo com a utilização das ALUs (unidades aritméticas) no processador.
fonte
A resposta de Andrew Toulouse pode ser estendida à divisão.
A divisão por constantes inteiras é considerada em detalhes no livro "Hacker's Delight" de Henry S. Warren (ISBN 9780201914658).
A primeira ideia para implementar a divisão é escrever o valor inverso do denominador na base dois.
Por exemplo,
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....
Portanto,
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
para aritmética de 32 bits.Ao combinar os termos de maneira óbvia, podemos reduzir o número de operações:
b = (a >> 2) + (a >> 4)
b += (b >> 4)
b += (b >> 8)
b += (b >> 16)
Existem maneiras mais interessantes de calcular a divisão e os remanescentes.
EDIT1:
Se o OP significa multiplicação e divisão de números arbitrários, não a divisão por um número constante, este tópico pode ser útil: https://stackoverflow.com/a/12699549/1182653
EDIT2:
Uma das maneiras mais rápidas de dividir por constantes inteiras é explorar a aritmética modular e a redução de Montgomery: Qual é a maneira mais rápida de dividir um inteiro por 3?
fonte
b += r * 11 >> 5
comr = a - q * 3
. Link: hackersdelight.org/divcMore.pdf página 2+.X * 2 = deslocamento de 1 bit para a esquerda
X / 2 = deslocamento de 1 bit para a direita
X * 3 = deslocamento para a esquerda 1 bit e, em seguida, adicionar X
fonte
add X
por aquele último?x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k
Você pode usar essas mudanças para fazer qualquer operação de multiplicação. Por exemplo:
x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)
Para dividir um número por uma não potência de dois, não conheço nenhuma maneira fácil, a menos que você queira implementar alguma lógica de baixo nível, usar outras operações binárias e usar alguma forma de iteração.
fonte
fonte
Traduzi o código Python para C. O exemplo dado tinha uma pequena falha. Se o valor do dividendo ocupasse todos os 32 bits, a mudança falharia. Acabei de usar variáveis de 64 bits internamente para contornar o problema:
fonte
Um procedimento para dividir inteiros que usa deslocamentos e adições pode ser derivado de forma direta da divisão decimal à mão, conforme ensinado na escola primária. A seleção de cada dígito quociente é simplificada, pois o dígito é 0 e 1: se o resto atual for maior ou igual ao divisor, o bit menos significativo do quociente parcial é 1.
Assim como na divisão longa decimal, os dígitos do dividendo são considerados do mais significativo para o menos significativo, um dígito de cada vez. Isso é facilmente realizado por um deslocamento à esquerda na divisão binária. Além disso, os bits de quociente são reunidos deslocando-se para a esquerda os bits de quociente atuais em uma posição e, em seguida, acrescentando o novo bit de quociente.
Em um arranjo clássico, esses dois deslocamentos à esquerda são combinados no deslocamento à esquerda de um par de registros. A metade superior mantém o restante atual, a metade inferior inicial contém o dividendo. À medida que os bits do dividendo são transferidos para o registro restante pelo deslocamento para a esquerda, os bits menos significativos não usados da metade inferior são usados para acumular os bits quocientes.
Abaixo está a linguagem assembly x86 e as implementações C desse algoritmo. Esta variante particular de uma divisão shift & add é algumas vezes referida como a variante "sem desempenho", uma vez que a subtração do divisor do resto atual não é executada a menos que o resto seja maior ou igual ao divisor. Em C, não há noção do sinalizador de transporte usado pela versão do assembly no deslocamento à esquerda do par de registradores. Em vez disso, ele é emulado, com base na observação de que o resultado de um módulo de adição 2 n pode ser menor que qualquer um dos adendos apenas se houver um carry out.
fonte
Pegue dois números, digamos 9 e 10, escreva-os como binários - 1001 e 1010.
Comece com um resultado, R, de 0.
Pegue um dos números, 1010 neste caso, vamos chamá-lo de A, e desloque-o um bit à direita, se você deslocar um, adicione o primeiro número, vamos chamá-lo de B, para R.
Agora mude B para a esquerda um bit e repita até que todos os bits tenham sido deslocados de A.
É mais fácil ver o que está acontecendo se você vir escrito, este é o exemplo:
fonte
Retirado daqui .
Isso é apenas para divisão:
fonte
é basicamente multiplicar e dividir com a potência de base 2
deslocar para a esquerda = x * 2 ^ y
deslocar para a direita = x / 2 ^ y
shl eax, 2 = 2 * 2 ^ 2 = 8
shr eax, 3 = 2/2 ^ 3 = 1/4
fonte
eax
não pode conter um valor fracionário como1/4
. (A menos que você esteja usando ponto fixo em vez de inteiro, mas você não especificou isso)Isso deve funcionar para multiplicação:
fonte
O método abaixo é a implementação da divisão binária considerando que ambos os números são positivos. Se a subtração for uma preocupação, podemos implementar isso também usando operadores binários.
Código
Para multiplicação:
fonte
-(int)multiplyNumber:(int)num1 withNumber:(int)num2
?Para qualquer pessoa interessada em uma solução x86 de 16 bits, há um trecho de código de JasonKnight aqui 1 (ele também inclui um trecho de multiplicação assinado, que não testei). No entanto, esse código tem problemas com entradas grandes, onde a parte "add bx, bx" estouraria.
A versão fixa:
Ou o mesmo na montagem em linha do GCC:
fonte
Experimente isso. https://gist.github.com/swguru/5219592
fonte