16進バイト列を10進数文字列に変換する

8 バイトの数値を 10 進文字列に変換するなら glibc の sprintf に書式文字列 "%llu" を与えて得られることはわかったんだけれど、任意長のバイト列を 10 進数文字列に変換するって、できるかな?というチャレンジが心をよぎったのでやってみた。

#include <vector>
#include <iostream>
#include <iterator>

typedef std::vector<bool> bits;
typedef std::vector<int> nums;

// バイト列をビット列に変換する
// バイト列はリトルエンディアン、つまり大きい桁から順に格納されているものとみなす。
// 後にでてくる convert 関数の条件分岐を減らすために、冒頭に 0 を意味する false を入れている。
static bits from(const unsigned char *const begin, const unsigned char *const end) {
  bits bs;
  bs.push_back(false);
  for (const unsigned char *p = begin; p != end; ++p) {
    bs.push_back(*p & 0x80);
    bs.push_back(*p & 0x40);
    bs.push_back(*p & 0x20);
    bs.push_back(*p & 0x10);
    bs.push_back(*p & 0x08);
    bs.push_back(*p & 0x04);
    bs.push_back(*p & 0x02);
    bs.push_back(*p & 0x01);
  }
  return bs;
}

// 10 進数字配列であらわされる値を二倍した数字配列を返す
// 配列の添字 0 が一桁目、添字 1 が二桁目、…というように 10 進数をあらわす。
static nums dbl(nums ns) {
  for(size_t i = 0; i < ns.size(); ++i)
    ns[i] *= 2;
  for(size_t i = 0; i < ns.size(); ++i)
    if (ns[i] >= 10) { ++ns[i + 1]; ns[i] -= 10; }
  return ns;
}

// 10 進数字配列であらわされる値を 1 増やした数字配列を返す
static nums inc(nums ns) {
  ++ns[0];
  for(size_t i = 0; i < ns.size(); ++i)
    if (ns[i] >= 10) { ++ns[i + 1]; ns[i] -= 10; }
  return ns;
}

