2015年9月2日水曜日

BCDを計算するメソッド

たまーに使われてる二進化十進表現(BCD)をC#で扱うためのメソッド。

二進化十進表現とは10進数の何桁かの数字を16進数で見やすくするためのエンコードの事。二進化というけど、2進数での表現ではあまり人間にはメリットがない。
そもそもBCDが便利ということはほとんど無く、実際に使うのは7セグLEDデコーダチップのインターフェースだったりとかで使われているだけ。他にはシリアルポートに特定のデータを流したくない場合にBCDでエンコードするとかもやるみたい。例えばスタートバイトをとストップバイトを0xF0以降に配置しておき、数値データはBCDで流した場合、16進表示のシリアルモニタで見るときに人間がわかりやすいし、BCDエンコードしてしまえば0x00-0x99の範囲しか扱わないのでストップバイトと競合することもない ということらしい。

本体
public class BCD
{
    static public UInt64 To(UInt64 HEX)
    {
        UInt64 val = 0;

        val |= (HEX % 10) << 4 * 0; HEX /= 10;
        val |= (HEX % 10) << 4 * 1; HEX /= 10;
        val |= (HEX % 10) << 4 * 2; HEX /= 10;
        val |= (HEX % 10) << 4 * 3; HEX /= 10;
        val |= (HEX % 10) << 4 * 4; HEX /= 10;
        val |= (HEX % 10) << 4 * 5; HEX /= 10;
        val |= (HEX % 10) << 4 * 6; HEX /= 10;
        val |= (HEX % 10) << 4 * 7; HEX /= 10;
        val |= (HEX % 10) << 4 * 8; HEX /= 10;
        val |= (HEX % 10) << 4 * 9; HEX /= 10;
        val |= (HEX % 10) << 4 * 10; HEX /= 10;
        val |= (HEX % 10) << 4 * 11; HEX /= 10;
        val |= (HEX % 10) << 4 * 12; HEX /= 10;
        val |= (HEX % 10) << 4 * 13; HEX /= 10;
        val |= (HEX % 10) << 4 * 14; HEX /= 10;
        val |= (HEX % 10) << 4 * 15;

        return (val);
    }

    static public UInt64 From(UInt64 BCD)
    {
        UInt64 val = 0;

        val += (BCD & 0xF) * 1; BCD >>= 4;
        val += (BCD & 0xF) * 10; BCD >>= 4;
        val += (BCD & 0xF) * 100; BCD >>= 4;
        val += (BCD & 0xF) * 1000; BCD >>= 4;
        val += (BCD & 0xF) * 10000; BCD >>= 4;
        val += (BCD & 0xF) * 100000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000000000;

        return (val);
    }
}

単体テスト
[TestClass()]
public class BCDTests
{
    [TestMethod()]
    public void ToTest()
    {
        Assert.AreEqual(0x000000000000ul, BCD.To(0));
        Assert.AreEqual(0x000000000001ul, BCD.To(1));
        Assert.AreEqual(0x000000000009ul, BCD.To(9));
        Assert.AreEqual(0x000000000010ul, BCD.To(10));
        Assert.AreEqual(0x123456789012ul, BCD.To(123456789012));
        Assert.AreEqual(0x999999999999ul, BCD.To(999999999999));
    }

    [TestMethod()]
    public void FromTest()
    {
        Assert.AreEqual(0ul, BCD.From(0x0000000000000000));
        Assert.AreEqual(1ul, BCD.From(0x0000000000000001));
        Assert.AreEqual(9ul, BCD.From(0x0000000000000009));
        Assert.AreEqual(10ul, BCD.From(0x0000000000000010));
        Assert.AreEqual(123456789012ul, BCD.From(0x123456789012));
        Assert.AreEqual(999999999999ul, BCD.From(0x999999999999));
    }
}

Visual Studioの単体テストの本来の使い方をやっと(偶然)発見したのだけど、結構便利。あとどれくらい時間がかかったのかがわかるのでちょっとプレッシャーがかかる。Fromのテストでは1msec未満だが、Toのテストでは4msec以上かかっている。パフォーマンスの差が激しいのが不思議。無理やり解釈すれば、Toはコンパイラ最適化によってビット演算のみで計算され、Fromはビット演算以外に加算と乗算が発生しているため とか?

0 件のコメント:

コメントを投稿