プログラミングや情報処理を学んでいると、排他的論理和(Exclusive OR)、通称XOR(エックスオア)という演算に出会います。
この演算は一見地味に見えますが、実は暗号化、データの整合性チェック、高速な値の交換など、様々な場面で活躍する超重要な演算なんです。「2つの値が異なる時に真(1)になる」というシンプルなルールから、驚くほど多彩な応用が生まれます。
例えば、変数の値を一時変数なしで交換する、データの破損を検出する、簡単な暗号を作る、といったテクニックは全てXORの性質を利用しています。
この記事では、排他的論理和の基本から、ビット演算としての使い方、実践的な応用例、そして知っておくと便利な性質まで、初心者の方にも分かりやすく解説していきます!
排他的論理和(XOR)とは?基本概念

排他的論理和(はいたてきろんりわ)、英語でExclusive OR、略してXORは、論理演算の一種です。
簡単に言うと
「2つの値が異なる時に真(1)、同じ時に偽(0)になる演算」
日常的な例:
「AさんまたはBさんのどちらか一方だけが来る」という状況を考えましょう。
- A君だけ来た → 真(条件を満たす)
- B君だけ来た → 真(条件を満たす)
- 2人とも来た → 偽(両方はダメ)
- 誰も来ない → 偽(誰もいない)
これが排他的論理和の考え方です。「どちらか一方だけ」がポイントなんですね。
記号と表記
数学的表記:
A ⊕ B(⊕マークが一般的)A XOR B
プログラミング言語:
- C/C++/Java/JavaScript等:
A ^ B - Python:
A ^ B - SQL:
A XOR B
読み方
- 日本語:エックスオア、エクスクルーシブオア、排他的論理和
- 英語:XOR(エックスオア)、Exclusive OR(エクスクルーシブ・オア)
真理値表:XORの動作を完全理解
真理値表で、XORの動作を正確に把握しましょう。
基本的な真理値表
2つの入力A、Bに対するXOR:
| A | B | A ⊕ B(結果) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
ルール:
- 異なる値同士 → 結果は1(真)
- 同じ値同士 → 結果は0(偽)
言葉での説明
0 ⊕ 0 = 0
- 両方とも0(偽)→ 結果は0
0 ⊕ 1 = 1
- 異なる値 → 結果は1
1 ⊕ 0 = 1
- 異なる値 → 結果は1
1 ⊕ 1 = 0
- 両方とも1(真)→ 結果は0
他の論理演算との比較
理解を深めるため、他の論理演算と比較してみましょう。
OR(論理和)との比較:
| A | B | A OR B | A XOR B |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 1 |
| 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 0 |
違い:
- OR:「どちらか一方、または両方」→ 1,1の時も1
- XOR:「どちらか一方だけ」→ 1,1の時は0
AND(論理積)との比較:
| A | B | A AND B | A XOR B |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 0 |
違い:
- AND:「両方とも真」の時だけ1
- XOR:「どちらか一方だけ真」の時に1
プログラミングでのXOR演算
実際にコードで使ってみましょう。
基本的な使い方
C/C++/Java/JavaScript:
int a = 5; // 0101 (2進数)
int b = 3; // 0011 (2進数)
int c = a ^ b; // 0110 = 6
printf("5 ^ 3 = %d\n", c); // 出力: 6
Python:
a = 5 # 0b0101
b = 3 # 0b0011
c = a ^ b # 0b0110 = 6
print(f"5 ^ 3 = {c}") # 出力: 6
論理値(bool)でのXOR:
# Pythonの例
true_val = True
false_val = False
print(true_val ^ false_val) # True (異なる)
print(true_val ^ true_val) # False (同じ)
print(false_val ^ false_val) # False (同じ)
ビット単位のXOR
数値のXOR演算は、各ビットごとにXORを適用します。
例:5 XOR 3
5 = 0101 (2進数)
3 = 0011 (2進数)
-------------
5^3 = 0110 (2進数) = 6 (10進数)
ビットごとに見ると:
- 右から1ビット目:1 ⊕ 1 = 0
- 右から2ビット目:0 ⊕ 1 = 1
- 右から3ビット目:1 ⊕ 0 = 1
- 右から4ビット目:0 ⊕ 0 = 0
結果:0110 = 6
より複雑な例
// C言語の例
unsigned char data = 0xAB; // 10101011
unsigned char mask = 0x0F; // 00001111
unsigned char result = data ^ mask;
// 10101011
// 00001111
// --------
// 10100100 = 0xA4
printf("0xAB ^ 0x0F = 0x%02X\n", result); // 0xA4
XORの重要な性質:数学的な特徴
XORには非常に興味深い性質があります。これらを理解すると、応用範囲が広がります。
1. 交換法則が成り立つ
順序を入れ替えても結果は同じ:
A ⊕ B = B ⊕ A
例:
5 ^ 3 = 6
3 ^ 5 = 6 // 同じ結果
2. 結合法則が成り立つ
複数のXORは、どの順番でまとめても同じ:
(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
例:
(5 ^ 3) ^ 7 = 5 ^ (3 ^ 7)
6 ^ 7 = 5 ^ 4
1 = 1 // 同じ結果
3. 自分自身とのXORは0
同じ値同士のXORは必ず0:
A ⊕ A = 0
例:
5 ^ 5 = 0
123 ^ 123 = 0
ビットレベルで見ると:
0101
⊕ 0101
------
0000 // すべてのビットが同じなので、全て0
この性質は超重要!
暗号化、データ検証、値の交換など、多くの応用の基礎になります。
4. 0とのXORは元の値
0とXORすると値が変わらない:
A ⊕ 0 = A
例:
5 ^ 0 = 5
ビットレベル:
0101
⊕ 0000
------
0101 // 0とXORしてもビットは変わらない
これも重要な性質!
初期化や、XORマスクの基本になります。
5. 同じ値で2回XORすると元に戻る
最も美しい性質:
(A ⊕ B) ⊕ B = A
証明:
(A ⊕ B) ⊕ B
= A ⊕ (B ⊕ B) // 結合法則
= A ⊕ 0 // B ⊕ B = 0
= A // A ⊕ 0 = A
具体例:
int original = 42;
int key = 17;
int encrypted = original ^ key; // 暗号化
int decrypted = encrypted ^ key; // 復号化
// decrypted == original (元に戻る!)
これが暗号化の基礎です!
6. 1とのXORはビット反転
1とXORすると、そのビットが反転:
0 ⊕ 1 = 1 // 0が1に反転
1 ⊕ 1 = 0 // 1が0に反転
例:
unsigned char data = 0x0F; // 00001111
unsigned char mask = 0xFF; // 11111111
data ^ mask = 0xF0; // 11110000 (全ビット反転)
実践的な応用例:XORの活用テクニック
XORの性質を活かした実用的なテクニックを紹介します。
応用1:変数の値を交換(スワップ)
通常の方法(一時変数使用):
int a = 5;
int b = 3;
int temp = a; // 一時変数が必要
a = b;
b = temp;
XORを使った方法(一時変数不要):
int a = 5;
int b = 3;
a = a ^ b; // a = 5 ^ 3 = 6
b = a ^ b; // b = 6 ^ 3 = 5 (元のaの値)
a = a ^ b; // a = 6 ^ 5 = 3 (元のbの値)
// 結果: a = 3, b = 5 (交換完了!)
動作原理:
最初: a = 5, b = 3
ステップ1: a = a ^ b = 5 ^ 3 = 6
a = 6, b = 3
ステップ2: b = a ^ b = 6 ^ 3 = 5 (元のa)
a = 6, b = 5
ステップ3: a = a ^ b = 6 ^ 5 = 3 (元のb)
a = 3, b = 5
注意点:
- 同じメモリ位置を指している場合は使えない
- 可読性が低いので、通常は一時変数を使う方が良い
- コンパイラの最適化で速度差はほぼない
応用2:簡易暗号化
XORを使った単純な暗号化:
#include <stdio.h>
#include <string.h>
void xor_encrypt_decrypt(char *data, char *key, size_t data_len) {
size_t key_len = strlen(key);
for (size_t i = 0; i < data_len; i++) {
data[i] = data[i] ^ key[i % key_len]; // 鍵を繰り返し使用
}
}
int main() {
char message[] = "Hello World";
char key[] = "SECRET";
printf("Original: %s\n", message);
// 暗号化
xor_encrypt_decrypt(message, key, strlen(message));
printf("Encrypted: %s\n", message); // 文字化けした文字列
// 復号化(同じ操作をもう一度)
xor_encrypt_decrypt(message, key, strlen(message));
printf("Decrypted: %s\n", message); // 元に戻る!
return 0;
}
出力例:
Original: Hello World
Encrypted: ??\?▒▒?????
Decrypted: Hello World
重要:
これは教育目的の例です。実際のセキュリティ用途では、AESなどの強力な暗号化アルゴリズムを使ってください。
応用3:重複要素の検出
配列内で1つだけ異なる要素を見つける:
問題:
配列に1から100までの数字があり、1つだけ欠けています。どの数字が欠けているでしょうか?
XORを使った解法:
int find_missing_number(int arr[], int n) {
int xor_all = 0;
int xor_arr = 0;
// 1からn+1までの全数値をXOR
for (int i = 1; i <= n + 1; i++) {
xor_all ^= i;
}
// 配列の全要素をXOR
for (int i = 0; i < n; i++) {
xor_arr ^= arr[i];
}
// 欠けている数字 = xor_all ^ xor_arr
return xor_all ^ xor_arr;
}
// 使用例
int arr[] = {1, 2, 3, 5, 6, 7, 8, 9, 10}; // 4が欠けている
int missing = find_missing_number(arr, 9);
printf("Missing number: %d\n", missing); // 4
原理:
- 全ての数字をXORすると、重複する数字は消える(A ⊕ A = 0)
- 残るのは欠けている数字だけ
応用4:ビットの反転
特定のビットだけを反転:
unsigned char data = 0b10101010; // 170
// 下位4ビットを反転
unsigned char mask = 0b00001111;
data = data ^ mask;
// 結果: 0b10100101 (下位4ビットが反転)
printf("Result: 0x%02X\n", data); // 0xA5
用途:
- フラグの切り替え
- ビットマスク操作
- グラフィックス処理
応用5:パリティチェック
データの整合性確認:
// 8ビットデータのパリティビット計算
int calculate_parity(unsigned char data) {
int parity = 0;
for (int i = 0; i < 8; i++) {
parity ^= (data >> i) & 1; // 各ビットをXOR
}
return parity; // 1ビットの数が奇数なら1、偶数なら0
}
// 使用例
unsigned char data = 0b10110101; // 1が5個(奇数)
int parity = calculate_parity(data);
printf("Parity: %d\n", parity); // 1
用途:
- データ転送のエラー検出
- RAIDシステムのパリティ計算
- 通信プロトコル
応用6:2つの数が同じか判定
0判定による効率的な比較:
int are_equal(int a, int b) {
return (a ^ b) == 0; // 同じなら0、異なるなら0以外
}
// 使用例
printf("%d\n", are_equal(5, 5)); // 1 (true)
printf("%d\n", are_equal(5, 3)); // 0 (false)
用途:
- 高速な比較処理
- ハッシュ関数の実装
- データ構造の最適化
応用7:配列内の単独要素を見つける
問題:
配列内のすべての要素は2回現れますが、1つだけ1回だけ現れます。それを見つけてください。
int find_single_number(int arr[], int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result ^= arr[i]; // すべてをXOR
}
return result; // ペアは相殺され、単独要素だけが残る
}
// 使用例
int arr[] = {2, 3, 5, 4, 5, 3, 4}; // 2だけが単独
int single = find_single_number(arr, 7);
printf("Single number: %d\n", single); // 2
原理:
- A ⊕ A = 0(ペアは消える)
- 0 ⊕ B = B(残った値)
XORの視覚的理解

