Os algarismos binários acabam por corresponder diretamente aos transístores da tua CPU ou RAM, e ao facto de cada um estar "ligado" ou "desligado".
A manipulação de baixo nível, informalmente chamada "bit-twiddling", é particularmente importante nas linguagens de sistema.
As linguagens de alto nível, como a Julia, costumam abstrair a maior parte destes detalhes. No entanto, existe toda uma gama de operações ao nível dos bits disponível na linguagem base.
Nota: Para ver resultados binários legíveis por humanos no REPL, quase todos os exemplos abaixo têm de ser envolvidos numa função bitstring().
Isto distrai visualmente, por isso a maioria das ocorrências desta função foi retirada.
Os tipos inteiros, com ou sem sinal, podem ser representados como uma string de 1s e 0s.
julia> bitstring(UInt8(5))
"00000101"
Os deslocamentos de bits limitam-se a mover tudo para a esquerda ou para a direita um número especificado de posições.
Com os tipos UInt, alguns bits saem por uma extremidade e a outra extremidade é preenchida com zeros:
julia> ux::UInt8 = 5
5
julia> bitstring(ux)
"00000101"
julia> ux << 2 # left by 2
"00010100"
julia> ux >> 1 # right by 1
"00000010"
Cada deslocamento para a esquerda duplica o valor e cada deslocamento para a direita divide-o por dois (sujeito a truncagem). Isto é mais óbvio na representação decimal:
julia> 3 << 2
12
julia> 24 >> 3
3
Este tipo de deslocamento de bits é muito mais rápido do que a aritmética "a sério", o que torna a técnica muito popular na programação de baixo nível.
Com os inteiros com sinal, temos de ter um pouco mais de cuidado.
Os deslocamentos para a esquerda são relativamente simples:
julia> sx = Int8(5)
5
julia> sx # positive integer
"00000101"
julia> sx << 2
"00010100"
julia> -sx # negative integer
"11111011"
julia> -sx << 2
"11101100"
Assim, deslocar para a esquerda inteiros com sinal positivos é o mesmo que com inteiros sem sinal.
Os valores negativos são armazenados na forma de complemento de dois, o que significa que o bit mais à esquerda é 1. Não há problema para um deslocamento para a esquerda, mas ao deslocar para a direita, como preenchemos os bits mais à esquerda?
julia> sx >> 2 # simple for positive values!
"00000001"
julia> -sx # negative integer
"11111011"
julia> -sx >> 2 # pad with repeated sign bit
"11111110"
julia> -sx >>> 2 # pad with 0
"00111110"
O operador >> faz um deslocamento aritmético, preservando o bit de sinal.
O operador >>> faz um deslocamento lógico, preenchendo com zeros como se o número não tivesse sinal.
Vimos num Conceito anterior que os operadores && (and), || (or) e ! (not) se usam com valores Boolean.
Existem operadores equivalentes, & (and bit a bit), | (or bit a bit) e ~ (um til, not bit a bit), para comparar os bits de dois inteiros.
julia> 0b1011 & 0b0010 # bit is 1 in both numbers
"00000010"
julia> 0b1011 | 0b0010 # bit is 1 in at least one number
"00001011"
julia> ~0b1011 # flip all bits
"11110100"
julia> xor(0b1011, 0b0010) # bit is 1 in exactly one number, not both
"00001001"
Aqui, xor() é o ou-exclusivo, usado como função.
O teu amigo acabou de te enviar uma mensagem com um segredo importante. Para não facilitar a leitura a outras pessoas, a mensagem foi encriptada através de uma série de manipulações de bits. Vais precisar de escrever as funções que ajudam a desencriptar a mensagem.
O primeiro passo para desencriptar a mensagem é desfazer o deslocamento feito no processo de encriptação, deslocando os bits de volta para a direita. Haverá passos seguintes no processo de desencriptação que assumem que são inseridos zeros do lado esquerdo.
Implementa a função shift_back(), que recebe um valor e o número de posições a deslocar, e realiza o deslocamento.
shift_back(0b1001, 2) # => 0b0010
De seguida, há alguns bits que precisam de ser colocados a 1.
Implementa a função set_bits(), que recebe um valor e uma máscara e devolve o resultado de colocar os bits no valor a 1.
Um bit de value deve ser colocado a 1 quando o bit em mask também for 1.
Todos os outros bits devem permanecer inalterados.
set_bits(0b0110, 0b0101) # => 0b0111
Alguns bits são invertidos durante a encriptação. Vão precisar de ser invertidos de volta para desencriptar a mensagem.
Implementa a função flip_bits(), que recebe um valor e a máscara.
A máscara indica quais os bits do valor a inverter.
Se o bit for 1 em mask, o bit é invertido em value.
Todos os outros bits permanecem inalterados.
flip_bits(0b1100, 0b0101) # => 0b1001
Por fim, há também certos bits que ficam sempre a 0 depois de desencriptados.
Implementa a função clear_bits(), que recebe um valor e uma máscara.
Os bits em value devem ser colocados a 0 quando o bit na máscara for 1.
Todos os outros bits devem permanecer inalterados.
clear_bits(0b0110, 0b0101) # => 0b0010
Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.