Secretos

Secretos

Ejercicio de aprendizaje

Introducción

Los dígitos binarios, en última instancia, se corresponden directamente con los transistores de tu CPU o RAM, y con si cada uno está «encendido» o «apagado».

La manipulación de bajo nivel, llamada informalmente «bit-twiddling», es especialmente importante en los lenguajes de sistemas.

Los lenguajes de alto nivel como Julia normalmente abstraen la mayor parte de este detalle. Sin embargo, el lenguaje base ofrece una gama completa de operaciones a nivel de bits.

Nota: Para ver una salida binaria legible para humanos en el REPL, casi todos los ejemplos a continuación deben envolverse en una función bitstring(). Esto distrae visualmente, por lo que se han eliminado la mayoría de las apariciones de esta función.

Operaciones de desplazamiento de bits

Los tipos de enteros, con signo o sin signo, se pueden representar como un string de unos y ceros.

julia> bitstring(UInt8(5))
"00000101"

Los desplazamientos de bits simplemente mueven todo a la izquierda o a la derecha una cantidad especificada de posiciones. Con los tipos UInt, algunos bits se pierden por un extremo y el otro extremo se rellena con ceros:

julia> ux::UInt8 = 5
5

julia> bitstring(ux)
"00000101"

julia> ux << 2 # left by 2
"00010100"

julia> ux >> 1 # right by 1
"00000010"

Cada desplazamiento a la izquierda duplica el valor y cada desplazamiento a la derecha lo reduce a la mitad (sujeto a truncamiento). Esto es más evidente en la representación decimal:

julia> 3 << 2
12

julia> 24 >> 3
3

Este tipo de desplazamiento de bits es mucho más rápido que la aritmética «correcta», lo que hace que la técnica sea muy popular en la programación de bajo nivel.

Con los enteros con signo, debemos tener un poco más de cuidado.

Los desplazamientos a la izquierda son 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"

Así, desplazar a la izquierda enteros con signo positivos es lo mismo que con enteros sin signo.

Los valores negativos se almacenan en forma de complemento a dos, lo que significa que el bit más a la izquierda es 1. No hay problema para un desplazamiento a la izquierda, pero al desplazar a la derecha, ¿cómo rellenamos los bits más a la izquierda?

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"

El operador >> realiza un desplazamiento aritmético, preservando el bit de signo.

El operador >>> realiza un desplazamiento lógico, rellenando con ceros como si el número no tuviera signo.

Lógica bit a bit

Vimos en un Concepto anterior que los operadores && (y), || (o) y ! (no) se usan con valores Boolean.

Existen operadores equivalentes & (AND bit a bit), | (OR bit a bit) y ~ (una tilde, NOT bit a bit) para comparar los bits de dos enteros.

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"

Aquí, xor() es la operación OR exclusiva, usada como función.

Instrucciones

Una amistad tuya te acaba de enviar un mensaje con un secreto importante. Como no quería que a otras personas les resultara fácil leerlo, el mensaje se cifró mediante una serie de manipulaciones de bits. Vas a tener que escribir las funciones que ayuden a descifrar el mensaje.

1. Desplaza los bits hacia atrás

El primer paso para descifrar el mensaje es deshacer el desplazamiento que hizo el proceso de cifrado, desplazando los bits de vuelta hacia la derecha. Más adelante, el proceso de descifrado tendrá otros pasos que dan por hecho que se insertan ceros desde el lado izquierdo.

Implementa la función shift_back() que recibe un valor y el número de posiciones que hay que desplazar, y realiza el desplazamiento.

shift_back(0b1001, 2) # => 0b0010

2. Establece algunos bits

Luego, hay algunos bits que deben establecerse en 1.

Implementa la función set_bits() que recibe un valor y una máscara, y devuelve el resultado de establecer en 1 los bits del valor. Un bit de value debe establecerse en 1 cuando el bit correspondiente de la mask también es 1. Todos los demás bits deben quedar sin cambios.

set_bits(0b0110, 0b0101) # => 0b0111

3. Invierte bits específicos

Algunos bits se invierten durante el cifrado. Habrá que invertirlos de nuevo para descifrar el mensaje.

Implementa la función flip_bits() que recibe un valor y la máscara. La máscara indica cuáles bits del valor hay que invertir. Si el bit es 1 en mask, el bit se invierte en el value. Todos los demás bits quedan sin cambios.

flip_bits(0b1100, 0b0101) # => 0b1001

4. Limpia bits específicos

Por último, también hay ciertos bits que siempre se descifran como 0.

Implementa la función clear_bits() que recibe un valor y una máscara. Los bits del value deben establecerse en 0 cuando el bit correspondiente de la máscara es 1. Todos los demás bits deben quedar sin cambios.

clear_bits(0b0110, 0b0101) # => 0b0010
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
Julia Exercism

¿Todo listo para empezar Secretos?

Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.