Los dígitos binarios se corresponden en última instancia con los transistores de tu CPU o tu 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 sistema.
Los lenguajes de alto nivel como Julia normalmente abstraen la mayor parte de este detalle. Sin embargo, el lenguaje base incluye toda una gama de operaciones a nivel de bits.
Nota: Para ver una salida binaria legible para humanos en el REPL, casi todos los ejemplos de abajo tienen que ir envueltos en una función bitstring().
Esto distrae visualmente, así que se ha eliminado la mayoría de las apariciones de esta función.
Los tipos enteros, con signo o sin signo, se pueden representar como una cadena de 1's y 0's.
julia> bitstring(UInt8(5))
"00000101"
Los desplazamientos de bits simplemente mueven todo a la izquierda o a la derecha un número de posiciones determinado.
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 divide por dos (sujeto a truncamiento). Esto se ve más claro en representación decimal:
julia> 3 << 2
12
julia> 24 >> 3
3
Este tipo de desplazamientos de bits es mucho más rápido que la aritmética «de verdad», lo que hace que la técnica sea muy popular en la programación de bajo nivel.
Con los enteros con signo hay que tener un poco más de cuidado.
Los desplazamientos a la izquierda son relativamente sencillos:
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 positivos con signo 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 de 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, que conserva el bit de signo.
El operador >>> realiza un desplazamiento lógico, rellenando con ceros como si el número no tuviera signo.
En un concepto anterior vimos que los operadores && (and), || (or) y ! (not) se usan con valores Boolean.
Hay 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 el or exclusivo, usado como función.
Tu amigo acaba de enviarte un mensaje con un secreto importante. Como no quería que los demás lo leyeran con facilidad, el mensaje se cifró realizando una serie de manipulaciones de bits. Tendrás que escribir las funciones que ayuden a descifrar el mensaje.
El primer paso para descifrar el mensaje es deshacer el desplazamiento del proceso de cifrado desplazando los bits de nuevo a la derecha. En el proceso de descifrado habrá pasos posteriores que dan por sentado que se insertan 0s 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
A continuación, hay algunos bits que hay que poner a 1.
Implementa la función set_bits(), que recibe un valor y una máscara y devuelve el resultado de poner a 1 los bits de value.
Un bit de value debe ponerse a 1 cuando el bit correspondiente de mask también sea 1.
Todos los demás bits deben dejarse sin cambios.
set_bits(0b0110, 0b0101) # => 0b0111
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 qué bits del valor hay que invertir.
Si el bit es 1 en mask, el bit se invierte en value.
Todos los demás bits se dejan sin cambios.
flip_bits(0b1100, 0b0101) # => 0b1001
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 de value deben ponerse a 0 cuando el bit de la máscara sea 1.
Todos los demás bits deben dejarse sin cambios.
clear_bits(0b0110, 0b0101) # => 0b0010
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.