sábado, 26 de setembro de 2015

Tutorial Matemática de Bits no Arduino


Introdução

Normalmente quando programamos no ambiente computacional do Arduino (ou em qualquer outro computador), habilidades como manipular bits individualmente se tornam úteis e até necessárias. Aqui algumas situações onde a Matemática de Bits pode auxiliar:

  • Economizar memória, armazenar 8 valores binários (true/false) em apenas 1 byte
  • Ativar/Desativar bits individualmente para controlar Registradores de Porta
  • Executar determinadas operações aritméticas, envolvendo cálculos em potência de base 2

Neste tutorial primeiro exploraremos o básico dos operadores de bit disponíveis na linguagem C++. Então aprendermos a combinar essas técnicas para efetuar certas operações bem úteis.


Sistema Binário

Para explicar melhor os operadores de bit, este tutorial apresentará a maioria dos valores inteiros usando a notação binária, também conhecida como “Base de 2” ou “Base Binária”. Neste sistema binário, todos os valores inteiros são expressos utilizando-se apenas dígitos 0 e 1. É dessa forma que todos os equipamentos eletrônicos armazenam dados internamente. Cada dígito 0 ou 1 é chamado de bit, que é um apelido para “binary digit” (dígito binário, em inglês).

No sistema decimal (base 10), que estamos acostumados, fatorando um número como 572 temos 5*102 + 7*101 + 2*100. Da mesma forma, fatorando um número binário como 11010, temos 1*24 + 1*23 + 0*22 + 1*21 + 0*20 = 16 + 8 + 2 = 26 (em decimal).

Infelizmente a maioria dos compiladores C++ não fornecem quaisquer meios para o programador expressar números binários diretamente no código. Mas felizmente o Arduino, desde a versão 0007, possui as constantes B0 até B11111111 definidas por para nos ajudar a representar números binários de 8 bits, do 0 ao 255 (em decimal).

Dica: Lembre-se que 28 = 256


Operador de Bit AND

O operador de bit chamado AND no padrão C++ é expresso por um “ampersand” &, usado entre duas expressões numéricas. O operador AND manipula cada posição de bit, atuando sobre os números envolvidos na operação, de acordo com a seguinte regra: se ambos os bits da mesma posição em ambos os lados da expressão forem 1, o resultado também é 1, caso contrário o resultado é 0. Uma outra forma de escrever isso é:

0 & 0 == 0
0 & 1 == 0
1 & 0 == 0
1 & 1 == 1
No Arduino, o tipo de variável int é um valor de 16-bits, então ao usarmos o operador & entre duas expressões int, temos 16 operações simultâneas ocorrendo, uma para cada posição de bit. Em um fragmento de código como este:

int a =  92;    // em binario: 0000000001011100
int b = 101;    // em binario: 0000000001100101
int c = a & b;  // resultado:  0000000001000100, or 68 in decimal.
Cada um dos 16 bits da variável a ou b são processados usando o operador AND, e todos os 16 resultados são gravados na variável c, gerando o valor 01000100 em binário, que é 68 em decimal.

Um dos usos mais comuns para o operador de bit AND é selecionar um (ou mais) bit(s) a partir de um valor inteiro, o que chamamos de “masking” (mascarar). Por exemplo, se você quiser acessar o último bit menos significante da variável x, e guardá-lo na variável y, você poderia usar o código a seguir:

int x = 5;       // em binario: 101
int y = x & 1;   // agora y == 1
x = 4;           // em binario: 100
y = x & 1;       // agora y == 0
Dica: Use essa técnica para saber se o número é par ou impar. Se você verificar o bit mais alto, você consegue determinar se o número é positivo ou negativo. Veja mais abaixo...

Operador de Bit OR

O operador OR em C++ é o símbolo de barra vertical |. Assim como o operador &, o operador | atua independentemente em cada bit dos dois lados da operação. Mas o que ele faz é diferente (claro). O operador OR retorna 1 quando qualquer um dos bits de entrada for 1, caso contrário, retorna 0. Em outras palavras:

0 | 0 == 0
0 | 1 == 1
1 | 0 == 1
1 | 1 == 1
Abaixo podemos ver um exemplo de uso do operador OR em um trecho de código C++:

int a =  92;    // em binario: 0000000001011100
int b = 101;    // em binario: 0000000001100101
int c = a | b;  // resultado:  0000000001111101, ou 125 em decimal.
O operador de bit OR é normalmente usado para garantir que um bit esteja ligado (configurado com valor 1) em uma determinada expressão. Por exemplo, para copiar os bits da variável a para variável b, garantindo que o último bit seja 1, use o seguinte código:

b = a | 1;


Operador de Bit XOR

Temos aqui um operador não muito usual em C++ chamado operador de bit exclusivo OR, também conhecido por XOR. Este operador é expresso pelo símbolo de circunflexo ^, e é similar ao operador OR, exceto pelo fato que ele retorna 1 quando apenas um dos lados da operação tem entrada com valor 1. Se ambos os bits dos dois lados da operação forem 0 ou forem 1, ambos ao mesmo tempo, o retorno do XOR será 0, conforme segue:

0 ^ 0 == 0
0 ^ 1 == 1
1 ^ 0 == 1
1 ^ 1 == 0
Outra forma de olhar para o operador XOR é que o resultado é 1 quando ambos bits da operação são diferentes; e o resultado é 0 quando são iguais, ao mesmo tempo.

Temos aqui um código de exemplo:

