2016/02/20

C#の構造体の引き渡し方によるパフォーマンスの違い(修正版)

C#の構造体の引き渡し方によるパフォーマンスの違いの調査用コードにミスがあるとyosolaさんからコメントで指摘をもらっていたのでその修正版のエントリ。

修正後のコードは次の通り。
修正したのはクラス名とPoint.AddRefStructのケースでの処理時間が加算ではなく代入になっていた点(104行目の"refStructTicks += sw.ElapsedTicks;")
結果は次の通り。
size: 1000
AddStruct: 104 (1.00)
AddRefStruct: 23 (0.22)
AddInterface: 148 (1.42)
size: 10000
AddStruct: 250 (1.00)
AddRefStruct: 220 (0.88)
AddInterface: 914 (3.65)
size: 100000
AddStruct: 2332 (1.00)
AddRefStruct: 2184 (0.94)
AddInterface: 7715 (3.31)
size: 1000000
AddStruct: 21937 (1.00)
AddRefStruct: 21208 (0.97)
AddInterface: 73993 (3.37)
読み取れること:
  • 繰り返し回数が少ない場合はインターフェースを経由しない参照渡しが速い
  • ただし繰り返し回数を増やすとインターフェースを経由しない参照渡しと値渡しとの差は数パーセントまで小さくなる
  • インターフェースを経由させると経由させない場合と比較して3倍以上遅くなる
繰り返し数回実行してみたけど傾向は変わらなかった。
この結果からメソッドに構造体を渡す場合は値渡しでいいと思う。ref修飾子を書いて参照渡しにしても数パーセントしか違いがないならわざわざそんなことをする意味はない。どうせほかの部分の処理時間で関係なくなる。
またインターフェース経由で渡した場合は遅いので実行速度のパフォーマンスが気になる個所ではそれは避けるようにしたほうがいい。