ビット演算としてのXORを視覚的に理解しましょう。
ビットパターンで見るXOR
例1:単純な8ビット
データ1: 11001100 (204)
データ2: 10101010 (170)
----------
XOR結果: 01100110 (102)
説明:
位置1: 1⊕1=0
位置2: 1⊕0=1
位置3: 0⊕1=1
位置4: 0⊕0=0
...
マスクパターンの応用
特定ビットの操作:
unsigned char data = 0b11110000; // 240
// 下位4ビットを反転
data ^= 0b00001111;
// 結果: 0b11111111 (255)
// 上位4ビットを反転
data ^= 0b11110000;
// 結果: 0b00001111 (15)
// 全ビットを反転
data ^= 0b11111111;
// 結果: 0b11110000 (240) - 元に戻る
ビットフラグの切り替え
フラグの管理:
#define FLAG_READ 0b00000001 // ビット0
#define FLAG_WRITE 0b00000010 // ビット1
#define FLAG_EXECUTE 0b00000100 // ビット2
unsigned char permissions = 0b00000101; // 読み取り+実行
// 書き込み権限を切り替え(トグル)
permissions ^= FLAG_WRITE;
// 結果: 0b00000111 (読み取り+書き込み+実行)
// もう一度切り替え
permissions ^= FLAG_WRITE;
// 結果: 0b00000101 (読み取り+実行) - 元に戻る
プログラミング言語別の実装
各言語でのXORの使い方をまとめます。
C/C++
// 整数のXOR
int a = 5;
int b = 3;
int result = a ^ b; // 6
// ビット反転にも使える
unsigned char data = 0xAA;
data ^= 0xFF; // 全ビット反転 -> 0x55
// 複合代入演算子
int x = 10;
x ^= 5; // x = x ^ 5
Python
# 整数のXOR
a = 5
b = 3
result = a ^ b # 6
# ブール値のXOR
print(True ^ False) # True
print(True ^ True) # False
# 大きな数でも可能
big1 = 123456789
big2 = 987654321
print(big1 ^ big2) # 1040208568
# 複合代入
x = 10
x ^= 5 # x = x ^ 5
Java
// 整数のXOR
int a = 5;
int b = 3;
int result = a ^ b; // 6
// ブール値のXOR
boolean bool1 = true;
boolean bool2 = false;
boolean boolResult = bool1 ^ bool2; // true
// ビット演算
byte data = (byte)0xAA;
data ^= 0xFF; // 全ビット反転
JavaScript
// 整数のXOR
let a = 5;
let b = 3;
let result = a ^ b; // 6
// 注意: JavaScriptは32ビット整数として扱う
console.log(0xFFFFFFFF ^ 0x00000000); // -1
// 複合代入
let x = 10;
x ^= 5; // x = x ^ 5
PHP
// 整数のXOR
$a = 5;
$b = 3;
$result = $a ^ $b; // 6
// 文字列のXOR(文字単位)
$str1 = "Hello";
$str2 = "World";
$xor_str = $str1 ^ $str2; // バイナリ文字列
// 複合代入
$x = 10;
$x ^= 5;
SQL
-- MySQLの例
SELECT 5 XOR 3; -- 結果: 1(真)
SELECT 5 XOR 5; -- 結果: 0(偽)
-- 論理値での使用
SELECT (1 < 2) XOR (3 < 4); -- 結果: 0(両方真なので偽)
SELECT (1 > 2) XOR (3 < 4); -- 結果: 1(一方だけ真なので真)
よくある間違いと注意点
XORを使う際の落とし穴を押さえておきましょう。
間違い1:論理演算子と混同
誤り:
if (a ^ b) { // ビット演算
// これは a ^ b の結果が0以外の時
}
正しい(論理XORを意図する場合):
if ((a && !b) || (!a && b)) { // 論理XOR
// aとbの一方だけが真の時
}
// または
if (!a != !b) { // より簡潔
// 真偽値が異なる時
}
間違い2:順序の誤解
ビット単位の演算順序:
int result = a ^ b & c; // これは a ^ (b & c)
int correct = (a ^ b) & c; // 意図した順序
演算子の優先順位:
&(AND)^(XOR)|(OR)
推奨:
括弧を使って明示的に順序を指定する。
間違い3:符号付き整数での問題
負の数のXOR:
int a = -5;
int b = 3;
int result = a ^ b; // -8
// ビットパターン(32ビット環境)
// a = 11111111111111111111111111111011 (2の補数)
// b = 00000000000000000000000000000011
// = 11111111111111111111111111111000 = -8
注意点:
符号付き整数のビット演算では、2の補数表現を理解する必要があります。
間違い4:浮動小数点数でのXOR
誤り:
double a = 5.5;
double b = 3.3;
// double c = a ^ b; // コンパイルエラー!
理由:
XORはビット演算なので、浮動小数点数には直接使えません。
解決策(バイト列として扱う):
double a = 5.5;
double b = 3.3;
unsigned char *pa = (unsigned char *)&a;
unsigned char *pb = (unsigned char *)&b;
for (int i = 0; i < sizeof(double); i++) {
pa[i] ^= pb[i];
}
間違い5:暗号化での誤用
危険な例:
// これだけでは安全な暗号化にならない
void weak_encrypt(char *data, char key) {
while (*data) {
*data ^= key; // 同じ鍵を繰り返し使用
data++;
}
}
問題点:
- 鍵が短すぎる
- 統計的な攻撃に弱い
- 既知平文攻撃に脆弱
正しいアプローチ:
AES、ChaCha20などの検証済み暗号アルゴリズムを使用する。
XORの数学的性質:まとめ
重要な性質を一覧にします。
基本性質
1. 恒等元(Identity):
A ⊕ 0 = A
2. 自己逆元(Self-inverse):
A ⊕ A = 0
3. 交換法則(Commutative):
A ⊕ B = B ⊕ A
4. 結合法則(Associative):
(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
5. 逆元(Inverse):
A ⊕ B ⊕ B = A
(A ⊕ B) ⊕ A = B
派生的な性質
6. 二重適用で元に戻る:
(A ⊕ B) ⊕ B = A
7. 連鎖的なXOR:
A ⊕ B ⊕ C ⊕ ... ⊕ A ⊕ B ⊕ C ⊕ ... = 0
(各要素が偶数回現れる場合)
8. 全ビット反転:
A ⊕ 1...1 = ~A(NOT A)
群論的な解釈
XORは加法群を形成します:
- 閉包性:A ⊕ B の結果も同じ集合の要素
- 結合法則:(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
- 単位元:0
- 逆元:各要素自身(A ⊕ A = 0)
- 可換性:A ⊕ B = B ⊕ A
これがアーベル群(可換群)です。
高度な応用例
さらに進んだ使い方を紹介します。
応用1:Gray Code(グレイコード)
グレイコードの生成:
// n番目のグレイコード
unsigned int binary_to_gray(unsigned int n) {
return n ^ (n >> 1);
}
// グレイコードから通常の2進数へ
unsigned int gray_to_binary(unsigned int gray) {
unsigned int binary = 0;
while (gray) {
binary ^= gray;
gray >>= 1;
}
return binary;
}
// 使用例
for (int i = 0; i < 16; i++) {
printf("%2d: Binary=%04b, Gray=%04b\n",
i, i, binary_to_gray(i));
}
グレイコードの特徴:
- 隣接する値が1ビットだけ異なる
- エンコーダやセンサーで使用
- エラー耐性が高い
応用2:チェックサム計算
XORを使った簡単なチェックサム:
unsigned char calculate_xor_checksum(unsigned char *data, size_t len) {
unsigned char checksum = 0;
for (size_t i = 0; i < len; i++) {
checksum ^= data[i];
}
return checksum;
}
// 使用例
unsigned char data[] = {0x12, 0x34, 0x56, 0x78};
unsigned char checksum = calculate_xor_checksum(data, 4);
printf("Checksum: 0x%02X\n", checksum);
// 検証
data[4] = checksum; // チェックサムを追加
unsigned char verify = calculate_xor_checksum(data, 5);
printf("Verification: 0x%02X\n", verify); // 0なら正常
応用3:ビットマスクの最適化
複数のフラグを効率的に管理:
#define PERM_NONE 0x00
#define PERM_READ 0x01
#define PERM_WRITE 0x02
#define PERM_EXEC 0x04
#define PERM_ALL 0x07
// フラグの切り替え
void toggle_permission(unsigned char *perms, unsigned char flag) {
*perms ^= flag;
}
// 使用例
unsigned char user_perms = PERM_READ | PERM_EXEC; // 読み取り+実行
toggle_permission(&user_perms, PERM_WRITE); // 書き込みを追加
toggle_permission(&user_perms, PERM_WRITE); // 書き込みを削除(トグル)
応用4:リンクリストの反転(XOR Linked List)
メモリ効率的な双方向リスト:
struct XORNode {
int data;
struct XORNode *npx; // 前のノードと次のノードのXOR
};
// XOR演算子でポインタを計算
struct XORNode *XOR(struct XORNode *a, struct XORNode *b) {
return (struct XORNode *)((uintptr_t)a ^ (uintptr_t)b);
}
// 挿入、削除、走査などの操作が可能
// 通常の双方向リストより省メモリ
利点:
- ポインタを1つしか持たない
- メモリ使用量が少ない
欠点:
- 可読性が低い
- ポインタ演算が複雑
- デバッグが困難
まとめ:XORは奥深い演算
排他的論理和(XOR)は、シンプルながら非常に強力な演算です。
この記事のポイント:
- XORは「異なる時に真」という単純なルール
- A ⊕ A = 0という性質が最重要
- 同じ値で2回XORすると元に戻る
- 変数の交換が一時変数なしでできる
- 簡易暗号化の基礎として使える
- パリティチェックでエラー検出
- ビット操作の基本テクニック
- 数学的に美しい性質(アーベル群)
- 様々な分野で応用される
- プログラミング言語で
^演算子として使用
XORは、低レベルプログラミング、暗号化、データ圧縮、エラー検出、最適化など、様々な場面で活躍します。
最初は「異なる時に1」という単純なルールですが、その性質を理解すると、驚くほど多彩な応用が可能になるんです。
プログラミングスキルを向上させたい方は、ぜひXORの性質を使いこなせるようになってください。きっとコードがより効率的で、エレガントになりますよ!


コメント