LoginSignup
6
5

More than 5 years have passed since last update.

C#でXOR交換が使えなかった話

Last updated at Posted at 2015-07-19

ソートとかで2つの変数の値を入れ替えるのはよくあることですが
C#ではなぜかXORで交換するアルゴリズムが(VS2010で)うまく動作しませんでした。
(この際一時変数を使うのが速いとか言う話は置いておきます)
まず

Cでの実装


xorswap.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での実装


xorswap.cs
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.cs
a ^= b ^= a ^= b;

b.cs
    a ^= b;
    b ^= a;
    a ^= b;

にしてみます。

バラした結果

RawData a:45450721 b:19198080
Swapped a:19198080 b:45450721

C#ちゃんは一気になんかやると死ぬっぽいというのがわかります。

unsafeしてみる


ダメでした。

もしかしてC#は


式中に同じ変数2回出て来た時に値が変わってると未定義動作なのではないかと思って

C

ipp.c
#include <stdio.h>

int main(void)
{
    int i = 2;
    i += (i += 1);
    printf("%d\n", i);
    return 0;
}

結果は
6

CSharp

ipp.cs
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交換が使えないと。なるほどね。

解決方法


要するに一番左じゃなくしてあげればいいので

correct.cs
a = b ^ (a ^= b ^= a ^= b);

とすればよいです。

[追記]
一番左だけじゃなかったみたいですね。+=展開するときに変数を読みに行かないであらかじめ値が固定になるっぽいです。
あとa = b ^ (a ^ (b ^= a ^= b)); のほうが速いです。
でも一時変数を使って交換した方が2倍以上速いです。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5