// ビット列から数字配列に変換する
static nums convert(bits bs) {
  nums ns((bs.size() - 1) / 3); // 多めに桁を確保する
  for(bits::iterator it = bs.begin(); it != bs.end(); ++it) {
    ns = dbl(ns);
    if (*it) ns = inc(ns);
  }

template<typename T, size_t N> inline T* end_(T (&a)[N]) { return a + N; }

int main() {
  // ULONG_MAX + 1
  unsigned char hex[] = {0x01,0x0,0x0,0x0,0x0};

  // 変換する
  nums ns = convert(from(hex, end_(hex)));

  // 数字配列を、添字逆順に標準出力にコピーする
  std::copy(ns.rbegin(), ns.rend(), std::ostream_iterator<int>(std::cout));
  std::cout << std::endl;

  return 0;
}

着想は単純で、二進数を数値に変換する処理をなぞって、二倍する処理と、一増やす処理を10進数値配列に対して適用できるようにしたもの。

書き直すとよい、あるいは改善できる点。:

  • main 関数の引数で 16 進文字列を受け取って、それぞれの 10 進数値を出力できるようにする
  • convert 関数で、上位桁で連続する 0 を消去する
  • convert 関数の、ビット列を表現するために必要な桁数を見積もりかた
  • あるいは dbl と inc の両関数で、桁が必要なだけ増やすようにする

コンパイル・リンク、実行結果はこんな感じ。:

$ make hex2dec CXXFLAGS="-O2 -Wall" && ./hex2dec 
g++ -O2 -Wall    hex2dec.cc   -o hex2dec
0004294967296

書き直すと良い点を反映してみた

自分でつくったお題ながら楽しくてかないません。アホだなあ…

#include <vector>
#include <iostream>
#include <iterator>
#include <cstring>

typedef std::vector<bool> bits;
typedef std::vector<int> nums;

template<typename T, size_t N> inline T* end_(T (&a)[N]) { return a + N; }

static bits from(const char *const begin, const char *const end) {
  bits bs;
  bs.push_back(false);
  for (const char *p = begin; p != end; ++p) {
    static const char hex[] = "0123456789ABCDEFabcdef";
    size_t d = std::distance(hex, std::find(hex, end_(hex), *p));
    if (22 < d) continue;
    if (16 <= d && d < 22) d -= 6;
    bs.push_back(d & 0x08);
    bs.push_back(d & 0x04);
    bs.push_back(d & 0x02);
    bs.push_back(d & 0x01);
  }
  return bs;
}

static nums carry(nums ns) {
  for(nums::iterator it = ns.begin(); it != ns.end(); ++it) {
    if (10 <= *it) {
      *it -= 10;
      nums::iterator n = it;
      if (++n != ns.end()) *n += 1; else { ns.push_back(1); break; }
    }
  }
  return ns;
}

static nums dbl(nums ns) {
  for(nums::iterator it = ns.begin(); it != ns.end(); ++it)
    *it *= 2;
  return carry(ns);
}

static nums inc(nums ns) {
  return ns.empty() ? nums(1,1) : (++ns.front(), carry(ns));
}

static nums convert(bits bs) {
  nums ns;
  for(bits::iterator it = bs.begin(); it != bs.end(); ++it)
    ns = *it ? inc(dbl(ns)) : dbl(ns);
  return ns;
}

int main(int argn, char **args) {
  while((++args, --argn)) {
    nums ns = convert(from(*args, *args + strlen(*args)));
    std::copy(ns.rbegin(), ns.rend(), std::ostream_iterator<int>(std::cout));
    std::cout << std::endl;
  }
  return 0;
}

コマンドラインの文字列から値を読んで出力します。:

$ make hex2dec CXXFLAGS="-O2 -Wall" &&\
 ./hex2dec 1 10 100 1000 10000 100000 1000000\
 10000000 100000000 1000000000 10000000000
g++ -O2 -Wall    hex2dec.cc   -o hex2dec
1
16
256
4096
65536
1048576
16777216
268435456
4294967296
68719476736
1099511627776

カッとなって STL アルゴリズムを導入した

ただもうひたすら趣味の世界。

#include <vector>
#include <iostream>
#include <iterator>
#include <cstring>
#include <algorithm>
#include <numeric>

typedef std::vector<bool> bits;
typedef std::vector<int> nums;

template<typename T, size_t N> inline T* end_(T (&a)[N]) { return a + N; }

static bits from(const char *const begin, const char *const end) {
  bits bs;
  bs.push_back(false);
  for (const char *p = begin; p != end; ++p) {
    static const char hex[] = "0123456789ABCDEFabcdef";
    size_t d = std::distance(hex, std::find(hex, end_(hex), *p));
    if (22 < d) continue;
    if (16 <= d && d < 22) d -= 6;
    bs.push_back(d & 0x08);
    bs.push_back(d & 0x04);
    bs.push_back(d & 0x02);
    bs.push_back(d & 0x01);
  }
  return bs;
}

static nums carry(nums ns) {
  for(nums::iterator it = ns.begin(); it != ns.end(); ++it) {
    if (10 <= *it) {
      *it -= 10;
      nums::iterator n = it;
      if (++n != ns.end()) *n += 1; else { ns.push_back(1); break; }
    }
  }
  return ns;
}

static void dbl_aux(int &v) { v *= 2; }
static nums dbl(nums ns)
{ return std::for_each(ns.begin(), ns.end(), dbl_aux), carry(ns); }

static nums inc(nums ns)
{ return ns.empty() ? nums(1,1) : (++ns.front(), carry(ns)); }

static nums cnv_aux(nums ns, bool v) { return v ? inc(dbl(ns)) : dbl(ns); }
static nums convert(bits bs)
{ return std::accumulate(bs.begin(), bs.end(), nums(), cnv_aux); }

static inline void print(const nums &ns) {
  std::copy(ns.rbegin(), ns.rend(), std::ostream_iterator<int>(std::cout));
  std::cout << std::endl;
}

int main(int argn, char **args) {
  while((++args, --argn)) print( convert(from(*args, *args + strlen(*args))) );
  return 0;
}

dbl_aux や cnv_aux のような小さな関数を、アルゴリズムに渡すために名前をつけてファイルスコープで定義しないとならないのが不便。このあたり、 C++0x の lambda に過剰な期待を抱いてしまうんだけど…使いにくかろうなあやっぱり。

carry 関数を書き直してみた

ループの中で常に次の要素の有無を調べて、なければ(最後の桁上がりを追加するために)ループをまわすイテレーターを破壊する ns.push_back を呼び出して break するという処理が醜悪におもえてしかたなかったからである。
また、加算での桁上がりは 1 ずつに過ぎないからとさぼっていた桁上がりの処理を、あるべきように書き換えた。

typedef std::pair<nums,int> carry_t;
static inline nums cons(nums ns, int value) { ns.push_back(value); return ns; }
static inline carry_t carry_aux(carry_t pair, int v) {
  // v には次の桁の値が、ペアの二番目の要素には桁上がりの値が入っている
  const int t = pair.second + v;
  return std::make_pair(cons(pair.first, t % 10), t / 10);
}
static nums carry(nums ns) {
  carry_t pair = std::accumulate(ns.begin(), ns.end(), carry_t(), carry_aux);
  return pair.second ? cons(pair.first, pair.second) : pair.first;
}

ループを accumulate で置き換えて、ループを抜けてから carry の有無を調べて必要に応じて最上位桁を追加するようにした。
このように整理した後でも、 carry_aux 関数と carry 関数とに、構造が非常ににかよった cons があるところが気に入らない。