int x = 12;     // em binario: 1100
int y = 10;     // em binario: 1010
int z = x ^ y;  // em binario: 0110, ou em decimal = 6
O operador ^ é normalmente usado para inverter um valor de 1 para 0 e vice-versa. Atuando em apenas um ou mais bits da equação e deixando outros em paz. Por exemplo:

y = x ^ 1;   // inverte o bit menos significativo em x
   // e guarda resultado em y.


Operador de Bit NOT

O operador NOT em C++ é escrito usando o símbolo do til ~. Ao contrario dos operadores & e |, o operador NOT é aplicado a um único operando que fica a direita. O operador NOT muda cada bit para seu oposto: 0 vira 1 e 1 vira 0. Por exemplo:

int a = 103;    // em binario:  0000000001100111
int b = ~a;     // em binario:  1111111110011000 = -104
Você deve estar surpreso de ver um número negativo como resultado da operação, como -104. Isto acontece porque o bit mais alto de um número inteiro é chamado de bit de sinal. Se o maior bit do número, em questão, for 1, significa que ele é interpretado como negativo. Esta codificação de positivo e negativo de um número é chamada de “complemento de dois”.

As vezes um inteiro com sinal pode causar problemas na programação. Veremos mais adiante.

Dica: Para retirar o sinal de um tipo de variável, declare-a antecedendo com a palavra "unsigned". Exemplos: unsigned int a; unsigned long b; unsigned char c;

Operadores de Deslocamento de Bit

Existem dois operadores de deslocamento na linguagem C++: o operador de deslocamento para a esquerda << e deslocamento para direita >>. Estes operadores deslocam os bits do operando da esquerda da equação pela quantidade de posições determinadas pelo operando da direita. Por exemplo:

int a = 5;        // em binario: 0000000000000101
int b = a << 3;   // em binario: 0000000000101000 = 40
int c = b >> 3;   // em binario: 0000000000000101 = 5
Quando você desloca um valor x por y bits (x << y), a quantidade y de bits mais a esquerda de x será perdida, deslocada para o além:

int a = 5;        // em binario: 0000000000000101
int b = a << 14;  // em binario: 0100000000000000
   // descartou primeiro 1 de 101
O importante é que não existam bits de interesse mais a esquerda quando for efetuar um deslocamento para esquerda. E a mesma coisa vale para o deslocamento a direita. Uma forma simples de encarar este operador é que o deslocamento para a esquerda é o mesmo que 2 elevado ao operando da direita. Por exemplo, para gerar potências de 2, as seguintes expressões podem ser usadas:

1 <<  0  ==    1
1 <<  1  ==    2
1 <<  2  ==    4
1 <<  3  ==    8
...
1 <<  8  ==  256
1 <<  9  ==  512
1 << 10  == 1024
...
Quando você desloca x a direita por y bits (x >> y), e o bit mais alto é 1, o comportamento depende do tipo de dados da variável x. Se x for do tipo int, é bit mais alto é o que controla o sinal, determinando se x é positivo ou negativo. Como discutido acima, nesse caso, o bit do sinal é copiado para os bits mais baixos, por razões historicamente esotéricas!

int x = -16;     // em binario: 1111111111110000
int y = x >> 3;  // em binario: 1111111111111110
Esse fenômeno é chamado de extensão de sinal. E normalmente não é o efeito que você quer. Ao invés disso, você esperararia que zeros fossem preenchendo os bits novos mais a esquerda. Acontece que as regras para deslocamento a direita são diferentes para as variáveis do tipo unsigned int, então você pode usar um “cast” para suprimir os 1's aparecendo na esquerda.

int x = -16;               // em binario: 1111111111110000
int y = unsigned(x) >> 3;  // em binario: 0001111111111110
Se você tomar cuidado em evitar tipos de dados com sinal, você então pode usar o operador de deslocamento a direita >> como uma forma de dividir pelas potências de 2. Por exemplo:

int x = 1000;
int y = x >> 3;   // divisao de 1000 por 8
    // resultado y = 125.

Operadores de Atribuição

Muitas vezes na programação queremos operar nos valores de uma variável x e guardar essa informação modificada de volta na mesma variável x. Na maioria das linguagens de programação, por exemplo, você poderia incrementar o valor por 7, usando o seguinte código:

x = x + 7;    // incrementa x em 7

Por causa desse tipo de coisa ocorrer tão frequentemente, a programação C++ oferece um atalho em forma de operadores de atribuição especiais. O código acima pode ser escrito de forma mais concisa, assim:

x += 7;    // incrementa x em 7

Acontece que os operadores de bit AND e OR, deslocamento para direita e deslocamento para a esquerda, todos eles também possuem atalhos de atribuição. Vejamos um exemplo:

int x = 1;  // em binario: 0000000000000001
x <<= 3;    // em binario: 0000000000001000
x |= 3;     // em binario: 0000000000001011
            // porque 3 = 11 em binario
x &= 1;     // em binario: 0000000000000001
x ^= 4;     // em binario: 0000000000000101
     // inverte usando mascara 100
x ^= 4;     // em binario: 0000000000000001
     // inverte usando mascara 100 de novo
Não existe atalho para o operador de bit NOT, então se você precisar inverter os bits de uma variável x, você precisará fazer assim:

x = ~x;    // inverte todos os bits de x
    // guarda de volta em x

Tradução livre do original http://playground.arduino.cc/Code/BitMath
Por Renato Aloi


Um comentário: