SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

δ符号によるデータ領域の節約

α符号・γ符号・δ符号の違いと実装例

  • このエントリーをはてなブックマークに追加

メモリへのBit単位での書き出し/読み込みの実装例と、それを利用したδ符号(自然数を表現する不定長のビット列)のエンコード/デコードルーチンの実装例を紹介します。

  • このエントリーをはてなブックマークに追加

はじめに

 α(アルファ)符号・γ(ガンマ)符号・δ(デルタ)符号は、自然数を表現するための不定長ビット列です。筆者は、δ符号については、雑誌『Interface』の2002年10月号で知りました。以下、δ符号について詳細を解説します。

対象読者

 データ圧縮、特に独自方式での高密度データ格納に興味がある方を対象としています。この記事を読むためには、C++の基本的な文法および演算子についての知識が必要です。クラスやテンプレート、STLなどは使用していません。

必要な環境

 本稿の対象環境は、Microsoft Visual C++ 6.0以降のMicrosoft社製C++コンパイラです。一部にインラインアセンブラ、およびPentium命令を使用しています。他のC++環境への移植はさほど困難ではありません。しかし、C環境に移植する場合は、関数の多重定義に留意してください。

δ符号について

 通常、データ中に数値を格納する場合、その数値が取りうる範囲を根拠に、適当なデータ領域を確保します(0~255の範囲に収まる範囲なら、当然8ビット)。ここで、必要なデータ領域を節約するために、例えば比較的小さな値が多い場合には、δ符号を用いて数値を格納すると、平易な実装の割に良好な結果が得られます。

 δ符号の理解に必要な前提知識として、まずα符号とγ符号を取り上げます。

α符号

 1進数のようなイメージでとらえてください。nという値を表現するには、nビットを用います。n=1の場合は、1(1ビット)となり、n>1の場合は、n-1桁だけ0のビットを列挙し、最後のビットのみ1となります。

4は、0001(4ビット)となります。

γ符号

 2進数表記した数値を基準に考えます。nという値を表現するには、まずnが何ビットの値かを調べ、これをα符号で表記します。桁数を表現するこのα符号に続けて、nの2進数表記を最上位ビットを取り除いて連結します。n=1の場合は、1(1ビット)となります。一般に、nがxビットで表現できる場合、γ符号は2x-1ビットで表現されます。

4は、00100(5ビット)となります

δ符号

 基本的な考え方はγ符号と同様ですが、δ符号の場合は、表現する値のビット数をγ符号で記します。n=1の場合は、1(1ビット)となります。

4は、01100(5ビット)となります。

 以下に、数値とα・γ・δ符号の関係を一覧表で示します。nが2~3と8~31までの間は、γ符号の方がδ符号よりも1ビット短いものの、32以上の値ではδ符号の方が短くなることが分かります。

α・γ・δ符号一覧
nαγδ
1111
20101 0010 0
300101 1010 1
40001001 00011 00
500001001 01011 01
6000001001 10011 10
70000001001 11011 11
8000000010001 00000100 000
32・・・・000001 0000000110 00000
128・・・・00000001 00000000001000 0000000

δ符号を使ったCODECのインターフェイス関数

 ここでは、データを圧縮・伸張するソフトウェアCODECのインターフェイス関数をδ符号を使って実装します。各関数の詳細は以下の通りです。

 以下に、ヘッダおよびソースコードを示します。各関数の使用方法については、ヘッダのコメントを参照して下さい。

ヘッダファイル
//
// Delta.h
//
// 標準仕様のδ-符号CODECのインターフェイス関数
//
// (C)2004, 05, 06 BeautyPlanets

typedef unsigned char BYTE;
typedef unsigned long DWORD;

//
//    名称    void DeltaInit(void *pWork);
//    機能    δ-符号CODEC(1)の初期化を行う。
//    引数    void *    δ-符号の格納先メモリアドレス
//    返値    なし
//    解説    CODEC側では、メモリは確保しない。
//            また、負荷軽減のため、ポインタのエラーチェックを行わない。
//            初期化忘れやNULLの引き渡しをすると、確実に*飛ぶ*ので
//            呼び出し側で責任を持つこと。
void DeltaInit(void *pWork);

