sábado, 2 de janeiro de 2016

Programação - Operador de deslocamento de bits

Falando de C existem três operadores que causam muita confusão na cabeça dos programadores. Chamados de deslocamento de bits e são << (deslocamento a esquerda), >> (deslocamento a direita com sinal) e >>> (deslocamento a direita com sinal).

Antes de falar como ocorre com os operadores de deslocamento, devemos entender como os números são formados no computador. Sabemos que possuímos 4 tipos de inteiros:
  • byte - com 8 bits
  • char e short - com 16 bits (sendo que o char não possui o bit de sinal)
  • int - com 32 bits
Analisaremos o byte para entendermos o conceito, essa é uma regra se aplica a todos os tipos. Pensemos em um valor qualquer, por exemplo: 47. Como é a representação desse decimal em binário? Esqueça aquele negócio de elevar a 2 ou qualquer maluquice do gênero. Crie uma régua iniciando do 1 e sempre dobrando seu valor para o bit seguinte, então a representação de um byte (que possui 8 bits) será:
128 64 32 16 8 4 2 1
Não precisamos de mais nada, agora devemos fazer uma pergunta para cada bit: 47 é maior ou igual ao seu valor? Se sim marque 1 e subtraia o valor do bit (para o próximo bit use esse resultado ao invés de 47) senão marque 0. Faça isso para todos os números e o resultado será o seguinte:
128 64 32 16 8 4 2 1
  0  0  1  0 1 1 1 1
47 é maior que 32 e sobra 15, 15 é maior que 8 e sobra 77 é maior que 4 e sobra 33 é maior que 2 e sobra 1 e marcamos a coluna do 1 por ser igual. Todos os outros ganham 0. Devemos saber que o primeiro bit é de sinal, se ele fosse marcado o valor seria negativo.

Para converter um binário em decimal, se o número é positivo (ou seja, o bit de sinal é 0) basta somar o valor dos bits ligados (que possuem o valor 1), 32+8+4+2+1 = 47. Se o número fosse negativo faríamos 128 menos a soma dos outros. Curiosamente o número -1 é representado com todos os bits ligados.

Pronto agora que sabemos realizar a conversão, podemos partir para o operador de deslocamento. Apenas devemos ter em mente uma propriedade do Cqualquer operação realizada com tipos inteiros o resultado é automaticamente passado para um tipo int. Ou seja, se deslocarmos os bits de um byte o resultado será um int (e não um byte), o que isso tem a ver? Saímos de 8 bits e entramos em 32 bits, por exemplo: 47 << 2, já sabemos como é a representação binária de 47, então basta andar com os bits 2 casas a esquerda resultando em:
128 64 32 16 8 4 2 1
  1  0  1  1 1 1 0 0
E parece que agora temos um número negativo, se fosse um tipo byte isso seria verdade, porém a representação do tipo int conta com mais 3 conjuntos desse e o resultado é 188. Observe que preenchemos todos os espaços vazios com 0. Já o deslocamento a direita, por exemplo 47 >> 2 resulta em:
128 64 32 16 8 4 2 1
  0  0  0  0 1 0 1 1
O que dá o valor 11. Preenchemos com 0 os espaços vazios pois é um valor positivo, se fosse negativo seria preenchido com 1, a diferença para o outro operador com 3 sinais é que nesse sempre é preenchido com 0 (ou seja, um número negativo vira positivo qualquer que seja seu deslocamento).

Tudo bem, entendemos como é realizado o processo, mas para que serve? Deslocamento a esquerda é a multiplicação do número por 2 elevado ao valor deslocado e deslocamento a direita a divisão do número por 2 elevado ao valor deslocado. Isso é realizado para buscar uma melhor performance e simplificar programas que envolvem muitos cálculos matemáticos, vamos comparar a performance em 2 programas escritos em Java e Python usando esses operadores e seus equivalentes matemáticos.

Observação, lembro que Java não existe operador para exponenciação e devemos usar o método Math.pow(n,e), já em Python usamos o operador **.

Em Java

import java.text.SimpleDateFormat;

public class Desloca {

    private SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSSS");
    
    public static void main(String [] args) {
        new Desloca().testar();
    }
    
    private void testar() {
        porMultiplicacao();
        porDeslocamentoEsq();
        porDivisao();
        porDeslocamentoDir();
    }
    
    private void porMultiplicacao() {
        int i = 3000;
        double j = 0;
        System.out.println("Por Multiplicação: " + sdf.format(new java.util.Date()));
        for (int z = 0; z < 1000000; z++) {
            j = i * Math.pow(2,7);
        }
        System.out.println("Por Multiplicação: " + sdf.format(new java.util.Date()));
        System.out.println(j);
    }

    private void porDeslocamentoEsq() {
        int i = 3000;
        double j = 0;
        System.out.println("Por Desl.Esq: " + sdf.format(new java.util.Date()));
        for (int z = 0; z < 1000000; z++) {
            j = i << 7;
        }
        System.out.println("Por Desl.Esq: " + sdf.format(new java.util.Date()));
        System.out.println(j);
    }

    private void porDivisao() {
        int i = 3000;
        double j = 0;
        System.out.println("Por Divisão: " + sdf.format(new java.util.Date()));
        for (int z = 0; z < 1000000; z++) {
            j = i / Math.pow(2,7);
        }
        System.out.println("Por Divisão: " + sdf.format(new java.util.Date()));
        System.out.println(j);
    }

    private void porDeslocamentoDir() {
        int i = 3000;
        double j = 0;
        System.out.println("Por Desl.Dir: " + sdf.format(new java.util.Date()));
        for (int z = 0; z < 1000000; z++) {
            j = i >> 7;
        }
        System.out.println("Por Desl.Dir: " + sdf.format(new java.util.Date()));
        System.out.println(j);
    }
}

Em Python

import datetime

def porMultiplicacao():
    i = 3000
    j = 0
    print("Multiplicar: ", datetime.datetime.now().time())
    for z in range(0, 1000000):
        j = i * (2 ** 7)
    print("Multiplicar: ", datetime.datetime.now().time())
    print(j);

def porDeslocamentoEsq():
    i = 3000
    j = 0
    print("Por Desl.Esq: ", datetime.datetime.now().time())
    for z in range(0, 1000000):
        j = i << 7;
    print("Por Desl.Esq: ", datetime.datetime.now().time())
    print(j);

def porDivisao():
    i = 3000
    j = 0
    print("Dividir: ", datetime.datetime.now().time())
    for z in range(0, 1000000):
        j = i / (2 ** 7);
    print("Dividir: ", datetime.datetime.now().time())
    print(j);

def porDeslocamentoDir():
    i = 3000
    j = 0
    print("Por Desl.Dir: ", datetime.datetime.now().time())
    for z in range(0, 1000000):
        j = i >> 7;
    print("Por Desl.Dir: ", datetime.datetime.now().time())
    print(j);

def testar():
    porMultiplicacao()
    porDeslocamentoEsq()
    porDivisao()
    porDeslocamentoDir()

if __name__ == '__main__':
    testar()

Realizamos um milhão de vezes cada cálculo e mostramos o tempo inicial e final. Não estou dizendo que a partir de agora está proibido realizar o operador elevado a dois, quis apenas demonstrar a utilidade e razão da existência deste operador de deslocamento. Usá-lo ou não depende de muitos fatores e ao bom senso dos programadores.

Obrigado e até a próxima
Fernando Anselmo





0 comentários:

Postar um comentário