XOR, operatore booleano
La tabella di verità dello XOR o OR Esclusivo, è nella forma:
in generale, quindi, quando i due operandi sono uguali si ottiene un FALSE altrimenti un TRUE.
È possibile esprimere lo XOR in funzione degli altri operatori più conosciuti nella forma:
(a OR b) AND (NOT(a AND b))
Usi (quasi) pratici dello XOR
È possibile usare questo operatore in diverse situazioni pratiche anche se spesso è possibile farne a meno, preferendo forme più esplicite e meno efficienti a favore della legibilità.
L'operatore XOR viene può essere usato nelle tecniche di bit masking per attuare il flip di un bit
Ad esempio, supponiamo di avere l'espressione,in pseudo codice:
if ( condition ){
toggle = NOT toggle;
}
È possibile scrivere la stessa cosa nella forma:
toggle = toggle XOR condition;
Un'altro statement analogo, dati tre element a, b e x, è il seguente:
e può essere tradotto nella formula:
Infatti sostituendo all'elemento x, l'elemento a si ottiene x= a xor a xor b = b; avendo x=b si otterrebbe x = b xor b xor a = a.
Uno uso, in passato, più conosciuto è quello dello Swap tra interi, per cui è possibile attuare lo scambio di due valori numerici senza usare una variabile di appoggio; normalmente infatti avremmo:
opA = 5
opB = 3
tmp = opA
opA = opB
opB = tmp
utilizzando lo XOR, risulterà:
opA = opA xor opB
opB = opA xor opB
opA = opA xor opB
Possiamo vedere la differenza tra i due approcci con del codice d'esempio
class IntHolder {
public int value;
public IntHolder() {
this(0);
}
public IntHolder(int value) {
this.value = value;
}
public String toString() {
return String.valueOf(value);
}
}
// ...
public static void normalSwap(IntHolder a, IntHolder b) {
int valA = a.value;
int valB = b.value;
int swap = valA;
valA = valB;
valB = swap;
a.value = valA;
b.value = valB;
}
public static void xorSwap(IntHolder a, IntHolder b) {
int valA = a.value;
int valB = b.value;
valA = valA ^ valB;
valB = valA ^ valB;
valA = valA ^ valB;
a.value = valA;
b.value = valB;
}
XOR e strutture dati
Nell'ambito delle strutture dati è da citarsi la XOR Linked-List. Questa è una particolare variante della Double Linked-List, sviluppata per diminuire lo spazio occupato in memoria, sfruttando l'aritmetica dei puntatori e lo XOR: è possibile infatti memorizzare l'indirizzo dell'elemento precedente e dell'elemento successivo in un unico campo a differenza del normale approccio in cui sono necessari due puntatori. I difetti di questa struttura dati sono molteplici e di fatto la rendono utilizzabile in ambiti ridotti.
In Java è possibile simulare questa struttura ricreando i puntatori con la classe sun.misc.Unsafe come nel codice che segue:
import sun.misc.Unsafe;
import java.lang.reflect.*;
public class PointerUtils {
//
private static Unsafe unsafe;
//
private static int fieldOffset;
//
private static PointerUtils instance = new PointerUtils();
static {
try {
Class objectStreamClass = Class.forName("java.io.ObjectStreamClass$FieldReflector");
Field unsafeField = objectStreamClass.getDeclaredField("unsafe");
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);
fieldOffset = unsafe.fieldOffset(PointerUtils.class.getDeclaredField("obj"));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
};
//
private Object obj;
public synchronized static long toAddress(Object o) {
instance.obj = o;
return unsafe.getLong(instance, fieldOffset);
}
public synchronized static Object toObject(long address) {
unsafe.putLong(instance, fieldOffset, address);
return instance.obj;
}
}// END
Un Java XORLinkedList può essere la seguente:
XOR ed encryption
Lo XOR può essere utilizzato per un semplice meccanismo di encryption, supponendo di avere una chiave formata da elementi effettivamente casuali. Se data[] è un array di byte di lunghezza N e supponiamo di avere una chiave formata da un array key[] della stessa lunghezza e del tutto casuale nella composizione, possiamo utilizzare l'algoritmo che segue:
byte[] e = new byte[data.length];
for ( int idx=0; idx<data.length; idx++ ){
e[idx] = (byte)( a[idx] ^ k[idx] );
}
È ora possibile ottenere i dati originali applicando sempre lo XOR a quanto trasmesso; è evidente la necessità dello scambio della chiave grazie alla quale sarà possibile attuare il procedimento inverso a quello ora esposto. L'algoritmo è nella forma:
byte[] data = new byte[e.length];
for ( int idx=0; idx<e.length; idx++ ){
data[idx] = (byte)( e[idx] ^ k[idx] );
}
Un soluzione di questo tipo non è adottabile in contesti reali, anche se l'operatore XOR è pesantemente usato in molti algoritmi di crypting tra cui DES.
XOR e numeri casuali
Un utilizzo interessante e con un buon riscontro pratico di questo operatore booleano è nella generazione di numeri casuali. Si deve ad un ricercatore, George Marsaglia, dell'università dello stato della Florida la formulazione di generatori di numeri casuali basati su operazioni binarie e precisamento utilizzando XOR e SHIFT. La velocità di generazione e la sufficiente resistenza alle collisioni di valori rende questa tecnica particolarmente efficace e discapito di tecniche che richiedono molta più memoria e potenza di calcolo. Un esempio di questa tecnica può essere il seguente, partendo da un seed dato dall'attuale time si sistema:
public class XorShift {
private static int base = (int) System.currentTimeMillis();
private int x = 0;
public XorShift() {
x =((int) System.currentTimeMillis() + base);
}
public int next() {
x ^= x << 6;
x ^= x >>> 21;
x ^= (x << 7);
return x;
}
}
Come si vede la routine di generazione sfrutta operazioni di shift oltre allo XOR e quindi sfrutta operazioni proprie dei più comuni processori. Si pensi che sulla generazione di 100000000(cento milioni) di numeri non si verifica alcuna collisione, esplicitando una buona casualità.