//    名称    BYTE *SetBoundery();
//    機能    境界調整を行う。
//    引数    なし
//    返値    δ-符号列が格納された最終アドレスの次のアドレス
//    解説    前述の通りδ-符号は不定長なので、末尾の1~7ビットが
//            メモリ上に存在する可能性がある。
//            外部からは、これらの管理情報に関与する必要はないが、
//            エンコードが完了した際に、この関数を使用すると、
//            上述のアドレスを返す。この返値によって、エンコードされた
//            δ-符号列のデータサイズを求めることができる。
BYTE *SetBoundery();

//    名称    BYTE *Delta(DWORD dwValue);
//    機能    数値からδ-符号への変換
//    引数    DWORD    δ-符号へ変換する数値
//    返値    8ビットのδ-符号を、次に格納するアドレス
//    解説    1つの数値をδ-符号へ変換し、指定されたバッファに出力する。
//            δ-符号は不定長なので、連続して呼び出した場合は、
//            直前のδ-符号の最終ビットの次のビットから格納される。
//            返値は、これから出力するアドレスなので、この値を
//            チェックすることによってバッファオーバーランを防止できる。
BYTE *Delta(DWORD dwValue);

//    名称    DWORD Delta();
//    機能    δ-符号からの変換
//    引数    なし
//    返値    DWORD    デコードした値
//    解説    δ-符号でエンコードされたデータ列のバッファから、
//            デコードした数値を1つ取得する。
//            メモリ範囲チェックができないので、
//            最初から取得する個数を考慮しておくこと。
DWORD Delta();
ソースコード
//
// Delta.cpp
//
// 標準仕様のδ-符号CODECのインターフェイス関数
//
// (C)2004, 05, 06 BeautyPlanets

typedef unsigned long DWORD;
typedef unsigned char BYTE;
typedef unsigned short WORD;

BYTE *g_pbyPtr;
BYTE g_byMask;
// codec初期化
void DeltaInit(void *pWork)
{
    g_byMask = 0x80;
    g_pbyPtr = (BYTE*)pWork;
}

// bit boundery調整
BYTE *SetBoundery()
{
    if(g_byMask != 0x80)
    {
        g_pbyPtr++;
        g_byMask = 0x80;
    }
    return g_pbyPtr;
}

// 1 bit read
inline DWORD Bit()
{
    DWORD dwRV = (*g_pbyPtr & g_byMask) ? 1 : 0;
    if(!(g_byMask >>= 1))
    {
        g_byMask = 0x80;
        g_pbyPtr++;
    }
    return dwRV;
}

// n bit read
inline DWORD Bits(int iCount)
{
    DWORD dwRV = 0;
    BYTE by = *g_pbyPtr;
    for(int i = 0; i<iCount; i++)
    {
        dwRV <<= 1;
        if(by & g_byMask)
        {
            dwRV++;
        }
        if(!(g_byMask >>= 1))
        {
            g_byMask = 0x80;
            by = *++g_pbyPtr;
        }
    }
    return dwRV;
}
// 1 bit write
// 引数        DWORD bit        :    出力する値(0, 1)
inline void Bit(DWORD bit)
{
// エンコード済データの格納領域をcallocで確保する場合は、
// 次の行をコメントアウトする。
#define ALLOC_WITH_MALLOC
    if(bit)
    {
        *g_pbyPtr |= g_byMask;
    }
#ifdef ALLOC_WITH_MALLOC
    else
    {
        *g_pbyPtr &= ~g_byMask;
    }
#endif
    if(!(g_byMask >>= 1))
    {
        g_byMask = 0x80;
        g_pbyPtr++;
    }
}

// n bit write
// 引数        DWORD dwCode    :    出力する値
//            int iCount        :    出力するビット数
inline void Bits(DWORD dwCode, int iCount)
{
    DWORD dwMask = 1L << iCount;
    BYTE by = *g_pbyPtr;
#ifdef ALLOC_WITH_MALLOC
    by &= ~(g_byMask | (g_byMask - 1));
#endif
    while(dwMask >>= 1)
    {
        if(dwMask & dwCode)
        {
            by |= g_byMask;
        }
        if(!(g_byMask >>= 1))
        {
            g_byMask = 0x80;
            *g_pbyPtr++ = by;
            by = 0;
        }
    }
    *g_pbyPtr = by;
}
__declspec(naked) int bsr(DWORD dwValue)
{
    __asm {
        mov        ecx, [esp+4]
        bsr        eax, ecx
        inc        eax
        ret
    }
}
// γ符号の出力
inline BYTE *Gamma(DWORD dwValue)
{
    if(dwValue)
    {
        if(1 == dwValue)
        {
            Bit(1);
        }
        else
        {
            /*
            for(DWORD dwLGamma = 2; dwLGamma < 32; dwLGamma++)
                if(dwValue < ( (DWORD)1 << dwLGamma ) )
                    break;
            */
            DWORD dwLGamma = bsr(dwValue);
            if(dwLGamma < 16)
            {
                Bits(dwValue, dwLGamma * 2 - 1);
            }
            else
            {
                Bits((DWORD)0, dwLGamma - 1);
                Bits(dwValue, dwLGamma );
            }
        }
    }
    return g_pbyPtr;
}