最後に、前回の計測と今回の計測では環境が違う。
前回はたぶんWindows 7, Visual Studio 2010、今回はWindows 10, Visual Studio 2015。それに加えてPCも違う。
前回の計測ではインターフェース経由でない値渡しの場合とインターフェース経由の場合での差が1.0倍から1.7倍くらいだったが、今回の計測ではそれが3倍以上になっているのは、Visual Studio(Visual C#)のコード生成処理が更新されていることが原因だと思う。

2016/02/19

「公的個人認証クライアントソフト(JPKI利用者ソフト) Ver3.0」で個人番号カードの証明書確認をすると「予期せぬエラーが発生しました。(CryptAcquireContext:0x80090006)」となる場合の対処法

来年も同じことでつまづきそうなので書いておく。
Windows Updateで配信される更新を適用している環境ではこれは起こらないと思う。
自分の場合は年一回の確定申告のために常用環境にソフトをインストールするのがイヤで、手元にあったプレーンなWindows 7 SP1の仮想環境で確定申告をしようとしたところ引っかかった。

まずは現象の再現。
  1. 「公的個人認証サービス」-「JPKI利用者ソフト」を起動する。
  2. 表示されたダイアログの「証明書をみる」の「自分の証明書」をクリックする。
  3. 表示されたダイアログの「署名用電子証明書」を選択して「OK」をクリックする。
  4. ここで「エラーが発生しました。予期せぬエラーが発生しました。(CryptAcquireContext:0x80090006)」というダイアログが表示される。

もちろんこれではe-Taxで申告書等を提出できない。
試行錯誤してしまったのはスルーして、「公的個人認証クライアントソフト Ver3.0」の動作環境を確認すると更新プログラムの適用が必要とのこと。
前提となるWindows更新プログラム
 利用者クライアントソフト(Windows版)は、SHA256に対応する為、 下記のWindows更新プログラムの適用が必須です。
・Windows8.1の場合
  Windowsの更新プログラム(KB2919355)
・Windows7/VISTAの場合
  Windowsの更新プログラム(KB3035131)
  Windowsの更新プログラム(KB3033929)
Windows 8.1の場合とWindows 7/Vistaの場合とx86/x64に注意して上記のWindows更新プログラムを適用することで、「公的個人認証クライアントソフト Ver3.0」が正常に動作してe-Taxで申請書等を提出できた。

2014/03/27

「C/C++プログラミングの「迷信」と「誤解」」を読んだ

図書館で見かけたC/C++プログラミングの「迷信」と「誤解」を読んで知ったこと。
自分みたいなC++に中途半端な人にはとても有用だった。Cの細かいところとか落とし穴的なところを押さえるにもいいと思う。
C/C++迷信集 | 株式会社きじねこで読めるのかもしれない。
C++についてのみ。Cについては知識/経験なさすぎなのでスルー。
実行環境としてはVisual C++ 2013でWin32 コンソール アプリケーションで空のプロジェクトを作ってTest.cppなファイルにコードを書いてビルド&実行。

一重引用符の中には一文字しか書けない!?(p54)

コード:
// 一重引用符の中には一文字しか書けない!?(p54)
#include <iostream>
#include <ios>

int main(int argc, char* argv[]) {
    const auto ns = 'a';  // 1文字の場合はchar型
    const auto nm = 'abcd';  // 複数文字の場合はint型で値は処理系依存
    std::cout << std::hex <<
        typeid(ns).name() << ", " << ns << std::endl <<
        typeid(nm).name() << ", " << nm << std::endl <<
        std::endl;

    const auto ws = L'a';
#pragma warning(push)
#pragma warning(disable: 4066)
    const auto wm = L'abcd';  // warning C4066: ワイド文字定数の文字列は無視されます。
#pragma warning(pop)
    std::wcout << std::hex <<
        typeid(ws).name() << ", " << ws << std::endl <<
        typeid(wm).name() << ", " << wm << std::endl <<
        std::endl;

    return 0;
}
実行結果:
char, a
int, 61626364

wchar_t, a
wchar_t, a
多文字リテラル(multicharacter literal)の存在とかそれがint型になるとか全く知らなかった。
これに関してはワイド文字は素直でいい。

absは負の値を返さない!?(p123)

コード:
// absは負の値を返さない!?(p123)
#include <cmath>
#include <limits>
#include <iostream>

int main(int argc, char* argv[]) {
    const int x = std::numeric_limits<int>::min();  // -2147483648 トラップ表現
    const int y = std::abs(x);
    std::cout <<
        x << std::endl <<
        y << std::endl <<
        std::endl;

    return 0;
}
実行結果:
-2147483648
-2147483648
説明されてみれば確かにそうなんだけど全く意識したことがなかった。これ踏んでるコードってけっこうありそうな気がするんだけどこれって初見なのでどうなんだろうと不安がある。
absなんか使った記憶ほとんどないけど使いまくりなfabsはどうだろうと確認してしまった。

ポインタを型変換してもアドレスは変わらない!?(p158)

コード:
// ポインタを型変換してもアドレスは変わらない!?(p158)
#include <iostream>

class Foo {
public:
    Foo() {}
    virtual ~Foo() {}
};

class Bar {
public:
    Bar() {}
    virtual ~Bar() {}
};

class Baz: public Foo, public Bar {
public:
    Baz(): Foo(), Bar() {}
    virtual ~Baz() {}
};

int main(int argc, char* argv[]) {
    Baz baz;
    Foo *pfoo = &baz;
    Bar *pbar = &baz;
    Baz *pbaz = &baz;
    std::cout <<
        "pfoo: " << pfoo << std::endl <<
        "pbar: " << pbar << std::endl <<
        "pbaz: " << pbaz << std::endl <<
        std::endl;
    return 0;
}
実行結果:
pfoo: 001BF79C
pbar: 001BF7A0
pbaz: 001BF79C
結構初歩的なことな気がするけど知らなかった。正直恥ずかしい気とまずいコードを書いたことがあるかもしれない。
pfooとpbazの値が一致しないケースを作れない点で自分の低スキルぶりを再認識。

配列や構造体をmemsetで一括ゼロクリア!?(p174)

コード:
// 配列や構造体をmemsetで一括ゼロクリア!?(p174)
#include <iostream>

struct Foo {
    char *c;
    int n;
    double d;
    wchar_t *wcs[10];
};

void write(const Foo& foo) {
    const char* x = reinterpret_cast<const char*>(&foo);
    for (size_t n = 0; n < sizeof Foo; ++n) {
        std::cout << int(*x++);
    }
    std::cout << std::endl;
}

int main(int argc, char* argv[]) {
    //Foo foo;
    //Foo foo = {0};
    Foo foo = {};
    write(foo);

    //Foo *pfoo = new Foo;
    Foo *pfoo = new Foo();
    write(*pfoo);
    delete pfoo;

    //Foo *fs = new Foo[10];
    Foo *fs = new Foo[10]();
    write(fs[0]);
    write(fs[1]);
    write(fs[9]);
    delete[] fs;

    return 0;
}
実行結果:
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000
memsetとかstd::fillとかZeroMemoryを使ったりしてサーセン、理解or覚えてなかった。

2013/03/20

2012/03/19

e-Tax平成23年分(2011年分)確定申告の先物取引のAutoItによる自動入力

e-Taxの平成23年分(2011年分)確定申告書等作成コーナーの先物取引(日経225先物など)の入力を支援するAutoItスクリプト。
基本的に去年のものと同じ。 違うのは最後の一件を入力した後に次に進まないようにしたことだけ(e-Taxの使い勝手はホントにアレ)。使い方は去年のエントリを参照。
スクリプトはetax-future-2011.au3

2011/01/23

e-Tax平成22年分(2010年分)確定申告の先物取引と配当のAutoItによる自動入力

e-Taxの平成22年分(2010年分)確定申告書等作成コーナーの先物取引(日経225先物など)と上場株式の配当の入力を支援するAutoItスクリプトを書いた。
動機はただ単純に手動で入力するのが面倒だったから。特に「先物取引に係る雑所得等」は最大で990件(330ページ * 3件)入力可能にされているのに、構造化したデータをアップロードするなどの方式が提供されずにWebブラウザで手動での入力が想定されているのはおかしい。面倒ってレベルじゃない。
去年もAutoItのスクリプトを書いたけど、去年のものはキー入力やマウス操作の命令ばかり使ったもので処理が遅くて不安定で良い出来ではなかった。今年はIE操作用のUDF "IE.au3"を使ってそれなりにまともなものができたと思うので晒してみる。

動作を確認した環境は次の通り。
  • Windows 7 Pro. x64
  • InternetExplorer 8
  • AutoIt v3 (v3.3.6.1)
  • 2011/01/22時点のe-Tax平成22年分確定申告書作成コーナー
InternetExplorerはAutoItやe-Taxのサイトが対応していればいいと思う。

内容の簡単な説明としては、普通にInternetExplorerを操作してデータを入力するページ(「先物取引に係る雑所得等」ページとか)を開いた状態にしてAutoItのスクリプトを実行するというのが主操作手順。そうするとAutoItがデータ(タブ区切りテキスト形式で事前に作成しておく)を入力してくれて、それが完了したらまた自分でInternetExplorerを操作してデータを保存するとか次の処理に進んだりできる。
AutoItで処理するのはデータの入力部分だけで、保存しておいた確定申告書データを読み込んで作成を再開するとかデータの入力後に確定申告書データを保存するとかの処理は、自然に手動で操作するだけで済むようになっている(AutoItで処理するよりも柔軟かつ分かりやすい)。またAutoItで処理する範囲が小さくて済むためAutoItのスクリプトも簡単になっている。

処理の所要時間は私の環境で1件の入力に約0.8秒だった。まあこんなものだと思う。
オンライン送信ではなく郵送すれば済む話だけど、折角可能なんだからオンライン送信に固執するつもりで書いた。

先物取引のデータの入力支援のスクリプトの使い方

入力データの作成

入力する先物取引の内訳をタブ区切りテキスト形式で作成する。
テンプレートはetax-future-sample.xlsx
入力項目は「【確定申告書作成コーナー】-先物取引に係る雑所得等」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subB62.jsp)の取引の内訳と全く同じ。

etax-future-sample.xlsxの列順でデータを作成してタブ区切りテキスト形式で保存して、項目の説明である先頭の2行を削除すればいい。入力データのサンプルはetax-future-sample.txt

入力データ作成時の注意点:
  • 正しいデータを作成すること
  • 作成されたデータはそのまま入力ページへの入力に使われる
  • スクリプトではデータのバリデーションをしていない。おかしなデータが含まれていると入力ページのバリデーション処理によりメッセージを通知するダイアログが表示されるため、データの入力処理がそこで止まってしまう。

スクリプトの実行

InternetExplorerを操作して(保存しておいた確定申告書データから作成を再開するなどして)「【確定申告書作成コーナー】-先物取引に係る雑所得等」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subB62.jsp)を開いておく。

その状態でAutoItのスクリプトetax-future-2010.au3を実行する。

スクリプトの10行目付近の$filename変数が入力データ(タブ区切りテキスト形式)のファイル名。
とりあえず次のようにしてある。この場合etax-future-2010.txtという名前のファイルが読み込まれる。
;$filename = "etax-future-sample.txt"  ; 取引の内訳データのファイル名(タブ区切りテキストファイル)
$filename = "etax-future-2010.txt"  ; 取引の内訳データのファイル名(タブ区切りテキストファイル)

1件入力するごとに進捗を標準出力に書き出すのでそれを確認できる環境でスクリプトを実行するのが良い。
コマンドラインでもいいし、AutoIt Script Editor(SciTE4AutoIt3)(AutoIt Full Installationに含まれる)でスクリプトを開いてメニューの「Tools」-「Go」で実行すると、エディタのOutputペインが開いてそれが標準出力になるので、スクリプトを変更して実行する場合にはこれが便利。

