Le cifre binarie, in ultima analisi, corrispondono direttamente ai transistor della tua CPU o della tua RAM, e al fatto che ciascuno sia «acceso» o «spento».
La manipolazione di basso livello, chiamata informalmente «bit-twiddling», è particolarmente importante nei linguaggi di sistema.
I linguaggi di alto livello come Julia di solito astraggono gran parte di questi dettagli. Tuttavia, nel linguaggio di base è disponibile un'intera gamma di operazioni a livello di bit.
Nota: Per vedere un output binario leggibile nel REPL, quasi tutti gli esempi seguenti devono essere racchiusi in una funzione bitstring(). Questo disturba visivamente, quindi la maggior parte delle occorrenze di questa funzione è stata rimossa.
I tipi interi, con o senza segno, possono essere rappresentati come una stringa di 1 e di 0.
julia> bitstring(UInt8(5))
"00000101"
Gli shift spostano semplicemente tutto a sinistra o a destra di un numero di posizioni specificato.
Con i tipi UInt, alcuni bit escono da un'estremità e l'altra estremità viene riempita con zeri:
julia> ux::UInt8 = 5
5
julia> bitstring(ux)
"00000101"
julia> ux << 2 # left by 2
"00010100"
julia> ux >> 1 # right by 1
"00000010"
Ogni shift a sinistra raddoppia il valore e ogni shift a destra lo dimezza (fatti salvi i troncamenti). Questo è più evidente nella rappresentazione decimale:
julia> 3 << 2
12
julia> 24 >> 3
3
Questo tipo di shift è molto più veloce dell'aritmetica «vera»: per questo la tecnica è molto popolare nella programmazione di basso livello.
Con i numeri interi con segno, dobbiamo fare un po' più attenzione.
Gli shift a sinistra sono relativamente semplici:
julia> sx = Int8(5)
5
julia> sx # positive integer
"00000101"
julia> sx << 2
"00010100"
julia> -sx # negative integer
"11111011"
julia> -sx << 2
"11101100"
Lo shift a sinistra di interi con segno positivi è quindi identico a quello degli interi senza segno.
I valori negativi sono memorizzati nella forma in complemento a due, il che significa che il bit più a sinistra è 1. Nessun problema per uno shift a sinistra, ma quando si fa uno shift a destra, come riempiamo i bit più a sinistra?
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"
L'operatore >> esegue uno shift aritmetico, conservando il bit di segno.
L'operatore >>> esegue uno shift logico, riempiendo con zeri come se il numero fosse senza segno.
In un concetto precedente abbiamo visto che gli operatori && (and), || (or) e ! (not) si usano con i valori booleani.
Esistono operatori equivalenti, & (and bit a bit), | (or bit a bit) e ~ (una tilde, not bit a bit), per confrontare i bit di due numeri interi.
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"
Qui xor() è l'exclusive-or, usato come funzione.
Il tuo amico ti ha appena inviato un messaggio con un segreto importante. Per non rendere facile agli altri leggerlo, il messaggio è stato cifrato eseguendo una serie di manipolazioni sui bit. Dovrai scrivere le funzioni che aiutano a decifrare il messaggio.
Il primo passo per decifrare il messaggio è annullare lo scorrimento fatto durante la cifratura, spostando i bit di nuovo verso destra. Ci saranno altri passaggi del processo di decifratura che presuppongono l'inserimento di 0 dal lato sinistro.
Implementa la funzione shift_back(), che prende un valore e il numero di posizioni di cui scorrere ed esegue lo scorrimento.
shift_back(0b1001, 2) # => 0b0010
Ora ci sono alcuni bit che devono essere impostati a 1.
Implementa la funzione set_bits(), che prende un valore e una maschera e restituisce il risultato dell'impostazione a 1 dei bit nel valore.
Un bit di value deve essere impostato a 1 dove anche il bit della mask è 1.
Tutti gli altri bit vanno lasciati invariati.
set_bits(0b0110, 0b0101) # => 0b0111
Alcuni bit vengono invertiti durante la cifratura. Bisognerà invertirli di nuovo per decifrare il messaggio.
Implementa la funzione flip_bits(), che prende un valore e la maschera.
La maschera indica quali bit del valore invertire.
Se il bit è 1 nella mask, il bit viene invertito nel value.
Tutti gli altri bit vanno lasciati invariati.
flip_bits(0b1100, 0b0101) # => 0b1001
Infine, ci sono anche alcuni bit che una volta decifrati valgono sempre 0.
Implementa la funzione clear_bits(), che prende un valore e una maschera.
I bit nel value devono essere impostati a 0 dove il bit della maschera è 1.
Tutti gli altri bit vanno lasciati invariati.
clear_bits(0b0110, 0b0101) # => 0b0010
Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.