ソートとかで2つの変数の値を入れ替えるのはよくあることですが
C#ではなぜかXORで交換するアルゴリズムが(VS2010で)うまく動作しませんでした。
(この際一時変数を使うのが速いとか言う話は置いておきます)
まず
Cでの実装
#include <stdio.h>
int main(void)
{
int a, b;
a = 45450721;
b = 19198080;
printf("RawData a:%d b:%d\n", a, b);
a ^= b ^= a ^= b;
printf("Swapped a:%d b:%d\n", a, b);
return 0;
}
こんな感じ。
Cの結果
RawData a:45450721 b:19198080
Swapped a:19198080 b:45450721
よきに。
CSharpでの実装
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
public static void Main(string[] args)
{
int a = 45450721;
int b = 19198080;
Console.WriteLine("RawData a:{0} b:{1}", a, b);
a ^= b ^= a ^= b;
Console.WriteLine("Swapped a:{0} b:{1}", a, b);
Console.ReadKey();
}
}
}
CSharpの結果
RawData a:45450721 b:19198080
Swapped a:0 b:45450721
良くわからない動作ですね。
XOR交換において0になるというのはaとbが同じアドレスの時だと思いますが、
違う変数なので当然アドレスは違うでしょうし、bは正常に交換が終了しています。
バラしてみる
a ^= b ^= a ^= b;
を
a ^= b;
b ^= a;
a ^= b;
にしてみます。
バラした結果
RawData a:45450721 b:19198080
Swapped a:19198080 b:45450721
C#ちゃんは一気になんかやると死ぬっぽいというのがわかります。
unsafeしてみる
ダメでした。
もしかしてC#は
式中に同じ変数2回出て来た時に値が変わってると未定義動作なのではないかと思って
C
#include <stdio.h>
int main(void)
{
int i = 2;
i += (i += 1);
printf("%d\n", i);
return 0;
}
結果は
6
CSharp
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
public static void Main(string[] args)
{
var i = 2;
i += (i += 1);
Console.WriteLine("{0}", i);
Console.ReadKey();
}
}
}
5
ということは、要するにiの値が式の途中で変更されても一番左のiの参照すべき値が更新されずに2のままで、
+1されたiの値である3と足し合わされて5になったと推測できます。
つまり元々の a ^= b ^= a ^= b;では、一番左の参照すべきaの値(a = a ^ (~)のシンタックスシュガーなので2番目のaのこと)
は式全体を評価する前からC#では決まっているということになります。
なのでXOR交換が使えないと。なるほどね。
解決方法
要するに一番左じゃなくしてあげればいいので
a = b ^ (a ^= b ^= a ^= b);
とすればよいです。
[追記]
一番左だけじゃなかったみたいですね。+=展開するときに変数を読みに行かないであらかじめ値が固定になるっぽいです。
あとa = b ^ (a ^ (b ^= a ^= b)); のほうが速いです。
でも一時変数を使って交換した方が2倍以上速いです。