配当のデータの入力支援のスクリプトの使い方

構成や使い方は先物取引のデータ入力と同じなので省略。
スクリプトやテンプレートとサンプルはksksts / junk / source – Bitbucketのetax-dividend-*という名前のファイル。
対象としているのは「【確定申告書作成コーナー】-配当所得、配当控除(上場株式等)」ページ(https://www.keisan.nta.go.jp/h22/syotoku/ta_subX5b.jsp)への入力。
これは「配当所得、配当控除(取引区分の選択)」ページで「1 上場株式等」-「(2) 源泉徴収口座への受入れを行っていない配当等」を選択したケースのこと。

2010/09/29

C#の構造体の引き渡し方によるパフォーマンスの違い

2016/02/20追記--
yosolaさんの指摘の通り調査用のコードにミスがありました。
このエントリの結果は間違いです。調査用のコードを修正したエントリをご覧ください。
--

メソッドの引数として構造体を渡す場合にちょっと気になったので調査用のコードを書いた。
比較したのは次の3つのケース。
  • 構造体を値渡し
  • 構造体を参照渡し(ref修飾子)
  • 構造体が実装するインターフェースで渡す
コードは次の通り。struct Pointがinterface ITupleを実装している。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace StructInterface
{
    interface ITuple
    {
        int X { get; set; }
        int Y { get; set; }
        int Z { get; set; }
    }

    struct Point : ITuple
    {
        public int X { get; set; }
        public int Y { get; set; }
        public int Z { get; set; }
        public Point(int x, int y, int z) : this() { X = x; Y = y; Z = z; }
        public void AddStruct(Point point)
        {
            X += point.X;
            Y += point.Y;
            Z += point.Z;
        }
        public void AddRefStruct(ref Point point)
        {
            X += point.X;
            Y += point.Y;
            Z += point.Z;
        }
        public void AddInterface(ITuple tuple)
        {
            X += tuple.X;
            Y += tuple.Y;
            Z += tuple.Z;
        }
        public override bool Equals(object obj)
        {
            if (!(obj is Point))
                return false;
            var p = (Point)obj;
            var result = X == p.X && Y == p.Y && Z == p.Z;
            return result;
        }
        public override int GetHashCode()
        {
            var result = X ^ Y ^ Z;
            return result;
        }
        public override string ToString()
        {
            var str = string.Format("{0}, {1}, {2}, ", X, Y, Z);
            return str;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            for (var size = 1000; size <= 1000000; size *= 10)
                Run(size);
        }

        static void Run(int size)
        {
            Console.WriteLine("size: {0}", size);

            var count = 10;
            var sw = new Stopwatch();
            var structTicks = 0L;
            var refStructTicks = 0L;
            var interfaceTicks = 0L;

            for (var c = 0; c <= count; c++)
            {
                var random = new Random(count);
                var points = Enumerable.Range(0, size)
                                       .Select(_ => new Point(random.Next(), random.Next(), random.Next()))
                                       .ToList();

                // Point.AddStruct
                sw.Reset();
                sw.Start();
                var sp = new Point();
                foreach (var point in points)
                    sp.AddStruct(point);
                sw.Stop();
                structTicks += sw.ElapsedTicks;

                // Point.AddRefStruct
                sw.Reset();
                sw.Start();
                var rsp = new Point();
                foreach (var point in points)
                {
                    var p = new Point(point.X, point.Y, point.Z);
                    rsp.AddRefStruct(ref p);
                }
                sw.Stop();
                refStructTicks = sw.ElapsedTicks;

                // Point.AddInterface
                sw.Reset();
                sw.Start();
                var ip = new Point();
                foreach (var point in points)
                    ip.AddInterface(point);
                sw.Stop();
                interfaceTicks += sw.ElapsedTicks;


                // correctness
                if (!sp.Equals(rsp))
                    Console.WriteLine("!sp.Equals(rsp)");
                if (!sp.Equals(ip))
                    Console.WriteLine("!sp.Equals(ip)");
            }

            Console.WriteLine("AddStruct: {0} ({1:F})", structTicks / count, structTicks / (double)structTicks);
            Console.WriteLine("AddRefStruct: {0} ({1:F})", refStructTicks / count, refStructTicks / (double)structTicks);
            Console.WriteLine("AddInterface: {0} ({1:F})", interfaceTicks / count, interfaceTicks / (double)structTicks);
        }
    }
}
ksksts / junk / source — bitbucket.org

結果は次の通り。
size: 1000
AddStruct: 832 (1.00)
AddRefStruct: 31 (0.04)
AddInterface: 789 (0.95)
size: 10000
AddStruct: 1023 (1.00)
AddRefStruct: 119 (0.12)
AddInterface: 1803 (1.76)
size: 100000
AddStruct: 9916 (1.00)
AddRefStruct: 1100 (0.11)
AddInterface: 16277 (1.64)
size: 1000000
AddStruct: 102814 (1.00)
AddRefStruct: 11699 (0.11)
AddInterface: 159081 (1.55)
感想としては、はじめ参照渡しが早いことを意外に感じた(値型と参照型のインスタンス作成の速度差から値型のインスタンスのコピーはそれほどの負荷にならないと思っていた)けど、コピーを発生させるよりは参照渡しにした方が軽いということは納得できた。
インターフェース経由が遅いのは疑問。型変換的な処理が入ってしまうのかな。

メソッドの引数に構造体を渡す場合に(変更しないのに)ref修飾子を使いまくるか、素直にコピーを発生させるか迷う。

2010/03/21

Google Chromeの検索エンジンの設定

Firefoxから移行してある程度経ったのにGoogle Chromeのデフォルトの検索エンジンの設定が気に入らない。悪いというわけじゃなくてFirefoxを使っていた時と同じような検索結果が欲しいから、Google Chromeの検索エンジンの設定を変更してみた。
まずはデフォルトのGoogleでの検索エンジン設定を削除する。
追加するのはGoogleの日本語版と英語版の2つ。

日本語版(google.co.jp):
  • 名前とキーワード: google.co.jp
  • URL: http://www.google.co.jp/search?q=%s&hl=jp&lr=lang_ja&safe=off&ie=utf-8&oe=utf-8&aq=t
非日本語限定版(google.com):
  • 名前とキーワード: google.com
  • URL: http://www.google.com/search?q=%s&hl=en&safe=off
エロ判定なコンテンツが含まれているせいでのフィルタリングを避けたいからsafe=offなんだけど状況によっては画像検索に勇気が必要だったりするからこれは別にするのがいいのかもしれない。


2010/01/23

MSTestExpressionAssertion - VisualStudioの単体テスト用のGallio(MbUnit)のAssertEx.Thatの改造版

VisualStudio 2008の単体テスト機能のカスタマイズ(VS2008 custom assertion example)の続き。
これも放置していたけど一段落つけるまでやった。

前のエントリで書いた通りGallioAssertEx.Thatで構成要素の式ををキャプチャしてその結果を出力できるのがおもしろくて同じようなことをVisualStudioの単体テストで使いたいと思ったのが動機。
いじり方としては、Gallioのソースから欲しいファイルを部分を抜き出してそのまま使ったり変更したりしてMSTestExpressionAssertion.dllという名前のアセンブリを作った。
これに含まれるAssertExクラスのIsTrueメソッドとIsFalseメソッドが、Microsoft.VisualStudio.TestTools.UnitTesting.AssertクラスのIsTrueとIsFalseの式ツリーを受け取るバージョン相当。
MSTestExpressionAssertion.dllを参照に追加して、
AssertEx.IsTrue(() => true)
と書いたテストは成功して、
AssertEx.IsTrue(() => false)
と書いたテストは失敗する。


テストコード:
var p = new Point(1.0, 2.0);
var tole = 1.0e-12;
AssertEx.IsFalse(() => Math.Abs(p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5.0)) <= tole);

