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
Muito bom!
ResponderExcluir