[英]How does XOR variable swapping work?
有人可以向我解釋一下沒有臨時變量的兩個變量的異或交換是如何工作的嗎?
void xorSwap (int *x, int *y)
{
if (x != y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
我了解它的作用,但是有人可以引導我了解它的工作原理嗎?
您可以通過替換來查看它是如何工作的:
x1 = x0 xor y0
y2 = x1 xor y0
x2 = x1 xor y2
代替,
x1 = x0 xor y0
y2 = (x0 xor y0) xor y0
x2 = (x0 xor y0) xor ((x0 xor y0) xor y0)
因為 xor 是完全結合和可交換的:
y2 = x0 xor (y0 xor y0)
x2 = (x0 xor x0) xor (y0 xor y0) xor y0
由於x xor x == 0
對於任何 x,
y2 = x0 xor 0
x2 = 0 xor 0 xor y0
由於x xor 0 == x
對於任何 x,
y2 = x0
x2 = y0
並且交換完成。
其他人已經解釋過了,現在我想解釋為什么這是一個好主意,但現在不是。
早在我們擁有簡單的單周期或多周期 CPU 的那一天,使用這個技巧來避免代價高昂的內存取消引用或將寄存器溢出到堆棧中會更便宜。 但是,我們現在擁有具有大量流水線的 CPU。 P4 的流水線在其流水線中有 20 到 31 個(左右)級不等,其中讀取和寫入寄存器之間的任何依賴都可能導致整個事情停止。 xor 交換在 A 和 B 之間有一些非常重的依賴關系,這些依賴關系實際上根本不重要,但實際上會使管道停滯。 停滯的管道會導致代碼路徑變慢,如果此交換位於您的內部循環中,那么您的移動速度將非常緩慢。
在一般實踐中,當您使用臨時變量進行交換時,您的編譯器可以確定您真正想要做什么,並且可以將其編譯為單個 XCHG 指令。 使用異或交換使編譯器更難猜測您的意圖,因此更不可能正確優化它。 更不用說代碼維護等。
我喜歡用圖形而不是數字來思考它。
假設您從 x = 11 和 y = 5 開始二進制(我將使用假設的 4 位機器),這里是 x 和 y
x: |1|0|1|1| -> 8 + 2 + 1
y: |0|1|0|1| -> 4 + 1
現在對我來說,異或是一個反轉操作,做兩次就是一面鏡子:
x^y: |1|1|1|0|
(x^y)^y: |1|0|1|1| <- ooh! Check it out - x came back
(x^y)^x: |0|1|0|1| <- ooh! y came back too!
這是一個應該更容易理解的一個:
int x = 10, y = 7;
y = x + y; //x = 10, y = 17
x = y - x; //x = 7, y = 17
y = y - x; //x = 7, y = 10
現在,通過理解^可以被認為是+或-可以更容易地理解 XOR 技巧。 正如:
x + y - ((x + y) - x) == x
, 所以:
x ^ y ^ ((x ^ y) ^ x) == x
大多數人會使用臨時變量交換兩個變量 x 和 y,如下所示:
tmp = x
x = y
y = tmp
這是一個巧妙的編程技巧,可以在不需要臨時值的情況下交換兩個值:
x = x xor y
y = x xor y
x = x xor y
使用 XOR 交換兩個變量中的更多詳細信息
在第 1 行,我們結合 x 和 y(使用 XOR)來獲得這個“混合”,並將其存儲回 x。 XOR 是保存信息的好方法,因為您可以通過再次執行 XOR 來刪除它。
在第 2 行。我們將混合與 y 異或,這消除了所有 y 信息,只剩下 x。 我們將這個結果保存回 y,所以現在它們已經交換了。
在最后一行,x 仍然具有混合值。 我們再次與 y (現在與 x 的原始值)進行異或,以從混合中刪除所有 x 的痕跡。 這給我們留下了y,交換完成!
計算機實際上有一個隱含的“temp”變量,它在將中間結果寫回寄存器之前存儲它們。 例如,如果將 3 添加到寄存器(在機器語言偽代碼中):
ADD 3 A // add 3 to register A
ALU(算術邏輯單元)實際上是執行指令 3+A 的部分。 它接受輸入 (3,A) 並創建一個結果 (3 + A),然后 CPU 將其存儲回 A 的原始寄存器。 因此,在得到最終答案之前,我們將 ALU 用作臨時暫存空間。
我們認為 ALU 的隱式臨時數據是理所當然的,但它始終存在。 類似地,ALU 可以在 x = x xor y 的情況下返回 XOR 的中間結果,此時 CPU 將其存儲到 x 的原始寄存器中。
因為我們不習慣考慮可憐的、被忽視的 ALU,所以 XOR 交換似乎很神奇,因為它沒有顯式的臨時變量。 有些機器有一個 1 步交換 XCHG 指令來交換兩個寄存器。
它起作用的原因是因為 XOR 不會丟失信息。 如果你可以忽略溢出,你可以用普通的加法和減法做同樣的事情。 例如,如果變量對 A,B 最初包含值 1,2,您可以像這樣交換它們:
// A,B = 1,2
A = A+B // 3,2
B = A-B // 3,1
A = A-B // 2,1
順便說一句,在單個“指針”中編碼 2 路鏈表有一個老技巧。 假設您有一個位於地址 A、B 和 C 的內存塊列表。每個塊中的第一個字分別是:
// first word of each block is sum of addresses of prior and next block
0 + &B // first word of block A
&A + &C // first word of block B
&B + 0 // first word of block C
如果您可以訪問塊 A,它會為您提供 B 的地址。要到達 C,您需要 B 中的“指針”並減去 A,依此類推。 它也可以向后工作。 要沿着列表運行,您需要保留指向兩個連續塊的指針。 當然,您會使用 XOR 代替加法/減法,因此您不必擔心溢出。
如果您想玩得開心,可以將其擴展到“鏈接網絡”。
@VonC說得對,這是一個巧妙的數學技巧。 想象一下 4 位字,看看這是否有幫助。
word1 ^= word2;
word2 ^= word1;
word1 ^= word2;
word1 word2
0101 1111
after 1st xor
1010 1111
after 2nd xor
1010 0101
after 3rd xor
1111 0101
XOR方法基本上有3個步驟:
a' = a XOR b (1)
b' = a' 異或 b (2)
a” = a' XOR b' (3)
要了解為什么這樣做,首先要注意:
在步驟 (1) 之后,a 的二進制表示將僅在 a 和 b 具有相反位的位位置具有 1 位。 即 (ak=1, bk=0) 或 (ak=0, bk=1)。 現在,當我們在步驟(2)中進行替換時,我們得到:
b' = (a XOR b) XOR b
= a XOR (b XOR b) 因為 XOR 是關聯的
= 一個 XOR 0 因為上面的 [4]
= a 由於 XOR 的定義(參見上面的1 )
現在我們可以代入步驟(3):
a” = (a XOR b) XOR a
= (b XOR a) XOR a 因為 XOR 是可交換的
= b XOR (a XOR a) 因為 XOR 是關聯的
= b XOR 0 因為上面的 [4]
= b 由於 XOR 的定義(見上面的1 )
此處提供更多詳細信息: 必要和充分
作為旁注,幾年前我以交換整數的形式獨立地重新發明了這個輪子,方法是:
a = a + b
b = a - b ( = a + b - b once expanded)
a = a - b ( = a + b - a once expanded).
(上面以一種難以閱讀的方式提到了這一點),
完全相同的推理也適用於異或交換:a ^ b ^ b = a 和 a ^ b ^ a = a。 由於 xor 是可交換的,x ^ x = 0 和 x ^ 0 = x,這很容易看出,因為
= a ^ b ^ b
= a ^ 0
= a
和
= a ^ b ^ a
= a ^ a ^ b
= 0 ^ b
= b
希望這可以幫助。 這個解釋已經給出了......但不是很清楚imo。
我只是想添加一個數學解釋,以使答案更完整。 在群論中,異或是一個阿貝爾群,也稱為交換群。 這意味着它滿足五個要求:閉包、關聯性、標識元素、逆元素、交換性。
異或交換公式:
a = a XOR b
b = a XOR b
a = a XOR b
將公式展開,將 a、b 替換為前面的公式:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
交換性意味着“a XOR b”等於“b XOR a”:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
關聯性意味着“(a XOR b) XOR c”等於“a XOR (b XOR c)”:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
XOR 中的逆元素是自身,這意味着任何值與自身 XOR 都為零:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
= a XOR 0
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
= b XOR 0 XOR 0
XOR 中的單位元為零,這意味着任何與零的 XOR 值都保持不變:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
= a XOR 0
= a
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
= b XOR 0 XOR 0
= b XOR 0
= b
您可以在群論中獲得更多信息。
其他人已經發布了解釋,但我認為如果它帶有一個很好的例子會更好地理解。
如果我們考慮上面的真值表並取值A = 1100
和B = 0101
,我們可以這樣交換值:
A = 1100
B = 0101
A ^= B; => A = 1100 XOR 0101
(A = 1001)
B ^= A; => B = 0101 XOR 1001
(B = 1100)
A ^= B; => A = 1001 XOR 1100
(A = 0101)
A = 0101
B = 1100
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.