エラーメッセージ:
AssertEx.IsFalse failed.
Math.Abs((p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5))) <= tole: True
 Math.Abs((p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5))): 0
  p.CalculateDistance(Point.ORIGIN) - Math.Sqrt(5): 0
   p.CalculateDistance(Point.ORIGIN): 2.23606797749979
    p: (1, 2)
    Point.ORIGIN: (0, 0)
   Math.Sqrt(5): 2.23606797749979
 tole: 1E-12
まあこんなものかと。 式の中でのリテラルが浮動小数点数ではなくなっていたり、結果の出力はToString()メソッドにしているせいでそのあたりにも少し不満があるけど。


テストコード:
var m = Matrix.MakeRotation(Math.PI * 0.25);
var tole = 1.0e-12;
AssertEx.IsFalse(() => m.Transform(new Point(1.0, 0.0)).CalculateDistance(new Point(Math.Sqrt(2.0) * 0.5, Math.Sqrt(2.0) * 0.5)) <= tole);

エラーメッセージ:
AssertEx.IsFalse failed.
m.Transform(new Point(1, 0)).CalculateDistance(new Point((Math.Sqrt(2) * 0.5), (Math.Sqrt(2) * 0.5))) <= tole: True
 m.Transform(new Point(1, 0)).CalculateDistance(new Point((Math.Sqrt(2) * 0.5), (Math.Sqrt(2) * 0.5))): 1.11022302462516E-16
  m.Transform(new Point(1, 0)): (0.70710678118654757, 0.70710678118654746)
   m: (0.70710678118654757, 0.70710678118654757, 
       0.70710678118654746, 0)
   new Point(1, 0): (1, 0)
  new Point((Math.Sqrt(2) * 0.5), (Math.Sqrt(2) * 0.5)): (0.70710678118654757, 0.70710678118654757)
   Math.Sqrt(2) * 0.5: 0.707106781186548
    Math.Sqrt(2): 1.4142135623731
   Math.Sqrt(2) * 0.5: 0.707106781186548
    Math.Sqrt(2): 1.4142135623731
 tole: 1E-12
この程度で既にうるさく感じる。 "Math.Sqrt(2) * 0.5"(とその下の"Math.Sqrt(2)")が2回出力されるのもどうかと思うけど、オブジェクトが変更されるケースも当然あるから同一の式&結果ならまとめるとかいうのもあまりよくない気がする。 細かいけどデフォルトのフォント設定ではmの結果のインデントがずれる(結果が複数行の場合のインデント処理をせっかく入れたのに)。


例外をスローするテストコード:
AssertEx.IsTrue(() => string.Format("{0}", null) == "");