// δ符号の出力
BYTE *Delta(DWORD dwValue)
{
    if(dwValue)
    {
        if(1 == dwValue)
            Bit(1);
        else
        {
            /*
            for(DWORD dwLDelta = 2; dwLDelta < 32; dwLDelta++)
                if(dwValue < ((DWORD)1 << dwLDelta) )
                    break;
                    */
            DWORD dwLDelta = bsr(dwValue);
            Gamma(dwLDelta);
            Bits(dwValue, dwLDelta - 1);
        }
    }
    return g_pbyPtr;
}
// α符号読込

inline DWORD Alpha()
{
    DWORD dwCount = 1;
    while(!Bit())dwCount++;
    return dwCount;
}

// γ符号読込
inline DWORD Gamma()
{
    DWORD dwLength = Alpha() - 1;
    if(!dwLength)
    {
        return 1;
    }
    return Bits(dwLength) + ((DWORD)1 << dwLength);
}

// δ符号読込
DWORD Delta(){
    DWORD dwLength = Gamma() - 1;
    dwLength &= 0x1f;
    if(!dwLength)
    {
        return 1;
    }
    return Bits(dwLength) + ((DWORD)1 << dwLength);
}

bsr関数について

 ソースコード中、1点だけ注釈が必要と思われる箇所がありますので解説します。bsr関数は、unsigned longの値において、最も左にある1のビットの位置を得る関数です。LSBの場合は1を、MSBの場合は32を返します。bsr関数を呼び出す箇所の直前の数行のコメントは、bsr関数と同等の処理をC++のコードにて実装したものです。この値はPentium以降のCPUが持っているbsr命令の結果に1を加えて得られるものです。Pentium/MMXにおいては時間のかかる命令(11+2*n)ですが、Pentium Pro以降のCPUであれば1~2サイクルしかかからないので、インラインアセンブラを使用して命令を埋め込んでいます。

サンプルコードについて

 実装例として、カンマ区切りの十進数⇔δ符号バイナリの相互変換を行う、対話型のコマンドラインツールを用意しています。Visual Studio .NET 2003のsolution形式なので、Visual Studio 6.0で使用する場合は、Win32のコンソールプロジェクトを作って利用してください。同ツールの操作方法は、実行すると表示されます。

 なお、入力されたデータは、その件数を先頭に付与した形でエンコードされます。また、デコードしようとするデータは、そのδ符号のバイナリ列が含むデータ件数を、先頭にδ符号の形で格納されているものとします。

1,1,1,1(入力:十進カンマ区切り)
→ 4,1,1,1,1(先頭にデータ件数を付加)
→ 01100 1 1 1 1(δ/二進) 
→ 67 80(出力:δ/十六進)

まとめ

 ここでは、δ符号を用いたCODECのインターフェイス関数を紹介しました。本稿の続編として、δ符号の使用範囲を自然数から整数全体に拡張した応用事例と、Bit単位での入出力を完全に排除した超高速CODECルーチンを予定しています。

参考資料

  1. データ圧縮ハンドブック 改訂第2版』 Mark Nelson・Jean-Loup Gailly 著、山口英・荻原剛志 訳、ピアソンエデュケーション、2000年4月
  2. Interface 2002年10月号 『組み込みデータベースを使ったテキスト全文検索エンジンの実装』

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

BeautyPlanets(ビューティープラネッツ)

独学にてi8080,Z80,MC68000,6502,i8086,x86系のアセンブリ言語を習得する傍ら、フリーで開発請負を続けている。初期は、N88(86)BASIC、VBやOfficeアプリケーションの開発に従事。GeoBase, SISを利用したGISシステムの設計、開発を経て、Windows用電子辞典アプリケーションのデータフォーマット設計、及びCOMによる電子辞典データエンジンの設計、開発を行う。趣味と実益を兼ねて、AIやオプティマイズ...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/475 2006/08/15 00:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング