Segredos

Segredos

Exercício de aprendizagem

Introdução

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.

Operações de deslocamento de bits

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.

Lógica bit a bit

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.

Instruções

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.

1. Desloca os bits para trás

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

2. Define alguns bits

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

3. Inverte bits específicos

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

4. Limpa bits específicos

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
Editar via GitHub A ligação abre numa nova janela ou separador
Julia Exercism

Estás pronto para começar Segredos?

Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.