エラーメッセージ:
テスト メソッド MSTestExpressionAssertionTest.AssertExSample.Test02 は例外をスローしました:  System.ArgumentNullException: 値を Null にすることはできません。
パラメータ名: args。
スタックトレース:
System.String.Format(IFormatProvider provider, String format, Object[] args)
lambda_method(ExecutionScope )
Gallio.Common.Linq.ExpressionInstrumentor.Intercept[T](Expression expr, Func`1 continuation)
Intercept[T](Expression expr, Func`1 continuation)
Gallio.Common.Linq.ExpressionInstrumentor.InterceptNonVoid[T](Expression expr, Func`1 continuation)
lambda_method(ExecutionScope )
Gallio.Common.Linq.ExpressionInstrumentor.Intercept[T](Expression expr, Func`1 continuation)
Intercept[T](Expression expr, Func`1 continuation)
Gallio.Common.Linq.ExpressionInstrumentor.InterceptNonVoid[T](Expression expr, Func`1 continuation)
lambda_method(ExecutionScope )
Eval(Expression`1 condition)
MSTestExpressionAssertion.AssertEx.IsTrue(Expression`1 condition, String message, Object[] parameters)
MSTestExpressionAssertion.AssertEx.IsTrue(Expression`1 condition)
MSTestExpressionAssertionTest.AssertExSample.Test02() C:\home\development\projects\bitbucket\junk\cs\MSTestExpressionAssertion\MSTestExpressionAssertionTest\AssertExSample.cs 内: 行 42
スローされた例外クラスはSystem.ArgumentNullExceptionクラス。
例外クラスの型とスタックトレースの両方をうまく維持する方法が分らなかった(というか多分ない)から、スローされる例外クラスを優先した。そのためスタックトレースにMSTestExpressionAssertionのコードも含まれてしまっている。


Gallioからの主な変更内容:
  • 式ツリーに含まれる定数式以外のすべての式とその結果を出力するようにした
  • 式の中で発生した例外はcatchせずにそのまま挙げるようにした
  • 結果の出力はToString()メソッドを使うようにした
その他感想とか:
  • すべての式を出力するための式のフォーマッタが面倒だった。GallioのExpressionFormattingRule.csをベースにしたExpressionFormatter.cs(と補助的にExpressionExtensions.cs)がその部分。staticメンバやthisのメンバの判定処理についてはもっとうまい方法があるかも。
  • デバッガで楽にテストの式にステップインできるようにMSTestExpressionAssertionプロジェクトのReleaseビルドでは/debug:none指定。デバッグシンボルがないアセンブリを読み込むと警告を表示するのがデフォルトなのがうざい。対象のコードにDebuggerStepThroughAttributeDebuggerHiddenAttributeDebuggerNonUserCodeAttributeを指定しようかと思ったけどGallioのコードを変更するのを避けるため止めた。
  • でも式が連続して実行される感じではなくなっている(結果を取得するため分解して書き換えているから)のでステップ実行が微妙。まあ使えなくはない。
  • それにしてもExpressionInstrumentor.csExpressionFormattingRule.csはうまい。こんなのよく書くなと同時によく書けるなと思う。
ソースはBitBucketのMSTestExpressionAssertion
Gallioのソースからの変更部分はmodified-files.diff

2010/01/16

MiniTwitter-ma - MiniTwitterの複数アカウント対応版+α

MiniTwitterの複数アカウント対応の続き。
放置していたけど手をつけたからには一段落するまでやっておく。

(まめ)しばやんさんのMiniTwitterのバージョン1.05.2(たぶんchangeset 62377)をベースにして複数アカウント対応(同時に扱えるのではなくアカウントを切り替えられるという感じ)とたくさんのポストを表示できるタイムライン表示機能を追加してみた。

複数アカウント対応については次の通り。
「What's happening?」の右側に現在のアカウントを表示するボタンを追加。このボタンをクリックするとアカウントを選択するメニューがポップアップされる。
複数のアカウントの登録できるように設定ダイアログを変更。




タイムライン表示の変更については次の通り。
スクリーンショットは「標準」と「タイト」。どちらもテーマは「ネットブック最適化」を選択。
「タイト」の表示は「標準」をベースにしてフォントサイズと行の高さとマージンを小さめに変更したもの。



ソースはBitbucketのMiniTwitter-ma。オリジナルのMiniTwitterからの変更箇所はmodified-files.diff
ライセンスはオリジナルと同じくApache License, Version 2.0
使う人がいるとはあまり思わないけど一応ビルド済みバイナリをWindows LiveのSkyDriveに置いた

やってみた感想:
  • WPFおもしろい(しばやんさん&MiniTwitterに感謝)。
  • Apache Licenseの4-2が微妙。変更したファイルの中に変更箇所を明示する必要はなく、変更したファイルがあればそれを明示しろと解釈した(※それでは条項を満たさないと思われる場合は教えてください)。
  • 相場関連を別アカウントでやっていたから複数アカウント対応が欲しかったけどアカウントを分けない方が良いように思えてきた。分けたときは「人生オワタ\(^o^)/」とか素人丸出しなポストと結び付けたくないなと思っていたけど、前者になるような取引をしそうにないし、後者はそもそも素人レベルなんだからそれを隠そうとするのもどうよって思うようになってきた。フォローする人たちが全然違うけどアカウントを分けるのを止めてまとめようかと思う。
  • オリジナルのMiniTwitterのタイムライン表示はスペースを贅沢に使いすぎ、件数少なすぎ、どんだけの解像度で使っているんだよw、というくらいに思っていたけど自然に作るとそんな感じになると思った。
  • その上で余白少なめ、行間小さめなタイムライン表示を追加したらEee PC 900-Xでも使えなくないな、と。ただしUIデザインセンスのない効率性重視(<-機能的に必要なものを詰め込む傾向)だなぁ。
  • いじってみた上でやっぱりWPF面白い。学習が必要。
@yuki1090さんMiniTwitter勝手に改造版のテキスト選択できるようにFlowDocumentを使うのはいいなぁ…と思って今見てみたらなんか作成中プロジェクトがある。期待してみる。

2009/12/19

MiniTwitterの複数アカウント対応

TwitterクライアントをEchofonからMiniTwitterに切り替えようかと思ってMiniTwitterを複数アカウント対応にしてみていた。対応の仕方としてはEchofonのように複数のアカウント情報を保存&容易に切り替え可能という感じ、複数アカウントを同時に操作とかは要らない(それだったら複数のクライアントを使えばいい)。
きっかけは普段使いのWebブラウザをFirefoxからGoogle Chromeに変更しようかと考えたこと。面倒くさくなってきてもう止めるかもしれないから今のうちに書いておく。
ベースはChange Set 61673。これをやっている間にバージョン1.05系列がリリースされてた。

変更内容は次の通り。
メインウィンドウのupdateボタンの右に最後にログインしたユーザーを表示。


Echofonと同じようにこのログインユーザー表示をクリックすると切り替えるアカウントを選択するメニューがポップアップ。ここでアカウントを選択するとログインユーザーが切り替わる。


アカウント情報を登録したり編集したりするフォームは至ってノーマルのはず。


ログ保存機能は使う気がないから後回し。
もうちょっとやりたいことをやったら変更部分の扱いをどうするか考える。
MiniTwitter勝手に改造版のコードも見てみる。

この程度のことをしただけなのにXAMLとWPFについてけっこう調べて勉強になった。XAML/WPFおもしろそう。これからはWindowsフォームなんか使わないでWPFを使おう。

2009/11/22

VisualStudio 2008の単体テスト機能のカスタマイズ(VS2008 custom assertion example)

結構前にGallioAssertEx.Thatの式ツリーの要素をキャプチャして出力できる点がすごいと思ってちょっといろいろいじっていた。Gallioは素晴らしいけどちょっと重い感じがするし、機能とか装備されているものが豊富なのはいいんだけど、もうちょっと簡単にいじれる大きすぎないフレームワークを使いたいというのが感想。
最近それをある程度の形にしようと思って、アレンジしてVisual Studio 2008の単体テスト機能で使えるようにしようとしてVisual Studio 2008の単体テスト機能のカスタマイズを調べてみた。

基本的には自前のassertionでテスト失敗の場合はAssertFailedExceptionをスローすればいいんだけど、そうするとスタックトレースに診断処理や例外をスローする処理のメソッドが含まれてしまう。
これはDebuggerHiddenAttributeDebuggerNonUserCodeAttributeを使っても避けられない。デバッガとスタックトレースは別。


スタックトレースに含まれないように回避するための属性はないみたいなので、スタックトレースを操作する方向で行こうとすると例外のスタックトレース(Exception.StackTraceプロパティとか)はStackTrace型ではなくて文字列だったりする上にSetterがない。
どうすればいいの?例外のスタックトレースは変更できないの?と思ったけどよく見るとException.StackTraceプロパティはvirtual宣言されているから派生させてオーバーライドすれば、このプロパティで返される値は変更できる。

それでこんなコードでスタックトレースに含められないようにできそう。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MSTestCustomAssertion
{
    public class ExpressionAssert
    {
        public static void IsTrue(Expression<Func<bool>> expr)
        {
            var result = expr.Compile().Invoke();
            if (!result)
                throw new FailedException("ExpressionAssert.IsTrue failed");
        }

        public class FailedException : AssertFailedException
        {
            public FailedException() : base() { }
            public FailedException(string msg) : base(msg) { }
            public override string StackTrace
            {
                get
                {
                    var lines = base.StackTrace.Split(new string[] { Environment.NewLine, }, StringSplitOptions.None);
                    var index = Array.FindIndex(lines, x => !x.Contains("ExpressionAssert."));
                    var st = string.Join(Environment.NewLine, lines, index, lines.Length - index);
                    return st;
                }
            }
        }
    }
}
ksksts / junk / source — bitbucket.org

Visual Studioの単体テスト機能でテストを失敗させた場合のスタックトレースにExpressionAssertクラスの処理が含まれない。


Visual Studioに統合された単体テストの機能以外で試してもいないし、例外クラスがAssertFailedExceptionではないという点が気になるけど、とりあえず手軽にするならこの方法でもいいかもしれない。

手間は増えるだろうけどInternalPreserveStackTraceを使ったやり方のほうがちゃんとしていそう。

2009/11/15

配列やList<T>をIList<T>型のインデクサでアクセスするとパフォーマンスが悪い(don't use IList<T>'s indexer)

C#でのソートアルゴリズムのまとめ(sort algorithms in C#)とかIList<T>の走査処理の比較(インデクサ/GetEnumerator/foreach)で「配列の添字アクセスが遅い」とか書いたけどこれは間違い。「配列をIList<T>型にキャストしてインデクサでアクセスすると遅い」というのが正しい。
実際には配列をそのまま添字でアクセスする処理はList<T>のインデクサアクセスよりも速い。
それにList<T>もIList<T>型にキャストしてインデクサでアクセスすのは(List<T>のインデクサよりも)遅い。
インターフェースってこんなに影響するものなのか。配列やList<T>の特殊な最適化処理があったりしてそれが適用されなくなったりした影響なのかな。

測定した結果は次の通り。
int[]に対する配列の添字アクセスが最も速くてIList<T>のインデクサを通すとなぜか10倍弱の時間がかかるようになる。
List<T>のインデクサアクセスは配列の添字アクセスより遅いらしい。これもIList<T>のインデクサを通すと配列ほどじゃないけど遅くなる。
array: 662 (1.00)
(IList<T>)array: 6259 (9.45)
list: 1325 (2.00)
(IList<T>)list: 1592 (2.40)
IListArrayWrapper: 1166 (1.76)
ksksts / junk / source — bitbucket.org

IListArrayWrapperは値型の配列をラップしてポインタを使ったunsafeなインデクサを実装したクラス。
ジェネリックに実装しようとしたけどジェネリック引数の型のポインタを取得する方法が分からなかった。"where T : struct"の指定があればOKにしてもいいんじゃないかと思うけど、とりあえずできなかったからint型用のコードにした。

ダメだったコード:
public class IListArrayWrapper<T> where T : struct
    {
        public T[] Source { get; set; }
        unsafe public T this[int index]
        {
            get
            {
                fixed (T* p = &Source[0])
                    return *(p + index);
            }
            set
            {
                fixed (T* p = &Source[0])
                    *(p + index) = value;
            }
        }
        public IListArrayWrapper(IList<T> source)
        {
            Source = (T[])source;
        }
    }
ksksts / junk / source — bitbucket.org

まとめとしては、配列とIList<T>を実装したランダムアクセス可能なコレクションの両方を処理する&パフォーマンスを気にするコードを書く場合は分けたほうがいいみたい。

C#でのソートアルゴリズムのまとめ(sort algorithms in C#)

stereopsis : graphics : radix tricksSTLportのソースをパクったりしたC#でのソートのまとめ。

それぞれのアルゴリズムとか関連エントリとか。

配列に対するソートとList<T>に対するソートに対するソートとの差やEnumerable.OrderBy メソッド (System.Linq)とかも含めた測定結果は次の通り。
size: 1000
Array.Sort: 1186 (1.00)
List.Sort: 1050 (0.89)
Array.OrderBy: 6592 (5.56)
List.OrderBy: 3305 (2.78)
CombSort Array: 31971 (26.94)
CombSort List: 6909 (5.82)
NumberRadixSorter.Sort Array: 4943 (4.17)
NumberRadixSorter.Sort List: 2291 (1.93)
InPlaceMergeSorter.Sort Array: 40326 (33.98)
InPlaceMergeSorter.Sort List: 12642 (10.65)
MergeSorter.Sort Array: 24463 (20.61)
MergeSorter.Sort List: 9604 (8.09)
IntroSorter.Sort Array: 22909 (19.30)
IntroSorter.Sort List: 4158 (3.50)

size: 10000
Array.Sort: 12771 (1.00)
List.Sort: 12497 (0.98)
Array.OrderBy: 41599 (3.26)
List.OrderBy: 41126 (3.22)
CombSort Array: 465802 (36.47)
CombSort List: 101879 (7.98)
NumberRadixSorter.Sort Array: 27803 (2.18)
NumberRadixSorter.Sort List: 22234 (1.74)
InPlaceMergeSorter.Sort Array: 553976 (43.38)
InPlaceMergeSorter.Sort List: 202408 (15.85)
MergeSorter.Sort Array: 224039 (17.54)
MergeSorter.Sort List: 128939 (10.10)
IntroSorter.Sort Array: 158357 (12.40)
IntroSorter.Sort List: 52881 (4.14)

size: 100000
Array.Sort: 146888 (1.00)
List.Sort: 145938 (0.99)
Array.OrderBy: 520984 (3.55)
List.OrderBy: 518649 (3.53)
CombSort Array: 6044506 (41.15)
CombSort List: 1311650 (8.93)
NumberRadixSorter.Sort Array: 279857 (1.91)
NumberRadixSorter.Sort List: 218608 (1.49)
InPlaceMergeSorter.Sort Array: 8215845 (55.93)
InPlaceMergeSorter.Sort List: 2860999 (19.48)
MergeSorter.Sort Array: 2931658 (19.96)
MergeSorter.Sort List: 1869126 (12.72)
IntroSorter.Sort Array: 1945971 (13.25)
IntroSorter.Sort List: 658093 (4.48)

size: 1000000
Array.Sort: 1666666 (1.00)
List.Sort: 1668472 (1.00)
Array.OrderBy: 6961424 (4.18)
List.OrderBy: 6901036 (4.14)
CombSort Array: 76084478 (45.65)
CombSort List: 16674472 (10.00)
NumberRadixSorter.Sort Array: 2864637 (1.72)
NumberRadixSorter.Sort List: 2245710 (1.35)
InPlaceMergeSorter.Sort Array: 110817348 (66.49)
InPlaceMergeSorter.Sort List: 38880094 (23.33)
MergeSorter.Sort Array: 35271234 (21.16)
MergeSorter.Sort List: 20756358 (12.45)
IntroSorter.Sort Array: 24093398 (14.46)
IntroSorter.Sort List: 8047601 (4.83)
ksksts / junk / source — bitbucket.org

まとめ:

2009/11/14

イントロソートも書いてみた(introsort in C#)

バッファを使わないマージソートバッファを使うマージソートのついでにイントロソートSTLportからパクってみた。

STLportの_algo.c, _heap.h, _heap.cの__introsort_loop, __unguarded_partition, __final_insertion_sort, __unguarded_linear_insert, __partial_sort, __make_heap, __adjust_heap, __push_heap_aux, __pop_heap_aux, sort_heapあたりをC#で書いただけ。
かなり手抜きだと思う。マージソートとの速度差をみたいだけだから。

結果は次の通り。
Array.Sortの十数倍の時間がかかっている。バッファを使用するマージソートよりは多少速い。
ソート対象を配列からList<T>に変更するだけで数倍良くなるはずだから後でまとめて試してみる。
今になって基数ソート(radix sort)がかなり速いということを実感。
size: 1000
IntroSorter.Sort: 20622 (18.78)
Array.Sort: 1098 (1.00)
size: 10000
IntroSorter.Sort: 139073 (10.98)
Array.Sort: 12667 (1.00)
size: 100000
IntroSorter.Sort: 1771360 (12.22)
Array.Sort: 144995 (1.00)
size: 1000000
IntroSorter.Sort: 21275858 (12.85)
Array.Sort: 1656005 (1.00)
ksksts / junk / source — bitbucket.org

2009/11/12

バッファを使用するマージソートも書いてみた(merge sort with buffer in C#)

in-place merge sortEQUATEC Profilerでプロファイルしてみたけど特におかしいと感じるところは見つからず、結局マージ回数が多すぎじゃないかと思う(あとお手玉Rotatenってやっぱり速いのかなと)。
in-placeじゃないアルゴリズムとの比較をするためにSTLport_algo.cのstable_sortから__stable_sort_adaptive, __merge_sort_with_buffer, __chunk_insertion_sort, __merge_sort_loop, mergeあたりを辿って、ソート対象の要素個数分のバッファを使用するマージソートをC#で書いてみた。
結果は下記の通り。in-place merge sortよりは良い。でもまだArray.Sortの十数倍の時間がかかっている。
いろいろ小賢しいことをやっているからもっとシンプルな素直なコードを書いてみたほうがいいかもしれない。
size: 1000
InPlaceMergeSorter.Sort: 37314 (33.24)
MergeSorter.Sort: 21869 (19.48)
Array.Sort: 1122 (1.00)
size: 10000
InPlaceMergeSorter.Sort: 502465 (39.24)
MergeSorter.Sort: 198861 (15.53)
Array.Sort: 12805 (1.00)
size: 100000
InPlaceMergeSorter.Sort: 7425488 (47.00)
MergeSorter.Sort: 2556782 (16.18)
Array.Sort: 157988 (1.00)
size: 1000000
InPlaceMergeSorter.Sort: 98928451 (58.09)
MergeSorter.Sort: 31298754 (18.38)
Array.Sort: 1703143 (1.00)
ksksts / junk / source — bitbucket.org

2009/11/10

C#でin place merge sortを書いてみたらかなり遅かった

C#で簡単に使いまわせる安定なソートを用意しておこうと思ってIn place stable Sort (merge sort)を参考に書いてみたらかなり遅かった。
In place stable Sort (merge sort)のソースの不自然さが気になったからSTLport_algo.cとか_algobase.cとかも参考にした。
Array.Sortの数十倍時間がかかるとか、これじゃあまり使う気にならない。
何かミスっているのかもしれないし、in placeでなければもうちょっとましかもしれない。明日もう少し調べてみる。
size: 1000
merge sort: 37580 (30.34)
Array.Sort: 1238 (1.00)
size: 10000
merge sort: 506318 (41.50)
Array.Sort: 12199 (1.00)
size: 100000
merge sort: 7170923 (50.21)
Array.Sort: 142813 (1.00)
size: 1000000
merge sort: 97304833 (60.94)
Array.Sort: 1596662 (1.00)
ksksts / junk / source — bitbucket.org

2009/11/07

C#で符号付整数/浮動小数点数対応の基数ソート(radix sort)

Radium Software Development経由でRadix Sort Revisitedを見て、さらに2006-07-23 - togeの日記経由でstereopsis : graphics : radix tricksも読んだから書いてみた。
ksksts's blog: C#で実装したradix sortとArray.Sortのquick sortとの比較で実行速度で.NET FrameworkのArray.Sort メソッド (System)とかを上回るのは難しいと感じた時点で目的を実装例を示すだけに変更。Decimal型String型に対応しようと思っていたけど、面倒くさくなったから符号なし整数型、符号付整数型、浮動小数点数型で止めとく。

stereopsis : graphics : radix tricksのコードを参考にして書いた。
おおまかな処理の流れは次の通り。
  1. ヒストグラムをまとめて作成(histgramming)
  2. ヒストグラムの値を加算(sum the histgrams)
  3. 要素の並べ替え(read/write histgram, copy)

インターフェースはこんな感じ。
RadixSorter.cs
public static void Sort<T>(IList<T> list, Func<T, UInt32> converter, SortOrder sortOrder)

ソートに使用するキーを指定しながらソートできるのがおもしろい。
struct Pair<TFirst, TSecond>
{
    public TFirst First { get; set; }
    public TSecond Second { get; set; }
}
このPair型に対して、
RadixSorter.Sort(result, x => x.Second, RadixSorter.SortOrder.Ascending);
RadixSorter.Sort(result, x => x.First, RadixSorter.SortOrder.Ascending);
とすると、次のCompareTo(Firstで比較&Firstが等しい場合はSecondで比較)でソートした場合と同じ結果が得られる。
public int CompareTo(Pair<TFirst, TSecond> x)
{
    var result = First.CompareTo(x.First);
    if (result == 0)
        result = Second.CompareTo(x.Second);
    return result;
}

あとは書いてみて思ったこととか。

ヒストグラムをずらして作っておくと加算する処理が簡単になること、降順にソートする場合はヒストグラムの加算の処理で対応できること。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
// histgramming
            var histgramOffset = ascending ? 1 : -1;
            foreach (var x in list)
            {
                var y = x;
                for (var p = 0; p < tables.Length; p++)
                {
                    tables[p][(y + histgramOffset) & 0xFF]++;
                    y = y >> 8;
                }
            }

            // sum the histgrams
            if (ascending)
            {
                foreach (var table in tables)
                {
                    table[0] = 0;
                    for (var n = 1; n < table.Length; n++)
                        table[n] += table[n - 1];
                }
            }
            else
            {
                foreach (var table in tables)
                {
                    table[0xFF] = 0;
                    for (var n = 0xFE; n >= 0x00; n--)
                        table[n] += table[n + 1];
                }
            }

符号付整数型でも同様。ただし符号ビットを含む部分だけ工夫する。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
// sum the histgrams
            if (ascending)
            {
                for (var p = 0; p < tables.Length - 1; p++)
                {
                    var table = tables[p];
                    table[0] = 0;
                    for (var n = 1; n < table.Length; n++)
                        table[n] += table[n - 1];
                }
                {
                    var table = tables[tables.Length - 1];
                    table[0x80] = 0;
                    for (var n = 0x81; n <= 0xFF; n++)
                        table[n] += table[n - 1];
                    table[0x00] += table[0xFF];
                    for (var n = 0x01; n < 0x80; n++)
                        table[n] += table[n - 1];
                }
            }
            else
            {
                for (var p = 0; p < tables.Length - 1; p++)
                {
                    var table = tables[p];
                    table[0xFF] = 0;
                    for (var n = 0xFE; n >= 0x00; n--)
                        table[n] += table[n + 1];
                }
                {
                    var table = tables[tables.Length - 1];
                    table[0x7F] = 0;
                    for (var n = 0x7E; n >= 0x00; n--)
                        table[n] += table[n + 1];
                    table[0xFF] += table[0x00];
                    for (var n = 0xFE; n >= 0x80; n--)
                        table[n] += table[n + 1];
                }
            }

浮動小数点数の場合は、要素が数値でそれを変更できる場合はヒストグラムを作るときにビットをflipして、ソート後に元に戻すとかできる(stereopsis : graphics : radix tricksのコードでやっている)。
NumberRadixSorter.csのSort(IList list, SortOrder sortOrder)
// histgramming
            var histgramOffset = ascending ? 1 : -1;
            for (var n = 0; n < list.Count; n++)
            {
                var y = BitConverter.DoubleToInt64Bits(list[n]);
                y ^= -(Int64)((UInt64)y >> 63) | unchecked((Int64)0x8000000000000000);  // flip
                list[n] = BitConverter.Int64BitsToDouble(y);
                for (var p = 0; p < tables.Length; p++)
                {
                    tables[p][(y + histgramOffset) & 0xFF]++;
                    y = y >> 8;
                }
            }
// read/write histgram, copy
            IList<Double> array = new Double[list.Count];
            Action swap = () => { var temp = list; list = array; array = temp; };
            for (var p = 0; p < tables.Length - 1; p++)
            {
                var table = tables[p];
                foreach (var x in list)
                {
                    var y = BitConverter.DoubleToInt64Bits(x);
                    y = (y >> p * 8) & 0xFF;
                    array[table[y]] = x;
                    table[y]++;
                }
                swap();
            }
            {
                var p = tables.Length - 1;
                var table = tables[p];
                foreach (var x in list)
                {
                    var w = BitConverter.DoubleToInt64Bits(x);
                    var y = (w >> p * 8) & 0xFF;
                    w ^= (Int64)((UInt64)w >> 63) - 1 | unchecked((Int64)0x8000000000000000);  // flip back
                    array[table[y]] = BitConverter.Int64BitsToDouble(w);
                    table[y]++;
                }
                swap();
            }

ksksts / junk / source — bitbucket.org

2009/11/06

Decimal型のフォーマット

Decimal.GetBits メソッド (System)で解説されている。
Decimal 数値のバイナリ表現は、1 ビットの符号、96 ビットの整数、および整数値を除算し、小数部を指定するために使用するスケール ファクタから構成されます。スケール ファクタは黙示的に数値 10 になり、0 から 28 の範囲の指数で累乗されます。
戻り値は、4 要素の 32 ビット符号付き整数配列です。
この配列の 1 ~ 3 番目の要素は、96 ビット整数の下位 32 ビット、中位 32 ビット、および上位 32 ビットをそれぞれ格納しています。
4 番目の要素は、スケール ファクタと符号を格納しています。この要素は、次に示す部分から構成されています。
ビット 0 ~ 15 の下位ワードは未使用で、0 である必要があります。
ビット 16 ~ 23 には、0 から 28 までの範囲の指数部を格納する必要があります。この指数部は、整数を除算する 10 の累乗を示します。
ビット 24 ~ 30 は未使用で、0 である必要があります。
ビット 31 は符号を格納している必要があります。0 は正、1 は負を表します。
ビット形式では負の 0 と正の 0 が区別されます。これらの値はすべての演算で等値として扱われます。
Decimal.GetBits メソッドの戻り値はint[]でいまいち分かりにくいから確認用のコードを書いた(下手なコードだと思うけど良い方法を思いつけなかった)。
指数部が10の累乗なんだからSingle/Doubleとは違う分かりやすい値に変えた。
unsafe private static void PrintDecimal()
        {
            Func<Decimal, byte[]> toBytes = x =>
            {
                var p = (byte*)&x;
                var bytes = new byte[sizeof(Decimal)];
                for (var n = 0; n < bytes.Length; n++)
                    bytes[n] = *p++;
                return bytes;
            };
            WriteLine("Decimal:");
            WriteLine("0.0M", toBytes(0.0M));
            WriteLine("-0.0M", toBytes(-0.0M));
            WriteLine("0.12M", toBytes(0.12M));
            WriteLine("1.2M ", toBytes(1.2M));
            WriteLine("12M  ", toBytes(12M));
            WriteLine("120M ", toBytes(120M));
            WriteLine("1200M", toBytes(1200M));
            WriteLine("-0.12M", toBytes(-0.12M));
            WriteLine("-1.2M ", toBytes(-1.2M));
            WriteLine("-12M  ", toBytes(-12M));
            WriteLine("-120M ", toBytes(-120M));
            WriteLine("-1200M", toBytes(-1200M));
            WriteLine("Decimal.MinValue", toBytes(Decimal.MinValue));
            WriteLine("Decimal.MaxValue", toBytes(Decimal.MaxValue));
            WriteLine("");
        }


        private static void WriteLine(string value)
        {
            Console.WriteLine(value);
        }

        private static void WriteLine(string label, byte[] bytes)
        {
            Console.Write("{0}: 0x", label);
            var bs = (byte[])bytes.Clone();
            Array.Reverse(bs);
            foreach (var x in bs)
                Console.Write("{0:X2}", x);
            Console.WriteLine();
        }
ksksts / junk / source — bitbucket.org

結果は次の通り。0.0Mと-0.0Mの指数部は1になるみたい。
Decimal:
0.0M: 0x00000000000000000000000000010000
-0.0M: 0x00000000000000000000000080010000
0.12M: 0x000000000000000C0000000000020000
1.2M : 0x000000000000000C0000000000010000
12M  : 0x000000000000000C0000000000000000
120M : 0x00000000000000780000000000000000
1200M: 0x00000000000004B00000000000000000
-0.12M: 0x000000000000000C0000000080020000
-1.2M : 0x000000000000000C0000000080010000
-12M  : 0x000000000000000C0000000080000000
-120M : 0x00000000000000780000000080000000
-1200M: 0x00000000000004B00000000080000000
Decimal.MinValue: 0xFFFFFFFFFFFFFFFFFFFFFFFF80000000
Decimal.MaxValue: 0xFFFFFFFFFFFFFFFFFFFFFFFF00000000