Alguns dias atrás, o membro do StackExchange Anto perguntou sobre usos válidos para operadores em bits. Afirmei que o deslocamento era mais rápido do que multiplicar e dividir números inteiros por potências de dois. O membro do StackExchange Daemin rebateu afirmando que a mudança à direita apresentava problemas com números negativos.
Naquele momento, eu nunca havia pensado em usar os operadores shift com números inteiros assinados. Eu usei essa técnica principalmente no desenvolvimento de software de baixo nível; portanto, sempre usei números inteiros não assinados. C executa mudanças lógicas em números inteiros não assinados. Nenhuma atenção é dada ao bit de sinal ao executar uma mudança lógica à direita. Os bits desocupados são preenchidos com zeros. No entanto, C executa uma operação de deslocamento aritmético ao deslocar um número inteiro assinado para a direita. Os bits desocupados são preenchidos com o bit de sinal. Essa diferença faz com que um valor negativo seja arredondado para o infinito, em vez de ser truncado para zero, que é um comportamento diferente da divisão inteira assinada.
Alguns minutos de reflexão resultaram em uma solução de primeira ordem. A solução converte condicionalmente valores negativos em valores positivos antes de mudar. Um valor é convertido condicionalmente de volta à sua forma negativa após a execução da operação de mudança.
int a = -5;
int n = 1;
int negative = q < 0;
a = negative ? -a : a;
a >>= n;
a = negative ? -a : a;
O problema com esta solução é que as instruções de atribuição condicional geralmente são convertidas para pelo menos uma instrução de salto, e as instruções de salto podem ser caras em processadores que não decodificam os dois caminhos de instrução. Ter que refazer o pipeline de instruções duas vezes prejudica qualquer ganho de desempenho obtido pela troca de divisão.
Com o exposto, acordei no sábado com a resposta para o problema de atribuição condicional. O problema de arredondamento que experimentamos ao executar uma operação de mudança aritmética ocorre apenas ao trabalhar com a representação de complemento de dois. Isso não ocorre com a representação de complemento de alguém. A solução para o problema envolve a conversão do valor do complemento de dois para o valor de complemento de alguém antes de executar a operação de turno. Em seguida, temos que converter o valor do complemento de uma pessoa em um valor de complemento de dois. Surpreendentemente, podemos executar esse conjunto de operações sem converter valores negativos condicionalmente antes de executar a operação de mudança.
int a = -5;
int n = 1;
register int sign = (a >> INT_SIZE_MINUS_1) & 1
a = (a - sign) >> n + sign;
O valor negativo do complemento de dois é convertido no valor negativo do complemento de alguém subtraindo um. Por outro lado, o valor negativo do complemento de uma pessoa é convertido no valor negativo do complemento de dois adicionando um. O código listado acima funciona porque o bit de sinal é usado para converter o complemento de dois em complemento de um e vice-versa . Somente valores negativos terão seus bits de sinal definidos; portanto, o sinal da variável será igual a zero quando a for positivo.
Com o exposto acima, você pode pensar em outros hacks pouco inteligentes, como o descrito acima, que fizeram parte da sua mochila de truques? Qual é o seu truque bit-wise favorito? Estou sempre procurando por novos hacks bit a bit orientados para o desempenho.
fonte
Respostas:
Eu amo o hack de Gosper (HAKMEM # 175), uma maneira muito esperta de pegar um número e obter o próximo número com o mesmo número de bits definido. É útil, por exemplo, na geração de combinações de
k
itens a partir den
:fonte
O método Raiz quadrada inversa rápida usa as técnicas de nível de bits mais bizarras para calcular o inverso de uma raiz quadrada que eu já vi:
fonte
Divisão por 3 - sem recorrer a uma chamada de biblioteca em tempo de execução.
Acontece que a divisão por 3 (graças a uma dica sobre Stack Overflow) pode ser aproximada como:
X / 3 = [(x / 4) + (x / 12)]
E X / 12 é (x / 4) / 3. Há um elemento de recursão aparecendo subitamente aqui.
Acontece também que, se você limitar o intervalo dos números em que está reproduzindo, poderá limitar o número de iterações necessárias.
E assim, para números inteiros não assinados <2000, o seguinte é um algoritmo / 3 rápido e simples. (Para números maiores, basta adicionar mais etapas). Os compiladores otimizam tudo isso para que ele seja rápido e pequeno:
fonte
.0101010101
(aprox. 1/3). Ponta Pro: você também pode multiplicar por.000100010001
e101
(que leva apenas 3 bitshifts, ainda tem a melhor aproximação.010101010101
Se você procurar em Erlang, há um DSL inteiro para realizar operações de bits. Portanto, você pode simplesmente dividir uma estrutura de dados por bits dizendo algo como isto:
<> = << 1,17,42: 16 >>.
Detalhes completos aqui: http://www.erlang.org/doc/reference_manual/expressions.html#id75782
fonte